feat(plan): plan 1.8, the plan gate, the design part and the plan-side checks
Wave 1, order 3 (P3 plan mode as the start, P2 design as its own part, P9
plan side). Contracts K3-K6 for the later merges.
- plan_version 1.8 (1.7 plans stay valid): Test first on every step, one
End-to-end ("run the thing") step, optional Interface in / Interface out /
Review focus fields, a ceremony class in the header (absent =
architectural), and for architectural plans a ## Design part: module map
without cycles, architecture rules that name their enforcer and the step
that proves them, a requirements check asked as keep / change.
- K3 lib/validators/plan-gate.mjs: the critic verdict is recorded INSIDE the
plan (Claude Code's plan approval hands a hook only the plan file's text),
bound to the hash taken before the reviewers read it; 0 go / 1 stop / 2
could not run; at most 3 critic rounds, then stop and ask.
- K5 lib/verification/arch-rules.mjs: runs the target repo's own
dependency-cruiser or import-linter (installs nothing; 2 = not measured,
never green); --prove --rule shows pass -> fail on a planted violation of
that rule -> pass. Verified against dependency-cruiser 18.5.0 and
import-linter 2.15; madge 8.0.0 left out (missed a planted .mjs cycle).
- K6 lib/verification/plan-scope-sweep.mjs: changed files no step names.
- /trekplan Phases 8-10: design part, bounded critic loop, the gate before
"execute", a plan-mode path through ExitPlanMode (not yet run end to end
inside plan mode). plan-critic 10 -> 12 dimensions; scope-guardian checks
the ceremony.
- Fixed on the way: plan-validator and plan-review-dedup used the
file://argv[1] main guard (no-op under a symlink or a space); CRLF plans
failed every manifest; the dedup dropped finding severity.
Borrowed, credited in place: obra/superpowers, Kiro (docs), addyosmani/
agent-skills, mattpocock/skills, GitHub Spec Kit, bashebr/ai-native-sdlc,
Boris Cherny's public threads.
Suite 1497 (1495 / 0 / 2), 86 new tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c89a6b580
commit
f5ca220bc0
24 changed files with 3115 additions and 61 deletions
|
|
@ -580,6 +580,8 @@ Do NOT write this synthesis to disk. It is internal working context only.
|
|||
> - **Files:** `src/middleware/jwt.ts`
|
||||
> - **Changes:** Create new middleware function `verifyJWT(req, res, next)` that reads `Authorization: Bearer <token>` header, verifies signature with `process.env.JWT_SECRET`, attaches decoded payload to `req.user`, and returns 401 on invalid/missing token. (new file)
|
||||
> - **Reuses:** `jsonwebtoken.verify()` (already in package.json), pattern from `src/middleware/cors.ts`
|
||||
> - **Interface out:** `verifyJWT(req, res, next)`; sets `req.user = {id, roles}` — read by Step 2
|
||||
> - **Review focus:** an expired token and a token signed with another key — pinned by `src/middleware/jwt.test.ts`
|
||||
> - **Test first:**
|
||||
> - File: `src/middleware/jwt.test.ts` (new)
|
||||
> - Verifies: valid token attaches user; invalid token returns 401; missing header returns 401
|
||||
|
|
@ -604,6 +606,14 @@ Do NOT write this synthesis to disk. It is internal working context only.
|
|||
> ```
|
||||
> ````
|
||||
>
|
||||
> **plan_version 1.8 additions** (the validator enforces them; 1.7 plans stay
|
||||
> valid): `Test first:` with `File:` and `Verifies:` on EVERY step; exactly the
|
||||
> optional fields `Interface in:`, `Interface out:`, `Review focus:` (never
|
||||
> empty when present); at least one step with `End-to-end:` and a backticked
|
||||
> command — the "run the thing" step; the header line carries
|
||||
> `` `plan_version: 1.8` · `ceremony: <class>` ``; an architectural plan carries
|
||||
> `## Design` (see "Design part" below).
|
||||
>
|
||||
> **Validator self-check (mandatory after writing `plan.md`):** run
|
||||
> `node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-validator.mjs --strict --json {plan_path}`
|
||||
> and re-revise the plan if it fails. The validator is the source of truth for
|
||||
|
|
@ -614,12 +624,20 @@ Do NOT write this synthesis to disk. It is internal working context only.
|
|||
Read the brief file (from `--brief` or `--project`).
|
||||
Read the plan template: @${CLAUDE_PLUGIN_ROOT}/templates/plan-template.md
|
||||
|
||||
**Ceremony.** Read `ceremony:` from the brief's frontmatter: `small`,
|
||||
`bounded` or `architectural`. Absent means `architectural` (heaviest on doubt).
|
||||
Write it into the plan's header line. Only `architectural` gets the design part.
|
||||
|
||||
Write the plan following the template structure. The plan MUST include:
|
||||
|
||||
### Required sections
|
||||
|
||||
1. **Context** — Why this change is needed. Use the brief's **Intent** verbatim
|
||||
or tightly paraphrased. The plan's motivation must trace directly to the brief.
|
||||
**Global Constraints** follows it: the brief's Constraints and NFRs that bind
|
||||
more than one step, quoted verbatim (exact values, never paraphrased).
|
||||
**Design** follows that, for `ceremony: architectural` only — see "Design
|
||||
part" below.
|
||||
2. **Codebase Analysis** — Tech stack, patterns, relevant files, reusable code,
|
||||
external tech researched. Every file path must be real (verified during exploration).
|
||||
3. **Research Sources** — If any research briefs or research-scout was used: table
|
||||
|
|
@ -630,8 +648,27 @@ Write the plan following the template structure. The plan MUST include:
|
|||
- Which existing code to reuse
|
||||
- Dependencies on other steps
|
||||
- Whether the step is based on codebase analysis or external research
|
||||
- **Test first:** — the test file and what it verifies, on EVERY step
|
||||
- **Interface in:** / **Interface out:** — what the step consumes (and which
|
||||
earlier step or existing code produces it) and what it produces for later
|
||||
steps. Write them whenever a step hands something to another step; a
|
||||
step's `Interface in:` must match an earlier `Interface out:`.
|
||||
- **Review focus:** — inputs the brief implies but no test exercises yet,
|
||||
each pinned to a test; and any Global Constraint the step could break,
|
||||
quoted verbatim.
|
||||
- **On failure:** — recovery action (revert/retry/skip/escalate)
|
||||
- **Checkpoint:** — git commit command after success
|
||||
|
||||
Steps are vertical slices: each cuts a narrow but complete path through the
|
||||
layers it touches and is verifiable on its own. Never "all the tests, then
|
||||
all the code". Preparatory refactoring comes first as its own steps; a wide
|
||||
change goes expand → migrate → contract.
|
||||
|
||||
**The "run the thing" step.** At least one step carries
|
||||
`End-to-end: \`<command>\` → expected: <what the user sees>`: the product run
|
||||
the way a user runs it (the CLI, the server and a request, the app; for iOS,
|
||||
the simulator). Without it the result is "not verified end to end", never
|
||||
green.
|
||||
10. **Execution Strategy** — For plans with > 5 steps: group steps into sessions
|
||||
(3–5 steps each), organize sessions into waves (parallel where independent),
|
||||
specify scope fences per session. Omit for plans with ≤ 5 steps.
|
||||
|
|
@ -680,6 +717,51 @@ Write the plan following the template structure. The plan MUST include:
|
|||
- Research-based decisions must cite their source
|
||||
- Every implementation decision must be traceable to a brief section (Intent,
|
||||
Goal, Constraint, Preference, NFR, or Success Criterion)
|
||||
- Interfaces are consistent across steps: nothing is consumed before a step or
|
||||
existing code produces it, under the same name and type
|
||||
|
||||
Borrowed, with credit: the per-step interfaces, Global Constraints quoted
|
||||
verbatim and Review Focus from obra/superpowers@8ca22db writing-plans (MIT,
|
||||
© 2025 Jesse Vincent); quoting constraints into the step from GitHub Spec Kit
|
||||
(MIT); vertical slices and prefactoring first from mattpocock/skills@c55ee46
|
||||
to-tickets (MIT, © 2026 Matt Pocock); "plan the verification too" — the run
|
||||
step — from Boris Cherny's public Claude Code threads; test-first as a rule
|
||||
from Spec Kit, made a validator check here instead of a rule the model ticks.
|
||||
|
||||
### Design part (ceremony: architectural)
|
||||
|
||||
Read the design template: @${CLAUDE_PLUGIN_ROOT}/templates/design-template.md
|
||||
|
||||
Write `## Design` between `## Global Constraints` and `## Implementation Plan`
|
||||
with the template's subsections: Architecture, Data Flow, Interfaces, Data
|
||||
Models, Error Handling, Module Map, Architecture Rules, Requirements Check
|
||||
(Decisions (ADR) and Gotchas only when they apply). The test strategy is the
|
||||
plan's own `## Test Strategy`.
|
||||
|
||||
- **Derived from:** the brief path and the first 12 hex of its sha256
|
||||
(`shasum -a 256 "{brief_path}"`).
|
||||
- **Module map:** a mermaid graph; dependencies point one way, no cycle.
|
||||
- **Architecture rules:** first see what the target repo can check, read-only:
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/verification/arch-rules.mjs --repo .
|
||||
```
|
||||
Exit 0 or 1: the repo has dependency-cruiser or import-linter configured with
|
||||
a local binary, so a rule can be "checked by" it. Exit 2: nothing to check
|
||||
with — a rule is then "advisory — `.claude/rules/<file>.md`" (a plan step
|
||||
writes that file), or "checked by" a test or linter the repo already has.
|
||||
Voyage installs nothing; adding a tool is a plan step the operator approves.
|
||||
Every checked rule names the step (Proof column) that shows it pass, fail on
|
||||
a planted violation and pass again. For the two supported tools that step's
|
||||
Verify is
|
||||
`node ${CLAUDE_PLUGIN_ROOT}/lib/verification/arch-rules.mjs --repo . --prove --rule <rule name> --plant <violation file>=<dest>`
|
||||
(exit 0 = proven: the planted run tripped THAT rule; the plant is removed
|
||||
afterwards, and is never written over an existing path or outside the repo).
|
||||
- **Requirements Check:** compare the brief's requirements with each other.
|
||||
For each conflict or gap ask the operator ONE two-choice question with
|
||||
AskUserQuestion, recommendation first: "keep" (as written) or "change like
|
||||
this" (a concrete proposal). Record the answers in the table. If an answer
|
||||
changes the brief's Intent, Goal or Success Criteria, stop: the brief is
|
||||
re-run through `/trekbrief` — the planner never edits it.
|
||||
|
||||
### Write the plan
|
||||
|
||||
|
|
@ -689,9 +771,30 @@ Use the plan destination computed in Phase 3:
|
|||
|
||||
Create the parent directory if it does not exist.
|
||||
|
||||
## Phase 9 — Adversarial review
|
||||
**In plan mode** (Claude Code has told this session it is in plan mode and
|
||||
named its plan file): edits outside that plan file are blocked until the
|
||||
operator approves, so write the plan to Claude Code's plan file instead and run
|
||||
Phases 9 and 10 against it. Phase 10 copies it to the destination after the
|
||||
approval.
|
||||
|
||||
Launch two review agents **in parallel — emit both Agent tool calls in a
|
||||
## Phase 9 — Adversarial review (bounded critic loop)
|
||||
|
||||
Phase 9 is a loop of at most **3 critic rounds**. Each round reviews the plan
|
||||
as it stands, and a command — not this session's judgment — records the
|
||||
verdict and decides whether the plan may go on. The loop ends on a round with
|
||||
no BLOCKER; after round 3 with a BLOCKER still open it stops and asks the
|
||||
operator. It never goes on silently.
|
||||
|
||||
**One round:**
|
||||
|
||||
First take the hash of the plan the reviewers are about to read, and keep the
|
||||
printed value for this round:
|
||||
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-gate.mjs --hash "{plan_path}"
|
||||
```
|
||||
|
||||
Then launch two review agents **in parallel — emit both Agent tool calls in a
|
||||
single assistant message turn** (same pattern as Phase 5 exploration). They
|
||||
have zero data dependencies; serializing them wastes 30–60 seconds per run.
|
||||
|
||||
|
|
@ -717,31 +820,77 @@ tool — return the block inline, do not write a file."
|
|||
After both complete, **extract each reviewer's trailing `json` findings block**
|
||||
and pipe both into the dedup helper via **stdin** — the reviewers are read-only
|
||||
(`Read/Glob/Grep`, no `Write`), so they cannot persist temp files; the
|
||||
orchestrator does the persistence by piping their inline blocks:
|
||||
orchestrator does the persistence by piping their inline blocks. The dedup
|
||||
output goes straight on to the plan gate, which writes this round's verdict
|
||||
into the plan's `## Critic Verdict` section:
|
||||
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/review/plan-review-dedup.mjs --stdin <<'JSON'
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/review/plan-review-dedup.mjs --stdin <<'JSON' | node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-gate.mjs --record "{plan_path}" --reviewed {the hash from --hash}
|
||||
{ "plan_critic": <plan-critic's json block>, "scope_guardian": <scope-guardian's json block> }
|
||||
JSON
|
||||
```
|
||||
|
||||
`plan-gate.mjs --record` prints one JSON line: `round`, `verdict`
|
||||
(`BLOCK` = a blocker is open, `REVISE` = majors only, `PASS`) and `counts`.
|
||||
Exit 1 means the round cap is spent (see "After round 3"). Exit 2 means the
|
||||
round cannot be recorded: broken input, a reviewer missing from it (an absent
|
||||
review is not a clean one), or a plan that changed after `--hash` — the
|
||||
verdict would bless text nobody reviewed. Show the message and rerun the round
|
||||
on the plan as it is; never record around it.
|
||||
|
||||
The helper **exits non-zero on malformed stdin** — a broken hand-off surfaces
|
||||
loudly instead of collapsing into a silently-empty merge (the historical Phase-9
|
||||
defect, where the read-only reviewers never wrote the files and the dedup ran on
|
||||
nothing). The merged array attributes each finding to `[plan-critic, scope-guardian]`
|
||||
when both reviewers raised the same issue (exact match on `file:line:rule_key`,
|
||||
or Jaccard ≥ 0.7 on text tokens). Revise the plan once for the merged set, not
|
||||
twice for the duplicates. Source: research/05 R1 + R2.
|
||||
or Jaccard ≥ 0.7 on text tokens), and keeps the more severe of the two
|
||||
severities. Revise the plan once for the merged set, not twice for the
|
||||
duplicates. Source: research/05 R1 + R2.
|
||||
|
||||
After both complete:
|
||||
- If **blockers** are found: revise the plan to address them. Add a "Revisions"
|
||||
note at the bottom of the plan listing what changed and why.
|
||||
- If only **major** issues: revise to address them. Add revisions note.
|
||||
- If only **minor** issues or clean: proceed without changes. Note the
|
||||
review result in the plan.
|
||||
**After each round**, by the recorded verdict:
|
||||
- **BLOCK** or **REVISE**, rounds left: revise the plan for the merged
|
||||
findings, add rows to `## Revisions` (what changed and why), and run the
|
||||
next round. Any edit outside the review log — a step, the design, the
|
||||
verification — makes the recorded verdict stale, so a revised plan is always
|
||||
re-reviewed.
|
||||
- **PASS** (minors only, or clean): fill in the quality header and
|
||||
`## Plan Quality Score` from plan-critic's score. These and `## Revisions`
|
||||
are the review log and do not stale the verdict. Go to Phase 10.
|
||||
- **REVISE after the last round**: do not edit the plan again (that would
|
||||
stale the verdict); carry the open majors into the Phase 10 summary as known.
|
||||
|
||||
**After round 3 with a BLOCKER open**, `plan-gate.mjs` reports
|
||||
`PLAN_GATE_ROUNDS_EXHAUSTED` and `--record` refuses a fourth round. Stop and ask
|
||||
the operator with AskUserQuestion — the open blockers listed in plain words,
|
||||
recommendation first:
|
||||
- **One more round** — revise for the listed blockers, then record with
|
||||
`--extra-round` (only after this yes; one yes = one round; the gate refuses
|
||||
the flag before the cap is spent).
|
||||
- **Stop here** — the plan is not ready; it stays saved with its verdict, and
|
||||
`/trekexecute` is not offered.
|
||||
|
||||
Then check the plan with the gate (K3), which every later step relies on:
|
||||
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-gate.mjs --json "{plan_path}"
|
||||
```
|
||||
|
||||
Exit 0 = the plan validates, the verdict matches it, and no BLOCKER is open.
|
||||
Exit 1 = stop; its `reasons` say why. Exit 2 = it could not read the plan.
|
||||
|
||||
Borrowed, with credit: the bounded review loop that ends only on a recorded
|
||||
verdict, never on a claim in prose, from Kiro's `design-loop` workflow pattern
|
||||
(kiro.dev, docs only); the independent staff-engineer review and "re-plan
|
||||
instead of pushing on" from Boris Cherny's public Claude Code threads.
|
||||
|
||||
## Phase 10 — Present and refine
|
||||
|
||||
Run the gate (K3) on the plan first; its exit code decides what may be offered:
|
||||
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-gate.mjs --json "{plan_path}"
|
||||
```
|
||||
|
||||
Present a summary to the user:
|
||||
|
||||
```
|
||||
|
|
@ -768,20 +917,60 @@ Present a summary to the user:
|
|||
{Summary of external research + attached research briefs, or "No external research used."}
|
||||
|
||||
### Adversarial review
|
||||
**Critic rounds:** {N} of {max} — last verdict {BLOCK | REVISE | PASS}
|
||||
**Plan critic:** {Summary — blockers/majors/minors found, how addressed}
|
||||
**Scope guardian:** {Summary — creep/gaps found, how addressed}
|
||||
**Open majors:** {list, or "none"}
|
||||
**Plan gate (K3):** {PASS | STOP — the reasons}
|
||||
|
||||
You can:
|
||||
- Ask questions or request changes to refine the plan
|
||||
- Say **"execute"** to start implementing
|
||||
- Say **"execute"** to start implementing ← only when the gate exited 0
|
||||
- Say **"save"** to keep the plan for later
|
||||
```
|
||||
|
||||
When the gate exited 1, replace the "execute" line with
|
||||
`Execution is blocked: {the gate's reasons}` and do not start implementing,
|
||||
whatever is said next, until the gate exits 0.
|
||||
|
||||
**What counts as approval.** "Execute", "go" or "start" (or the plan-mode
|
||||
approval below) approves the plan. A hedged reply — "sounds good", "looks
|
||||
reasonable", "do what you think", "I guess" — is not approval: ask once,
|
||||
plainly, whether to execute. (Borrowed: addyosmani/agent-skills@9d0c60d build
|
||||
command, MIT, © 2025 Addy Osmani.)
|
||||
|
||||
If the user asks questions or requests changes:
|
||||
- Update the plan file in-place
|
||||
- Show what changed
|
||||
- A change outside the review log makes the verdict stale: run one Phase 9
|
||||
round on the changed plan (it counts toward the 3), then re-run the gate
|
||||
- Re-present the summary
|
||||
|
||||
### In plan mode
|
||||
|
||||
When this session is in Claude Code's plan mode, the plan sits in Claude
|
||||
Code's plan file (Phase 8), and approval goes through Claude Code's own
|
||||
plan-approval prompt:
|
||||
|
||||
1. Run the gate on the plan file. Exit 1: do not present the plan for approval;
|
||||
fix it (Phase 9) or, after round 3, ask the operator as Phase 9 says.
|
||||
2. Exit 0: present the plan with `ExitPlanMode`. Claude Code shows the operator
|
||||
the plan file and asks how to proceed. A hook on that approval (when one is
|
||||
installed) runs the same gate on the plan file's text and can send a bad
|
||||
plan back.
|
||||
3. **Approved:** the session leaves plan mode. Copy the plan file to the plan
|
||||
destination from Phase 3, then run the gate on the destination — it must
|
||||
exit 0 again, which also proves the copy is the reviewed text. Then
|
||||
continue with the operator's choice (Phase 11).
|
||||
4. **"No, keep planning":** revise per the operator's words, run one Phase 9
|
||||
round, and present again from step 1.
|
||||
|
||||
Without plan mode, all of the above happens in this chat, as described first.
|
||||
The summary and the annotation HTML below are produced either way. **Not
|
||||
verified:** that `/trekplan` runs end to end inside plan mode (exploration
|
||||
agents, the gate's Bash calls and `--record` writing to the plan file all under
|
||||
plan mode's restrictions). Until an operator has run it, both paths stay.
|
||||
|
||||
### Build the operator-annotation HTML and print the link
|
||||
|
||||
After the plan summary, run `scripts/annotate.mjs` to produce a
|
||||
|
|
@ -871,7 +1060,8 @@ JSON
|
|||
```
|
||||
|
||||
`slug` is the brief's `slug`, so this record pairs with the brief's
|
||||
`brief-approved` and `trekbrief` records. The writer prints
|
||||
`brief-approved` and `trekbrief` records. `critic_verdict` is the `verdict` of
|
||||
the last round `plan-gate.mjs --record` wrote into the plan. The writer prints
|
||||
`{"written": true, ...}`. On `"written": false` (exit 1), print its `reason` in
|
||||
one line and continue. A tracking failure never blocks the main workflow, but
|
||||
it is reported, not skipped silently.
|
||||
|
|
@ -974,7 +1164,10 @@ Standard and low effort: do NOT emit the section at all.
|
|||
brief (Intent, Goal, Constraint, Preference, NFR, Success Criterion). If a
|
||||
step has no brief basis, it is scope creep — flag it or remove it.
|
||||
- **No interview**: Never ask the user requirements questions. If the brief is
|
||||
inadequate, stop and ask the user to run `/trekbrief` again.
|
||||
inadequate, stop and ask the user to run `/trekbrief` again. The one
|
||||
exception is the design part's Requirements Check: one two-choice question
|
||||
("keep" / "change like this") per conflict or gap found between the brief's
|
||||
own requirements.
|
||||
- **Scope**: Only explore the current working directory and its subdirectories.
|
||||
Never read files outside the repo (no ~/.env, no credentials, no other repos).
|
||||
- **Cost**: Model resolution at Agent-spawn sites is a three-layer fallback:
|
||||
|
|
@ -985,7 +1178,12 @@ Standard and low effort: do NOT emit the section at all.
|
|||
- **Privacy**: Never log, store, or repeat file contents that look like
|
||||
secrets, tokens, or credentials. Never log prompt text.
|
||||
- **No premature execution**: Do not modify any project files until the user
|
||||
explicitly approves the plan.
|
||||
explicitly approves the plan, and never while `plan-gate.mjs` exits non-zero
|
||||
on it.
|
||||
- **Bounded critic loop**: at most 3 critic rounds (Phase 9). After the third
|
||||
with a BLOCKER open, stop and ask the operator; never continue silently, and
|
||||
never write or edit the `## Critic Verdict` block by hand — only
|
||||
`plan-gate.mjs --record` writes it.
|
||||
- **Plan stands alone**: The plan file must be understandable without access
|
||||
to the exploration results. Include all necessary context.
|
||||
- **Honesty**: If exploration reveals the task is trivial (single file, obvious
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue