Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
94ae1d6
docs: competitive research, spec, and plan for review config layer
dr-bizz Jun 23, 2026
9ef58de
docs: revise plan for CommonJS engine + Yarn PnP constraints
dr-bizz Jun 23, 2026
8a4f986
feat(review): scaffold config engine deps, runner + JSON schema
dr-bizz Jun 23, 2026
b0e119c
feat(review): config loader + ajv validation
dr-bizz Jun 23, 2026
3bfac9d
feat(review): config-driven risk scorer
dr-bizz Jun 23, 2026
8f73e31
feat(review): config-driven agent selection
dr-bizz Jun 23, 2026
d5c60ce
feat(review): rule resolver (agent + path rules)
dr-bizz Jun 23, 2026
2c8c0bf
feat(review): deterministic special-pattern detection
dr-bizz Jun 23, 2026
1044a06
feat(review): plan CLI entry assembling risk + agents + rules
dr-bizz Jun 23, 2026
52f64b6
feat(review): author config.yml migrated from code-review.md
dr-bizz Jun 23, 2026
433cc43
feat(review): migrate prose focus-areas into rules/*.md
dr-bizz Jun 23, 2026
eeecbe4
refactor(review): drive risk + agent selection from config engine
dr-bizz Jun 23, 2026
3365548
chore(review): supersede code-review.md with pointer to review core
dr-bizz Jun 23, 2026
b9faba0
build(review): commit PnP map for yaml/minimatch/ajv (Zero-Install)
dr-bizz Jun 23, 2026
8d019c9
docs: spec for agent-review index layer (Gap 2 / Phase B)
dr-bizz Jun 23, 2026
e7d618b
docs: implementation plan for agent-review index layer (Gap 2)
dr-bizz Jun 23, 2026
25cb249
feat(review): import specifier resolver for index graph
dr-bizz Jun 23, 2026
eda56d5
feat(review): file-level import graph builder
dr-bizz Jun 23, 2026
7622a98
feat(review): transitive impact query over import graph
dr-bizz Jun 23, 2026
bbc2d37
feat(review): HEAD-keyed import-graph cache (indexStore)
dr-bizz Jun 23, 2026
0e4579e
feat(review): impact CLI emitting dependents report
dr-bizz Jun 23, 2026
5a4719a
feat(review): wire impact analysis into agent-review Stage 1B
dr-bizz Jun 23, 2026
a4df948
docs: spec for agent-review learning layer (Gap 3 / Phase C)
dr-bizz Jun 23, 2026
0bc25ce
docs: implementation plan for agent-review learning layer (Gap 3)
dr-bizz Jun 23, 2026
22b21f1
feat(review): stable finding signature for learning
dr-bizz Jun 23, 2026
ecff08e
feat(review): mine feedback into proposed learnings
dr-bizz Jun 23, 2026
2fd74c3
feat(review): apply approved learnings (suppress + rule injection)
dr-bizz Jun 23, 2026
08f52f3
feat(review): learnings store + emit/ingest/mine/rules/filter CLI
dr-bizz Jun 23, 2026
ca8489e
feat(review): enable learning layer + wire feedback/learn into command
dr-bizz Jun 23, 2026
266509a
docs: spec for agent-review CLI (Phase D)
dr-bizz Jun 23, 2026
d4ad437
docs: implementation plan for agent-review CLI (Phase D)
dr-bizz Jun 23, 2026
4398e6d
feat(review): pure CLI helpers (status, list, preflight)
dr-bizz Jun 23, 2026
07fd592
feat(review): unified review CLI dispatcher (config/index/impact/feed…
dr-bizz Jun 23, 2026
e374025
feat(review): review run pre-flight + claude -p launch
dr-bizz Jun 23, 2026
0e9a16a
docs: spec for agent-review plugin distribution (Phase E)
dr-bizz Jun 24, 2026
71ea930
docs: implementation plan for agent-review plugin distribution (Phase E)
dr-bizz Jun 24, 2026
a5b6bc8
fix(review): address dogfood-review findings
dr-bizz Jun 25, 2026
eeff15c
fix(review): close deferred dogfood items
dr-bizz Jun 25, 2026
bbdbc5e
ci: run review engine tests (yarn test:review) only when review files…
dr-bizz Jun 26, 2026
57e5e55
Merge origin/main into review-config-layer
dr-bizz Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 182 additions & 182 deletions .claude/commands/agent-review.md

Large diffs are not rendered by default.

327 changes: 327 additions & 0 deletions .claude/docs/competitive-research-greptile-coderabbit.md

Large diffs are not rendered by default.

176 changes: 176 additions & 0 deletions .claude/review/cli.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
'use strict';
const { join } = require('node:path');
const { execFileSync } = require('node:child_process');
const { readFileSync, writeFileSync, existsSync, rmSync } = require('node:fs');
const os = require('node:os');
const { loadConfig } = require('./engine/loadConfig.cjs');
const { buildPlan, linesChangedFromStat } = require('./engine/plan.cjs');
const { loadOrBuildIndex, gitHead, listRepoFiles } = require('./engine/indexStore.cjs');
const { queryImpact } = require('./engine/queryImpact.cjs');
const { mineLearnings } = require('./engine/mineLearnings.cjs');
const { parsePending, appendFeedback, loadFeedback, loadLearnings, saveLearnings, mergeProposals } = require('./engine/learningsStore.cjs');
const { setLearningStatus, listLearnings, preflightSummary } = require('./engine/cliCommands.cjs');

const ROOT = process.cwd();
const RD = join(ROOT, '.claude/review');
const CONFIG = join(RD, 'config.yml');
const SCHEMA = join(RD, 'config.schema.json');
const INDEX = join(RD, 'index');
const FEEDBACK = join(RD, 'learnings/feedback.jsonl');
const LEARNINGS = join(RD, 'learnings/learnings.yml');
const MODES = ['quick', 'standard', 'deep'];

function out(s) { process.stdout.write(s + '\n'); }

// Returns the value after `name`, or undefined if absent or the next token is itself a flag.
function flag(argv, name) {
const i = argv.indexOf(name);
if (i < 0) return undefined;
const v = argv[i + 1];
return v === undefined || v.startsWith('--') ? undefined : v;
}

function validRef(ref) {
return /^[A-Za-z0-9._/~^-]+$/.test(ref) && !ref.startsWith('-');
}

function changedFiles(base) {
let b = base;
if (b && !validRef(b)) throw new Error(`invalid --base ref: "${b}"`);
if (!b) {
try { b = execFileSync('git', ['-C', ROOT, 'merge-base', 'main', 'HEAD'], { encoding: 'utf8' }).trim(); }
catch { b = 'HEAD~1'; }
}
let raw;
try {
raw = execFileSync('git', ['-C', ROOT, 'diff', '--name-only', `${b}...HEAD`], { encoding: 'utf8' });
} catch (e) {
throw new Error(`could not determine a diff base (tried "${b}"). Pass --base <ref>. [${e.message.split('\n')[0]}]`);
}
return { base: b, files: raw.split('\n').map((s) => s.trim()).filter(Boolean) };
}

function indexOpts(cfg) {
const ix = (cfg && cfg.index) || {};
return { aliases: ix.aliases, exts: ix.extensions, roots: ix.roots };
}

function loadIndex(cfg, { force } = {}) {
const c = cfg || loadConfig({ configPath: CONFIG, schemaPath: SCHEMA });
const indexPath = c.index && c.index.path ? join(ROOT, c.index.path) : INDEX;
if (force) {
const gf = join(indexPath, 'graph.json');
if (existsSync(gf)) rmSync(gf);
}
const opts = indexOpts(c);
return loadOrBuildIndex({ repoRoot: ROOT, indexPath, head: gitHead(ROOT), files: listRepoFiles(ROOT, opts), opts });
}

const USAGE = `usage: yarn review <command>
config show|validate|get <k> show / validate / read a config value
index rebuild the import-graph cache
impact [--base <ref>] cross-file blast radius for the current diff
feedback <pendingFile> ingest marked outcomes
learn [--min-support N] mine feedback into proposed learnings
learnings [--status S] list learnings
approve <id> | reject <id> set a learning's status
run [--base <ref>] [--scope <s>] [mode] pre-flight + launch the Claude Code review
help`;

function main(argv) {
const cmd = argv[0];
const rest = argv.slice(1);
switch (cmd) {
case 'config': {
const cfg = loadConfig({ configPath: CONFIG, schemaPath: SCHEMA });
if (rest[0] === 'validate') { out('config OK'); return 0; }
if (rest[0] === 'get') {
if (!rest[1]) { out('usage: yarn review config get <dot.path>'); return 1; }
const val = rest[1].split('.').reduce((o, k) => (o == null ? undefined : o[k]), cfg);
out(val !== null && typeof val === 'object' ? JSON.stringify(val) : String(val));
return 0;
}
out(JSON.stringify(cfg, null, 2));
return 0;
}
case 'index': {
const g = loadIndex(undefined, { force: rest.includes('--force') });
out(`Indexed ${g.fileCount} files; ${Object.keys(g.importedBy).length} have dependents.`);
return 0;
}
case 'impact': {
const { files } = changedFiles(flag(rest, '--base'));
out(JSON.stringify(queryImpact(files, loadIndex(), {}), null, 2));
return 0;
}
case 'feedback': {
if (!rest[0]) { out('usage: yarn review feedback <pendingFile>'); return 1; }
const entries = parsePending(readFileSync(rest[0], 'utf8')).map((e) => ({ ts: new Date().toISOString(), ...e }));
appendFeedback(FEEDBACK, entries);
out(`Ingested ${entries.length} outcomes`);
return 0;
}
case 'learn': {
let minSupport = 3;
const ms = flag(rest, '--min-support');
if (ms !== undefined) {
const n = Number(ms);
if (!Number.isInteger(n) || n < 1) { out('error: --min-support must be a positive integer'); return 1; }
minSupport = n;
}
const proposals = mineLearnings(loadFeedback(FEEDBACK), { minSupport });
const merged = mergeProposals(loadLearnings(LEARNINGS), proposals);
saveLearnings(LEARNINGS, merged);
out(`Mined ${proposals.length} proposals; ${merged.learnings.length} total`);
return 0;
}
case 'learnings': {
out(JSON.stringify(listLearnings(loadLearnings(LEARNINGS), flag(rest, '--status')), null, 2));
return 0;
}
case 'approve':
case 'reject': {
if (!rest[0]) { out(`usage: yarn review ${cmd} <id>`); return 1; }
const status = cmd === 'approve' ? 'approved' : 'rejected';
saveLearnings(LEARNINGS, setLearningStatus(loadLearnings(LEARNINGS), rest[0], status));
out(`${rest[0]} -> ${status}`);
return 0;
}
case 'run': {
const base = flag(rest, '--base');
const scope = flag(rest, '--scope') || 'single_feature';
const mode = rest.find((a) => !a.startsWith('--') && a !== base && a !== scope) || 'standard';
if (!MODES.includes(mode)) { out(`error: unknown mode "${mode}" (use ${MODES.join('/')})`); return 1; }
const { base: b, files } = changedFiles(base);
const diff = execFileSync('git', ['-C', ROOT, 'diff', `${b}...HEAD`], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
const stat = execFileSync('git', ['-C', ROOT, 'diff', '--stat', `${b}...HEAD`], { encoding: 'utf8' });
const cfg = loadConfig({ configPath: CONFIG, schemaPath: SCHEMA });
const plan = buildPlan({ files, diffText: diff, linesChanged: linesChangedFromStat(stat), scope }, cfg);
const impact = cfg.index && cfg.index.enabled ? queryImpact(files, loadIndex(cfg), {}) : null;
out(preflightSummary(plan, impact));
writeFileSync(join(os.tmpdir(), 'review_plan.json'), JSON.stringify({ ...plan, impact }, null, 2));
if (rest.includes('--no-launch')) { out(`\nwould run: claude -p "/agent-review ${mode}"`); return 0; }
out(`\nlaunching: claude -p "/agent-review ${mode}" ...\n`);
try { execFileSync('claude', ['-p', `/agent-review ${mode}`], { stdio: 'inherit' }); }
catch (e) {
out(`(could not launch claude automatically: ${e.message})`);
out(`Run it manually in Claude Code: /agent-review ${mode}`);
}
return 0;
}
case 'help':
case undefined:
out(USAGE);
return 0;
default:
out(`unknown command: ${cmd}\n\n${USAGE}`);
return 1;
}
}

if (require.main === module) {
try { process.exit(main(process.argv.slice(2))); }
catch (e) { process.stderr.write(`error: ${e.message}\n`); process.exit(1); }
}

module.exports = { main };
134 changes: 134 additions & 0 deletions .claude/review/config.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://mpdx.org/review/config.schema.json",
"title": "MPDX Agent-Review Config",
"type": "object",
"additionalProperties": false,
"required": ["version", "profile", "risk", "agents", "excluded_paths"],
"properties": {
"version": { "type": "integer", "enum": [1] },
"profile": { "type": "string", "enum": ["chill", "standard", "assertive"] },
"risk": {
"type": "object",
"additionalProperties": false,
"required": ["patterns", "volume_multiplier", "scope_multiplier", "special", "levels"],
"properties": {
"patterns": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["glob", "points"],
"properties": {
"glob": { "type": "string" },
"points": { "type": "integer", "minimum": 0 },
"tier": { "type": "string", "enum": ["critical", "high", "medium", "low"] }
}
}
},
"volume_multiplier": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["upTo", "points"],
"properties": {
"upTo": { "type": ["integer", "null"] },
"points": { "type": "integer", "minimum": 0 }
}
}
},
"scope_multiplier": { "type": "object", "additionalProperties": { "type": "number" } },
"special": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["when", "points"],
"properties": {
"when": { "type": "string" },
"points": { "type": "integer", "minimum": 0 },
"packages": { "type": "array", "items": { "type": "string" } }
}
}
},
"levels": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["range", "level", "reviewer"],
"properties": {
"range": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": ["integer", "null"] } },
"level": { "type": "string", "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"] },
"reviewer": { "type": "string" }
}
}
}
}
},
"agents": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id"],
"properties": {
"id": { "type": "string" },
"enabled": { "type": "boolean" },
"always": { "type": "boolean" },
"model": { "type": "string", "enum": ["smart", "opus", "sonnet", "haiku"] },
"triggers": {
"type": "object",
"additionalProperties": false,
"properties": {
"paths": { "type": "array", "items": { "type": "string" } },
"content": { "type": "array", "items": { "type": "string" } }
}
},
"rules": { "type": "array", "items": { "type": "string", "pattern": "^rules/[A-Za-z0-9._-]+\\.md$" } }
}
}
},
"path_rules": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["paths", "rules"],
"properties": {
"paths": { "type": "array", "items": { "type": "string" } },
"rules": { "type": "array", "items": { "type": "string", "pattern": "^rules/[A-Za-z0-9._-]+\\.md$" } }
}
}
},
"excluded_paths": { "type": "array", "items": { "type": "string" } },
"index": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"path": { "type": "string" },
"roots": { "type": "array", "items": { "type": "string" } },
"aliases": { "type": "array", "items": { "type": "string" } },
"extensions": { "type": "array", "items": { "type": "string" } }
}
},
"learning": {
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": { "type": "boolean" },
"path": { "type": "string" },
"approval_required": { "type": "boolean" },
"min_support": { "type": "integer", "minimum": 1 },
"scope": { "type": "string", "enum": ["local", "global"] }
}
},
"enforcement": {
"type": "object",
"additionalProperties": false,
"properties": { "mode": { "type": "string", "enum": ["warn", "block"] } }
}
}
}
Loading
Loading