granthi-review v1: estate-wide AI code review service

Webhook-driven central review service for Granthi forges: HMAC-verified
push/pull_request webhooks, per-repo serialization with global LLM
concurrency 1, shre-router powered rubric review (8 axes, strict JSON,
tolerant extractor + retry), commit statuses + PR scorecard comments,
sqlite attribution ledger with Co-Authored-By trailer parsing, per-repo
HTML history dashboard, nightly per-agent digests, fail-open-with-
visibility when the router is unavailable.

Posture: BLOCK on confirmed critical/high correctness+security findings;
admin merge is the human override.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nirav Patel
2026-08-18 23:24:27 -04:00
co-authored by Claude Fable 5
commit 9ffd810943
22 changed files with 1255 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
// Verdict derivation. Posture: BLOCK on confirmed critical/high findings in
// correctness/security; everything else is warn-or-pass. Human override =
// admin merge (branch protection does not apply to admins).
const BLOCKING_SEVERITIES = new Set(['critical', 'high']);
const BLOCKING_AXES = new Set(['correctness', 'security']);
export function isBlockingFinding(f) {
if (!f || typeof f !== 'object') return false;
const sev = String(f.severity || '').toLowerCase();
const conf = String(f.confidence || '').toLowerCase();
if (!BLOCKING_SEVERITIES.has(sev)) return false;
if (conf !== 'confirmed') return false;
// If the model tagged an axis, only correctness/security block.
// Untagged critical+confirmed findings block too (conservative).
const axis = f.axis ? String(f.axis).toLowerCase() : null;
return axis === null || BLOCKING_AXES.has(axis);
}
export function deriveVerdict(review) {
const findings = Array.isArray(review?.findings) ? review.findings : [];
if (findings.some(isBlockingFinding)) return 'fail';
const hasWarn = findings.some(f => {
const sev = String(f?.severity || '').toLowerCase();
return sev === 'critical' || sev === 'high' || sev === 'medium';
});
if (hasWarn) return 'warn';
// trust the model's verdict if it said warn without matching findings
if (review?.verdict === 'warn') return 'warn';
return 'pass';
}
export function normalizeReview(raw) {
if (!raw || typeof raw !== 'object') return null;
const axesNames = ['correctness', 'security', 'tests_coverage', 'design_simplicity',
'performance', 'style', 'breaking_changes', 'docs'];
const axes = {};
for (const name of axesNames) {
const a = raw.axes?.[name];
axes[name] = {
score: clampScore(a?.score),
rationale: typeof a?.rationale === 'string' ? a.rationale.slice(0, 500) : ''
};
}
const findings = (Array.isArray(raw.findings) ? raw.findings : []).map(f => ({
title: String(f?.title ?? 'untitled').slice(0, 200),
file: String(f?.file ?? '').slice(0, 300),
line: Number.isFinite(Number(f?.line)) ? Number(f.line) : null,
severity: pick(String(f?.severity || '').toLowerCase(), ['critical', 'high', 'medium', 'low'], 'low'),
confidence: pick(String(f?.confidence || '').toLowerCase(), ['confirmed', 'plausible'], 'plausible'),
axis: f?.axis ? String(f.axis).toLowerCase() : null,
detail: String(f?.detail ?? '').slice(0, 2000)
}));
const review = {
axes,
overall: clampScore(raw.overall),
grade: pick(String(raw.grade || '').toUpperCase(), ['A', 'B', 'C', 'D', 'F'], 'C'),
findings,
verdict: pick(String(raw.verdict || '').toLowerCase(), ['pass', 'warn', 'fail'], 'pass')
};
review.verdict = deriveVerdict(review); // service-side derivation is authoritative
return review;
}
function clampScore(v) {
const n = Number(v);
if (!Number.isFinite(n)) return 0;
return Math.max(0, Math.min(100, Math.round(n)));
}
function pick(v, allowed, fallback) {
return allowed.includes(v) ? v : fallback;
}