merge: wave 1 order 2 (intent) into main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-06 20:37:12 +02:00
commit 21c28df3bc
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
16 changed files with 1527 additions and 85 deletions

View file

@ -33,10 +33,9 @@ foreground if the user opts in.
Parse `$ARGUMENTS`:
1. If arguments start with `--quick`: set **mode = quick**. The interview
starts more compactly (fewer opening probes per section) but still
escalates automatically if quality gates fail. There is no hard cap on
question count — quality drives the loop, not a counter. Strip the flag;
remainder is the task description.
proposes `small` as the starting size class (Phase 3 still says the class
aloud, the operator can override it, and it moves up if the task proves
bigger). Strip the flag; remainder is the task description.
2. Otherwise: **mode = default**. Interview probes each section until the
completeness gate (Phase 3) and brief-review gate (Phase 4) both pass.
@ -131,7 +130,9 @@ can polish a wrong premise with reasoning power.
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:
It is the **first question of the Phase 3 round** — asked in the same
`AskUserQuestion` call as the round's other questions, not as a call of its
own — with four canonical options:
| Option | Maps to `framing:` | Meaning |
|--------|--------------------|---------|
@ -147,7 +148,8 @@ against is exactly "delta from last is a dangerous default anchor after a reject
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.
without a committed framing value. A hollow answer («høres bra ut», «gjør som
du vil», an empty reply) is not a framing value either: ask it again.
Report:
```
@ -156,13 +158,105 @@ Framing: {preserve | refine | replace | new-direction}
## Phase 3 — Completeness loop
Phase 3 is a **section-driven completeness loop**. Instead of a numbered
question list, maintain an internal state of brief sections and keep asking
until every required section has substantive content. Quality drives the
loop — there is no hard cap on question count.
Phase 3 gathers the brief's content in **one question round**, sized by the
task's **size class** (ceremony by size). Facts are looked up, never asked;
then every question that can be answered now goes out at once, each with the
recommended answer first. Use `AskUserQuestion` for every question.
Use `AskUserQuestion` for every question. **Ask one question at a time.**
Never dump multiple questions.
### Step 3a — Look up facts first
Never ask the operator a fact a helper can look up: which framework the repo
uses, where a module lives, what the test command is, whether a file exists.
Read the repo (Glob / Grep / Read, or a subagent for a wide search — never with
the Agent tool's `name` parameter) and carry what you found into the round. The
operator answers decisions and intent, not lookups.
### Step 3b — Ceremony by size (K1)
Put the task in one of three size classes. In doubt, take the heaviest that fits.
| Class | Typical task | What the pipeline does |
|---|---|---|
| `small` | one change in one place, no new dependency, no interface change, easy to undo | one round, no follow-up; brief-reviewer runs once and its gaps become written assumptions, not questions; Phase 3.5 is skipped |
| `bounded` | several files or one module behind a clear boundary, no architecture decision | the round + at most one follow-up; at most one brief-review follow-up; Phase 3.5 asks one question for all four phases |
| `architectural` | a new module, interface, dependency, data model or migration; anything hard to undo | follow-up rounds while answers open new branches; the full brief-review loop; Phase 3.5 asks per phase |
- **Said aloud.** State the class and why in one line *before* the round
("This reads as `small`: one setting on one screen, no new dependency."),
and put it to the operator as one of the round's questions: your class as
the recommendation, the other two as the override.
- **Only ever moves up.** When an answer, the brief-reviewer or the repo shows
more than the class assumed (a migration, a new dependency, an interface
change), raise the class, say so in one line, and use the new budget from
then on. You never lower it yourself; only the operator's explicit override
lowers it.
- **Written to the brief** as `ceremony: <class>` in Step 4a. An absent field
reads as `architectural`, so a brief from another producer gets the
heaviest treatment downstream.
### Step 3c — The question round
1. **Candidates.** The coverage list is the question bank below (Intent, Goal,
Success Criteria, Research Plan, Non-Goals, Constraints, Preferences, NFRs,
Prior Attempts), plus three fixed questions every round carries: framing
(Phase 2.5, first), the size class (Step 3b) and where the brief is stored
(`artifact_store`, below).
2. **Drop what you can predict.** If the task description, the repo and the
answers so far let you predict the answer, do not ask: write the prediction
into `## Open Questions / Assumptions`, marked "assumed, not asked", where
the operator sees it before approving.
3. **Rank the rest by Impact × Uncertainty**: how much the plan changes with
the answer, times how unsure you are of it. Ask the highest within the
class budget. Everything over the budget goes on a **Deferred** list, one
line of why each, printed with the round and written into
`## Open Questions / Assumptions` — the budget never hides a gap.
4. **One round.** All questions that can be answered now, in one
`AskUserQuestion` call. The tool takes at most 4 questions per call, so a
`bounded` or `architectural` round is consecutive calls with no drafting in
between. Each question is a full sentence in plain words, says in one
clause why it matters, and lists the **recommended answer first**, marked
"(Recommended)", with the consequence of each option in its description.
5. **Stop rule.** Stop asking as soon as you can predict the operator's
next three answers; write those predictions down as assumptions instead. A
follow-up round is only for an answer that opens a branch you cannot
predict (`small`: never; `bounded`: at most one).
6. **Answers that decide nothing.** «høres bra ut», «gjør som du vil», "sounds
good", "whatever you think" and an empty reply are not a decision. Record
the recommended answer as an assumption ("not confirmed — the answer was
«…»"), never as the operator's choice.
**Budget per class** (interview questions, the three fixed ones included; the
Phase 4h approval is not counted):
| Class | Questions | Rounds |
|---|---|---|
| `small` | 4 | one `AskUserQuestion` call, no follow-up |
| `bounded` | 8 | the round + at most one follow-up |
| `architectural` | 12 per round | the round + follow-ups while answers open new branches |
**Where the brief is stored** (`artifact_store`; operator decision
2026-10-06: the approval log is always committed, the brief only by choice).
One question every round. Recommend `git` for a private repo and `local` when
the repo is public or you cannot tell:
| Option | Maps to | Consequence |
|---|---|---|
| **In git** | `artifact_store: git` | `brief.md` (and later `plan.md`) is committed together with its approval. |
| **Only on this machine** | `artifact_store: local` | The project folder is kept out of git through the repo's `.git/info/exclude`, a file that is never committed. |
The approval log is committed either way; it holds hashes, times and file
names, never the text.
**Borrowed, with credit.** The size classes, "heaviest in doubt" and the
one-way ratchet: obra/superpowers@8ca22db (MIT, © 2025 Jesse Vincent),
skills/brainstorming. The class ladder: Kiro's docs (kiro.dev — Plan mode,
Quick Spec, full spec; read 2026-10-06; an idea only, Kiro is a closed
product). The round, the recommended answer first and "never ask a fact":
mattpocock/skills@c55ee46 (MIT), productivity/grilling. The budget,
Impact × Uncertainty and the Deferred list: GitHub Spec Kit (MIT),
templates/commands/clarify.md at 2e941fe. The stop rule and the answers that
decide nothing: addyosmani/agent-skills@9d0c60d (MIT, © 2025 Addy Osmani),
skills/interview-me.
### Internal state
@ -179,6 +273,10 @@ state = {
preferences: { content: [], probes: 0 }, # optional
nfrs: { content: [], probes: 0 }, # optional
prior_attempts: { content: "", probes: 0 }, # optional
ceremony: "small | bounded | architectural", # Step 3b; only moves up
artifact_store: "git | local", # Step 3c
assumptions: [], # predicted, hollow or force-stopped answers
deferred: [], # over budget, with why
question_history: [] # list of questions asked
}
```
@ -195,7 +293,9 @@ Four sections MUST have substantive content before exiting Phase 3:
2. **Goal** — full sentence or paragraph
3. **Success Criteria** — at least one concrete, testable item
4. **Research Plan** — either ≥ 1 topic probed, OR the user has explicitly
confirmed "no external research needed"
confirmed "no external research needed", OR (when you can predict it) it
is written down as the assumption "no external research needed", which the
operator sees in the brief before approving
"Substantive" means: non-empty, not a trivial one-word reply, not
"I don't know" without a recorded assumption. The strict falsifiability
@ -209,9 +309,9 @@ assumed" in Phase 4's draft.
### Question bank (per section)
Pick the next question from the section's bank based on `content` and
`probes`. Wording must stay conversational — only the *selection* is
section-driven, not the tone.
The coverage list Step 3c draws its candidates from. Each variant is a
starting point for a round question, not a script; write it as a full
sentence with your recommended answer first.
**Intent** (required):
- _Anchor_ (probes=0, content empty): "Why are we doing this? What is the
@ -269,25 +369,20 @@ section-driven, not the tone.
### Selection rule
On each loop iteration:
Within the round (Step 3c), and in a follow-up round:
1. Compute the next section to probe:
- If any required section is below the initial-signal gate → pick the
weakest required section in this priority order:
Intent → Goal → Success Criteria → Research Plan.
- Else if an optional section is clearly missing and likely material
to scope (heuristic: the task description hints at constraints or
NFRs) → probe it at most once.
- Else: exit Phase 3.
2. Within the chosen section, pick the question variant:
- If `probes == 0` and content is empty → _Anchor_.
- If content exists but is shallow → _Follow-up_ or _Sharpen_.
- If the section is Research Plan and topics exist → iterate per-topic
sharpen across incomplete topics.
3. Ensure the exact question is NOT already in `question_history`. If it
is, pick the next variant or skip to the next weakest section.
4. Ask via `AskUserQuestion`. Append question to history. Increment probes.
5. Record the answer into `content`. Never overwrite — merge.
1. A required section below the initial-signal gate outranks every optional
one, in this order: Intent → Goal → Success Criteria → Research Plan — unless
you can predict it from the task and the repo, in which case it becomes a
written assumption the operator sees before approving.
2. An optional section is a candidate only when the task hints that it
matters to scope (constraints, NFRs); it is asked at most once.
3. Per section, pick the variant: _Anchor_ when nothing is known, _Follow-up_
or _Sharpen_ when an answer is shallow, per-topic sharpen for Research Plan
topics.
4. Never repeat a question in `question_history`. Append each asked question,
increment its section's `probes`, and merge the answer into `content` —
never overwrite.
### Research topic identification
@ -317,10 +412,10 @@ list. Trust the user. If no topics emerge after probing, the user confirms
### Quick mode adjustments
If **mode = quick**:
- For optional sections, cap probes at 1 each. Do not revisit optional
sections during Phase 3.
- Required sections still have no probe cap — quality gate still applies.
- Prefer _Anchor_ variants over _Sharpen_ on the first pass.
- Propose `small` as the class in Step 3b. It is still said aloud, the
operator can still override it, and it still moves up when the task proves
bigger; `--quick` then follows the new class's budget.
- Prefer _Anchor_ variants over _Sharpen_.
### Force-stop path
@ -334,12 +429,17 @@ similar, break the loop immediately:
### Exit condition
Exit Phase 3 when:
- All four required sections meet the initial-signal gate, OR
- All four required sections meet the initial-signal gate (asked, or written
down as an assumption the operator will see), OR
- The class budget is used up — what is left goes on the Deferred list, OR
- The user has force-stopped.
Report:
```
Phase 3 complete: {N} questions asked across {M} sections.
Class: {small | bounded | architectural}{ (raised from X: <why>) }
Phase 3 complete: {N} of {budget} questions asked in {R} round(s).
Assumed, not asked: {K} Deferred: {D}
Stored: {in git | only on this machine}
Proceeding to draft and review.
```
@ -364,11 +464,17 @@ Before entering Phase 3.5 the following must be populated:
If any are absent: skip Phase 3.5 entirely and write `phase_signals_partial:
true` to the draft frontmatter. Do not block.
### --quick mode
### By size class (and --quick)
If the operator launched with `--quick`: skip Phase 3.5 entirely and
auto-write `phase_signals_partial: true` to draft frontmatter. The brief
will satisfy the v5.1 sequencing gate without going through the dialog.
- `small` (and `--quick` while the class stays `small`): skip Phase 3.5
entirely and auto-write `phase_signals_partial: true` to draft frontmatter.
The brief satisfies the v5.1 sequencing gate without the dialog, and the
downstream commands fall back to the profile.
- `bounded`: ask ONE question in the Step 3c round — the tier for all four
phases, with 4 options from the mapping below — and write the same tier
for `research`, `plan`, `execute` and `review`.
- `architectural`: the per-phase dialog below, as one `AskUserQuestion` call
carrying four questions (one per phase).
### Default-derivation heuristic (LLM judgment, not algorithmic)
@ -384,10 +490,10 @@ these signals — they are weak heuristics, not rules:
Mix these into one proposed default per phase. Document the proposed tier
in the question body so the operator sees why it was picked.
### The loop — 4 tier-coupled AskUserQuestion calls
### The dialog — one call, one tier-coupled question per phase
Loop over `[research, plan, execute, review]` in order. For each phase,
issue one `AskUserQuestion` with 4 options:
For `[research, plan, execute, review]`, ask one question each, all four in
one `AskUserQuestion` call, each question with 4 options:
| Option | Maps to phase_signals entry |
|--------|----------------------------|
@ -397,9 +503,10 @@ issue one `AskUserQuestion` with 4 options:
| **Fable (max quality)** | `{phase: <name>, effort: high, model: fable}` |
The proposed tier per phase (from the default-derivation heuristic) MUST be
labelled `(default)` in the option list so the operator can one-click
accept. Commit the chosen tier immediately to an in-memory `effort_state`
dict — no bulk summary-before-commit. The loop is interruptible.
labelled `(default)` and listed first so the operator can one-click
accept. Commit the chosen tiers to an in-memory `effort_state` dict as the
answers come back — no bulk summary-before-commit. A hollow or empty answer
for a phase leaves that phase uncommitted (the force-stop path below).
The mapping table is canonical:
- `low → {effort: low, model: sonnet}` (force sonnet for the low-cost path)
@ -417,7 +524,7 @@ saved for one model does not carry to another.
### Force-stop handling
If during any of the four `AskUserQuestion` calls the operator says "stop",
If in answer to the dialog the operator says "stop",
"skip", "enough", "just write it", or similar, do NOT exit silently — apply
the Phase 4f force-stop pattern verbatim:
@ -477,8 +584,12 @@ Read the brief template first:
### Loop bound
**Maximum 3 review iterations.** This bounds cost in the worst case while
leaving room for two rounds of targeted follow-ups.
**The cap follows the size class:** `small` 1 review iteration (no
follow-up question: what fails is written into `## Open Questions /
Assumptions` and `## Brief Quality`), `bounded` 2 (one follow-up),
`architectural` 3 (two rounds of targeted follow-ups). A follow-up question
counts against the class budget; when the reviewer shows the task is bigger
than its class, raise the class first (Step 3b) and use the new cap.
### Iteration step-by-step
@ -491,7 +602,10 @@ Build the brief text from Phase 3 state by filling the template:
(will update in Phase 5 if user opts in), `interview_turns` (total
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).
2.5 — never omit; the validator blocks a 2.2 brief without it). Set
`ceremony: <state.ceremony>` and `artifact_store: <state.artifact_store>`
(both optional in the schema, so no `brief_version` bump; always written
by this command).
- **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
@ -509,7 +623,10 @@ Build the brief text from Phase 3 state by filling the template:
research needed — user confirmed solid knowledge of all plan
dependencies" note.
- **Open Questions / Assumptions:** from any `"I don't know"` answers
recorded during Phase 3, plus implicit gaps.
recorded during Phase 3, plus implicit gaps; every `state.assumptions`
entry marked "assumed, not asked" or "not confirmed — the answer was «…»"
(what you said apart from what the operator said); and the Deferred list,
one line of why each.
- **Prior Attempts:** from state, or "None — fresh task."
**Step 4b — Write draft to disk**
@ -597,7 +714,7 @@ stumble.)
`brief.md` fresh.
3. Break the loop and proceed to Step 4g.
**If gate fails AND iteration count < 3:**
**If gate fails AND iteration count < the class cap:**
1. Identify the weakest dimension (lowest score; tie broken by priority:
research_plan > memory_alignment > testability > completeness >
consistency > scope_clarity).
@ -630,7 +747,7 @@ stumble.)
> question_history}. Focus on issues that remain after those answers —
> do not re-raise gaps that have already been addressed."
**If gate fails AND iteration count == 3 (loop exhausted):**
**If gate fails AND iteration count == the class cap (loop exhausted):**
1. Move `brief.md.draft` → `brief.md`.
2. Add `brief_quality: partial` to the frontmatter (edit the file
post-rename — insert the key above the closing `---`).
@ -760,12 +877,27 @@ is no skip path and no default answer.
| Option | Effect |
|--------|--------|
| **Approve** | Stamp the brief (step 3). |
| **Approve** | Check the answer (step 3), then stamp, log and commit (steps 4–6). |
| **Revise first** | The operator states the change; edit `## Intent` / `## Goal` in `{BRIEF_PATH}`, re-run the Phase 4g validator, then return to step 1. |
| **Leave unapproved** | Write nothing. `/trekplan` will halt on this brief until `/trekbrief --approve {BRIEF_PATH}` is run. |
3. **Only on an explicit "Approve"** — never inferred from silence, from an
earlier answer, or from a "looks good" in free text — run:
3. **Is the answer an approval?** Only an explicit "Approve" is. Check the
operator's answer as given, verbatim, on stdin (never in the command line):
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/approval-log.mjs answer <<'ANSWER'
{OPERATOR_ANSWER}
ANSWER
```
Exit 0 → step 4. Exit 1 → it is not an approval: «høres bra ut»,
«gjør som du vil», "sounds good", "looks good", "whatever you think" and an
empty answer all land here, as does anything you would have to infer approval
from (silence, an earlier answer). Say in one line that the answer was not
taken as approval and why, then ask step 2 once more; a second
non-approval is "Leave unapproved". Never stamp after exit 1.
4. **Stamp the brief:**
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/intent-approval.mjs --stamp "{BRIEF_PATH}"
@ -778,13 +910,44 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/validators/intent-approval.mjs --stamp "{BRIEF_PA
`## Intent` or `## Goal` afterwards makes the approval stale — `/trekplan`
halts until the operator approves the new text.
What the stamp does NOT prove: any session on this machine could run the
same command. It is a trace that this step ran against this text, not the
operator's signature (see the header of `lib/validators/intent-approval.mjs`).
5. **Log the approval** (K2), whatever `artifact_store` says. `{INTENT_HASH}`
is the `intent_hash` the stamp just printed:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/approval-log.mjs append --kind intent --subject "{BRIEF_PATH}" --sha256 "{INTENT_HASH}"
```
It appends one hash-chained record to the target repo's
`.claude/voyage/approvals.jsonl`. On exit 1 or 2, print its `reason` in one
line: the brief is stamped but not logged, so `/trekplan` halts
(`not-logged`) until `/trekbrief --approve {BRIEF_PATH}` succeeds.
6. **Commit in the target repo.** `{ARTIFACT_STORE}` is the brief's
`artifact_store` (`local` when absent, e.g. in `--approve` mode on another
producer's brief); `{slug}` is the brief's `slug`:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/artifact-commit.mjs --store {ARTIFACT_STORE} --artifact "{BRIEF_PATH}" --message "docs(voyage): approve intent {slug}"
```
`git`: the brief and the log in one commit. `local`: only the log; the
project folder is kept out of git through `.git/info/exclude`. The commit
names its paths, so anything else the operator has staged stays staged and
uncommitted. On exit 1 (e.g. the repo ignores `.claude/`) or 2 (not a git
repository), print the `reason` in one line and continue: the approval is
logged on disk, not committed, and `/trekplan` reports it as uncommitted.
What the stamp and the log do NOT prove: any session on this machine could
run the same commands. They are a trace that this step ran against this
text, not the operator's signature; the commit is what makes an approval
visible in `git log` (see the headers of `lib/validators/intent-approval.mjs`
and `lib/util/approval-log.mjs`).
Report:
```
Intent: {approved (sha256:…12 hex) | left unapproved — /trekplan will halt}
Intent: {approved (sha256:…12 hex) | left unapproved — /trekplan will halt}
Logged: {record {seq} in .claude/voyage/approvals.jsonl | no — {reason}}
Committed: {{short sha}: brief + log | {short sha}: log only, brief kept on this machine | no — {reason}}
```
In `--approve` mode, run **Phase 7** (`"mode": "approve"`) when the stamp
@ -1035,10 +1198,13 @@ can audit which profile drove which session.
7. **Auto mode blocks foreground.** If the user opts into auto, this
session waits for research + planning to complete. Document this in
the opt-in question.
8. **Quality gates, not question counts.** Phase 3 and Phase 4 are
quality-gated loops; do not enforce a hard cap on interview questions.
The brief-review gate (Phase 4) caps at 3 review iterations to bound
cost, but Phase 3 has no cap — required-section content drives exit.
8. **Ceremony by size.** The size class sets the question budget and the
review cap (Phase 3, Phase 4). It is said aloud, the operator can override
it, and on your own you only ever move it up. The budget never hides a gap:
what it leaves out is on the Deferred list or written down as an assumption.
11. **Only an explicit approval approves.** «høres bra ut», «gjør som du vil»
and an empty answer decide nothing (Phase 4h step 3). Every approval is
logged in the target repo's approval log and committed there.
9. **Never write `brief.md` while the review gate is still pending.**
Draft lives in `brief.md.draft` until the loop terminates. A caller
that sees `brief.md` must be able to trust that Phase 4 finished.

View file

@ -205,12 +205,39 @@ the same trust class as self-stamping. What the
marker does NOT prove — it is a trace that the approval step ran against this
text, not a signature: see the header of `lib/validators/intent-approval.mjs`.
**Approval log gate (K2).** The marker passed; now check that the approval is
in the target repo's approval log, the chain is intact, and the logged
approval covers this intent. `{intent_approved_hash}` is the brief's
frontmatter value (the check above just confirmed it is current). Same scope
as the check above: both brief modes, `--quick` included, no override flag;
`trekreview` briefs skip it (they carry no intent hash).
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/approval-log.mjs verify --json --kind intent --subject "{brief_path}" --sha256 "{intent_approved_hash}"
```
Exit 0 (`approved`) → continue; if `uncommitted` is above 0, say in one line
that N approvals are in the log but not committed yet. Exit 1 → **halt** — no
agent, no plan — print `status` and `detail`:
| `status` | Meaning | What the operator does |
|----------|---------|------------------------|
| `broken` | A record in `.claude/voyage/approvals.jsonl` was edited, removed or reordered | Restore the log from git (`git checkout -- .claude/voyage/approvals.jsonl`), then re-run |
| `diverged` | The committed log is no longer the start of the file: committed approvals were rewritten | Same: restore from git; the rewrite shows in `git diff` |
| `not-logged` | No intent approval for this brief in the log (approved before the log existed, or the log step failed) | `/trekbrief --approve {brief_path}` |
| `mismatch` | The latest logged approval covers other intent text | `/trekbrief --approve {brief_path}` |
Exit 2, or no JSON at all → **halt** and show the raw error: a log check that
could not run is never a pass.
Read the brief file and parse its frontmatter. Extract:
- `task` — one-line task description
- `slug` — slug for plan filenames
- `project_dir` — if present, overrides derived project path (optional)
- `research_topics` — N (used as a sanity check)
- `research_status` — `pending | in_progress | complete | skipped`
- `ceremony` — size class (K1) `small | bounded | architectural`; absent = `architectural` (heaviest in doubt)
- `artifact_store` — `git | local`, where Phase 11 keeps the plan; absent = `local`
If `research_status == pending` and `research_topics > 0`:
- Warn the user: "Brief declares {N} research topics but research is still
@ -225,6 +252,7 @@ Mode: {foreground | quick | decompose}
Brief: {brief_path}
Project: {project_dir or "-"}
Research: {N local briefs, M extra via --research}
Class: {small | bounded | architectural} {(absent → architectural)}
```
### When the input is type:trekreview (Handover 6)
@ -1013,6 +1041,40 @@ the tab and reopen the same file.
## Phase 11 — Handoff
### Plan approval (before every handoff below)
Whatever the operator answered in Phase 10 ("save", "execute", …), first ask
whether this plan, as written now, is approved. The approval binds to the
plan file's sha256, so any later edit makes it stale.
1. Ask via `AskUserQuestion` — one question: **"Approve this plan as written?"**
Options: **Approve** (recommended only when the critic left no open
BLOCKER) / **Not yet**.
2. Check the answer as given, verbatim, on stdin:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/approval-log.mjs answer <<'ANSWER'
{OPERATOR_ANSWER}
ANSWER
```
Exit 1 is not an approval («høres bra ut», «gjør som du vil», an empty
answer): say so in one line and treat it as **Not yet**.
3. On exit 0, log the approval and commit it in the target repo
(`{artifact_store}` from Phase 1; `local` keeps `plan.md` out of git and
commits only the log):
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/approval-log.mjs append --kind plan --subject "{plan_path}" --sha256-of "{plan_path}"
node ${CLAUDE_PLUGIN_ROOT}/lib/util/artifact-commit.mjs --store {artifact_store} --artifact "{plan_path}" --message "docs(voyage): approve plan {slug}"
```
Print one line each: `Plan approval: record {seq}` and `Committed: {short
sha} | no — {reason}`. A failed append or commit is reported, never hidden.
**Not yet** → "save" keeps the plan unapproved; "execute" does not start —
return to Phase 10 to refine.
### "save" / "later" / "done"
Confirm the plan and brief file locations and exit.