diff --git a/CLAUDE.md b/CLAUDE.md index eecad70..5ce6e0c 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. +> **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). > **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), alle BLOCKER ved brudd når v5.5 shipper:** (1) eksplisitt `framing: preserve|refine|replace|new-direction` i brief-frontmatter, `AskUserQuestion`-validert før brief-prosa skrives; (2) memory-alignment check som ny dimensjon i `brief-reviewer` — sammenlikner brief-prosa mot relevante memory-filer og rapporterer eksplisitte motsigelser; (3) obligatorisk `## TL;DR`-seksjon (≤ 5 linjer) øverst i `brief.md`. Implementeres i v5.5 (tracket i `STATE.md` § NESTE STEG når aktivt). Inntil shipping: operatør må manuelt sjekke at briefens framingord ikke motsier intent, særlig etter avvist iterasjon hvor "delta fra forrige" er en farlig default-ankring. diff --git a/docs/HANDOVER-CONTRACTS.md b/docs/HANDOVER-CONTRACTS.md index 9d2e1f8..4e2b47b 100644 --- a/docs/HANDOVER-CONTRACTS.md +++ b/docs/HANDOVER-CONTRACTS.md @@ -41,7 +41,9 @@ Every validator exposes a CLI: `node lib/validators/.mjs --json ` re --- -## Handover 1 — `brief.md` → research/ +## 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. As of the v5.4 contract formalization, `brief_version` **2.1** is the frozen public-contract baseline. **Producer:** `/trekbrief` Phase 4g (after `brief-reviewer` stop-gate passes or iteration cap is hit). @@ -56,7 +58,7 @@ Every validator exposes a CLI: `node lib/validators/.mjs --json ` re | Field | Type | Required | Allowed values | Notes | |---|---|---|---|---| | `type` | string | yes | `trekbrief` | Hard-coded discriminator | -| `brief_version` | string | yes | `"2.0"` (current) | Bump on schema change | +| `brief_version` | string | yes | `"2.1"` (current) | Bump on schema change | | `created` | date | yes | YYYY-MM-DD | | | `task` | string | yes | one-line description | | | `slug` | string | yes | URL-safe slug | Used in project_dir | @@ -91,7 +93,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. v5.4 may promote `phase_signals` from optional to required (breaking change → `3.0`). +**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 **freezes this schema 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 (→ `3.0`) under the protocol above — explicitly *not* part of v5.4. **Failure modes:** - `BRIEF_NOT_FOUND` → consumer halts with a usage message @@ -451,7 +453,7 @@ The `next-session-prompt-validator` (`lib/validators/next-session-prompt-validat | Handover | Validation strength | Owner | Risk | |---|---|---|---| -| 1. brief → research | strict at write, soft at read | this plugin | low | +| 1. brief → research | strict at write, soft at read | this plugin (**PUBLIC CONTRACT** — producer-facing) | low | | 2. research → plan | soft, drift-warn | this plugin | low | | 3. architecture → plan | discovery-only, drift-WARN | **external** (opt-in architect plugin, not bundled) | low — by design we tolerate drift | | 4. plan → execute | **strict, both ends** | this plugin | medium — Opus 4.7 narrative drift requires constant vigilance | diff --git a/tests/lib/doc-consistency.test.mjs b/tests/lib/doc-consistency.test.mjs index 8806379..224cbd6 100644 --- a/tests/lib/doc-consistency.test.mjs +++ b/tests/lib/doc-consistency.test.mjs @@ -620,3 +620,39 @@ test('v5.1.1 — commands/trekexecute.md contains ### High-effort behavior (v5.1 assert.match(t, /^### High-effort behavior \(v5\.1\.1\)$/m, 'trekexecute.md must contain ### High-effort behavior (v5.1.1) sub-section under Composition rule (gates_mode = closed)'); }); + +// --- v5.4 — brief.md formalized as PUBLIC CONTRACT (Handover 1) --- +// S5: the brief schema is the only public producer↔Voyage integration boundary +// (Trinity asymmetry invariant). These pin the formalization as enforced doc-truth, +// not prose — same pattern as the v5.1 Handover-1 pins above. Freeze, not promote: +// phase_signals stays optional; brief_version 2.1 is the frozen baseline (S3 decision). + +test('v5.4 — HANDOVER-CONTRACTS.md Handover 1 heading is labeled (PUBLIC CONTRACT)', () => { + const t = read('docs/HANDOVER-CONTRACTS.md'); + assert.match(t, /^## Handover 1 —.*\(PUBLIC CONTRACT\)\s*$/m, + 'Handover 1 heading must carry the (PUBLIC CONTRACT) label — symmetric with Handover 3 (EXTERNAL CONTRACT)'); +}); + +test('v5.4 — Handover 1 declares the PUBLIC CONTRACT callout + breaking-for-downstream guarantee', () => { + const t = read('docs/HANDOVER-CONTRACTS.md'); + assert.ok(t.includes('Stability tier: PUBLIC CONTRACT'), + 'Handover 1 must open with a "Stability tier: PUBLIC CONTRACT" callout'); + assert.ok(t.includes('breaking change for every downstream consumer'), + 'the callout must state that a brief-schema change is a breaking change for every downstream consumer'); +}); + +test('v5.4 — brief_version 2.1 is the frozen public-contract baseline (no stale 2.0)', () => { + const t = read('docs/HANDOVER-CONTRACTS.md'); + assert.ok(!t.includes('`"2.0"` (current)'), + 'Handover 1 schema table must not still mark brief_version "2.0" as current (stale)'); + assert.ok(t.includes('`"2.1"` (current)'), + 'Handover 1 schema table must mark brief_version "2.1" as current'); +}); + +test('v5.4 — phase_signals stays optional: v5.4 freezes, does not promote 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('freezes this schema as the public-contract baseline'), + 'the Versioning note must record that v5.4 freezes the schema as the public-contract baseline'); +});