feat(review): the review is a gate - verdict in the file, CC engine, project memory
Ordre 5 (wave 1, P11 + P5), review_version 1.1. - K7: `verdict: ALLOW | WARN | BLOCK` is required in review.md (strict error, soft warning for 1.0 reviews). New CLI lib/review/verdict-gate.mjs: 0 = ALLOW/WARN, 1 = BLOCK, 2 = could not decide (never green); a verdict greener than the file's own BLOCKERs or JSON block is exit 2. Realpath main guard (the old file:// guard exits 0 under a symlink - measured). - No finding is struck for length: succinctness trims only MINOR/SUGGESTION text (catalogue tier), keeps BLOCKER/MAJOR whole, counts trims; the coordinator lists what it declined to judge. - Claude Code's /code-review and /security-review run as the engine via the Skill tool (never --fix/--comment/--post/ultra); lib/review/engine-findings.mjs turns their findings into a reviewer payload; two catalogue keys (CODE_REVIEW_DEFECT, SECURITY_REVIEW_VULNERABILITY), catalogue 12 -> 14. An engine that did not run forbids ALLOW. - Every BLOCKER is written as a numbered `### Step R<n>` with its source finding (strict error REVIEW_MISSING_REMEDIATION_STEP). - P5: lib/review/recurrence.mjs - a finding class in two review.md files (current + git history, no new store) becomes one proposal, routed by the catalogue's memory_route; written only between Voyage markers in .claude/rules/voyage-review-lessons.md under a size budget, CLAUDE.md gets a pointer only; the operator approves every line (Phase 8.7). - Workflow substrate: catalogue enum in step; a missing reviewer or engine forbids ALLOW there too (it returned ALLOW on zero findings before). - Fixtures review-run-A/B said WARN over a BLOCKER; now BLOCK. Borrowed and credited in code: Superpowers (declined-to-judge), ai-native-sdlc (cap only nits; mistake/fix/check), Spec Kit (managed block; converge steps), Pocock (retro routing), Kiro (verdict file + gate; evidence per lesson), Cherny (engine -> brief control order; review -> memory; budget). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c89a6b580
commit
d88245c06e
28 changed files with 2052 additions and 110 deletions
|
|
@ -46,7 +46,14 @@ trailing JSON block) ready to write to disk.
|
|||
You will receive a prompt containing:
|
||||
- **Reviewer outputs** — JSON-block payloads from
|
||||
`brief-conformance-reviewer` and `code-correctness-reviewer` (in `quick`
|
||||
mode, only the latter).
|
||||
mode, only the latter), plus the engine payloads from Claude Code's own
|
||||
review: `claude-code-review` (`/code-review`) and `claude-security-review`
|
||||
(`/security-review`, not in `quick`). Engine findings carry the catalogue
|
||||
keys `CODE_REVIEW_DEFECT` / `SECURITY_REVIEW_VULNERABILITY` (or a narrower
|
||||
key) and are treated exactly like any reviewer's findings.
|
||||
- **Expected reviewers** — the names that were due, engines included, and
|
||||
each engine recorded as not run with its reason. A name that did not report
|
||||
forbids `ALLOW` (Pass 4).
|
||||
- **Triage map** — `{file → deep-review|summary-only|skip, reason}` from
|
||||
the /trekreview triage gate.
|
||||
- **Brief metadata** — `task`, `slug`, `project_dir`, `brief_path` from
|
||||
|
|
@ -89,7 +96,7 @@ is load-bearing** — see *Suppression is two-valued* below:
|
|||
|
||||
| Filter | Test | Fails if | Removed as |
|
||||
|--------|------|----------|------------|
|
||||
| Succinctness | `title.length ≤ 100` and `detail.length ≤ 800` chars | Title is a paragraph or detail is a wall of text | `unverified` (`succinctness:title` / `succinctness:detail`) |
|
||||
| Succinctness | `title.length ≤ 100` and `detail.length ≤ 800` chars | Title is a paragraph or detail is a wall of text | **never removed.** MINOR/SUGGESTION (catalogue tier): the text is trimmed (ending `[…trimmed]`), the finding is kept and counted. BLOCKER/MAJOR: kept whole, every character. |
|
||||
| Accuracy | `file` resolves under the repo root AND `line` is plausible (≥ 0; ≤ file line count when known) | Path traversal escape, negative line, or impossibly large line number | **dropped** (`accuracy:refuted`) |
|
||||
| Actionability | `recommended_action` is non-empty AND begins with an imperative verb | Empty action, "consider …" hedges, or restating the title | `unverified` (`actionability:empty`) |
|
||||
|
||||
|
|
@ -98,6 +105,15 @@ examines the claim, so neither can establish the finding is unreal. Accuracy
|
|||
does: a citation that escapes the repo root refutes the finding as a claim
|
||||
about this codebase.
|
||||
|
||||
**No finding is struck for length** (ordre 5). Before review_version 1.1 an
|
||||
over-long `detail` sent a finding to `unverified`, and S118's most serious
|
||||
finding fell out of the Handover 6 list that way. Length is packaging, never
|
||||
a claim: only small findings are trimmed, and the trims are counted
|
||||
(`trimmed` in the JSON block). The trim decision reads the **catalogue** tier,
|
||||
so a mislabelled MAJOR is never cut. Borrowed: "report at most five nits …
|
||||
summarize the rest as a count" — cap only the nits (ai-native-sdlc
|
||||
`assets/REVIEW.md:7-23`, MIT).
|
||||
|
||||
When removing a finding, preserve a one-line note in the
|
||||
`Suppressed Findings` body section so the user knows why the count
|
||||
shrank.
|
||||
|
|
@ -136,7 +152,7 @@ reasonableness filtering is bypassed. To prevent unknown rule_keys
|
|||
from polluting downstream plans, the coordinator MUST substitute any
|
||||
rule_key not exported from `lib/review/rule-catalogue.mjs:RULE_KEYS`
|
||||
with the literal string `PLAN_EXECUTE_DRIFT` (the most general drift
|
||||
category from the 12-entry catalogue). The original rule_key is
|
||||
category in the catalogue). The original rule_key is
|
||||
preserved in the finding's `original_rule_key` field for diagnostic
|
||||
purposes. This normalization happens BEFORE writing review.md,
|
||||
ensuring all `rule_key` values in the final review match the
|
||||
|
|
@ -186,12 +202,15 @@ BLOCKER.
|
|||
|
||||
**When `ALLOW` is withheld, the Executive Summary's FIRST sentence must say
|
||||
so and name why** — e.g. "WARN: no blocking findings survived, but 1 finding
|
||||
could not be verified (succinctness:title) and brief-conformance-reviewer did
|
||||
not report." A withheld ALLOW that the reader cannot see is the same defect
|
||||
could not be verified (actionability:empty) and claude-security-review did
|
||||
not run (no origin remote)." A withheld ALLOW that the reader cannot see is the same defect
|
||||
in a new place.
|
||||
|
||||
Verdict is mechanical — never override. The verdict goes into the
|
||||
trailing JSON block AND the Executive Summary's first sentence.
|
||||
frontmatter `verdict:` field (REQUIRED, K7), the trailing JSON block AND the
|
||||
Executive Summary's first sentence — the same value in all three.
|
||||
`lib/review/verdict-gate.mjs` reads the frontmatter and refuses (exit 2,
|
||||
never green) a file whose JSON block or BLOCKER findings contradict it.
|
||||
|
||||
## Output: review.md content
|
||||
|
||||
|
|
@ -203,7 +222,7 @@ Produce the full review.md content as your output. The
|
|||
```yaml
|
||||
---
|
||||
type: trekreview
|
||||
review_version: "1.0"
|
||||
review_version: "1.1"
|
||||
created: {YYYY-MM-DD}
|
||||
task: "{from brief frontmatter}"
|
||||
slug: {from brief frontmatter}
|
||||
|
|
@ -212,6 +231,7 @@ brief_path: {brief_path from input}
|
|||
scope_sha_start: {scope_sha_start or null if mtime fallback}
|
||||
scope_sha_end: {scope_sha_end}
|
||||
reviewed_files_count: {N}
|
||||
verdict: {BLOCK | WARN | ALLOW}
|
||||
findings:
|
||||
- {finding-id-1-40-char-hex}
|
||||
- {finding-id-2-40-char-hex}
|
||||
|
|
@ -219,7 +239,9 @@ findings:
|
|||
```
|
||||
|
||||
The `findings:` field MUST use block-style YAML (one ID per line, ` - `
|
||||
prefix). Flow-style `findings: [a, b]` breaks the frontmatter parser.
|
||||
prefix). Flow-style `findings: [a, b]` breaks the frontmatter parser. With
|
||||
zero findings write `findings: []` — the empty list is the one inline form
|
||||
the parser reads; a bare `findings:` is null and fails validation.
|
||||
|
||||
### Body sections (in order)
|
||||
|
||||
|
|
@ -240,7 +262,27 @@ prefix). Flow-style `findings: [a, b]` breaks the frontmatter parser.
|
|||
Pass 2 or Pass 3, with the reason AND its fate, tagged `[dropped]` or
|
||||
`[unverified]`. Unverified lines come first: they are the ones that
|
||||
withheld `ALLOW`.
|
||||
9. `## Remediation Summary` — bullet count per severity + 1 sentence on
|
||||
9. `## Declined to Judge` — REQUIRED. One line per finding you set aside
|
||||
without ruling on it (every `[unverified]` one: `file:line rule_key —
|
||||
reason`), then one line `Trimmed: N` (MINOR/SUGGESTION text cut, findings
|
||||
kept). Write `None.` plus `Trimmed: 0` when empty. Borrowed: "Declined to
|
||||
judge … nothing you set aside is dropped silently" (Superpowers
|
||||
`skills/requesting-code-review/code-reviewer.md:43-48`, MIT).
|
||||
10. `## Remediation Steps` — REQUIRED when there is at least one BLOCKER. One
|
||||
numbered step per BLOCKER, in severity-then-file order, written so
|
||||
Handover 6 can append it to a plan unchanged:
|
||||
```
|
||||
### Step R1: {recommended_action, imperative}
|
||||
- **Files:** `{file}`
|
||||
- **Source finding:** `{40-char id}`
|
||||
- **Test first:** {a test that reproduces the finding and fails today}
|
||||
- **Verify:** {command that proves it fixed; default: re-run /trekreview → finding `{id}` absent}
|
||||
- **On failure:** escalate — the finding needs the operator, not improvisation
|
||||
```
|
||||
Steps are appended, numbered and never reworded across iterations
|
||||
(Spec Kit `/converge`, MIT). The strict validator fails a BLOCKER without
|
||||
its step (`REVIEW_MISSING_REMEDIATION_STEP`).
|
||||
11. `## Remediation Summary` — bullet count per severity + 1 sentence on
|
||||
what /trekplan will consume.
|
||||
|
||||
Each Findings subsection uses the `### {finding-id-40-char-hex}` heading
|
||||
|
|
@ -261,7 +303,9 @@ The LAST fenced block in the file is a `json` block:
|
|||
{
|
||||
"verdict": "BLOCK | WARN | ALLOW",
|
||||
"counts": { "BLOCKER": N, "MAJOR": N, "MINOR": N, "SUGGESTION": N },
|
||||
"allow_blocked_by": ["unverified:succinctness:title (1)", "missing-reviewer:brief-conformance-reviewer", "unattributable-payload (1)"],
|
||||
"allow_blocked_by": ["unverified:actionability:empty (1)", "missing-reviewer:claude-security-review", "unattributable-payload (1)"],
|
||||
"declined_to_judge": [{ "file": "lib/x.mjs", "line": 3, "rule_key": "MISSING_TEST", "reason": "actionability:empty" }],
|
||||
"trimmed": 0,
|
||||
"findings": [
|
||||
{
|
||||
"id": "<40-char-hex>",
|
||||
|
|
@ -303,8 +347,10 @@ for the ID list.
|
|||
`findings:` list and the JSON block IDs must match.
|
||||
- **Suppressed findings are accountable.** When you remove a finding via
|
||||
Pass 2 or Pass 3, log it in `## Suppressed Findings` with the reason and
|
||||
its fate (`[dropped]` / `[unverified]`). Silent drops break the audit
|
||||
trail.
|
||||
its fate (`[dropped]` / `[unverified]`), and list every `[unverified]` one
|
||||
again under `## Declined to Judge`. Silent drops break the audit trail.
|
||||
- **No finding is struck for length.** Trim only MINOR/SUGGESTION text and
|
||||
count it; a long BLOCKER or MAJOR keeps every character.
|
||||
- **Never spend an unexamined finding as evidence of a clean review.** If a
|
||||
removal did not refute the finding, it is `unverified` and `ALLOW` is off
|
||||
the table. This is the one place where you may not be minimal: when in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue