Brings the deterministic Han-flow conductor into BooCode: launch any read-only flow from BooChat or BooCoder, watch each agent stream live in a Paseo-style run pane, get an evidence-disciplined report — on local Qwen, persisted and resumable. Read-only enforced hard via qwen --approval-mode plan (orchestrator tasks fail closed if qwen is unavailable; never fall to write-capable native). Backend (apps/coder): re-homed conductor defs, flow_runs/flow_steps schema, flow-runner + dispatcher onTaskTerminal hook, restart-resume, runs routes (launch/list/get/cancel), user-channel WS. Contracts: two flow_run_* frames. Web: orchestrator pane kind + OrchestratorPane, Workflow button + slash flows (BooChat/BooCoder parity), FlowLauncherDialog, "New Orchestrator" in the + and split menus, runs history + export. Plan: openspec/changes/orchestrator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
5.1 KiB
TypeScript
102 lines
5.1 KiB
TypeScript
/**
|
|
* Han `code-review` — a bespoke pipeline, NOT a spine. Per-dimension reviewers
|
|
* fan out, then each dimension's findings are adversarially VERIFIED (false
|
|
* positives dropped) before they reach the report. The verification is a `code`
|
|
* step that itself dispatches an adversarial-validator per dimension in
|
|
* parallel — the conductor's scheduler runs the static steps; this step owns
|
|
* the dynamic, per-dimension fan-in.
|
|
*
|
|
* The dynamic dispatch inside the `verify` code step goes through
|
|
* `ctx.dispatch`, injected by the runner. The standalone Phase-1 CLI injects
|
|
* its `dispatchAgent`; the coder's flow-runner injects the DB-backed path.
|
|
*/
|
|
import type { Band, Flow, Step, StepContext } from '../types.js';
|
|
import { fastNote, readBand } from '../spine.js';
|
|
import { produceContract, reviewContract } from '../contracts.js';
|
|
import { slugify } from '../render.js';
|
|
import { q, repoLine } from './_util.js';
|
|
|
|
const BAND_ORDER: Record<Band, number> = { small: 0, medium: 1, large: 2 };
|
|
|
|
interface Dimension {
|
|
id: string;
|
|
agent: string;
|
|
label: string;
|
|
minBand: Band;
|
|
lens: string;
|
|
}
|
|
|
|
const DIMENSIONS: Dimension[] = [
|
|
{ id: 'correctness', agent: 'behavioral-analyst', label: 'Correctness & behaviour', minBand: 'small', lens: 'logic errors, incorrect behaviour, mishandled data flow and error propagation' },
|
|
{ id: 'structure', agent: 'structural-analyst', label: 'Structure & coupling', minBand: 'small', lens: 'coupling, boundary violations, duplication, dependency-direction problems' },
|
|
{ id: 'security', agent: 'adversarial-security-analyst', label: 'Security', minBand: 'medium', lens: 'exploitable vulnerabilities, each with file:line + an exploit path or a CVE' },
|
|
{ id: 'resilience', agent: 'on-call-engineer', label: 'Resilience', minBand: 'medium', lens: 'missing timeouts, retries without backoff, swallowed errors, unbounded results, blocking I/O in async paths' },
|
|
{ id: 'concurrency', agent: 'concurrency-analyst', label: 'Concurrency', minBand: 'large', lens: 'races, lock ordering, shared-resource contention, async error handling' },
|
|
];
|
|
|
|
function dimEnabled(ctx: StepContext, min: Band): boolean {
|
|
return BAND_ORDER[readBand(ctx.input)] >= BAND_ORDER[min];
|
|
}
|
|
|
|
function hasFindings(out: string | undefined): boolean {
|
|
return Boolean(out) && !/^\s*no findings/i.test(out!.trim());
|
|
}
|
|
|
|
const findSteps: Step[] = DIMENSIONS.map((d) => ({
|
|
id: d.id,
|
|
kind: 'agent',
|
|
agent: d.agent,
|
|
when: (ctx) => dimEnabled(ctx, d.minBand),
|
|
run: (ctx) =>
|
|
`Review the target below for ${d.lens}. Return a NUMBERED list of findings; for each: the issue, file:line, and why it matters. If there are none, reply exactly "No findings."${repoLine(ctx)}\n\nTARGET: ${q(ctx)}` +
|
|
produceContract(['evidence']) +
|
|
fastNote(ctx),
|
|
}));
|
|
|
|
const verifyStep: Step = {
|
|
id: 'verify',
|
|
kind: 'code',
|
|
deps: DIMENSIONS.map((d) => d.id),
|
|
run: async (ctx) => {
|
|
const withFindings = DIMENSIONS.filter((d) => hasFindings(ctx.results[d.id]));
|
|
if (withFindings.length === 0) return '_No findings to verify._';
|
|
// dispatch is injected by the runner; absent only in contexts that don't
|
|
// support dynamic sub-dispatch (e.g. a dry prompt-preview pass)
|
|
const dispatch = ctx.dispatch;
|
|
if (!dispatch) return '_Verification skipped: no dispatch capability in context._';
|
|
const verified = await Promise.all(
|
|
withFindings.map(async (d) => {
|
|
const out = await dispatch(
|
|
'adversarial-validator',
|
|
`Below are code-review findings in the "${d.label}" dimension. For EACH finding, try to refute it — is it a real, correct issue or a false positive? Return ONLY the surviving findings (drop refuted/false-positive ones), each with a one-line note on why it holds, and state how many you dropped.${reviewContract(['evidence'])}\n\n----- FINDINGS -----\n${ctx.results[d.id]}` +
|
|
fastNote(ctx),
|
|
);
|
|
return `### ${d.label}\n\n${out}`;
|
|
}),
|
|
);
|
|
return verified.join('\n\n');
|
|
},
|
|
};
|
|
|
|
function renderCodeReview(ctx: StepContext): string {
|
|
// model is injected by the flow-runner from flow_runs.model — no env var fallback
|
|
const model = ctx.model ?? 'llama-swap/qwen3.6-35b-a3b-mxfp4';
|
|
const band = readBand(ctx.input);
|
|
const parts: string[] = [
|
|
`# Conductor Report — code-review: ${q(ctx)}`,
|
|
`> BooCode code conductor · band=${band}${ctx.input.concise ? ' · fast' : ''} · workers on \`${model}\`. Per-dimension reviewers fan out, then each dimension's findings are adversarially verified — false positives dropped — before reaching this report.`,
|
|
`## Confirmed findings (after adversarial verification)\n\n${ctx.results.verify ?? '_none_'}`,
|
|
];
|
|
const raw = DIMENSIONS.filter((d) => ctx.results[d.id]).map((d) => `### ${d.label} (raw)\n\n${ctx.results[d.id]}`);
|
|
if (raw.length) parts.push(`## Appendix — raw findings before verification\n\n${raw.join('\n\n')}`);
|
|
return parts.join('\n\n') + '\n';
|
|
}
|
|
|
|
export const codeReview: Flow = {
|
|
name: 'code-review',
|
|
description: 'per-dimension review → adversarially verify each dimension (drops false positives)',
|
|
steps: [...findSteps, verifyStep],
|
|
render: renderCodeReview,
|
|
output: (ctx) => `conductor-report-code-review-${slugify(q(ctx))}.md`,
|
|
};
|