// lib/review/coordinator-contract.mjs // SKAL-1·4a — deterministic reference implementation of the review-coordinator // 4-pass contract (agents/review-coordinator.md §"Your 4-pass process"). // // This is a DETERMINISTIC SUBSET, not a full mirror of the LLM coordinator. // It implements the pure, hermetic passes and DELIBERATELY EXCLUDES the parts // that need a live filesystem or LLM judgement (which belong to the 4c // LLM-in-the-loop eval, not this all-agree foundation tier): // - Pass 2 "Accuracy" file-existence / line-plausibility glob (fs I/O). // - Pass 2 "Actionability" imperative-verb heuristic — the real coordinator // uses LLM judgement here; a verb-list approximation would DIVERGE from the // contract being mirrored, so only the deterministic "recommended_action is // present-and-non-empty when supplied" half is kept. // - The doc's 4-tuple `(file,line,rule_key,title)` id recompute — the shipped // `computeFindingId` is 3-arg `(file,line,rule_key)`; this module follows // the shipped code and flags the doc divergence (the 4-tuple id is not // producible by the current helper). // // What IS implemented, purely: Pass 1 (triplet dedup → highest-severity-wins // survivor + conformance tiebreak + detail concat + raised_by provenance), // Pass 2 succinctness + actionability-presence, Pass 3 reasonableness // (citation / unknown-rule_key drop, severity-mismatch correction), Pass 4 // verdict thresholds. No LLM, no network, no time, no randomness. // // Reuses: SEVERITY_VALUES / RULE_KEYS / getRule (rule-catalogue.mjs), // computeFindingId (finding-id.mjs, triplet), validateFindings // (findings-schema.mjs). Triplet key format mirrors // scripts/bakeoff-armA-merge.mjs:33; raised_by provenance mirrors // lib/review/plan-review-dedup.mjs. import { SEVERITY_VALUES, RULE_KEYS, getRule } from './rule-catalogue.mjs'; import { computeFindingId } from '../parsers/finding-id.mjs'; import { validateFindings } from './findings-schema.mjs'; export const JUDGE_TITLE_MAX = 100; export const JUDGE_DETAIL_MAX = 800; /** * Catalogue-tier rank of a severity: lower number = higher severity. * BLOCKER=0 … SUGGESTION=3; an unknown severity ranks last. * @param {string} severity * @returns {number} */ export function severityRank(severity) { const i = SEVERITY_VALUES.indexOf(severity); return i === -1 ? SEVERITY_VALUES.length : i; } function isConformance(reviewer) { return typeof reviewer === 'string' && reviewer.toLowerCase().includes('conformance'); } function tripletKey(f) { return `${f.file} ${f.line} ${f.rule_key}`; } /** * Validate each reviewer payload and collect findings from the VALID ones, * tagging each finding with its source reviewer (mirrors mergeArmA — invalid * payloads are skipped, not crashed-on). * @param {Array<{reviewer?: string, findings: object[]}>} reviewerPayloads * @returns {{ findings: object[], skipped: Array<{reviewer: string|null, error_codes: string[]}> }} */ export function ingest(reviewerPayloads) { const findings = []; const skipped = []; for (const payload of reviewerPayloads) { const r = validateFindings(payload); if (!r.valid) { skipped.push({ reviewer: payload?.reviewer ?? null, error_codes: r.errors.map((e) => e.code) }); continue; } for (const f of payload.findings) { findings.push({ ...f, reviewer: f.reviewer ?? payload.reviewer ?? f.owner_reviewer ?? null }); } } return { findings, skipped }; } /** * Pass 1 — dedup by (file, line, rule_key) triplet. Survivor = highest * catalogue severity; severity tie → prefer the conformance reviewer; carries * raised_by provenance, concatenates other reviewers' attribution into detail, * and recomputes the id over the triplet. * @param {object[]} findings * @returns {object[]} */ export function dedupByTriplet(findings) { const groups = new Map(); for (const f of findings) { const key = tripletKey(f); if (!groups.has(key)) groups.set(key, []); groups.get(key).push(f); } const out = []; for (const group of groups.values()) { let survivor = group[0]; for (const f of group.slice(1)) { const higher = severityRank(f.severity) < severityRank(survivor.severity); const tieToConformance = severityRank(f.severity) === severityRank(survivor.severity) && isConformance(f.reviewer) && !isConformance(survivor.reviewer); if (higher || tieToConformance) survivor = f; } const raised_by = [...new Set(group.map((f) => f.reviewer).filter(Boolean))]; const others = group.filter((f) => f !== survivor); let detail = survivor.detail; if (others.length > 0) { detail = survivor.detail ?? ''; for (const o of others) { detail += `\nAlso flagged by ${o.reviewer ?? 'unknown'}: ${o.title ?? o.rule_key}.`; } } const id = computeFindingId(survivor.file, survivor.line, survivor.rule_key); out.push({ ...survivor, id, ...(detail !== undefined ? { detail } : {}), raised_by }); } return out; } /** * Pass 2 — HubSpot Judge (deterministic subset): drop on succinctness * (title > 100 or detail > 800 chars) and actionability (recommended_action, * when present, must be a non-empty string). The imperative-verb test is * excluded (LLM judgement). * @param {object[]} findings * @returns {{ kept: object[], dropped: object[] }} */ export function judgeFilter(findings) { const kept = []; const dropped = []; for (const f of findings) { const titleLen = (f.title ?? '').length; const detailLen = (f.detail ?? '').length; let reason = null; if (titleLen > JUDGE_TITLE_MAX) reason = 'succinctness:title'; else if (detailLen > JUDGE_DETAIL_MAX) reason = 'succinctness:detail'; else if ('recommended_action' in f && (typeof f.recommended_action !== 'string' || f.recommended_action.trim().length === 0)) { reason = 'actionability:empty'; } if (reason) dropped.push({ ...f, suppressed_reason: reason }); else kept.push(f); } return { kept, dropped }; } /** * Pass 3 — Cloudflare reasonableness (deterministic subset): drop findings * with no citation (empty file / line < 0) or an unknown rule_key; CORRECT a * severity that does not match the catalogue tier (a correction, not a drop). * The fs file-existence glob is excluded (I/O). * @param {object[]} findings * @returns {{ kept: object[], dropped: object[] }} */ export function reasonablenessFilter(findings) { const kept = []; const dropped = []; for (const f of findings) { if (typeof f.file !== 'string' || f.file.length === 0 || (typeof f.line === 'number' && f.line < 0)) { dropped.push({ ...f, suppressed_reason: 'no-citation' }); continue; } if (!RULE_KEYS.has(f.rule_key)) { dropped.push({ ...f, suppressed_reason: 'unknown-rule_key' }); continue; } const rule = getRule(f.rule_key); if (rule && f.severity !== rule.severity) { kept.push({ ...f, severity: rule.severity, original_severity: f.severity }); } else { kept.push(f); } } return { kept, dropped }; } /** * Pass 4 — compute the verdict from severity counts (after dedup + filtering). * BLOCKER ≥ 1 → BLOCK; else MAJOR ≥ 1 → WARN; else ALLOW. * @param {object[]} findings * @returns {{ verdict: 'BLOCK'|'WARN'|'ALLOW', counts: Record }} */ export function computeVerdict(findings) { const counts = { BLOCKER: 0, MAJOR: 0, MINOR: 0, SUGGESTION: 0 }; for (const f of findings) { if (counts[f.severity] !== undefined) counts[f.severity] += 1; } let verdict; if (counts.BLOCKER >= 1) verdict = 'BLOCK'; else if (counts.MAJOR >= 1) verdict = 'WARN'; else verdict = 'ALLOW'; return { verdict, counts }; } /** * Run the full deterministic contract: ingest → Pass 1 → Pass 2 → Pass 3 → Pass 4. * @param {Array<{reviewer?: string, findings: object[]}>} reviewerPayloads * @returns {{ verdict: string, counts: Record, findings: object[], suppressed: object[], skipped: object[] }} */ export function runContract(reviewerPayloads) { const { findings: ingested, skipped } = ingest(reviewerPayloads); const deduped = dedupByTriplet(ingested); const judged = judgeFilter(deduped); const reasoned = reasonablenessFilter(judged.kept); const { verdict, counts } = computeVerdict(reasoned.kept); return { verdict, counts, findings: reasoned.kept, suppressed: [...judged.dropped, ...reasoned.dropped], skipped, }; } // ---- CLI shim ---------------------------------------------------------------- if (import.meta.url === `file://${process.argv[1]}`) { const args = process.argv.slice(2); const filePath = args.find((a) => !a.startsWith('--')); if (!filePath) { process.stderr.write('Usage: coordinator-contract.mjs [--json] \n'); process.exit(2); } const { readFileSync } = await import('node:fs'); const payloads = JSON.parse(readFileSync(filePath, 'utf-8')); const result = runContract(Array.isArray(payloads) ? payloads : [payloads]); if (args.includes('--json')) { process.stdout.write(JSON.stringify(result, null, 2) + '\n'); } else { process.stdout.write(`coordinator-contract: ${result.verdict} (${result.findings.length} findings, ${result.suppressed.length} suppressed)\n`); } process.exit(0); }