diff --git a/plugins/voyage/docs/HANDOVER-CONTRACTS.md b/plugins/voyage/docs/HANDOVER-CONTRACTS.md index a45eb05..9d2e1f8 100644 --- a/plugins/voyage/docs/HANDOVER-CONTRACTS.md +++ b/plugins/voyage/docs/HANDOVER-CONTRACTS.md @@ -10,7 +10,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated: | Artifact | Field | Current | |---|---|---| -| `brief.md` | `brief_version` (frontmatter) | `2.0` | +| `brief.md` | `brief_version` (frontmatter) | `2.1` | | `research/*.md` | (implicit; tracked via `type: trekresearch-brief`) | unversioned | | `plan.md` | `plan_version` (frontmatter) | `1.7` | | `progress.json` | `schema_version` (top-level) | `"1"` | @@ -67,6 +67,8 @@ Every validator exposes a CLI: `node lib/validators/.mjs --json ` re | `interview_turns` | number | optional | ≥ 0 | | | `source` | string | optional | `interview \| manual` | | | `brief_quality` | string | optional | `complete \| partial` | Set when iteration cap is hit | +| `phase_signals` | list | optional (v5.1+) | list of `{phase, effort?, model?}` entries | Per-phase effort + model commitment from Phase 3.5. Mutually exclusive with `phase_signals_partial`. | +| `phase_signals_partial` | bool | optional (v5.1+) | `true` | Force-stop record from Phase 3.5. Mutually exclusive with `phase_signals`. | **Body invariants:** required sections (validator runs in strict mode at write-time, soft mode at read-time): - `## Intent` @@ -84,11 +86,12 @@ Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preference | Type discriminator | every read | `type === "trekbrief"` | | Status enum | every read | `research_status ∈ allowed values` | | **State machine** | every read | `research_topics > 0 && research_status === "skipped"` requires `brief_quality === "partial"` | +| **v5.1 sequencing gate** | every read | `brief_version ≥ 2.1` requires `phase_signals` (list) OR `phase_signals_partial: true` — error `BRIEF_V51_MISSING_SIGNALS` on miss. Validator-only enforcement; commands surface, don't re-enforce. | | Body sections | strict only | All `BRIEF_BODY_SECTIONS` present | **State machine** detail: a brief that says it has research topics but skipped them must explicitly admit it (via `brief_quality: partial`). This is the most common failure mode the validator catches. -**Versioning:** current is `2.0`. There are no live `1.x` briefs; remove legacy paths in next major. +**Versioning:** current is `2.1` (v5.1 — adds optional `phase_signals` + `phase_signals_partial`). The forward-compat policy in `brief-validator.mjs` header still applies: unknown frontmatter keys flow through silently, so a `2.1` brief still validates against pre-v5.1 consumers. The version bump exists because v2.1 activates the **version-conditional sequencing gate** (above) — the only check in the validator that triggers on `brief_version` rather than field-presence. There are no live `1.x` briefs; remove legacy paths in next major. v5.4 may promote `phase_signals` from optional to required (breaking change → `3.0`). **Failure modes:** - `BRIEF_NOT_FOUND` → consumer halts with a usage message @@ -97,6 +100,12 @@ Optional but standard sections: `## Non-Goals`, `## Constraints`, `## Preference - `BRIEF_MISSING_FIELD` → strict halt; soft-mode warning - `BRIEF_STATE_INCOHERENT` → strict halt; soft-mode warning (incoherence will haunt downstream agents) - `BRIEF_MISSING_SECTION` → strict halt; soft-mode warning +- `BRIEF_V51_MISSING_SIGNALS` → strict halt (v5.1+ sequencing gate); soft-mode warning. Commands surface a friendly hint pointing back to `/trekbrief` (Phase 3.5). +- `BRIEF_INVALID_PHASE_SIGNALS` → strict halt; phase_signals must be a list of `{phase, effort?, model?}` entries. +- `BRIEF_INVALID_PHASE_SIGNAL_PHASE` → strict halt; phase ∉ `[research, plan, execute, review]`. +- `BRIEF_INVALID_EFFORT` → strict halt; effort ∉ `[low, standard, high]`. +- `BRIEF_INVALID_MODEL` → strict halt; model ∉ `BASE_ALLOWED_MODELS` (currently `[sonnet, opus]`). +- `BRIEF_SIGNALS_MUTUALLY_EXCLUSIVE` → strict halt; cannot set both `phase_signals` and `phase_signals_partial: true`. --- diff --git a/plugins/voyage/tests/lib/doc-consistency.test.mjs b/plugins/voyage/tests/lib/doc-consistency.test.mjs index d01a728..717ee29 100644 --- a/plugins/voyage/tests/lib/doc-consistency.test.mjs +++ b/plugins/voyage/tests/lib/doc-consistency.test.mjs @@ -551,3 +551,37 @@ test('operational files no longer reference trekrevise (v5.0.0 removal)', () => ); } }); + +// --- v5.1 — phase_signals + brief_version 2.1 --- + +test('v5.1 — templates/trekbrief-template.md declares brief_version: 2.1', () => { + const t = read('templates/trekbrief-template.md'); + assert.match(t, /^brief_version: 2\.1$/m, + 'trekbrief-template.md must declare brief_version: 2.1 at top of frontmatter'); +}); + +test('v5.1 — templates/trekbrief-template.md contains phase_signals: block', () => { + const t = read('templates/trekbrief-template.md'); + assert.match(t, /^phase_signals:$/m, + 'trekbrief-template.md must contain a phase_signals: block in frontmatter'); +}); + +test('v5.1 — HANDOVER-CONTRACTS.md schema row includes phase_signals + phase_signals_partial', () => { + const t = read('docs/HANDOVER-CONTRACTS.md'); + assert.ok(t.includes('| `phase_signals` |'), + 'HANDOVER-CONTRACTS must add a phase_signals row to the Handover 1 schema table'); + assert.ok(t.includes('| `phase_signals_partial` |'), + 'HANDOVER-CONTRACTS must add a phase_signals_partial row to the Handover 1 schema table'); +}); + +test('v5.1 — voyage CLAUDE.md mentions phase_signals', () => { + const t = read('CLAUDE.md'); + assert.ok(t.includes('phase_signals'), + 'voyage CLAUDE.md must document phase_signals (v5.1)'); +}); + +test('v5.1 — voyage README.md mentions phase_signals', () => { + const t = read('README.md'); + assert.ok(t.includes('phase_signals'), + 'voyage README.md must mention phase_signals (v5.1 "What\'s new" bullet)'); +});