docs(review): /trekreview chooses its security engine (E 3.2)
Phase 4.7 of /trekreview runs security-engine.mjs choose first. Claude
Security runs through its own skill (scan changes --base {before_sha}
--effort high); its cost question goes to the operator, and "No", a
question nobody can answer or a missing Workflow tool records it as not
run, never a fallback to /security-review. /security-review runs only
when choose picked it. Its report is converted by security-engine.mjs
convert, never transcribed; below_threshold is declined as
engine:below-threshold. With no engine the expected reviewer
security-engine never reports, so the verdict gate exits 2 and the
summary says "security: not measured" and why, never 0. --quick keeps no
security engine. The Engine summary line, the template's ran line, the
hard rules (the engine's report folder, scan changes only) and the
allowed-tools (Workflow) follow.
Engine prose follows in commands/trekexecute.md, docs/command-modes.md,
docs/architecture.md, CLAUDE.md, README.md, SECURITY.md and Handover 6:
Claude Security recommended, proprietary so called by name and never a
manifest dependency; without it /security-review; with neither "not
measured".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cb7c13b408
commit
d97e9b5b19
10 changed files with 279 additions and 49 deletions
|
|
@ -34,7 +34,7 @@ Voyage — a contract-driven Claude Code pipeline: brief, research, plan, execut
|
||||||
| `/trekresearch` | Research — deep local + external research, produces structured research brief. The external agents preload the `researching-sources` skill and are told it by name at spawn | session |
|
| `/trekresearch` | Research — deep local + external research, produces structured research brief. The external agents preload the `researching-sources` skill and are told it by name at spawn | session |
|
||||||
| `/trekplan` | Plan — brief-reviewer, explore, plan, review. Requires `--brief` or `--project`; halts unless the brief's intent is approved (hash-bound, stale on edit). Auto-discovers `architecture/overview.md` if present. Plan 1.9: the file first on every line — every `Files:` entry and `Test first:` `File:` line opens with its backticked path(s). From 1.8: `Test first:` per step + one `End-to-end:` step; every check written by `voyage:writing-criteria` (named where the plan is written and where its three reviewers start); a design part for `ceremony: architectural` (absent = architectural); critic loop of max 3 rounds, recorded in the plan, rounds 2+ in diff mode (rule A, counted by the gate; rule B in the reviewers), and `plan-gate.mjs` (0 go / 1 stop / 2 could not run) decides; open minors after it are listed for the operator to pick | session |
|
| `/trekplan` | Plan — brief-reviewer, explore, plan, review. Requires `--brief` or `--project`; halts unless the brief's intent is approved (hash-bound, stale on edit). Auto-discovers `architecture/overview.md` if present. Plan 1.9: the file first on every line — every `Files:` entry and `Test first:` `File:` line opens with its backticked path(s). From 1.8: `Test first:` per step + one `End-to-end:` step; every check written by `voyage:writing-criteria` (named where the plan is written and where its three reviewers start); a design part for `ceremony: architectural` (absent = architectural); critic loop of max 3 rounds, recorded in the plan, rounds 2+ in diff mode (rule A, counted by the gate; rule B in the reviewers), and `plan-gate.mjs` (0 go / 1 stop / 2 could not run) decides; open minors after it are listed for the operator to pick | session |
|
||||||
| `/trekexecute` | Execute — disciplined plan/session-spec executor: refuses a plan whose logged approval does not cover its current sha256 (K2); one fresh `step-implementer` helper per plan step, one controller that reads the diff (spec + quality verdicts), proof of red / floor guard / step check (K9) / `Regression:` (K10) / done line as commands, fix rounds that go up (max 5: helper → plan → operator), architecture rules (K5) + plan-file sweep (K6) after the last step, the app seen with Claude Code's built-in `run` while the `End-to-end:` command stays the verdict (`run` gives no exit code), then its own review (`/simplify` → `/trekreview` → verdict gate, K7; BLOCK keeps the merge into main closed), closed stop list + decision log in the final report | session |
|
| `/trekexecute` | Execute — disciplined plan/session-spec executor: refuses a plan whose logged approval does not cover its current sha256 (K2); one fresh `step-implementer` helper per plan step, one controller that reads the diff (spec + quality verdicts), proof of red / floor guard / step check (K9) / `Regression:` (K10) / done line as commands, fix rounds that go up (max 5: helper → plan → operator), architecture rules (K5) + plan-file sweep (K6) after the last step, the app seen with Claude Code's built-in `run` while the `End-to-end:` command stays the verdict (`run` gives no exit code), then its own review (`/simplify` → `/trekreview` → verdict gate, K7; BLOCK keeps the merge into main closed), closed stop list + decision log in the final report | session |
|
||||||
| `/trekreview` | Review — a gate: Claude Code's `/code-review` + `/security-review` as engine, then Voyage's brief control (which stops); writes `review.md` with a required verdict that `lib/review/verdict-gate.mjs` decides on (0 ALLOW, or WARN with every counting MAJOR answered · 1 BLOCK · 2 never green); the operator answers each counting MAJOR (Phase 8.8; `--answers` runs only that, no new round); proposes recurring finding classes as project memory, line by line (Handover 6). The reviewers and coordinator preload `voyage:reviewing-adversarially` and are told it by name at spawn; each reviewer's Declined to Judge lines reach `review.md`; the operator's notes from the annotation page go through `voyage:annotating` | session |
|
| `/trekreview` | Review — a gate: Claude Code's `/code-review` + one security engine (Claude Security, else `/security-review`; with neither, "not measured") as engine, then Voyage's brief control (which stops); writes `review.md` with a required verdict that `lib/review/verdict-gate.mjs` decides on (0 ALLOW, or WARN with every counting MAJOR answered · 1 BLOCK · 2 never green); the operator answers each counting MAJOR (Phase 8.8; `--answers` runs only that, no new round); proposes recurring finding classes as project memory, line by line (Handover 6). The reviewers and coordinator preload `voyage:reviewing-adversarially` and are told it by name at spawn; each reviewer's Declined to Judge lines reach `review.md`; the operator's notes from the annotation page go through `voyage:annotating` | session |
|
||||||
| `/trekcontinue` | Continue — resumes the next session of a multi-session voyage project. Reads `.session-state.local.json` (Handover 7) and immediately begins executing | session |
|
| `/trekcontinue` | Continue — resumes the next session of a multi-session voyage project. Reads `.session-state.local.json` (Handover 7) and immediately begins executing | session |
|
||||||
| `/trekendsession` | End-session — mark the current session complete and write session-state pointing at the next session. Helper for informal multi-session flows | session |
|
| `/trekendsession` | End-session — mark the current session complete and write session-state pointing at the next session. Helper for informal multi-session flows | session |
|
||||||
|
|
||||||
|
|
|
||||||
28
README.md
28
README.md
|
|
@ -343,7 +343,7 @@ A run stops only on a **closed list**: something that cannot be undone · someth
|
||||||
|
|
||||||
### The review at the end — a gate (P11)
|
### The review at the end — a gate (P11)
|
||||||
|
|
||||||
After the last step, and with nobody typing anything, the executor runs Cherny's order: **run the thing** (the plan's `## Verification`, its end-to-end criterion included) → **`/simplify`** (Claude Code's cleanup skill; edits outside the run's files are restored, and a cleanup that breaks the verification is reverted) → **`/trekreview`** (Claude Code's `/code-review` and `/security-review`, then Voyage's check against the brief) → **`verdict-gate.mjs`**.
|
After the last step, and with nobody typing anything, the executor runs Cherny's order: **run the thing** (the plan's `## Verification`, its end-to-end criterion included) → **`/simplify`** (Claude Code's cleanup skill; edits outside the run's files are restored, and a cleanup that breaks the verification is reverted) → **`/trekreview`** (Claude Code's `/code-review` and a security engine — Claude Security, else `/security-review` — then Voyage's check against the brief) → **`verdict-gate.mjs`**.
|
||||||
|
|
||||||
| Verdict gate | Merge into main |
|
| Verdict gate | Merge into main |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -470,7 +470,7 @@ and a contracted **Handover 6 (review → plan)** so findings can be fed back in
|
||||||
the iteration loop without ad-hoc conventions.
|
the iteration loop without ad-hoc conventions.
|
||||||
|
|
||||||
**The review is a gate.** In this order: Claude Code's own `/code-review` and
|
**The review is a gate.** In this order: Claude Code's own `/code-review` and
|
||||||
`/security-review` find correctness and security defects (started through the
|
a security engine find correctness and security defects (started through the
|
||||||
Skill tool, never with `--fix`); Voyage's control against the brief runs after
|
Skill tool, never with `--fix`); Voyage's control against the brief runs after
|
||||||
them and is the part that stops. The verdict is written in `review.md`
|
them and is the part that stops. The verdict is written in `review.md`
|
||||||
(`verdict: ALLOW | WARN | BLOCK`, required) and one command decides on it:
|
(`verdict: ALLOW | WARN | BLOCK`, required) and one command decides on it:
|
||||||
|
|
@ -485,9 +485,23 @@ review that was not performed in full stops a merge.
|
||||||
|
|
||||||
No finding is struck for length: only MINOR/SUGGESTION text is trimmed, and
|
No finding is struck for length: only MINOR/SUGGESTION text is trimmed, and
|
||||||
counted; what the coordinator set aside is listed under `## Declined to
|
counted; what the coordinator set aside is listed under `## Declined to
|
||||||
Judge`. An engine that could not run (older Claude Code, no `origin` remote)
|
Judge`. An engine that could not run (older Claude Code, no `origin` remote,
|
||||||
keeps the review from coming back `ALLOW` — and from passing the gate — and
|
Claude Security's cost question answered "No") keeps the review from coming
|
||||||
the summary says why.
|
back `ALLOW` — and from passing the gate — and the summary says why.
|
||||||
|
|
||||||
|
**The security engine.** Claude Security is the recommended one. When its
|
||||||
|
plugin is installed (`/plugin install claude-security@claude-plugins-official`;
|
||||||
|
it needs Python 3.9 or newer on `PATH` and Claude Code's Workflow tool),
|
||||||
|
`/trekreview` runs its `scan changes` over the review's range, after Claude
|
||||||
|
Security's own question about the scan's cost, which is put to you. Claude
|
||||||
|
Security has a proprietary licence, so Voyage calls it by name and it is never
|
||||||
|
a dependency in Voyage's manifest. Without it, `/trekreview` uses Claude Code's
|
||||||
|
`/security-review` (it needs an `origin` remote and reads the branch against
|
||||||
|
origin's default branch). With neither engine able to look (no Claude Security
|
||||||
|
and no `origin` remote, or a branch that is already on origin's default
|
||||||
|
branch), security is "not measured", never 0, and the verdict gate exits 2. A
|
||||||
|
Claude Security finding blocks only at confidence `high`; the rest are listed
|
||||||
|
under `## Declined to Judge` and keep the review from `ALLOW`.
|
||||||
|
|
||||||
**One stance for every reviewer.** Both reviewers and the coordinator preload
|
**One stance for every reviewer.** Both reviewers and the coordinator preload
|
||||||
the `voyage:reviewing-adversarially` skill, and `/trekreview` names it when it
|
the `voyage:reviewing-adversarially` skill, and `/trekreview` names it when it
|
||||||
|
|
@ -502,9 +516,9 @@ Declined to Judge list, and the coordinator carries those lines into
|
||||||
|
|
||||||
| Mode | Command | Description |
|
| Mode | Command | Description |
|
||||||
|------|---------|-------------|
|
|------|---------|-------------|
|
||||||
| **Default** | `/trekreview --project <dir>` | Claude Code `/code-review` + `/security-review`, then brief-conformance + code-correctness reviewers in parallel, coordinator dedup + verdict, write `{dir}/review.md`, verdict gate |
|
| **Default** | `/trekreview --project <dir>` | Claude Code `/code-review` + the security engine (Claude Security, else `/security-review`, else "not measured"), then brief-conformance + code-correctness reviewers in parallel, coordinator dedup + verdict, write `{dir}/review.md`, verdict gate |
|
||||||
| **Since ref** | `/trekreview --project <dir> --since <ref>` | Override "before" SHA for the diff range. Validated via `git rev-parse --verify` |
|
| **Since ref** | `/trekreview --project <dir> --since <ref>` | Override "before" SHA for the diff range. Validated via `git rev-parse --verify` |
|
||||||
| **Quick** | `/trekreview --project <dir> --quick` | `/code-review` + code-correctness reviewer only; skip brief-conformance reviewer, `/security-review` and the coordinator's reasonableness filter except the brief-conflict rule — fast correctness-only pass |
|
| **Quick** | `/trekreview --project <dir> --quick` | `/code-review` + code-correctness reviewer only, no security engine; skip brief-conformance reviewer and the coordinator's reasonableness filter except the brief-conflict rule — fast correctness-only pass |
|
||||||
| **Validate** | `/trekreview --project <dir> --validate` | Schema-only check on existing `review.md`. No LLM calls |
|
| **Validate** | `/trekreview --project <dir> --validate` | Schema-only check on existing `review.md`. No LLM calls |
|
||||||
| **Dry run** | `/trekreview --project <dir> --dry-run` | Print discovered scope + triage map; skip writes |
|
| **Dry run** | `/trekreview --project <dir> --dry-run` | Print discovered scope + triage map; skip writes |
|
||||||
|
|
||||||
|
|
|
||||||
17
SECURITY.md
17
SECURITY.md
|
|
@ -73,6 +73,23 @@ plan-injected destructive commands.
|
||||||
Read-only. Never write to user files. Used both by hooks and by command
|
Read-only. Never write to user files. Used both by hooks and by command
|
||||||
phases to detect malformed artifacts before they propagate.
|
phases to detect malformed artifacts before they propagate.
|
||||||
|
|
||||||
|
### The review's security engine (`/trekreview`)
|
||||||
|
|
||||||
|
`/trekreview` hands security review of a delivery to one engine and never
|
||||||
|
reports a 0 it did not measure. Claude Security is recommended: Anthropic's
|
||||||
|
plugin, with a proprietary licence, so Voyage calls it by name
|
||||||
|
(`claude-security:claude-security`, `scan changes` only, after its own cost
|
||||||
|
question to the operator) and it is never a dependency in Voyage's manifest.
|
||||||
|
Without it, Claude Code's `/security-review` is used. With neither, security
|
||||||
|
is "not measured" and the verdict gate exits 2 (never green).
|
||||||
|
`lib/review/security-engine.mjs` chooses the engine and converts Claude
|
||||||
|
Security's report (`CLAUDE-SECURITY-<ts>/`, written by Claude Security in the
|
||||||
|
repo root behind its own `.gitignore`) as data: nothing in it is run or
|
||||||
|
followed, and a stamp counts only when it names exactly the reviewed head and
|
||||||
|
base. A Claude Security finding blocks only at confidence `high`; the rest
|
||||||
|
are listed under Declined to Judge and keep the review from `ALLOW`. Neither
|
||||||
|
engine edits code.
|
||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
|
|
||||||
- **Opt-in upstream architect step.** Any external producer of
|
- **Opt-in upstream architect step.** Any external producer of
|
||||||
|
|
|
||||||
|
|
@ -2249,13 +2249,13 @@ never typed by the operator, in this order (Cherny's):
|
||||||
`simplify = "not-run"`, with the reason. Not a stop: `/simplify` is a
|
`simplify = "not-run"`, with the reason. Not a stop: `/simplify` is a
|
||||||
cleanup pass, not a gate. Continue.
|
cleanup pass, not a gate. Continue.
|
||||||
3. **`/trekreview`** — Voyage's review gate: Claude Code's `/code-review` and
|
3. **`/trekreview`** — Voyage's review gate: Claude Code's `/code-review` and
|
||||||
`/security-review` as the engine, then Voyage's control against the brief,
|
one security engine (Claude Security, else `/security-review`; with neither,
|
||||||
which is what stops. Start it through the Skill tool, with the run's range:
|
security is "not measured") as the engine, then Voyage's control against the brief, which is what stops. Start it through the Skill tool, with the run's range:
|
||||||
`Skill("voyage:trekreview", args: "--project {project_dir} --since {session_start_sha}")`.
|
`Skill("voyage:trekreview", args: "--project {project_dir} --since {session_start_sha}")`.
|
||||||
A plugin command is a skill the model may start unless it carries
|
A plugin command is a skill the model may start unless it carries
|
||||||
`disable-model-invocation`, which `/trekreview` does not (the Claude Code
|
`disable-model-invocation`, which `/trekreview` does not (the Claude Code
|
||||||
skills documentation, read 2026-10-08). Follow its phases to the end in
|
skills documentation, read 2026-10-08). Follow its phases to the end in
|
||||||
this session; its own questions (project-memory lines, one by one) are its
|
this session; its own questions (Claude Security's question about the scan's cost, project-memory lines one by one) are its
|
||||||
own. **Without `--project`** there is no brief to review against:
|
own. **Without `--project`** there is no brief to review against:
|
||||||
`review = {round: 1, path: null, gate_exit: 2, verdict: null}`, "review not
|
`review = {round: 1, path: null, gate_exit: 2, verdict: null}`, "review not
|
||||||
run — no project directory", and the merge stays closed (Phase 8).
|
run — no project directory", and the merge stays closed (Phase 8).
|
||||||
|
|
@ -3158,7 +3158,7 @@ pins and quotes in the local practice analysis.
|
||||||
- **Claude Code** itself: subagents for the helper and the controller; the
|
- **Claude Code** itself: subagents for the helper and the controller; the
|
||||||
`PreToolUse` guard behind the test lock (K8); `/simplify`, `/code-review` and
|
`PreToolUse` guard behind the test lock (K8); `/simplify`, `/code-review` and
|
||||||
`/security-review` as the review's engine, started through the Skill tool
|
`/security-review` as the review's engine, started through the Skill tool
|
||||||
(P11).
|
(P11); Claude Security, Anthropic's plugin, as the security engine where it is installed (E).
|
||||||
- **The review at the end (P11, wave 2)** — Cherny: the order — run the
|
- **The review at the end (P11, wave 2)** — Cherny: the order — run the
|
||||||
thing, simplify, then review.
|
thing, simplify, then review.
|
||||||
Kiro (`feature-pipeline`, documentation only): the gate — a verdict in a file.
|
Kiro (`feature-pipeline`, documentation only): the gate — a verdict in a file.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ description: |
|
||||||
review.md with severity-tagged findings (BLOCKER/MAJOR/MINOR/SUGGESTION)
|
review.md with severity-tagged findings (BLOCKER/MAJOR/MINOR/SUGGESTION)
|
||||||
per Handover 6 (review → plan).
|
per Handover 6 (review → plan).
|
||||||
argument-hint: "--project <dir> [--since <ref>] [--quick] [--validate] [--dry-run] [--answers]"
|
argument-hint: "--project <dir> [--since <ref>] [--quick] [--validate] [--dry-run] [--answers]"
|
||||||
allowed-tools: Agent, Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion, Skill
|
allowed-tools: Agent, Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion, Skill, Workflow
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ultrareview Local v1.0
|
# Ultrareview Local v1.0
|
||||||
|
|
@ -30,9 +30,11 @@ and the coordinator applies BOUNDED operations only. Synthesis-level
|
||||||
inference across files is forbidden in v1.0 (Judge Agent pattern).
|
inference across files is forbidden in v1.0 (Judge Agent pattern).
|
||||||
|
|
||||||
**The review is a gate (review_version 1.2, ordre 5).** In this order:
|
**The review is a gate (review_version 1.2, ordre 5).** In this order:
|
||||||
Claude Code's own `/code-review` and `/security-review` are the engine
|
Claude Code's own `/code-review` and one security engine (Claude Security
|
||||||
(Phase 4.7); Voyage's control against the brief runs after them and is the
|
where it is installed, else Claude Code's `/security-review`; with neither,
|
||||||
part that stops (Phase 5–6); the verdict is written in `review.md` and one
|
security is "not measured", never 0) are the engine (Phase 4.7); Voyage's
|
||||||
|
control against the brief runs after them and is the part that stops
|
||||||
|
(Phase 5–6); the verdict is written in `review.md` and one
|
||||||
command decides on it (`lib/review/verdict-gate.mjs`, Phase 8: 0 = ALLOW, or
|
command decides on it (`lib/review/verdict-gate.mjs`, Phase 8: 0 = ALLOW, or
|
||||||
WARN with every counting MAJOR answered, 1 = BLOCK, 2 = could not decide,
|
WARN with every counting MAJOR answered, 1 = BLOCK, 2 = could not decide,
|
||||||
never green). No finding is struck for
|
never green). No finding is struck for
|
||||||
|
|
@ -295,12 +297,12 @@ prompt in Phase 5, and its summary line goes into the Coverage section of
|
||||||
built by `formatCriteriaEvidence` precisely so the orchestrator cannot narrate a
|
built by `formatCriteriaEvidence` precisely so the orchestrator cannot narrate a
|
||||||
pass that never happened.
|
pass that never happened.
|
||||||
|
|
||||||
## Phase 4.7 — Claude Code's review engine (`/code-review`, `/security-review`)
|
## Phase 4.7 — The engine: Claude Code's `/code-review` and the security engine
|
||||||
|
|
||||||
**Skipped in `dry-run` and `validate`.** In `quick` mode only `/code-review`
|
**Skipped in `dry-run` and `validate`.** In `quick` mode only `/code-review`
|
||||||
runs. Wrap, not a new engine: Claude Code's built-in reviews find the
|
runs: no security engine. Wrap, not a new engine: `/code-review` and the
|
||||||
correctness and security defects; Voyage's brief control (Phase 5) runs after
|
security engine find the correctness and security defects; Voyage's brief
|
||||||
them and is what stops.
|
control (Phase 5) runs after them and is what stops.
|
||||||
|
|
||||||
Verified in Claude Code's live documentation (2026-10-06):
|
Verified in Claude Code's live documentation (2026-10-06):
|
||||||
- Claude may start `/code-review` itself (`code-review.md`: "Claude can start
|
- Claude may start `/code-review` itself (`code-review.md`: "Claude can start
|
||||||
|
|
@ -314,21 +316,55 @@ Verified in Claude Code's live documentation (2026-10-06):
|
||||||
default branch" and "Needs an `origin` remote". The docs do not state its
|
default branch" and "Needs an `origin` remote". The docs do not state its
|
||||||
output format or whether it runs in the background.
|
output format or whether it runs in the background.
|
||||||
|
|
||||||
Run both through the **Skill tool**, one after the other:
|
**Choose the security engine first** (not in `quick`; E 3.1, decisions 175,
|
||||||
|
196). Claude Security is `available` when the `claude-security:claude-security`
|
||||||
|
skill is in this session's skill list AND the Workflow tool is among the tools
|
||||||
|
you can call right now (look at your tools, do not assume); otherwise it is
|
||||||
|
`missing`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node ${CLAUDE_PLUGIN_ROOT}/lib/review/security-engine.mjs choose --repo . --base {before_sha} --head {after_sha} --claude-security {available|missing}
|
||||||
|
```
|
||||||
|
|
||||||
|
It prints `{engine, reason}`: `claude-security` when Claude Security is
|
||||||
|
available and `{before_sha}..{after_sha}` has committed changes; else
|
||||||
|
`security-review` when origin's HEAD resolves and `origin/HEAD...HEAD` is
|
||||||
|
non-empty; else `null`, with `no-engine` (no `origin` remote and no Claude
|
||||||
|
Security), `no-origin` (an `origin` remote whose HEAD does not resolve) or
|
||||||
|
`empty-diff` (the engine's range holds no committed change). Exit 2 = it could
|
||||||
|
not run: print the reason in one line and record the security engine as not
|
||||||
|
run. Keep `engine` and `reason` for Phases 5 and 9.
|
||||||
|
|
||||||
|
Then run, through the **Skill tool**, one after the other:
|
||||||
|
|
||||||
1. `Skill("code-review", args: "high {before_sha}...{after_sha}")` — the
|
1. `Skill("code-review", args: "high {before_sha}...{after_sha}")` — the
|
||||||
review's own SHA range from Phase 3. `high` is set explicitly; note in the
|
review's own SHA range from Phase 3. `high` is set explicitly; note in the
|
||||||
Executive Summary that this also becomes the level later `/code-review`
|
Executive Summary that this also becomes the level later `/code-review`
|
||||||
runs in this session reuse.
|
runs in this session reuse.
|
||||||
2. `Skill("security-review")` — no arguments; its base is fixed.
|
2. The security engine `choose` picked, and only that one:
|
||||||
|
- `claude-security` —
|
||||||
|
`Skill("claude-security:claude-security", args: "scan changes --base {before_sha} --effort high")`.
|
||||||
|
Claude Security asks one question of its own before the scan starts (the
|
||||||
|
scan's time and token cost): put it to the operator as it stands and never
|
||||||
|
answer it yourself. "No", a question nobody can answer (no operator
|
||||||
|
present), the Workflow tool missing after all, or a scan that ends without
|
||||||
|
a report: record Claude Security as not run, with the reason. **Never fall
|
||||||
|
back to `/security-review`**: the review is then not measured for
|
||||||
|
security.
|
||||||
|
- `security-review` — `Skill("security-review")`, no arguments; its base is
|
||||||
|
fixed (origin's default branch). `/security-review` runs only when
|
||||||
|
`choose` picked it.
|
||||||
|
- `null` — no security engine runs: record it as not run with `choose`'s
|
||||||
|
reason.
|
||||||
|
|
||||||
**Never** pass `--fix`, `--comment`, `--post` or `ultra`: a review never edits
|
**Never** pass `--fix`, `--comment`, `--post` or `ultra`: a review never edits
|
||||||
code, never posts anywhere, and never uploads the repo to a cloud review.
|
code, never posts anywhere, and never uploads the repo to a cloud review.
|
||||||
|
|
||||||
**Wait for the findings.** A background `/code-review` delivers its findings
|
**Wait for the findings.** A background `/code-review` delivers its findings
|
||||||
later in this conversation. Do not start Phase 6 before both engines have
|
later in this conversation, and Claude Security's scan runs as a workflow
|
||||||
delivered or have been recorded as not run. For each engine that delivered,
|
until it writes its report. Do not start Phase 6 before both engines have
|
||||||
transcribe every finding — file, line (0 when none), the one-sentence summary,
|
delivered or have been recorded as not run. For `/code-review` and
|
||||||
|
`/security-review`, transcribe every finding — file, line (0 when none), the one-sentence summary,
|
||||||
the failure scenario, the category if given (it decides the tier:
|
the failure scenario, the category if given (it decides the tier:
|
||||||
`simplification`, `efficiency`, `reuse`, `style` and `readability` enter as MINOR), the
|
`simplification`, `efficiency`, `reuse`, `style` and `readability` enter as MINOR), the
|
||||||
fix the engine proposes (`fix`) only when its text states one — never write one
|
fix the engine proposes (`fix`) only when its text states one — never write one
|
||||||
|
|
@ -349,7 +385,7 @@ node ${CLAUDE_PLUGIN_ROOT}/lib/review/engine-findings.mjs \
|
||||||
```
|
```
|
||||||
|
|
||||||
The converted payload is saved to `{project_dir}/.review-engine-payload-{engine}.json`
|
The converted payload is saved to `{project_dir}/.review-engine-payload-{engine}.json`
|
||||||
(`code-review` or `security-review`), the same stdout, also written: Phase 7's finalize
|
(`code-review`, `security-review` or `claude-security`), the same stdout, also written: Phase 7's finalize
|
||||||
reads it from there. Never edit that file by hand.
|
reads it from there. Never edit that file by hand.
|
||||||
Exit 0 prints a reviewer payload (`claude-code-review` /
|
Exit 0 prints a reviewer payload (`claude-code-review` /
|
||||||
`claude-security-review`) for Phase 6 plus `out_of_scope`: engine findings on
|
`claude-security-review`) for Phase 6 plus `out_of_scope`: engine findings on
|
||||||
|
|
@ -371,12 +407,40 @@ at least the engine's own (MAJOR for `/code-review`, BLOCKER for
|
||||||
`/security-review`); the adapter refuses to let a transcription lower an
|
`/security-review`); the adapter refuses to let a transcription lower an
|
||||||
engine finding.
|
engine finding.
|
||||||
|
|
||||||
|
**Claude Security's findings are converted, never transcribed.** When its
|
||||||
|
scan has finished it has written `CLAUDE-SECURITY-<ts>/` in the repo root
|
||||||
|
(behind its own `.gitignore`); convert that report, after the scope file
|
||||||
|
above exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node ${CLAUDE_PLUGIN_ROOT}/lib/review/security-engine.mjs convert --report-dir . \
|
||||||
|
--base {before_sha} --head {after_sha} --scope "{project_dir}/.review-scope.txt" \
|
||||||
|
> "{project_dir}/.review-engine-payload-claude-security.json" \
|
||||||
|
&& cat "{project_dir}/.review-engine-payload-claude-security.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit 0 prints the `claude-security` payload: `findings` and `out_of_scope` as
|
||||||
|
for any engine, and `below_threshold`, the findings Claude Security marked
|
||||||
|
below confidence `high` (decision 219). Pass `below_threshold` to the
|
||||||
|
coordinator as well: each one is listed under `## Declined to Judge`
|
||||||
|
(`engine:below-threshold`), never counted, and it forbids `ALLOW`. Exit 2 =
|
||||||
|
not measured (no report stamp of a changes scan of exactly
|
||||||
|
`{before_sha}..{after_sha}` — an empty diff writes none —, a scan not
|
||||||
|
verified, researchers that did not all return, an empty diff or scope, a
|
||||||
|
malformed record): print the reason and record Claude Security as not run;
|
||||||
|
the empty file the redirect leaves is no payload. Exit 1 = the scope list is
|
||||||
|
empty: rebuild it from Phase 3 and re-run.
|
||||||
|
|
||||||
**An engine that did not run** (Claude Code older than v2.1.246, the Skill
|
**An engine that did not run** (Claude Code older than v2.1.246, the Skill
|
||||||
call refused, no `origin` remote, an `ambiguous argument` error, or no findings
|
call refused, no `origin` remote, an `ambiguous argument` error, Claude
|
||||||
ever delivered) is recorded as not run with its reason. It stays in the
|
Security's question answered "No" or left unanswered, a report `convert`
|
||||||
expected reviewer set, so it forbids `ALLOW` (fail-closed); the Executive
|
reads as not measured, `choose` picking no engine, or no findings ever
|
||||||
Summary's first sentence names it. An engine that ran and found nothing
|
delivered) is recorded as not run with its reason. It stays in the expected
|
||||||
delivers an empty payload — that is a report, not an absence.
|
reviewer set, so it forbids `ALLOW` (fail-closed) and the verdict gate exits
|
||||||
|
2; the Executive Summary's first sentence names it. For security that
|
||||||
|
sentence says "security: not measured" and why (`choose`'s reason, or why
|
||||||
|
the engine did not run): never 0, never "no findings". An engine that ran and
|
||||||
|
found nothing delivers an empty payload — that is a report, not an absence.
|
||||||
|
|
||||||
The two engine payloads are NOT shown to the Phase 5 reviewers: reviewers stay
|
The two engine payloads are NOT shown to the Phase 5 reviewers: reviewers stay
|
||||||
independent, and the coordinator is the only place the outputs meet.
|
independent, and the coordinator is the only place the outputs meet.
|
||||||
|
|
@ -451,7 +515,7 @@ the round input:
|
||||||
Finalize (Phase 7) carries a previous finding until every reviewer that ran in
|
Finalize (Phase 7) carries a previous finding until every reviewer that ran in
|
||||||
its round marks it resolved, and an engine finding until its engine ran this
|
its round marks it resolved, and an engine finding until its engine ran this
|
||||||
round and did not raise it again. The engines (Phase 4.7) stay on the run range
|
round and did not raise it again. The engines (Phase 4.7) stay on the run range
|
||||||
(`/security-review` takes no range).
|
(`/security-review` takes no range; Claude Security scans from `{before_sha}`).
|
||||||
|
|
||||||
`brief-conformance-reviewer` additionally receives `sc_evidence_block` from
|
`brief-conformance-reviewer` additionally receives `sc_evidence_block` from
|
||||||
Phase 4.5, pasted verbatim — the command, exit code and first line of output for
|
Phase 4.5, pasted verbatim — the command, exit code and first line of output for
|
||||||
|
|
@ -502,8 +566,12 @@ Summary will note the brief-conformance pass was skipped.
|
||||||
|
|
||||||
Write down the expected reviewer set BEFORE the spawn: both reviewers in
|
Write down the expected reviewer set BEFORE the spawn: both reviewers in
|
||||||
default mode, `code-correctness-reviewer` alone in `quick` mode — plus, in
|
default mode, `code-correctness-reviewer` alone in `quick` mode — plus, in
|
||||||
every mode, the Phase 4.7 engines that were due (`claude-code-review`, and
|
every mode, the Phase 4.7 engines that were due: `claude-code-review`, and
|
||||||
`claude-security-review` outside `quick`). After the spawn, account for each
|
outside `quick` the security engine `choose` picked (`claude-security` for
|
||||||
|
Claude Security, `claude-security-review` for `/security-review`), or
|
||||||
|
`security-engine` when it picked none or could not run. `security-engine` never reports, so the
|
||||||
|
review carries `missing-reviewer:security-engine` and the verdict gate exits 2:
|
||||||
|
security not measured, never 0. After the spawn, account for each
|
||||||
one by name. An engine recorded as not run is passed on as missing, never
|
one by name. An engine recorded as not run is passed on as missing, never
|
||||||
left out of the set.
|
left out of the set.
|
||||||
|
|
||||||
|
|
@ -535,7 +603,8 @@ forbids `ALLOW` (`lib/review/coordinator-contract.mjs`, `missing_reviewers`).
|
||||||
## Phase 6 — Coordinator dedup + verdict
|
## Phase 6 — Coordinator dedup + verdict
|
||||||
|
|
||||||
Launch `review-coordinator` (Agent tool) with the merged findings array
|
Launch `review-coordinator` (Agent tool) with the merged findings array
|
||||||
from Phase 5 AND the Phase 4.7 engine payloads, plus the triage map, brief
|
from Phase 5 AND the Phase 4.7 engine payloads (Claude Security's
|
||||||
|
`below_threshold` list with them), plus the triage map, brief
|
||||||
metadata, SHA range, the expected reviewer set (engines included), and each
|
metadata, SHA range, the expected reviewer set (engines included), and each
|
||||||
not-run engine with its reason. Name `voyage:reviewing-adversarially` in the
|
not-run engine with its reason. Name `voyage:reviewing-adversarially` in the
|
||||||
coordinator's prompt as in Phase 5, and pass each reviewer's
|
coordinator's prompt as in Phase 5, and pass each reviewer's
|
||||||
|
|
@ -894,7 +963,7 @@ After the write succeeds, print:
|
||||||
**Scope:** {before_sha}..{after_sha} ({reviewed_files_count} files)
|
**Scope:** {before_sha}..{after_sha} ({reviewed_files_count} files)
|
||||||
**Verdict:** {BLOCK | WARN | ALLOW}
|
**Verdict:** {BLOCK | WARN | ALLOW}
|
||||||
**Gate:** verdict-gate exit {gate_exit} ({0 = ALLOW, or WARN with every counting MAJOR answered | 1 = BLOCK | 2 = could not decide, a control did not run, or a MAJOR without an answer or answered "fixed" and not yet confirmed — not green})
|
**Gate:** verdict-gate exit {gate_exit} ({0 = ALLOW, or WARN with every counting MAJOR answered | 1 = BLOCK | 2 = could not decide, a control did not run, or a MAJOR without an answer or answered "fixed" and not yet confirmed — not green})
|
||||||
**Engine:** /code-review {delivered N | not run: reason} · /security-review {delivered N | not run: reason}
|
**Engine:** /code-review {delivered N | not run: reason} · security {Claude Security delivered N, M below threshold | /security-review delivered N | not measured: reason | none in quick mode}
|
||||||
|
|
||||||
### Counts
|
### Counts
|
||||||
- BLOCKER: {N}
|
- BLOCKER: {N}
|
||||||
|
|
@ -966,9 +1035,12 @@ the contract for that handover (see `docs/HANDOVER-CONTRACTS.md`).
|
||||||
writes to anything outside `{project_dir}` —
|
writes to anything outside `{project_dir}` —
|
||||||
with one exception, only after the operator approved that exact line in
|
with one exception, only after the operator approved that exact line in
|
||||||
Phase 8.7: Voyage's own marked block in `.claude/rules/voyage-review-lessons.md`
|
Phase 8.7: Voyage's own marked block in `.claude/rules/voyage-review-lessons.md`
|
||||||
and a one-line pointer in `CLAUDE.md`.
|
and a one-line pointer in `CLAUDE.md`. Claude Security writes its own report
|
||||||
- **The engine never edits code.** `/code-review` and `/security-review` run
|
folder `CLAUDE-SECURITY-<ts>/` in the repo root, behind its own `.gitignore`;
|
||||||
without `--fix`, `--comment`, `--post` or `ultra`. A review that changes
|
`/trekreview` only reads it.
|
||||||
|
- **The engine never edits code.** `/code-review`, `/security-review` and Claude Security run
|
||||||
|
without `--fix`, `--comment`, `--post` or `ultra`; Claude Security runs
|
||||||
|
`scan changes` only, never its patch job. A review that changes
|
||||||
code is not a review.
|
code is not a review.
|
||||||
- **No finding is struck for length.** Only MINOR/SUGGESTION text is trimmed,
|
- **No finding is struck for length.** Only MINOR/SUGGESTION text is trimmed,
|
||||||
and every trim is counted; what the coordinator set aside is listed under
|
and every trim is counted; what the coordinator set aside is listed under
|
||||||
|
|
|
||||||
|
|
@ -460,12 +460,12 @@ Optional but standard sections: `## Findings (BLOCKER)`, `## Findings (MAJOR)`,
|
||||||
|
|
||||||
Since 1.1 (ordre 5):
|
Since 1.1 (ordre 5):
|
||||||
- `## Remediation Steps` — required at `review_version` ≥ 1.1 when there is a BLOCKER: one `### Step R<n>` per BLOCKER carrying `Source finding: <id>` (strict error `REVIEW_MISSING_REMEDIATION_STEP`, soft warning). Steps are appended and numbered, never reworded across iterations (Spec Kit `/converge`).
|
- `## Remediation Steps` — required at `review_version` ≥ 1.1 when there is a BLOCKER: one `### Step R<n>` per BLOCKER carrying `Source finding: <id>` (strict error `REVIEW_MISSING_REMEDIATION_STEP`, soft warning). Steps are appended and numbered, never reworded across iterations (Spec Kit `/converge`).
|
||||||
- `## Declined to Judge` — required at `review_version` ≥ 1.1 (strict error, soft warning; 1.0 reviews exempt): every finding set aside without a ruling, with its reason (including engine findings outside the review scope, `engine:out-of-scope`), plus `Trimmed: N`. **No finding is struck for length**: only MINOR/SUGGESTION text is trimmed, and counted (`trimmed` in the JSON block); a long BLOCKER/MAJOR keeps every character.
|
- `## Declined to Judge` — required at `review_version` ≥ 1.1 (strict error, soft warning; 1.0 reviews exempt): every finding set aside without a ruling, with its reason (including engine findings outside the review scope, `engine:out-of-scope`, and Claude Security findings below confidence `high`, `engine:below-threshold`), plus `Trimmed: N`. **No finding is struck for length**: only MINOR/SUGGESTION text is trimmed, and counted (`trimmed` in the JSON block); a long BLOCKER/MAJOR keeps every character.
|
||||||
- Findings from Claude Code's own `/code-review` and `/security-review` enter under the catalogue keys `CODE_REVIEW_DEFECT` (MAJOR), `CODE_REVIEW_IMPROVEMENT` (MINOR: a `/code-review` finding whose category is simplification, efficiency, reuse, style or readability) and `SECURITY_REVIEW_VULNERABILITY` (BLOCKER), or a narrower key, with `reviewer` `claude-code-review` / `claude-security-review`.
|
- Findings from the engines — Claude Code's own `/code-review` and one security engine, chosen by `lib/review/security-engine.mjs choose` (Claude Security where it is installed and the range has committed changes, else `/security-review`) — enter under the catalogue keys `CODE_REVIEW_DEFECT` (MAJOR), `CODE_REVIEW_IMPROVEMENT` (MINOR: a `/code-review` finding whose category is simplification, efficiency, reuse, style or readability) and `SECURITY_REVIEW_VULNERABILITY` (BLOCKER), or a narrower key, with `reviewer` `claude-code-review` / `claude-security` (engine `claude-security`) / `claude-security-review`. A Claude Security finding counts only at confidence `high` (decision 219); the rest stand under `## Declined to Judge` as `engine:below-threshold`, an unverified reason: not counted, it forbids `ALLOW`. With no security engine (`choose` gives `no-engine`, `no-origin` or `empty-diff`, or the chosen engine did not run) the expected reviewer `security-engine` never reports: `missing-reviewer:security-engine`, verdict gate exit 2 — security not measured, never 0.
|
||||||
- Working files of one review run, in the project folder (not part of `review.md`): the engine transcriptions `.review-engine-{code-review|security-review}.json` (`/trekreview` Phase 4.7) and, beside them, the converted payloads `.review-engine-payload-{engine}.json` (the `engine-findings.mjs` output, `{reviewer, findings, out_of_scope}`). `review-finalize.mjs` reads the payloads to record which engines ran and to put back, at its own severity, an engine finding at MAJOR or BLOCKER that no finding of `review.md` carries (the standard-path comparison); the verdict gate reads the payloads too, to compare them with `review.md`: an engine finding at MAJOR or BLOCKER missing from it is exit 2 (`review-not-measured: engine finding not in review`). `review-rounds.mjs prepare` deletes the payloads at the start of each round.
|
- Working files of one review run, in the project folder (not part of `review.md`): the engine transcriptions `.review-engine-{code-review|security-review}.json` (`/trekreview` Phase 4.7) and, beside them, the converted payloads `.review-engine-payload-{engine}.json` (the `engine-findings.mjs` output, `{reviewer, findings, out_of_scope}`). Claude Security's is never transcribed: `.review-engine-payload-claude-security.json` is the `security-engine.mjs convert` output from its own report, `{reviewer, findings, out_of_scope, below_threshold, report}`; `below_threshold` is never compared or put back. `review-finalize.mjs` reads the payloads to record which engines ran and to put back, at its own severity, an engine finding at MAJOR or BLOCKER that no finding of `review.md` carries (the standard-path comparison); the verdict gate reads the payloads too, to compare them with `review.md`: an engine finding at MAJOR or BLOCKER missing from it is exit 2 (`review-not-measured: engine finding not in review`). `review-rounds.mjs prepare` deletes the payloads at the start of each round.
|
||||||
|
|
||||||
Since 1.2 (every field and section optional; a malformed one present is a strict error, a soft warning):
|
Since 1.2 (every field and section optional; a malformed one present is a strict error, a soft warning):
|
||||||
- Trailing JSON block, additive: per finding `pre_existing` (boolean: the range did not add or change the cited line and `scope_sha_start` is bound to the fork point from main (the fork point itself, or a later commit reached through process files only); otherwise it is never true), `carried_from` (the previous round's finding id) and `instruction_ref` (the repo path of the instruction file a CLAUDE.md-key finding cites); top level `pre_existing_counts` (`{SEVERITY: n}`), `resolved` (the previous round's finding ids no longer raised) and `ran` (`{engines: [code-review|security-review], reviewers: [string]}`, written with `finalized: true`). Codes `REVIEW_PRE_EXISTING_MALFORMED`, `REVIEW_ROUND_MALFORMED`, `REVIEW_POLICY_MALFORMED`.
|
- Trailing JSON block, additive: per finding `pre_existing` (boolean: the range did not add or change the cited line and `scope_sha_start` is bound to the fork point from main (the fork point itself, or a later commit reached through process files only); otherwise it is never true), `carried_from` (the previous round's finding id) and `instruction_ref` (the repo path of the instruction file a CLAUDE.md-key finding cites); top level `pre_existing_counts` (`{SEVERITY: n}`), `resolved` (the previous round's finding ids no longer raised) and `ran` (`{engines: [code-review|security-review|claude-security], reviewers: [string]}`, written with `finalized: true`). Codes `REVIEW_PRE_EXISTING_MALFORMED`, `REVIEW_ROUND_MALFORMED`, `REVIEW_POLICY_MALFORMED`.
|
||||||
- `## Pre-existing` — the titles and counts of the findings marked `pre_existing`; written by `review-finalize.mjs`.
|
- `## Pre-existing` — the titles and counts of the findings marked `pre_existing`; written by `review-finalize.mjs`.
|
||||||
- `## Resolved since round <n>` — the previous round's findings this round no longer raises; written by `review-finalize.mjs`.
|
- `## Resolved since round <n>` — the previous round's findings this round no longer raises; written by `review-finalize.mjs`.
|
||||||
- `## Answers` — the operator's answer to each counting MAJOR, written by `/trekreview` Phase 8.8 after `## Remediation Steps` and before the trailing JSON block: a pipe table with the header exactly `| Finding | Answer | Reason | Record | Carried from |`; per row a 40-hex finding id (optionally `#n`), `accepted` or `fixed`, a reason (non-empty when `accepted`), the 64-hex hash of the K2 `review` record, and `-` or `review-round-<run12>-<n>.md#<id>`; at most one row per finding. A MAJOR answered `fixed` needs its `### Step R<n>` with `Source finding: <id>` under `## Remediation Steps`. Code `REVIEW_ANSWERS_MALFORMED`.
|
- `## Answers` — the operator's answer to each counting MAJOR, written by `/trekreview` Phase 8.8 after `## Remediation Steps` and before the trailing JSON block: a pipe table with the header exactly `| Finding | Answer | Reason | Record | Carried from |`; per row a 40-hex finding id (optionally `#n`), `accepted` or `fixed`, a reason (non-empty when `accepted`), the 64-hex hash of the K2 `review` record, and `-` or `review-round-<run12>-<n>.md#<id>`; at most one row per finding. A MAJOR answered `fixed` needs its `### Step R<n>` with `Source finding: <id>` under `## Remediation Steps`. Code `REVIEW_ANSWERS_MALFORMED`.
|
||||||
|
|
@ -474,13 +474,13 @@ Since 1.2 (every field and section optional; a malformed one present is a strict
|
||||||
|
|
||||||
**The brief-conflict mark (fields `engine`, `contradicts_brief`, `brief_lowered_from`).**
|
**The brief-conflict mark (fields `engine`, `contradicts_brief`, `brief_lowered_from`).**
|
||||||
Each finding may carry three more fields, in its body field list and in the trailing JSON block:
|
Each finding may carry three more fields, in its body field list and in the trailing JSON block:
|
||||||
`engine` (`code-review` | `security-review`, on a finding that came from an engine),
|
`engine` (`code-review` | `security-review` | `claude-security`, on a finding that came from an engine),
|
||||||
`contradicts_brief` (the brief's Non-Goal or stated choice the finding's recommended action contradicts, verbatim)
|
`contradicts_brief` (the brief's Non-Goal or stated choice the finding's recommended action contradicts, verbatim)
|
||||||
and `brief_lowered_from` (`MAJOR`, written only by the one lowering).
|
and `brief_lowered_from` (`MAJOR`, written only by the one lowering).
|
||||||
The mark is a field on a kept finding, never a `suppressed_reason`.
|
The mark is a field on a kept finding, never a `suppressed_reason`.
|
||||||
Only an engine finding is ever marked; a reviewer-agent finding (no `engine`) and a finding whose `rule_key` is not in the catalogue never are.
|
Only an engine finding is ever marked; a reviewer-agent finding (no `engine`) and a finding whose `rule_key` is not in the catalogue never are.
|
||||||
The one legal lowering: a `/code-review` MAJOR-tier finding at severity MAJOR drops to MINOR, with `brief_lowered_from: MAJOR`;
|
The one legal lowering: a `/code-review` MAJOR-tier finding at severity MAJOR drops to MINOR, with `brief_lowered_from: MAJOR`;
|
||||||
a BLOCKER-tier, a MINOR-tier and any `/security-review` finding keep their severity and only carry the mark.
|
a BLOCKER-tier, a MINOR-tier and any finding from a security engine (`/security-review`, Claude Security) keep their severity and only carry the mark.
|
||||||
On an ALLOW or WARN review the verdict gate exits 2 on every finding `illegalBriefLowering` (`lib/review/coordinator-contract.mjs`) names:
|
On an ALLOW or WARN review the verdict gate exits 2 on every finding `illegalBriefLowering` (`lib/review/coordinator-contract.mjs`) names:
|
||||||
any other `brief_lowered_from`, a mark on a reviewer-agent finding, a marked finding below its tier, a marked finding with an unknown key,
|
any other `brief_lowered_from`, a mark on a reviewer-agent finding, a marked finding below its tier, a marked finding with an unknown key,
|
||||||
and an engine finding below its tier, marked or not (a BLOCK review exits 1 as before).
|
and an engine finding below its tier, marked or not (a BLOCK review exits 1 as before).
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Imported from `CLAUDE.md` via pointer.
|
||||||
- `lib/util/{frontmatter,result,atomic-write,autonomy-gate}.mjs` — shared YAML-frontmatter parser + Result helpers + `atomicWriteJson(path, obj)` for tmp+rename writes + `--gates` flag parser (v3.4.0)
|
- `lib/util/{frontmatter,result,atomic-write,autonomy-gate}.mjs` — shared YAML-frontmatter parser + Result helpers + `atomicWriteJson(path, obj)` for tmp+rename writes + `--gates` flag parser (v3.4.0)
|
||||||
- `lib/parsers/{plan-schema,manifest-yaml,arg-parser,jaccard,finding-id}.mjs` — pure parsers (no I/O), unit-tested. `manifest-yaml` extended in v3.4.0 with additive `skip_commit_check` + `memory_write` flags (forward-compat: unknown keys ignored)
|
- `lib/parsers/{plan-schema,manifest-yaml,arg-parser,jaccard,finding-id}.mjs` — pure parsers (no I/O), unit-tested. `manifest-yaml` extended in v3.4.0 with additive `skip_commit_check` + `memory_write` flags (forward-compat: unknown keys ignored)
|
||||||
- `lib/review/{rule-catalogue,plan-review-dedup}.mjs` — version-pinned rule catalogue (18 keys) + Phase 9 inline dedup helpers (v3.4.0)
|
- `lib/review/{rule-catalogue,plan-review-dedup}.mjs` — version-pinned rule catalogue (18 keys) + Phase 9 inline dedup helpers (v3.4.0)
|
||||||
- `lib/review/{verdict-gate,engine-findings,recurrence}.mjs` — the review gate (K7: 0 ALLOW, or WARN with every counting MAJOR answered · 1 BLOCK · 2 never green), Claude Code `/code-review` + `/security-review` findings → reviewer payload, recurring finding classes → operator-approved project-memory lines (review_version 1.2); `lib/review/fill-finding-ids.mjs` computes the coordinator's `@@ID_<n>@@` placeholders over `(file, line, rule_key)`; `lib/validators/brief-review-stamp.mjs` lets `/trekplan` skip a brief review `/trekbrief` already ran on the same text
|
- `lib/review/{verdict-gate,engine-findings,security-engine,recurrence}.mjs` — the review gate (K7: 0 ALLOW, or WARN with every counting MAJOR answered · 1 BLOCK · 2 never green), Claude Code `/code-review` + `/security-review` findings → reviewer payload, the security engine chosen (Claude Security, else `/security-review`, else "not measured") and Claude Security's report converted (confidence `high` counts, the rest `below_threshold`), recurring finding classes → operator-approved project-memory lines (review_version 1.2); `lib/review/fill-finding-ids.mjs` computes the coordinator's `@@ID_<n>@@` placeholders over `(file, line, rule_key)`; `lib/validators/brief-review-stamp.mjs` lets `/trekplan` skip a brief review `/trekbrief` already ran on the same text
|
||||||
- `lib/validators/{brief,research,plan,progress,session-state}-validator.mjs` — schema validators with CLI shims (`node lib/validators/X.mjs --json <path>`)
|
- `lib/validators/{brief,research,plan,progress,session-state}-validator.mjs` — schema validators with CLI shims (`node lib/validators/X.mjs --json <path>`)
|
||||||
- `lib/validators/architecture-discovery.mjs` — drift-WARN external-contract discovery for `architecture/overview.md`
|
- `lib/validators/architecture-discovery.mjs` — drift-WARN external-contract discovery for `architecture/overview.md`
|
||||||
- `lib/validators/query-privacy-gate.mjs` — gates an outbound research query before it leaves the machine (no command calls it now: its callers were the removed STORM steps); the hard-block tier (secret-shaped strings) is not operator-overridable, so a query that trips it must be reformulated rather than forced through. CLI shim: `node lib/validators/query-privacy-gate.mjs "<query>"`
|
- `lib/validators/query-privacy-gate.mjs` — gates an outbound research query before it leaves the machine (no command calls it now: its callers were the removed STORM steps); the hard-block tier (secret-shaped strings) is not operator-overridable, so a query that trips it must be reformulated rather than forced through. CLI shim: `node lib/validators/query-privacy-gate.mjs "<query>"`
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,10 @@ Every mode that executes refuses a plan whose approval in the approval log does
|
||||||
|
|
||||||
| Flag | Behavior |
|
| Flag | Behavior |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
| _(default)_ | Claude Code `/code-review` + `/security-review` (engine, via the Skill tool, never `--fix`), then brief-conformance + code-correctness reviewers in parallel, coordinator dedup + verdict, write `{project_dir}/review.md` with the required `verdict:`, decide with `lib/review/verdict-gate.mjs` (0 ALLOW, or WARN with every counting MAJOR answered · 1 BLOCK · 2 never green), the operator answers each counting MAJOR (Phase 8.8), propose recurring finding classes as project memory (operator approves each line) |
|
| _(default)_ | Claude Code `/code-review` + one security engine chosen by `lib/review/security-engine.mjs` (Claude Security where it is installed, else `/security-review`; with neither, security is "not measured", never 0 — the engine, via the Skill tool, never `--fix`), then brief-conformance + code-correctness reviewers in parallel, coordinator dedup + verdict, write `{project_dir}/review.md` with the required `verdict:`, decide with `lib/review/verdict-gate.mjs` (0 ALLOW, or WARN with every counting MAJOR answered · 1 BLOCK · 2 never green), the operator answers each counting MAJOR (Phase 8.8), propose recurring finding classes as project memory (operator approves each line) |
|
||||||
| `--project <dir>` | **Required.** Path to trekplan project folder containing `brief.md`. Review is written to `{dir}/review.md` |
|
| `--project <dir>` | **Required.** Path to trekplan project folder containing `brief.md`. Review is written to `{dir}/review.md` |
|
||||||
| `--since <ref>` | Override "before" SHA for the diff range. Validated via `git rev-parse --verify` |
|
| `--since <ref>` | Override "before" SHA for the diff range. Validated via `git rev-parse --verify` |
|
||||||
| `--quick` | `/code-review` + code-correctness reviewer only; skip brief-conformance reviewer, `/security-review` and the coordinator's reasonableness filter except the brief-conflict rule — fast correctness-only pass |
|
| `--quick` | `/code-review` + code-correctness reviewer only, no security engine; skip brief-conformance reviewer and the coordinator's reasonableness filter except the brief-conflict rule — fast correctness-only pass |
|
||||||
| `--answers` | Only the operator's answers to the counting MAJORs (Phase 8.8) on the existing `{dir}/review.md`, then the verdict gate. No reviewer and no write of findings: it opens no new round and counts nothing against the cap |
|
| `--answers` | Only the operator's answers to the counting MAJORs (Phase 8.8) on the existing `{dir}/review.md`, then the verdict gate. No reviewer and no write of findings: it opens no new round and counts nothing against the cap |
|
||||||
| `--validate` | Schema-only check on existing `{dir}/review.md` plus the verdict gate. No LLM calls. A 1.0 review without `verdict:` fails here (it still seeds a plan in soft mode) |
|
| `--validate` | Schema-only check on existing `{dir}/review.md` plus the verdict gate. No LLM calls. A 1.0 review without `verdict:` fails here (it still seeds a plan in soft mode) |
|
||||||
| `--dry-run` | Print discovered scope + triage map; skip writes |
|
| `--dry-run` | Print discovered scope + triage map; skip writes |
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ plan-input.
|
||||||
"counts": { "BLOCKER": 1, "MAJOR": 1, "MINOR": 1, "SUGGESTION": 1 },
|
"counts": { "BLOCKER": 1, "MAJOR": 1, "MINOR": 1, "SUGGESTION": 1 },
|
||||||
"pre_existing_counts": { "BLOCKER": 0, "MAJOR": 0, "MINOR": 0, "SUGGESTION": 0 },
|
"pre_existing_counts": { "BLOCKER": 0, "MAJOR": 0, "MINOR": 0, "SUGGESTION": 0 },
|
||||||
"resolved": [],
|
"resolved": [],
|
||||||
"ran": { "engines": ["code-review", "security-review"], "reviewers": ["brief-conformance-reviewer", "code-correctness-reviewer"] },
|
"ran": { "engines": ["claude-security", "code-review"], "reviewers": ["brief-conformance-reviewer", "code-correctness-reviewer"] },
|
||||||
"allow_blocked_by": [],
|
"allow_blocked_by": [],
|
||||||
"declined_to_judge": [],
|
"declined_to_judge": [],
|
||||||
"known_outside": [],
|
"known_outside": [],
|
||||||
|
|
|
||||||
|
|
@ -362,3 +362,130 @@ test('[STP] /trekexecute Phase 7.2 step 4 runs prepare --dry-run after a round-2
|
||||||
}
|
}
|
||||||
assert.doesNotMatch(part, /one more round/i);
|
assert.doesNotMatch(part, /one more round/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- E 3.2: the security engine in /trekreview (decisions 175, 196, 219) -----------
|
||||||
|
|
||||||
|
const P47 = () => flat(section(CMD, '## Phase 4.7'));
|
||||||
|
|
||||||
|
test('[E32] trekreview — Phase 4.7 chooses the security engine first, with security-engine.mjs choose', () => {
|
||||||
|
const p = P47();
|
||||||
|
const at = p.indexOf('lib/review/security-engine.mjs choose --repo . --base {before_sha} --head {after_sha} --claude-security');
|
||||||
|
assert.ok(at >= 0, 'the choose call is written out');
|
||||||
|
for (const call of ['Skill("security-review")', 'Skill("claude-security:claude-security"']) {
|
||||||
|
assert.ok(p.indexOf(call) > at, `${call} comes after choose`);
|
||||||
|
}
|
||||||
|
assert.match(p, /`claude-security:claude-security` skill is in this session's skill list/, 'available = the skill is listed');
|
||||||
|
assert.match(p, /the Workflow tool is among the tools you can call/, 'available = the Workflow tool is callable');
|
||||||
|
for (const r of ['no-engine', 'no-origin', 'empty-diff']) assert.ok(p.includes(`\`${r}\``), `choose's reason ${r} is named`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — Claude Security runs through its own skill on the review range; its cost question goes to the operator', () => {
|
||||||
|
const p = P47();
|
||||||
|
const call = p.match(/Skill\("claude-security:claude-security", args: "([^"]*)"\)/);
|
||||||
|
assert.ok(call, 'the Claude Security call is written out');
|
||||||
|
assert.equal(call[1], 'scan changes --base {before_sha} --effort high');
|
||||||
|
for (const banned of ['--fix', '--comment', '--post', 'ultra', 'patch']) {
|
||||||
|
assert.ok(!call[1].includes(banned), `the Claude Security call must not carry ${banned}`);
|
||||||
|
}
|
||||||
|
assert.match(p, /put it to the operator as it stands and never answer it yourself/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — "No", an unanswerable question or no Workflow tool is recorded not run, never a silent fallback to /security-review', () => {
|
||||||
|
const p = P47();
|
||||||
|
assert.match(p, /"No", a question nobody can answer/);
|
||||||
|
assert.match(p, /the Workflow tool missing/);
|
||||||
|
assert.match(p, /\*\*Never fall back to `\/security-review`\*\*/);
|
||||||
|
assert.match(p, /`\/security-review` runs only when `choose` picked it/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — Claude Security\'s report is converted by security-engine.mjs convert, never transcribed; below_threshold is declined', () => {
|
||||||
|
const p = P47();
|
||||||
|
assert.ok(p.includes('lib/review/security-engine.mjs convert --report-dir .'), 'the convert call is written out');
|
||||||
|
assert.ok(p.includes('.review-engine-payload-claude-security.json'), 'the payload file finalize reads');
|
||||||
|
assert.match(p, /converted, never transcribed/);
|
||||||
|
assert.match(p, /`below_threshold`[^.]*confidence `high`/);
|
||||||
|
assert.ok(p.includes('`engine:below-threshold`'), 'the declined reason is named');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — the not-run sentence for security says "not measured" and why, never 0', () => {
|
||||||
|
const p = P47();
|
||||||
|
assert.match(p, /"security: not measured"/);
|
||||||
|
assert.match(p, /never 0/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — the expected security reviewer is the chosen engine; none chosen is security-engine, which the verdict gate reads as exit 2; --quick keeps none', () => {
|
||||||
|
const acc = section(CMD, '### Reviewer accounting');
|
||||||
|
assert.ok(acc.includes('`claude-security`') && acc.includes('`claude-security-review`'), 'both engine reviewer names');
|
||||||
|
assert.ok(acc.includes('`security-engine`'), 'the name expected when no engine was chosen');
|
||||||
|
assert.ok(acc.includes('`missing-reviewer:security-engine`'));
|
||||||
|
assert.match(acc, /outside `quick`/);
|
||||||
|
assert.match(P47(), /In `quick` mode only `\/code-review`\s+runs: no security engine/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — the Phase 9 Engine line names Claude Security, /security-review and "not measured"', () => {
|
||||||
|
const line = CMD.split('\n').find((l) => l.startsWith('**Engine:**'));
|
||||||
|
assert.ok(line, 'the Engine line is there');
|
||||||
|
assert.match(line, /Claude Security delivered N/);
|
||||||
|
assert.match(line, /\/security-review delivered N/);
|
||||||
|
assert.match(line, /not measured: reason/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — frontmatter declares the Workflow tool Claude Security runs on', () => {
|
||||||
|
const fm = CMD.slice(0, CMD.indexOf('\n---', 4));
|
||||||
|
assert.match(fm, /allowed-tools:.*\bWorkflow\b/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] trekreview — the engine never edits code (Claude Security included), and its report folder is the one write outside the project folder', () => {
|
||||||
|
const hard = section(CMD, '## Hard rules');
|
||||||
|
assert.match(hard, /`\/code-review`, `\/security-review` and Claude Security run/);
|
||||||
|
assert.match(hard, /`CLAUDE-SECURITY-<ts>\/`/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] template — the ran line names claude-security', () => {
|
||||||
|
const tpl = read('templates/trekreview-template.md');
|
||||||
|
assert.match(tpl, /"ran": \{ "engines": \["claude-security", "code-review"\]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] coordinator — Claude Security\'s below_threshold list is an input, declined as engine:below-threshold', () => {
|
||||||
|
assert.match(COORD, /`below_threshold`[\s\S]{0,200}`engine:below-threshold`/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] docs — the engine prose names Claude Security wherever /security-review is the engine', () => {
|
||||||
|
const cases = [
|
||||||
|
['CLAUDE.md', (t) => t.split('\n').find((l) => l.startsWith('| `/trekreview` |'))],
|
||||||
|
['docs/command-modes.md', (t) => t.split('\n').find((l) => l.startsWith('| _(default)_ |') && l.includes('/code-review'))],
|
||||||
|
['docs/architecture.md', (t) => t.split('\n').find((l) => l.includes('lib/review/{verdict-gate,engine-findings'))],
|
||||||
|
['commands/trekexecute.md', (t) => section(t, '## Phase 7.2')],
|
||||||
|
['README.md', (t) => t],
|
||||||
|
['docs/HANDOVER-CONTRACTS.md', (t) => section(t, '## Handover 6')],
|
||||||
|
];
|
||||||
|
for (const [rel, pick] of cases) {
|
||||||
|
const part = pick(read(rel));
|
||||||
|
assert.ok(part, `${rel}: the engine passage is there`);
|
||||||
|
assert.match(part, /Claude Security/, `${rel} names Claude Security`);
|
||||||
|
}
|
||||||
|
const quick = read('docs/command-modes.md').split('\n').find((l) => l.startsWith('| `--quick` |') && l.includes('/code-review'));
|
||||||
|
assert.ok(quick, 'the /trekreview --quick row is there');
|
||||||
|
assert.match(quick, /no security engine/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] README and SECURITY.md — Claude Security recommended, proprietary so called and never a manifest dependency; without it /security-review; with neither "not measured"', () => {
|
||||||
|
for (const rel of ['README.md', 'SECURITY.md']) {
|
||||||
|
const t = flat(read(rel));
|
||||||
|
assert.match(t, /Claude Security is (the )?recommended/, `${rel}: recommended`);
|
||||||
|
assert.match(t, /proprietary licen[cs]e/, `${rel}: the licence named`);
|
||||||
|
assert.match(t, /never a dependency in Voyage's manifest|not a dependency in Voyage's manifest/, `${rel}: not in the manifest`);
|
||||||
|
assert.match(t, /`\/security-review`/, `${rel}: the fallback named`);
|
||||||
|
assert.match(t, /not measured/, `${rel}: neither engine is "not measured"`);
|
||||||
|
}
|
||||||
|
const manifest = read('.claude-plugin/plugin.json');
|
||||||
|
assert.ok(!/claude-security/.test(manifest), 'the manifest names no Claude Security dependency');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[E32] Handover 6 — names the engine claude-security and its payload file with below_threshold', () => {
|
||||||
|
const h6 = section(read('docs/HANDOVER-CONTRACTS.md'), '## Handover 6');
|
||||||
|
assert.ok(h6.includes('.review-engine-payload-claude-security.json'));
|
||||||
|
assert.ok(h6.includes('below_threshold'));
|
||||||
|
assert.ok(h6.includes('`engine:below-threshold`'));
|
||||||
|
assert.match(h6, /`claude-security`/);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue