feat(review): two CLAUDE.md rule keys at MINOR, the catalogue counts 17
This commit is contained in:
parent
96fba75996
commit
ed67526987
9 changed files with 99 additions and 11 deletions
|
|
@ -33,7 +33,7 @@ You will receive a prompt containing:
|
|||
/trekreview triage gate. Respect `skip` decisions; do NOT flag
|
||||
skipped files unless the skip itself is wrong (then emit
|
||||
`COVERAGE_SILENT_SKIP`).
|
||||
- **Rule catalogue** — the 12-key catalogue in
|
||||
- **Rule catalogue** — the 17-key catalogue in
|
||||
`lib/review/rule-catalogue.mjs`. You may only emit findings whose
|
||||
`rule_key` is in this set.
|
||||
- **Success-criteria check results** — a table produced by /trekreview
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ You will receive a prompt containing:
|
|||
context only. The brief is not your contract — it is the conformance
|
||||
reviewer's contract. You evaluate code correctness regardless of
|
||||
what the brief promised.
|
||||
- **Rule catalogue** — the 12-key catalogue in
|
||||
- **Rule catalogue** — the 17-key catalogue in
|
||||
`lib/review/rule-catalogue.mjs`. You may only emit findings whose
|
||||
`rule_key` is in this set.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,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 + autonomy-gate state machine (v3.4.0)
|
||||
- `lib/parsers/{plan-schema,manifest-yaml,project-discovery,arg-parser,bash-normalize,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 (15 keys) + Phase 9 inline dedup helpers (v3.4.0)
|
||||
- `lib/review/{rule-catalogue,plan-review-dedup}.mjs` — version-pinned rule catalogue (17 keys) + Phase 9 inline dedup helpers (v3.4.0)
|
||||
- `lib/review/{verdict-gate,engine-findings,recurrence}.mjs` — the review gate (K7: 0 ALLOW/WARN · 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.1); `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/stats/event-emit.mjs` — single-source stats event emitter for autonomy-gate transitions and main-merge-gate (v3.4.0)
|
||||
- `lib/validators/{brief,research,plan,progress,session-state}-validator.mjs` — schema validators with CLI shims (`node lib/validators/X.mjs --json <path>`)
|
||||
|
|
@ -54,7 +54,7 @@ Doc-consistency test at `tests/lib/doc-consistency.test.mjs` pins agent-table co
|
|||
|
||||
**Continue:** `/trekcontinue` reads `{dir}/.session-state.local.json` (Handover 7), validates schema-v1 via `session-state-validator`, narrates a 3-line summary (project / next-session-label / brief-path), and immediately begins executing the next session. Auto-discovers active project state files under `.claude/projects/*/.session-state.local.json` if no explicit `<project-dir>` argument. Operator-invoked only — never auto-loaded via SessionStart. The `/trekendsession` helper is the informal-flow producer: writes the same state file for ad-hoc multi-session handovers that don't run through `/trekexecute`.
|
||||
|
||||
**Delegate the engine, keep the policy (cross-cutting; audited S32, 2026-06-20).** Every swarm and interview in the pipeline rides a *native* Claude Code primitive and re-implements no engine of its own: parallel sub-agent fan-out is a single-message multi-`Agent`-call dispatch (research swarm, exploration swarm, reviewer swarm), and interview turn-taking is `AskUserQuestion` (brief Phase 3, the research interview, the trekplan research-status gate, the trekreview scope gate). Voyage's value is the **policy** layered on top — never the scheduler, concurrency loop, or menu loop underneath: typed agent roles + effort defaults + codebase-size scaling (exploration); 4-angle decomposition (docs/community/security/contrarian) + per-source schemas + triangulation (research); the 12-key rule catalogue + no-cross-feed isolation + deterministic dedup/verdict (review); the section-driven completeness loop + framing gate (brief). Concretely: the brief's Phase-3 "selection rule" picks *which* question to ask (policy); `AskUserQuestion` does the asking (engine). The V01/V07/V08/V11/V24 balance-analysis audit confirmed all five already delegate natively — no engine re-implementation found; the doc-consistency test (`tests/lib/doc-consistency.test.mjs` § S32) pins the native-delegation prose so a hand-rolled engine cannot creep back in.
|
||||
**Delegate the engine, keep the policy (cross-cutting; audited S32, 2026-06-20).** Every swarm and interview in the pipeline rides a *native* Claude Code primitive and re-implements no engine of its own: parallel sub-agent fan-out is a single-message multi-`Agent`-call dispatch (research swarm, exploration swarm, reviewer swarm), and interview turn-taking is `AskUserQuestion` (brief Phase 3, the research interview, the trekplan research-status gate, the trekreview scope gate). Voyage's value is the **policy** layered on top — never the scheduler, concurrency loop, or menu loop underneath: typed agent roles + effort defaults + codebase-size scaling (exploration); 4-angle decomposition (docs/community/security/contrarian) + per-source schemas + triangulation (research); the 17-key rule catalogue + no-cross-feed isolation + deterministic dedup/verdict (review); the section-driven completeness loop + framing gate (brief). Concretely: the brief's Phase-3 "selection rule" picks *which* question to ask (policy); `AskUserQuestion` does the asking (engine). The V01/V07/V08/V11/V24 balance-analysis audit confirmed all five already delegate natively — no engine re-implementation found; the doc-consistency test (`tests/lib/doc-consistency.test.mjs` § S32) pins the native-delegation prose so a hand-rolled engine cannot creep back in.
|
||||
|
||||
**Operator-UX guarantee (since v5.0.2):** `/trekbrief`, `/trekplan`, and `/trekreview` MUST always emit (a) a plain `file://<abs path>` URL AND (b) a copy-pasteable `open file://<abs path>` command in the final report block. The file:// URL must use an ABSOLUTE path (not relative or `~/`-prefixed) so terminals with cmd+click support (Ghostty, iTerm2, modern Terminal.app) can resolve it without shell interpretation. This is a non-negotiable operator-UX contract — the doc-consistency test pins both forms in all three commands' final report blocks.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// lib/review/rule-catalogue.mjs
|
||||
// Canonical rule catalogue for /trekreview.
|
||||
//
|
||||
// 15 rule keys, 4-tier severity (matches brief contract).
|
||||
// 17 rule keys, 4-tier severity (matches brief contract). Decision 98 added
|
||||
// CLAUDE_MD_DEVIATION and CLAUDE_MD_STALE (both MINOR): a change that breaks, or
|
||||
// makes untrue, a line of the target repo's tracked instruction files.
|
||||
// llm-security 5-tier alignment is a v1.1 candidate.
|
||||
//
|
||||
// Ordre 5 (review_version 1.1) added the two engine keys: findings from Claude
|
||||
|
|
@ -138,6 +140,21 @@ export const RULE_CATALOGUE = Object.freeze([
|
|||
description: "A vulnerability reported by Claude Code's /security-review that no narrower rule_key covers.",
|
||||
memory_route: null,
|
||||
}),
|
||||
// memory_route null: a lesson about the instruction files would feed back into them.
|
||||
Object.freeze({
|
||||
rule_key: 'CLAUDE_MD_DEVIATION',
|
||||
severity: 'MINOR',
|
||||
category: 'conformance',
|
||||
description: "A change that breaks a line of the target repo's tracked CLAUDE.md, AGENTS.md or .claude/rules file.",
|
||||
memory_route: null,
|
||||
}),
|
||||
Object.freeze({
|
||||
rule_key: 'CLAUDE_MD_STALE',
|
||||
severity: 'MINOR',
|
||||
category: 'conformance',
|
||||
description: 'A change that makes a line of such an instruction file untrue.',
|
||||
memory_route: null,
|
||||
}),
|
||||
]);
|
||||
|
||||
export const MEMORY_ROUTES = Object.freeze(['check', 'rule']);
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const RULE_TIERS = {
|
|||
PLACEHOLDER_IN_CODE: 'MAJOR', MISSING_ERROR_HANDLING: 'MINOR', UNDECLARED_DEPENDENCY: 'MAJOR',
|
||||
PLAN_EXECUTE_DRIFT: 'MAJOR', BROKEN_SUCCESS_CRITERION: 'BLOCKER', COVERAGE_SILENT_SKIP: 'MAJOR',
|
||||
CODE_REVIEW_DEFECT: 'MAJOR', CODE_REVIEW_IMPROVEMENT: 'MINOR', SECURITY_REVIEW_VULNERABILITY: 'BLOCKER',
|
||||
CLAUDE_MD_DEVIATION: 'MINOR', CLAUDE_MD_STALE: 'MINOR',
|
||||
};
|
||||
const RULE_KEYS = Object.keys(RULE_TIERS);
|
||||
// Lower number = higher severity; an unknown severity ranks last (as
|
||||
|
|
|
|||
65
tests/lib/claude-md-scope.test.mjs
Normal file
65
tests/lib/claude-md-scope.test.mjs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// tests/lib/claude-md-scope.test.mjs
|
||||
// Fix round 4.5b2 part B, Step 1 (tag CAT): the rule catalogue holds the two
|
||||
// CLAUDE.md keys at MINOR (decision 98), and every text that counts the keys
|
||||
// says 17. Each [CAT] case fails at 96fba75 on an assertion.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { RULE_CATALOGUE, RULE_KEYS, getRule } from '../../lib/review/rule-catalogue.mjs';
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
const read = (rel) => {
|
||||
const p = join(ROOT, rel);
|
||||
assert.ok(existsSync(p), `${rel} exists`);
|
||||
return readFileSync(p, 'utf-8');
|
||||
};
|
||||
|
||||
test('[CAT] the catalogue holds CLAUDE_MD_DEVIATION and CLAUDE_MD_STALE at MINOR with memory_route null', () => {
|
||||
for (const key of ['CLAUDE_MD_DEVIATION', 'CLAUDE_MD_STALE']) {
|
||||
assert.ok(RULE_KEYS.has(key), `RULE_KEYS holds ${key}`);
|
||||
const rule = getRule(key);
|
||||
assert.ok(rule !== null, `getRule(${key}) finds the entry`);
|
||||
assert.equal(rule.severity, 'MINOR', `${key} is MINOR`);
|
||||
assert.equal(rule.category, 'conformance', `${key} is conformance`);
|
||||
assert.equal(rule.memory_route, null, `${key} is never proposed as project memory`);
|
||||
assert.ok(typeof rule.description === 'string' && rule.description.length > 0, `${key} has a description`);
|
||||
}
|
||||
assert.equal(
|
||||
getRule('CLAUDE_MD_DEVIATION').description,
|
||||
"A change that breaks a line of the target repo's tracked CLAUDE.md, AGENTS.md or .claude/rules file.",
|
||||
);
|
||||
assert.equal(
|
||||
getRule('CLAUDE_MD_STALE').description,
|
||||
'A change that makes a line of such an instruction file untrue.',
|
||||
);
|
||||
});
|
||||
|
||||
test('[CAT] the catalogue has 17 keys and docs/architecture.md and both reviewer agents say 17-key', () => {
|
||||
assert.equal(RULE_CATALOGUE.length, 17);
|
||||
assert.equal(RULE_KEYS.size, 17);
|
||||
const header = read('lib/review/rule-catalogue.mjs').split('\n').slice(0, 10).join('\n');
|
||||
assert.match(header, /\b17 rule keys\b/, 'the catalogue header counts 17 rule keys');
|
||||
const arch = read('docs/architecture.md');
|
||||
assert.match(arch, /rule catalogue \(17 keys\)/);
|
||||
assert.match(arch, /the 17-key rule catalogue/);
|
||||
assert.doesNotMatch(arch, /\b(12|15)-key rule catalogue|rule catalogue \((12|15) keys\)/);
|
||||
for (const agent of ['agents/code-correctness-reviewer.md', 'agents/brief-conformance-reviewer.md']) {
|
||||
const text = read(agent);
|
||||
assert.match(text, /the 17-key catalogue/, `${agent} says 17-key`);
|
||||
assert.doesNotMatch(text, /\b(12|15)-key catalogue/, `${agent} no longer says 12-key`);
|
||||
}
|
||||
});
|
||||
|
||||
test('[CAT] the armB RULE_TIERS table holds both keys at MINOR', () => {
|
||||
const src = read('scripts/trekreview-armB.workflow.mjs');
|
||||
const start = src.indexOf('const RULE_TIERS = {');
|
||||
assert.ok(start !== -1, 'the script carries a RULE_TIERS literal block');
|
||||
const block = src.slice(start, src.indexOf('};', start));
|
||||
const tiers = Object.fromEntries([...block.matchAll(/([A-Z_]+):\s*'([A-Z]+)'/g)].map((m) => [m[1], m[2]]));
|
||||
assert.equal(tiers.CLAUDE_MD_DEVIATION, 'MINOR');
|
||||
assert.equal(tiers.CLAUDE_MD_STALE, 'MINOR');
|
||||
assert.equal(Object.keys(tiers).length, 17);
|
||||
});
|
||||
|
|
@ -270,12 +270,12 @@ test('HANDOVER-CONTRACTS.md Handover 7 § Lifecycle names --cleanup and produced
|
|||
// file" (commands/trekcontinue.md exists, so that loop already asserts the
|
||||
// /trekcontinue reference). Single-command existence pin → redundant.
|
||||
|
||||
test('rule-catalogue has exactly 15 entries', async () => {
|
||||
test('rule-catalogue has exactly 17 entries', async () => {
|
||||
const mod = await import('../../lib/review/rule-catalogue.mjs');
|
||||
assert.strictEqual(
|
||||
mod.RULE_CATALOGUE.length,
|
||||
15,
|
||||
'lib/review/rule-catalogue.mjs RULE_CATALOGUE size invariant: must be 15 (12 v1.0 baseline + 2 engine keys, ordre 5, + CODE_REVIEW_IMPROVEMENT, lekerepo trial 2)',
|
||||
17,
|
||||
'lib/review/rule-catalogue.mjs RULE_CATALOGUE size invariant: must be 17 (12 v1.0 baseline + 2 engine keys, ordre 5, + CODE_REVIEW_IMPROVEMENT, lekerepo trial 2, + CLAUDE_MD_DEVIATION and CLAUDE_MD_STALE, decision 98)',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -39,9 +39,14 @@ test('RULE_CATALOGUE — all category values within enum', () => {
|
|||
}
|
||||
});
|
||||
|
||||
test('RULE_KEYS.size === RULE_CATALOGUE.length (== 15) — pinned by doc-consistency', () => {
|
||||
test('RULE_KEYS.size === RULE_CATALOGUE.length (== 17) — pinned by doc-consistency', () => {
|
||||
assert.equal(RULE_KEYS.size, RULE_CATALOGUE.length);
|
||||
assert.equal(RULE_CATALOGUE.length, 15);
|
||||
assert.equal(RULE_CATALOGUE.length, 17);
|
||||
});
|
||||
|
||||
test('CLAUDE_MD_DEVIATION and CLAUDE_MD_STALE are MINOR', () => {
|
||||
assert.equal(getRule('CLAUDE_MD_DEVIATION')?.severity, 'MINOR');
|
||||
assert.equal(getRule('CLAUDE_MD_STALE')?.severity, 'MINOR');
|
||||
});
|
||||
|
||||
test('RULE_CATALOGUE — the two Claude Code engine keys exist at their tiers (ordre 5)', () => {
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ test('A7 (review): the session-spec template stages before it commits too', () =
|
|||
});
|
||||
|
||||
test('docs (review): the counts and the Declined-to-Judge rule match the code', () => {
|
||||
assert.match(read('docs/architecture.md'), /rule catalogue \(15 keys\)/);
|
||||
assert.match(read('docs/architecture.md'), /rule catalogue \(17 keys\)/);
|
||||
assert.match(read('docs/HANDOVER-CONTRACTS.md'), /three engine rule keys/);
|
||||
assert.doesNotMatch(read('lib/review/engine-findings.mjs'), /it forbids ALLOW and is listed under/);
|
||||
assert.match(read('agents/review-coordinator.md'), /every `\[unverified\]` one, and every\s+out-of-scope engine finding/);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue