feat(review): the security engine chosen and Claude Security converted (E 3.1)

lib/review/security-engine.mjs (decisions 175, 196, 219):
- choose: Claude Security when available and base..head has committed
  changes; else /security-review when origin/HEAD resolves and
  origin/HEAD...HEAD is non-empty; else no engine (no-engine, no-origin,
  empty-diff), which is "not measured", never 0. A git failure or bad
  usage is exit 2, never a reason.
- convert: reads the newest CLAUDE-SECURITY-<ts>/ report whose stamp is a
  changes scan of exactly base..head; confidence high becomes engine
  findings through toReviewerPayload, the rest go to below_threshold and
  never into findings. An unverified scan, missing researcher counts,
  fewer researchers returned than dispatched, an empty diff or scope, or
  a malformed record is exit 2.
- NO_ENGINE_REVIEWER: with no engine chosen the expected reviewer
  security-engine never reports, so the verdict gate exits 2.

ENGINES gains claude-security (its own reviewer and brief_ref, the
engines' key) with a per-engine label for the written action; it is a
security engine in isSecurityFinding, RAN_ENGINES, the coordinator
contract (out-of-scope and brief-conflict rules) and the coordinator
prose; engine:below-threshold is an unverified reason fed like
engineOutOfScope. Pins of the changed coordinator prose follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-11-08 13:29:59 +01:00
commit cb7c13b408
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
18 changed files with 1045 additions and 44 deletions

View file

@ -32,18 +32,21 @@ This file holds only your own role: the bounded passes and the verdict.
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), 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` / `CODE_REVIEW_IMPROVEMENT` (MINOR: simplification,
efficiency, reuse, style, readability) / `SECURITY_REVIEW_VULNERABILITY` (or a
narrower key) and are treated exactly like any reviewer's findings.
mode, only the latter), plus the engine payloads: `claude-code-review`
(Claude Code's `/code-review`) and, not in `quick`, the one security engine
`/trekreview` chose: `claude-security` (Claude Security) or
`claude-security-review` (Claude Code's `/security-review`), or none. Engine
findings carry the catalogue keys `CODE_REVIEW_DEFECT` /
`CODE_REVIEW_IMPROVEMENT` (MINOR: simplification, efficiency, reuse, style,
readability) / `SECURITY_REVIEW_VULNERABILITY` (or a narrower key) and are
treated exactly like any reviewer's findings.
- **Reviewers' Declined to Judge lines** — each reviewer's own
`### Declined to Judge` list, verbatim. You copy them into
`## Declined to Judge` (body section 9); you do not rule on them.
- **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).
forbids `ALLOW` (Pass 4). When no security engine could run, the set holds
`security-engine`, which never reports: security is not measured, never 0.
- **Engine findings outside the review scope** — the `out_of_scope` list from
`lib/review/engine-findings.mjs`. List each one under `## Declined to Judge`
(`engine:out-of-scope`): a real claim on a file this review did not cover.
@ -51,6 +54,12 @@ You will receive a prompt containing:
`/code-review` (given exactly this range): that one is declined but does not
hold `ALLOW` back. A MAJOR one still does: a change that breaks an
unchanged caller is cited at the caller.
- **Claude Security findings below its threshold** — the `below_threshold`
list of the `claude-security` payload (`lib/review/security-engine.mjs`):
findings Claude Security marked below confidence `high` (decision 219). List
each one under `## Declined to Judge` (`engine:below-threshold`) and in the
JSON `declined_to_judge`. It is `unverified`: not counted, never in
`findings`, and it forbids `ALLOW`.
- **Triage map** — `{file → deep-review|summary-only|skip, reason}` from
the /trekreview triage gate.
- **Brief metadata** — `task`, `slug`, `project_dir`, `brief_path` from
@ -93,8 +102,8 @@ collide:
not in the catalogue (BLOCKER > MAJOR > MINOR > SUGGESTION);
2. if the catalogue tier ties (for a catalogue key it always does,
since the triplet shares the `rule_key`), prefer a reviewer-agent
finding over an engine finding (one from `claude-code-review` /
`claude-security-review`);
finding over an engine finding (one from `claude-code-review`,
`claude-security-review` or `claude-security`);
3. among findings of one kind, the higher asserted severity;
4. among reviewer-agent findings, prefer the finding from
`brief-conformance-reviewer` (its findings are anchored to the brief);
@ -125,9 +134,10 @@ is load-bearing** — see *Suppression is two-valued* below:
| Accuracy | `file` resolves under the repo root AND `line` ≥ 0 | Path traversal escape or negative line | **dropped** (`accuracy:refuted`). A line past the end of the file (when its line count is known) is not refuted: the citation is wrong, the claim is unchecked → `unverified` (`accuracy:line-out-of-range`) |
| Actionability | `recommended_action` is non-empty AND begins with an imperative verb | Empty action, "consider …" hedges, or restating the title | `unverified` (`actionability:empty`) |
A finding that carries `engine` (`code-review` / `security-review`) came
from Claude Code's own review through `lib/review/engine-findings.mjs`, which
writes its action from the engine's failure scenario; it is
A finding that carries `engine` (`code-review` / `security-review` /
`claude-security`) came from an engine through `lib/review/engine-findings.mjs`
(Claude Security's through `lib/review/security-engine.mjs`), which writes its
action from the engine's fix or failure scenario; it is
never set aside for Actionability. (Before this rule the converter wrote
`Resolve: <title>`, and 8 of 8 engine findings were set aside in the
voyage-lekerepo trial, 2026-10-06.)
@ -177,16 +187,16 @@ Remove findings that fail ANY of these tests:
catalogue tier; this is a correction, neither a drop nor an unverified.
**The brief-conflict rule (one fixed rule).** Read the **Brief choices** input.
An engine finding (`engine: code-review` or `security-review`) whose recommended action
An engine finding (`engine: code-review`, `security-review` or `claude-security`) whose recommended action
contradicts a passed Non-Goal or stated choice gets `contradicts_brief: "<verbatim>"`
(that Non-Goal or choice, word for word) and is kept, never removed;
only a `/code-review` finding at the MAJOR tier and at severity MAJOR then drops to MINOR, with `brief_lowered_from: MAJOR`
(in place of the catalogue reset above);
a BLOCKER-tier, a MINOR-tier and any `/security-review` finding only get the mark,
a BLOCKER-tier, a MINOR-tier and any finding from a security engine (`/security-review`, Claude Security) only get the mark,
and no engine finding is otherwise written below its catalogue tier.
The rule reads engine findings alone, so only an engine finding is ever marked:
`engine` is written only on a finding whose kept Pass 1 survivor itself came from an engine payload
(`claude-code-review` → `code-review`, `claude-security-review` → `security-review`),
(`claude-code-review` → `code-review`, `claude-security-review` → `security-review`, `claude-security` → `claude-security`),
and a reviewer-agent finding (no `engine`) and a finding whose `rule_key` is not in the catalogue are never marked.
Remove any `brief_lowered_from` an incoming finding carries, and any mark on a finding
without an `engine`: only this rule writes them.
@ -330,7 +340,8 @@ You never write `ran`, `finalized`, `round`, `round_base`, `resolved` or
withheld `ALLOW`.
9. `## Declined to Judge` — REQUIRED. One line per finding you set aside
without ruling on it (every `[unverified]` one, and every
out-of-scope engine finding: `file:line rule_key — reason`), then every
out-of-scope engine finding and Claude Security finding below its
threshold: `file:line rule_key — reason`), then every
line a reviewer wrote under its own `### Declined to Judge`, copied as
`{reviewer}: {line}` (they are not findings: they stay out of the JSON
`declined_to_judge` array and do not change the verdict), then one line `Trimmed: N` (MINOR/SUGGESTION text cut, findings
@ -363,7 +374,7 @@ followed by these fields:
- `- title: {short imperative title}`
- `- detail: {what is wrong, with citation}`
- `- recommended_action: {one imperative step}`
- `- engine: {code-review | security-review, when the finding came from an engine}`
- `- engine: {code-review | security-review | claude-security, when the finding came from an engine}`
- `- contradicts_brief: {verbatim Non-Goal or choice, only when set}`
- `- brief_lowered_from: {MAJOR, only when lowered for the brief}`