Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/review-dispatch-tax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"review": patch
---

Dispatch-tax trim: dedupe the specialist-lens discipline snippets. The v1.4.0 re-run's cost premium vs the v1.3.1 baseline sat at dispatch time (uncached input +51-65%, cache writes +18-34% per run), partly because the shared discipline blocks (bounded investigation, untrusted input, lens-owned skills with quote-the-rule, the schema-rules trailer, the tri-state hunt contract) were stamped verbatim into every one of the eleven specialist-lens definitions and paid on every dispatch. The shared text now lives once, in a marker-delimited "REVIEW DISCIPLINES" section of review.md's main body; Step 1 stages it to `/tmp/gh-aw/review/disciplines.md` with one whole-line-anchored `sed` extraction of the engine-provided rendered prompt (`$GH_AW_PROMPT`), verified by grep with a byte-for-byte heredoc fallback, and each lens carries only a pointer plus its own domain notes (its investigation examples, review rules, hunts, and output JSON, all unchanged). Behavior-neutral by construction: the instruction content is unchanged, the three variants that had already drifted apart are unified on the fullest wording, and the label-shape reviewers (whose variants differ materially) keep their own copies. Pinned by `lib/disciplines.test.ts`: the section extracts cleanly under the exact sed range semantics, every lens points at the staged file and carries no residual copy, and the label-shape reviewers still carry theirs.
14 changes: 13 additions & 1 deletion workflows/review/eval/corpus/live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,19 @@ export type LiveDefectSpec = {
*/
export type CaseLive = {
prContext: LivePrContext;
/** Case-dir-relative path to the post-change tree (default `tree`). */
/**
* Case-dir-relative path to the post-change tree (default `tree`).
*
* Trees are deliberately minimal: validation requires only the changed
* files to exist. The staged copy of this tree is the sub-agent's whole
* world (its cwd, with no network), so the agent WILL often try to read
* an imported module or caller that is not there; that read returns an
* ordinary not-found tool error the agent tolerates and works around,
* not a run failure. The cost of a missing file is realism, not a crash:
* include the context files whose absence would change what a reviewer
* concludes (e.g. the decorator module a sibling handler imports), and
* nothing more.
*/
tree: string;
/** Labeled defects a live run must catch. */
mustCatchSpecs?: LiveDefectSpec[];
Expand Down
164 changes: 164 additions & 0 deletions workflows/review/lib/disciplines.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import {readFileSync} from "node:fs";
import {join} from "node:path";

import {describe, it, expect} from "vitest";

import {SPECIALIST_LENSES} from "./router.ts";

/**
* Dispatch-tax dedupe smoke tests.
*
* The measured cost premium of the v1.4.0 re-run sat at dispatch time
* (uncached input +51-65%, cache writes +18-34% per run), partly because
* discipline snippets (bounded investigation, quote-the-rule via lens-owned
* skills, the schema-rules trailer, ...) were stamped verbatim into every one
* of the eleven specialist-lens definitions and paid on every dispatch. The
* shared text now lives once, in the marker-delimited REVIEW DISCIPLINES
* section of review.md's main body, which Step 1 stages to
* `/tmp/gh-aw/review/disciplines.md` with a mechanical `sed` range extraction.
*
* These tests pin the contract: the section exists and extracts cleanly (the
* same line-range semantics the sed command uses), it carries every section
* the lens pointer names, each lens definition points at the staged file and
* no longer carries its own copy, and the label-shape reviewers (whose
* variants differ materially) still carry theirs.
*/

const reviewMd = readFileSync(join(__dirname, "..", "review.md"), "utf8");

const BEGIN = "<!-- BEGIN REVIEW DISCIPLINES -->";
const END = "<!-- END REVIEW DISCIPLINES -->";

/**
* The sed range extraction from review.md Step 1, replicated line-for-line.
* The Step 1 command anchors both patterns to whole lines (`^...$`), so only
* the marker lines themselves open/close the range — never prose that mentions
* a marker, and never the sed command's own text.
*/
const sedRangeExtract = (text: string): string => {
const lines = text.split("\n");
const out: string[] = [];
let inRange = false;
for (const line of lines) {
if (!inRange && line === BEGIN) {
inRange = true;
}
if (inRange) {
out.push(line);
if (line === END) {
break;
}
}
}
return out.join("\n");
};

/** The main body (the orchestrator prompt): everything before the first agent. */
const mainBody = reviewMd.slice(0, reviewMd.indexOf("\n## agent: `"));

/** One lens's definition section, as the sub-agent extractor would cut it. */
const lensSection = (lens: string): string => {
const start = reviewMd.indexOf(`## agent: \`${lens}\``);
expect(start).toBeGreaterThan(-1);
const rest = reviewMd.slice(start);
const next = rest.indexOf("\n## agent: `");
return next === -1 ? rest : rest.slice(0, next);
};

const DISCIPLINE_HEADINGS = [
"## Staged inputs",
"## Untrusted input",
"## Read every line",
"## Bounded investigation",
"## Lens-owned skills",
"## Out-of-lane handoff",
"## Structured finding schema and hunts",
];

describe("the shared disciplines section", () => {
it("lives in the main body (the staged prompt), not in an agent section", () => {
expect(mainBody).toContain(BEGIN);
expect(mainBody).toContain(END);
});

it("extracts cleanly with the Step 1 sed range semantics", () => {
const extracted = sedRangeExtract(reviewMd);
expect(extracted.startsWith(BEGIN)).toBe(true);
expect(extracted.endsWith(END)).toBe(true);
for (const heading of DISCIPLINE_HEADINGS) {
expect(extracted).toContain(`\n${heading}\n`);
}
});

it("keeps the quote-the-rule discipline verbatim", () => {
const extracted = sedRangeExtract(reviewMd);
expect(extracted).toContain(
"Flag a skill violation only when you can quote **both** the exact rule",
);
expect(extracted).toContain("no spirit-of-the-doc inference");
});

it("keeps the tri-state hunt contract", () => {
const extracted = sedRangeExtract(reviewMd);
for (const state of ["`found`", "`ran`", "`not-applicable`"]) {
expect(extracted).toContain(state);
}
});

it("is staged by Step 1 with the sed extraction and a fallback guard", () => {
expect(mainBody).toContain("$GH_AW_PROMPT");
expect(mainBody).toContain("/tmp/gh-aw/review/disciplines.md");
expect(mainBody).toContain(
"grep -q '## Structured finding schema and hunts'",
);
});
});

describe("each specialist lens definition", () => {
for (const lens of SPECIALIST_LENSES) {
it(`${lens}: points at the staged disciplines and carries no copy`, () => {
const section = lensSection(lens);
expect(section).toContain("**Shared disciplines first.**");
expect(section).toContain("/tmp/gh-aw/review/disciplines.md");
// The deduped blocks must be gone from the lens definition.
expect(section).not.toContain("**Bounded investigation.**");
expect(section).not.toContain("**Untrusted input.**");
expect(section).not.toContain("Read from disk:");
expect(section).not.toContain("Schema rules");
expect(section).not.toContain(
"**Hand off, never drop, an out-of-lane observation.**",
);
// The domain-specific content stays.
expect(section).toContain("### Review rules");
expect(section).toContain("### Incident-derived hunts (tri-state)");
expect(section).toContain("### Output");
expect(section).toContain(
"{{#runtime-import .github/aw/review/skills.md}}",
);
expect(section).toContain(`\`lens\` is exactly \`${lens}\``);
expect(section).toContain(
"Domain notes for §Bounded investigation",
);
});
}
});

describe("the label-shape reviewers still carry their own disciplines", () => {
// Their variants differ materially (CLI cap invocation with per-agent id
// semantics, `discussion` instead of `evidence_trace`), so they were
// deliberately left out of the dedupe.
for (const agent of [
"correctness-reviewer",
"skill-auditor",
"claim-validator",
"holistic",
"completeness",
"test-adequacy",
"first-principles",
"conventions",
]) {
it(`${agent}: keeps its own bounded-investigation block`, () => {
expect(lensSection(agent)).toContain("**Bounded investigation.**");
});
}
});
Loading
Loading