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:
Kjell Tore Guttormsen 2026-11-08 13:39:44 +01:00
commit d97e9b5b19
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
10 changed files with 279 additions and 49 deletions

View file

@ -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);
});
// ---- 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`/);
});