merge: wave 1 order 2 (intent) into main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
21c28df3bc
16 changed files with 1527 additions and 85 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue