diff --git a/CLAUDE.md b/CLAUDE.md index ec9df4b..a46a184 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ Voyage — a contract-driven Claude Code pipeline: brief, research, plan, execut > **v3.0.0 — architect step extracted from this plugin.** The plan command still auto-discovers `architecture/overview.md` if present, so any compatible producer (architect plugin no longer publicly distributed; the architecture/overview.md slot remains available for any compatible producer) plugs into the same slot. See [CHANGELOG.md](CHANGELOG.md) for migration history. -> **Trinity context (2026-05-13, informational).** Voyage is Tier 1 (per-task) of a three-tier architecture in active design under the author's private marketplace: Tier 2 `app-creator` (per-app — "what does the app need, what's the next brief?") produces briefs Voyage consumes; Tier 3 `app-factory` (per-portfolio — "which app needs me now?") aggregates state across multiple app-creator instances. Both are pre-implementation and will ship to Forgejo when ready. **Asymmetry is a hard invariant:** Voyage stays unaware of Tier 2/3. Handover 1 (brief format) is the only integration point — any compatible producer can feed Voyage, app-creator is not privileged. Brief-schema changes are therefore breaking changes for downstream consumers, formalized as a public contract in v5.4 — see `docs/HANDOVER-CONTRACTS.md` §Handover 1 (PUBLIC CONTRACT). +> **Trinity context (2026-05-13, informational).** Voyage is Tier 1 (per-task) of a three-tier architecture in active design under the author's private marketplace: Tier 2 `app-creator` (per-app — "what does the app need, what's the next brief?") produces briefs Voyage consumes; Tier 3 `app-factory` (per-portfolio — "which app needs me now?") aggregates state across multiple app-creator instances. Both are pre-implementation and will ship to Forgejo when ready. **Asymmetry is a hard invariant:** Voyage stays unaware of Tier 2/3. Handover 1 (brief format) is the only integration point — any compatible producer can feed Voyage, app-creator is not privileged. Brief-schema changes are therefore breaking changes for downstream consumers, formalized as a public contract in v5.5.0 — see `docs/HANDOVER-CONTRACTS.md` §Handover 1 (PUBLIC CONTRACT). > **Cross-cutting invariant: brief framing must match operator intent (2026-05-15).** Etablert etter residiv. Briefen er pipelinens source of truth; operatørens intent lever i hodet + i memory-filer (`feedback_*`, `project_*`); pipelinen tvinger ikke alignment. Høyere reasoning-kraft polerer feil premiss istedenfor å utfordre det. **Tre lag av forsvar (input-siden), implementert i S6 som `brief_version 2.2`-gate (v5.5), alle BLOCKER ved brudd for briefer som deklarerer ≥ 2.2:** (1) eksplisitt `framing: preserve|refine|replace|new-direction` i brief-frontmatter, `AskUserQuestion`-validert i `/trekbrief` Phase 2.5 før brief-prosa skrives (ikke-skippbar, også i `--quick`); enum-feil → `BRIEF_INVALID_FRAMING` (alle versjoner), fravær ved ≥ 2.2 → `BRIEF_MISSING_FRAMING`; (2) memory-alignment som dimensjon 6 i `brief-reviewer` — sammenlikner brief-prosa + `framing` mot relevante memory-filer, rapporterer kun eksplisitte motsigelser (degraderer til score 5 N/A uten memory-kontekst), wired til Phase 4e-gate (`memory_alignment.score ≥ 4`); (3) obligatorisk `## TL;DR`-seksjon (≤ 5 linjer, soft-cap → `BRIEF_TLDR_TOO_LONG`) øverst i `brief.md`. Eksisterende 2.0/2.1-briefer forblir gyldige (forward+backward-compat, speiler `phase_signals ≥ 2.1`-presedensen); `trekreview`-briefer er unntatt. **Schema-akse bumpet (2.1→2.2); plugin-versjon-badge + CHANGELOG bumpes ved den koordinerte releasen (S10).** Kontrakt-evolusjon dokumentert i `docs/HANDOVER-CONTRACTS.md` §Handover 1. Den gamle manuelle stopgap-sjekken er dermed retired for ≥ 2.2-briefer. @@ -43,7 +43,7 @@ Full flag reference for each command (modes, `--gates`, `--profile`, breaking ch | brief-conformance-reviewer | opus | Brief conformance review (SC + Non-Goal traceability) | | code-correctness-reviewer | opus | Code correctness review (7 dimensions) | | review-coordinator | opus | Judge Agent — dedup + reasonableness filter + verdict | -| plan-critic | opus | Adversarial plan review (9 dimensions) | +| plan-critic | opus | Adversarial plan review (10 dimensions) | | scope-guardian | opus | Scope alignment (creep + gaps) | | session-decomposer | opus | Splits plans into headless sessions with dependency graph | | 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`) | diff --git a/README.md b/README.md index 10b7732..87bc9d2 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ Concrete capabilities, observable in the code — not aspirations. Interactive requirements-gathering command. Runs a **dynamic, quality-gated interview** and produces a **task brief** with an explicit research plan. Optionally orchestrates the rest of the pipeline. -A section-driven interview loop fills required brief sections (Intent / Goal / Success Criteria / Research Plan) until each shows initial signal, then `brief-reviewer` scores the draft on five dimensions (completeness, consistency, testability, scope clarity, research-plan validity) and gates publication. Max 3 review iterations; force-stop yields a `brief_quality: partial` brief with the failing dimensions documented. +A section-driven interview loop fills required brief sections (Intent / Goal / Success Criteria / Research Plan) until each shows initial signal, then `brief-reviewer` scores the draft on six dimensions (completeness, consistency, testability, scope clarity, research-plan validity, memory alignment) and gates publication. Max 3 review iterations; force-stop yields a `brief_quality: partial` brief with the failing dimensions documented. Output: `.claude/projects/{YYYY-MM-DD}-{slug}/brief.md` @@ -220,7 +220,7 @@ Research uses up to 5 local agents (architecture-mapper, dependency-tracer, task Produces an implementation plan detailed enough for autonomous execution. **v2.0 breaking change:** requires `--brief` or `--project`. There is no longer an interview inside `/trekplan` — use `/trekbrief` first. -After `brief-reviewer` validates the input brief, 6–8 exploration agents analyze the codebase in parallel and merge findings into a synthesis. Optional research briefs (`--research`, or auto-discovered in `{project_dir}/research/`) enrich the plan; `architecture/overview.md` priors are loaded if an opt-in upstream architect plugin (not bundled) produced one. Opus then writes the plan with per-step YAML manifests, which `plan-critic` (9 dimensions) and `scope-guardian` adversarially review before handoff. +After `brief-reviewer` validates the input brief, 6–8 exploration agents analyze the codebase in parallel and merge findings into a synthesis. Optional research briefs (`--research`, or auto-discovered in `{project_dir}/research/`) enrich the plan; `architecture/overview.md` priors are loaded if an opt-in upstream architect plugin (not bundled) produced one. Opus then writes the plan with per-step YAML manifests, which `plan-critic` (10 dimensions) and `scope-guardian` adversarially review before handoff. Output: - With `--project `: `{dir}/plan.md` @@ -263,7 +263,7 @@ Every implementation step includes: - **Checkpoint:** — git commit after success - **Manifest:** — the objective completion predicate (Hard Rule 17) -Exploration uses 6–8 agents in parallel (architecture-mapper, dependency-tracer, task-finder, test-strategist, git-historian, risk-assessor, plus convention-scanner on medium+ codebases and research-scout when unfamiliar tech is detected). Adversarial review then runs `brief-reviewer`, `plan-critic` (9 dimensions, no-placeholder enforcement, manifest audit), and `scope-guardian` (creep + gap detection). Per-agent details in [`agents/`](agents/). +Exploration uses 6–8 agents in parallel (architecture-mapper, dependency-tracer, task-finder, test-strategist, git-historian, risk-assessor, plus convention-scanner on medium+ codebases and research-scout when unfamiliar tech is detected). Adversarial review then runs `brief-reviewer`, `plan-critic` (10 dimensions, no-placeholder enforcement, manifest audit), and `scope-guardian` (creep + gap detection). Per-agent details in [`agents/`](agents/). --- @@ -801,12 +801,12 @@ Top-level layout: ``` trekplan/ -├── agents/ 23 specialized agents (all `model: opus`-pinned; per-phase model set by `--profile`) -├── commands/ 6 slash commands (trekbrief, trekresearch, trekplan, trekexecute, trekreview, trekcontinue) + trekplan-end-session helper +├── agents/ 24 specialized agents (all `model: opus`-pinned; per-phase model set by `--profile`) +├── commands/ 6 slash commands (trekbrief, trekresearch, trekplan, trekexecute, trekreview, trekcontinue) + trekendsession helper ├── templates/ Frontmatter templates for brief, research, plan, session, launch -├── hooks/ 5 hooks (pre-bash, pre-write, session-title, post-bash-stats, pre-compact-flush) +├── hooks/ 7 hooks (pre-bash, pre-write, session-title, post-bash-stats, pre-compact-flush, post-compact-flush, otel-export) ├── lib/ Zero-dep parsers and validators (CLI shims under lib/validators/) -├── tests/ 109 node:test cases — `npm test` is the fork-readiness gate +├── tests/ comprehensive node:test suite — `npm test` is the fork-readiness gate ├── docs/ HANDOVER-CONTRACTS.md + architect-bridge-test.md └── examples/ 01-add-verbose-flag/ — calibrated end-to-end pipeline demo ``` diff --git a/commands/trekplan.md b/commands/trekplan.md index 225b989..86effa8 100644 --- a/commands/trekplan.md +++ b/commands/trekplan.md @@ -571,7 +571,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 Opus 4.7).** +> `agents/planning-orchestrator.md` is not implicitly loaded by Opus 4.8).** > > The plan you write MUST satisfy these regexes. The executor parses with > strict regex matching; any deviation breaks parsing and forces a re-plan. diff --git a/docs/HANDOVER-CONTRACTS.md b/docs/HANDOVER-CONTRACTS.md index 55e4350..653c474 100644 --- a/docs/HANDOVER-CONTRACTS.md +++ b/docs/HANDOVER-CONTRACTS.md @@ -43,7 +43,7 @@ Every validator exposes a CLI: `node lib/validators/.mjs --json ` re ## Handover 1 — `brief.md` → research/ (PUBLIC CONTRACT) -**Stability tier: PUBLIC CONTRACT.** Handover 1 is the *only* public integration boundary of the pipeline: `brief.md` is what an upstream producer hands to Voyage, and Voyage consumes it without any knowledge of who produced it. This asymmetry is a hard invariant (see `CLAUDE.md` §Trinity context) — the interactive `/trekbrief` interview is just one producer; a `manual` brief, or an external per-app / per-portfolio producer, is equally valid as long as the artifact conforms to the schema below. No producer is privileged. The consequence: changing this schema — renaming or removing a field, narrowing an enum, or promoting an optional field to required — is a **breaking change for every downstream consumer** and MUST follow the [breaking-change protocol](#breaking-change-protocol) (version bump + N-1 compatibility window). Additive *optional* fields are non-breaking by design: the validator tolerates unknown frontmatter keys silently (forward-compat), so a newer brief still validates against an older consumer. The v5.4 contract formalization froze `brief_version` **2.1** as the public-contract baseline; **v5.5 evolves it to `2.2`** under the breaking-change protocol — adding the required `framing` field + `## TL;DR` section gated at ≥ 2.2 (existing 2.0/2.1 briefs stay valid). See the Versioning paragraph below for the 2.2 details. +**Stability tier: PUBLIC CONTRACT.** Handover 1 is the *only* public integration boundary of the pipeline: `brief.md` is what an upstream producer hands to Voyage, and Voyage consumes it without any knowledge of who produced it. This asymmetry is a hard invariant (see `CLAUDE.md` §Trinity context) — the interactive `/trekbrief` interview is just one producer; a `manual` brief, or an external per-app / per-portfolio producer, is equally valid as long as the artifact conforms to the schema below. No producer is privileged. The consequence: changing this schema — renaming or removing a field, narrowing an enum, or promoting an optional field to required — is a **breaking change for every downstream consumer** and MUST follow the [breaking-change protocol](#breaking-change-protocol) (version bump + N-1 compatibility window). Additive *optional* fields are non-breaking by design: the validator tolerates unknown frontmatter keys silently (forward-compat), so a newer brief still validates against an older consumer. The v5.5.0 contract formalization established `brief_version` **2.1** as the public-contract baseline and, in the same coordinated release, **evolved it to `2.2`** under the breaking-change protocol — adding the required `framing` field + `## TL;DR` section gated at ≥ 2.2 (existing 2.0/2.1 briefs stay valid). See the Versioning paragraph below for the 2.2 details. **Producer:** `/trekbrief` Phase 4g (after `brief-reviewer` stop-gate passes or iteration cap is hit). @@ -96,7 +96,7 @@ Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preference **State machine** detail: a brief that says it has research topics but skipped them must explicitly admit it (via `brief_quality: partial`). This is the most common failure mode the validator catches. -**Versioning:** current is `2.1` (v5.1 — adds optional `phase_signals` + `phase_signals_partial`). The forward-compat policy in `brief-validator.mjs` header still applies: unknown frontmatter keys flow through silently, so a `2.1` brief still validates against pre-v5.1 consumers. The version bump exists because v2.1 activates the **version-conditional sequencing gate** (above) — the only check in the validator that triggers on `brief_version` rather than field-presence. There are no live `1.x` briefs; remove legacy paths in next major. The v5.4 contract formalization **froze `2.1` as the public-contract baseline** (see the PUBLIC CONTRACT callout under the Handover 1 heading): per the S3 effort-axis decision, `phase_signals` stays **optional**, and the v2.1 sequencing gate (`phase_signals` **or** `phase_signals_partial`) is the stability mechanism. Promoting `phase_signals` to required would be a future breaking change under the protocol above — explicitly *not* part of v5.4. +**Versioning:** current is `2.1` (v5.1 — adds optional `phase_signals` + `phase_signals_partial`). The forward-compat policy in `brief-validator.mjs` header still applies: unknown frontmatter keys flow through silently, so a `2.1` brief still validates against pre-v5.1 consumers. The version bump exists because v2.1 activates the **version-conditional sequencing gate** (above) — the only check in the validator that triggers on `brief_version` rather than field-presence. There are no live `1.x` briefs; remove legacy paths in next major. The v5.5.0 contract formalization **established `2.1` as the public-contract baseline** (see the PUBLIC CONTRACT callout under the Handover 1 heading): per the S3 effort-axis decision, `phase_signals` stays **optional**, and the v2.1 sequencing gate (`phase_signals` **or** `phase_signals_partial`) is the stability mechanism. Promoting `phase_signals` to required would be a future breaking change under the protocol above — explicitly *not* part of the v5.5.0 formalization. **v5.5 → `2.2` (framing enforcement).** `2.2` adds two **required-at-2.2** elements — the `framing` enum field and the `## TL;DR` body section — gated identically to the v2.1 mechanism: the new requirements fire only on `brief_version ≥ 2.2`, so every existing `2.0` / `2.1` brief still validates (forward- and backward-compatible). Because adding required elements is a breaking change for any producer that declares `2.2`, this is a controlled version bump under the breaking-change protocol: downstream producers (e.g. a Tier-2 per-app producer) that emit `brief_version: "2.2"` MUST also emit `framing` + `## TL;DR`. The framing enum is additionally enforced on *any* version when the field is present (`BRIEF_INVALID_FRAMING`). The three framing-alignment layers (framing field, `brief-reviewer` memory-alignment dimension, obligatory TL;DR) implement the `CLAUDE.md` cross-cutting invariant. Note: the *plugin* version bump + CHANGELOG entry for this schema change land at the coordinated release (matrix §S10), separate from this schema axis. diff --git a/tests/lib/doc-consistency.test.mjs b/tests/lib/doc-consistency.test.mjs index 65465dc..671e7b3 100644 --- a/tests/lib/doc-consistency.test.mjs +++ b/tests/lib/doc-consistency.test.mjs @@ -18,6 +18,18 @@ const ROOT = join(HERE, '..', '..'); function read(rel) { return readFileSync(join(ROOT, rel), 'utf-8'); } function listMd(rel) { return readdirSync(join(ROOT, rel)).filter(f => f.endsWith('.md')); } +function listMjs(rel) { return readdirSync(join(ROOT, rel)).filter(f => f.endsWith('.mjs')); } + +// Normalize a " dimensions" descriptor to an integer (four..ten + digits). +const NUMWORDS = { four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10 }; +function dimCounts(text, anchor) { + // Every dimension count stated on a line mentioning `anchor`. + return text.split('\n') + .filter(l => l.includes(anchor) && /dimensions/.test(l)) + .map(l => l.match(/(\d+|four|five|six|seven|eight|nine|ten)\s+(?:numbered\s+)?dimensions/i)) + .filter(Boolean) + .map(m => /^\d+$/.test(m[1]) ? Number(m[1]) : NUMWORDS[m[1].toLowerCase()]); +} test('CLAUDE.md agents table row count == agents/*.md file count', () => { const md = read('CLAUDE.md'); @@ -35,6 +47,55 @@ test('CLAUDE.md agents table row count == agents/*.md file count', () => { ); }); +test('README architecture block agent-count == agents/*.md file count', () => { + const md = read('README.md'); + const m = md.match(/agents\/\s+(\d+)\s+specialized agents/); + assert.ok(m, 'README architecture block must state "N specialized agents"'); + assert.equal( + Number(m[1]), + listMd('agents').length, + `Drift: README says ${m[1]} specialized agents vs ${listMd('agents').length} agents/*.md files.`, + ); +}); + +test('README architecture block hook-count == hooks/scripts/*.mjs file count', () => { + const md = read('README.md'); + const m = md.match(/hooks\/\s+(\d+)\s+hooks\s*\(/); + assert.ok(m, 'README architecture block must state "N hooks (...)"'); + assert.equal( + Number(m[1]), + listMjs('hooks/scripts').length, + `Drift: README says ${m[1]} hooks vs ${listMjs('hooks/scripts').length} hooks/scripts/*.mjs files.`, + ); +}); + +test('plan-critic dimension count: README + CLAUDE.md match the agent definition', () => { + const dims = (read('agents/plan-critic.md').match(/^### \d+\./gm) || []).length; + assert.ok(dims > 0, 'agents/plan-critic.md must define numbered "### N." dimensions'); + const stated = [...dimCounts(read('README.md'), 'plan-critic'), ...dimCounts(read('CLAUDE.md'), 'plan-critic')]; + assert.ok(stated.length >= 2, 'README + CLAUDE.md must cite plan-critic (N dimensions)'); + for (const n of stated) { + assert.equal(n, dims, `Drift: a doc cites plan-critic (${n} dimensions) vs ${dims} numbered dims in agents/plan-critic.md.`); + } +}); + +test('brief-reviewer dimension count: README matches the agent definition', () => { + const agentDims = dimCounts(read('agents/brief-reviewer.md'), 'dimensions'); + assert.ok(agentDims.length >= 1, 'agents/brief-reviewer.md must state "N dimensions"'); + const expected = agentDims[0]; + // Only the line stating brief-reviewer's OWN count — exclude agent-list lines + // that co-mention `plan-critic (N dimensions)` (those carry plan-critic's count). + const readmeDims = read('README.md').split('\n') + .filter(l => l.includes('brief-reviewer') && /dimensions/.test(l) && !l.includes('plan-critic')) + .map(l => l.match(/(\d+|four|five|six|seven|eight|nine|ten)\s+dimensions/i)) + .filter(Boolean) + .map(m => /^\d+$/.test(m[1]) ? Number(m[1]) : NUMWORDS[m[1].toLowerCase()]); + assert.ok(readmeDims.length >= 1, 'README must cite brief-reviewer (N dimensions)'); + for (const n of readmeDims) { + assert.equal(n, expected, `Drift: README cites brief-reviewer (${n} dimensions) vs ${expected} in agents/brief-reviewer.md.`); + } +}); + test('CLAUDE.md commands table mentions every commands/*.md file', () => { const md = read('CLAUDE.md'); const commandFiles = listMd('commands'); @@ -670,12 +731,14 @@ test('v5.5 — brief_version 2.2 is current in the public-contract schema table 'Handover 1 schema table must mark brief_version "2.2" as current'); }); -test('v5.4 — phase_signals stays optional: not promoted to required', () => { +test('v5.5 — phase_signals stays optional: not promoted to required', () => { const t = read('docs/HANDOVER-CONTRACTS.md'); assert.ok(!t.includes('v5.4 may promote'), 'the speculative "v5.4 may promote phase_signals to required" line must be resolved (S3 froze the shape)'); - assert.ok(t.includes('froze `2.1` as the public-contract baseline'), - 'the Versioning note must record that v5.4 froze 2.1 as the public-contract baseline'); + assert.ok(!/v5\.4/.test(t), + 'the contract doc must not cite the phantom v5.4 release (v5.2–5.4 never shipped; S16 → v5.5.0)'); + assert.ok(t.includes('established `2.1` as the public-contract baseline'), + 'the Versioning note must record the v5.5.0 contract formalization establishing 2.1 as the baseline'); }); // --- v5.5 — framing enforcement: brief_version 2.2 (Handover 1 contract evolution) ---