fix(intent): M1 + M2 — the parser fails closed; --approve reaches any brief file

M1: headings inside fenced code blocks and HTML comments are text, not
headings, and ## Intent / ## Goal must each appear exactly once. A second
section, an example section in a code block or comment, or a '## ' line in
a code block inside Intent no longer carries the old approval: check gives
INVALID (duplicate) or STALE, stamp refuses a duplicate. A plain brief
hashes exactly as before (pinned), so no stamped brief goes stale.
Re-run of the PM's 29 probes: vi-a, vi-b, vii-a, vii-b now stop; the rest
unchanged. The PM's p7c probe was a no-op (its replace string is not in the
base brief); vii-c is covered by its own test, red on 66e1fa1.

M2: /trekbrief --approve takes <project-dir | brief-file>. New
intent-approval.mjs --resolve decides: a directory → <dir>/brief.md and
/trekplan --project; a file → itself and /trekplan --brief. Phase 4h and the
stamp line use {BRIEF_PATH}; /trekplan's halt table names
/trekbrief --approve {brief_path}, which works on both brief modes, and so
does the gate's hint. HANDOVER-CONTRACTS' "open to every producer's brief"
is now true. Chose fixing the path over rewording the contract because two
real briefs (docs/*-brief.md) had no approval path at all.

Red ff760ed 9/29 → green 29/29. Suite 1219: 1217 pass / 0 fail / 2 skip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-23 09:22:03 +02:00
commit 20f32bb06b
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
7 changed files with 129 additions and 45 deletions

View file

@ -1,7 +1,7 @@
---
name: trekbrief
description: Interactive interview that produces a task brief with explicit research plan. Feeds /trekresearch and /trekplan. Optionally orchestrates the full pipeline end-to-end.
argument-hint: "[--quick] <task description> | --approve <project-dir>"
argument-hint: "[--quick] <task description> | --approve <project-dir | brief-file>"
allowed-tools: Agent, Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion
---
@ -51,20 +51,30 @@ Parse `$ARGUMENTS`:
by the autonomy-gate state machine via the CLI shim:
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/autonomy-gate.mjs --state X --event Y --gates {true|false}`.
4. `--approve <project-dir>`: set **mode = approve**. No interview, no
drafting. Set `PROJECT_DIR = <project-dir>` (trim trailing slash). If
`{PROJECT_DIR}/brief.md` does not exist, print
`Error: no brief at {PROJECT_DIR}/brief.md — run /trekbrief first.` and stop.
Otherwise jump straight to **Phase 4h**, run it, print its report, and
stop. This is the remedy `/trekplan` names when it halts on an unapproved
or stale intent.
4. `--approve <project-dir | brief-file>`: set **mode = approve**. No
interview, no drafting. Set `APPROVE_ARG` to the argument and resolve it:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/intent-approval.mjs --resolve "{APPROVE_ARG}"
```
A directory resolves to `<dir>/brief.md` (a `/trekplan --project` brief). A
file resolves to itself, whatever its name (a `/trekplan --brief` brief).
On exit 1, print its `error` and stop. Otherwise set `BRIEF_PATH`,
`PROJECT_DIR` and `PLAN_COMMAND` from `brief_path`, `project_dir` and
`plan_command`, jump straight to **Phase 4h**, run it, print its report,
and stop. This is the remedy `/trekplan` names when it halts on an
unapproved or stale intent, on both of its brief modes.
In every other mode, `BRIEF_PATH = {PROJECT_DIR}/brief.md` and
`PLAN_COMMAND = /trekplan --project {PROJECT_DIR}`.
If no task description is provided (and mode is not approve), output usage and stop:
```
Usage: /trekbrief <task description>
/trekbrief --quick <task description>
/trekbrief --approve <project-dir>
/trekbrief --approve <project-dir | brief-file>
Modes:
default Dynamic interview until quality gates pass — brief with research plan
@ -742,7 +752,7 @@ both paths pass through it — and it is **asked even in `--quick` mode**; there
is no skip path and no default answer.
1. Show the operator the `## Intent` and `## Goal` sections of
`{PROJECT_DIR}/brief.md` **verbatim** — the exact text the approval will
`{BRIEF_PATH}` **verbatim** — the exact text the approval will
bind to. Do not summarize them.
2. Ask via `AskUserQuestion` — one question:
**"Is this the intent /trekplan should plan against?"**
@ -750,14 +760,14 @@ is no skip path and no default answer.
| Option | Effect |
|--------|--------|
| **Approve** | Stamp the brief (step 3). |
| **Revise first** | The operator states the change; edit `## Intent` / `## Goal` in `brief.md`, re-run the Phase 4g validator, then return to step 1. |
| **Leave unapproved** | Write nothing. `/trekplan` will halt on this brief until `/trekbrief --approve {PROJECT_DIR}` is run. |
| **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:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/intent-approval.mjs --stamp "{PROJECT_DIR}/brief.md"
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/intent-approval.mjs --stamp "{BRIEF_PATH}"
```
This writes `intent_approved_hash` (sha256 over the normalized `## Intent`
@ -777,7 +787,7 @@ Intent: {approved (sha256:…12 hex) | left unapproved — /trekplan will halt}
```
In `--approve` mode, stop after the report and print the next step:
`/trekplan --project {PROJECT_DIR}` (only if approved).
`{PLAN_COMMAND}` (only if approved).
## Phase 5 — Auto-orchestration opt-in (if research_topics > 0)

View file

@ -191,9 +191,9 @@ agent, write no plan. Print each error's `message` and `hint`, then stop:
| Code | Meaning | What the operator does |
|------|---------|------------------------|
| `BRIEF_INTENT_NOT_APPROVED` | No approval marker (`intent_approved_hash`) in the brief | `/trekbrief --approve {project_dir}` — read `## Intent` + `## Goal`, answer "Approve" |
| `BRIEF_INTENT_NOT_APPROVED` | No approval marker (`intent_approved_hash`) in the brief | `/trekbrief --approve {brief_path}` — read `## Intent` + `## Goal`, answer "Approve" |
| `BRIEF_INTENT_APPROVAL_STALE` | `## Intent` or `## Goal` changed after approval | Same command — the approval covers the old text, not this one |
| `BRIEF_INTENT_APPROVAL_INVALID` | Marker malformed, or `## Intent` / `## Goal` missing or empty | Fix the brief, then the same command |
| `BRIEF_INTENT_APPROVAL_INVALID` | Marker malformed, or `## Intent` / `## Goal` missing, empty or present more than once | Fix the brief, then the same command |
If the command does not print the JSON report (module not found, node
missing, an unresolved `${CLAUDE_PLUGIN_ROOT}`), **halt** as well and show the