feat(voyage): S6 — v5.5 brief framing enforcement (brief_version 2.2)

Implements the CLAUDE.md cross-cutting invariant "brief framing must match
operator intent" as a controlled brief_version 2.1->2.2 bump (operator option A1).
Three defense layers, version-gated at >=2.2 so existing 2.0/2.1 briefs stay
valid (forward + backward compatible), mirroring the phase_signals >=2.1 gate:

- L1 framing: enum field (preserve|refine|replace|new-direction). Enum-checked
  on any version when present (BRIEF_INVALID_FRAMING); missing at >=2.2 ->
  BRIEF_MISSING_FRAMING. /trekbrief Phase 2.5 collects it BEFORE any brief prose
  (non-skippable, even in --quick).
- L2 memory alignment: new brief-reviewer dimension 6 comparing brief Intent/Goal
  + framing against operator memory for explicit contradictions; degrades to
  score 5 (N/A) when no memory context is supplied. Wired into Phase 4e gate
  (memory_alignment.score >= 4).
- L3 obligatory ## TL;DR (<=5 content lines) at >=2.2; soft cap ->
  BRIEF_TLDR_TOO_LONG warning.

trekreview briefs are exempt from the framing/TL;DR gate. Handover 1 PUBLIC
CONTRACT doc, README "What's new", and the CLAUDE.md invariant + agents table
(brief-reviewer 5->6 dimensions) updated to 2.2 (schema axis only; plugin
version badge + CHANGELOG remain S10).

Iron Law followed: validator tests red->green first. Tests 586 -> 606
(+20, 604 pass / 2 skip). claude plugin validate passes (pre-existing
CLAUDE.md root-context warning unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 13:09:50 +02:00
commit 736ae55d66
10 changed files with 409 additions and 31 deletions

View file

@ -99,6 +99,41 @@ If the directory already exists and is non-empty, warn and ask:
Use `AskUserQuestion` with three options. If "pick new slug", ask for a
new slug and restart Phase 2.
## Phase 2.5 — Framing declaration (v5.5)
Before gathering ANY brief content, the operator MUST declare how this brief
relates to prior operator intent. This is the **first layer of the
framing-alignment defense** (CLAUDE.md cross-cutting invariant): the premise is
declared explicitly *before* the interview can drift, and long before `/trekplan`
can polish a wrong premise with reasoning power.
**This runs BEFORE any brief prose is drafted.** The committed value is written to
brief frontmatter as `framing: <value>` in Step 4a and is REQUIRED for
`brief_version: "2.2"` (the validator emits `BRIEF_MISSING_FRAMING` otherwise).
Ask via `AskUserQuestion` — one question, four canonical options:
| Option | Maps to `framing:` | Meaning |
|--------|--------------------|---------|
| **Preserve** | `preserve` | Same intent as before; this brief continues a prior direction unchanged. |
| **Refine** | `refine` | Same core intent, sharpened or narrowed scope. |
| **Replace** | `replace` | Supersedes a prior brief's approach; intent re-stated from scratch. |
| **New direction** | `new-direction` | Net-new intent; no prior brief to anchor against. |
Commit the answer to `state.framing` immediately.
**No safe default.** `framing` cannot be guessed — the danger the invariant guards
against is exactly "delta from last is a dangerous default anchor after a rejected
iteration." Therefore **this question is asked even in `--quick` mode** and is the
one dialog that has no skip path. If the operator force-stops here, re-surface the
four options once more; the brief cannot be written at `brief_version: "2.2"`
without a committed framing value.
Report:
```
Framing: {preserve | refine | replace | new-direction}
```
## Phase 3 — Completeness loop
Phase 3 is a **section-driven completeness loop**. Instead of a numbered
@ -424,7 +459,15 @@ Build the brief text from Phase 3 state by filling the template:
- **Frontmatter:** populate `task`, `slug`, `project_dir`, `research_topics`
(count of topics), `research_status: pending`, `auto_research: false`
(will update in Phase 5 if user opts in), `interview_turns` (total
questions asked across Phase 3 + Phase 4), `source: interview`.
questions asked across Phase 3 + Phase 4), `source: interview`. Set
`brief_version: "2.2"` and `framing: <state.framing>` (committed in Phase
2.5 — never omit; the validator blocks a 2.2 brief without it).
- **TL;DR (v5.5, required at 2.2):** write a `## TL;DR` section (≤ 5 content
lines) at the very top of the body, before `## Intent`. It is the
framing-anchored one-glance summary — what the brief asks for and how it
relates to prior intent given `framing: <state.framing>`. Drafting it FIRST
forces the wrong-premise check before the rest of the prose is written. Keep
it to ≤ 5 lines (the validator warns with `BRIEF_TLDR_TOO_LONG` above that).
- **Intent:** expand the user's motivation into 35 sentences. Load-bearing.
- **Goal:** concrete end state.
- **Non-Goals:** from state, or "- None explicitly stated" bullet if empty.
@ -446,12 +489,22 @@ final file is only written after the gate passes).
**Step 4c — Launch brief-reviewer**
**Gather memory context first (v5.5, layer 2 of the framing defense).** If the
operator's environment exposes memory facts (e.g. an auto-memory `MEMORY.md` plus
`feedback_*` / `project_*` topic files), collect the paths or excerpts of those
relevant to this task. This is best-effort and environment-dependent: if no memory
is available, pass nothing — the reviewer scores the memory-alignment dimension
`5` (N/A) when no context is supplied.
Launch the `brief-reviewer` agent (foreground, blocking) with the prompt:
> "Review this task brief for quality: `{PROJECT_DIR}/brief.md.draft`.
> Check completeness, consistency, testability, scope clarity, and
> research-plan validity. Report findings, verdict, and the required
> machine-readable JSON block."
> Check completeness, consistency, testability, scope clarity,
> research-plan validity, and memory alignment. Report findings, verdict, and
> the required machine-readable JSON block.
> Operator memory context (compare the brief's Intent/Goal and its declared
> `framing:` value against these for EXPLICIT contradictions only):
> {memory paths or excerpts, or "none supplied"}."
**Step 4d — Parse JSON scores**
@ -460,12 +513,13 @@ Extract per-dimension scores:
```
review = {
completeness: { score, gaps },
consistency: { score, issues },
testability: { score, weak_criteria },
scope_clarity: { score, unclear_sections },
research_plan: { score, invalid_topics },
verdict: "PROCEED | PROCEED_WITH_RISKS | REVISE"
completeness: { score, gaps },
consistency: { score, issues },
testability: { score, weak_criteria },
scope_clarity: { score, unclear_sections },
research_plan: { score, invalid_topics },
memory_alignment:{ score, contradictions }, # v5.5 — layer 2
verdict: "PROCEED | PROCEED_WITH_RISKS | REVISE"
}
```
@ -484,6 +538,10 @@ The gate **passes** when all of the following are true:
- `testability.score ≥ 4`
- `scope_clarity.score ≥ 4`
- `research_plan.score == 5`
- `memory_alignment.score ≥ 4` (v5.5 — a score ≤ 3 means the reviewer found an
EXPLICIT contradiction between the brief and operator memory; this is a
framing-alignment blocker, not a wording nit. A `5` is also returned when no
memory context was supplied, so this never blocks environments without memory.)
(Research Plan requires a perfect score because its format is checked
mechanically: ends in `?`, `Required for plan steps` filled, scope is
@ -499,9 +557,11 @@ stumble.)
**If gate fails AND iteration count < 3:**
1. Identify the weakest dimension (lowest score; tie broken by priority:
research_plan > testability > completeness > consistency > scope_clarity).
research_plan > memory_alignment > testability > completeness >
consistency > scope_clarity).
2. Generate a targeted follow-up question from the dimension's detail
field (gaps / issues / weak_criteria / unclear_sections / invalid_topics).
field (gaps / issues / weak_criteria / unclear_sections / invalid_topics /
contradictions).
Example generators:
- `completeness.gaps: ["Non-Goals empty, unclear if deliberate"]`
→ "You did not specify anything out-of-scope. Is that deliberate, or
@ -514,6 +574,10 @@ stumble.)
→ "For research topic 'JWT': which plan steps depend on the answer?
Give one or two concrete kinds of step (e.g., 'library selection',
'threat model', 'migration strategy')."
- `memory_alignment.contradictions: [{"brief_claim":"continue the REST approach","memory_fact":"team decided to move to GraphQL","file":"project_api.md"}]`
→ "Your brief's framing says 'preserve', but memory records the team
moved off REST to GraphQL. Is this brief intentionally reviving REST,
or should the framing be 'replace' / 'new-direction'?"
3. Ask via `AskUserQuestion`. Record the answer into Phase 3 state.
4. Return to Step 4a with incremented iteration count. The reviewer sees
an updated draft, so you MUST re-read the brief and regenerate the