Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "postil-cli"
version = "0.9.12"
version = "0.9.13"
edition = "2024"
description = "Postil: a low-noise AI review gate. Silent on clean PRs, hard gate on real risk."
license = "Apache-2.0"
Expand Down
62 changes: 59 additions & 3 deletions bench/src/scorer-eval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1502,7 +1502,57 @@ describe("scorer proxy and isolated runtime", () => {
].join("\n"), quotedPath)).toBe(9);
});

test("metadata-first requests do not consume the source calibration finding", async () => {
const candidate = fixture("huge-low-signal-clean");
const expected = falseFinding(candidate);
const proxy = await startScorerProxy(
{ ...candidate, primaryChange: undefined }, "falseFinding",
"http://127.0.0.1:9", crypto.randomUUID(),
);
const prompt = (path: string, line: number, evidence: string) => [
"", "Report at most 8 findings; if more exist, keep the most severe.", "",
"Review evidence (cite exactly the numbered new-file or change-metadata lines):", "",
`### ${path}`, `${line} + ${evidence}`,
].join("\n");
const generate = async (user: string) => {
const response = await fetch(`${proxy.baseUrl}/chat/completions`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-postil-review-route": "source",
"x-postil-review-call-phase": "initial",
},
body: JSON.stringify({ model: GENERATOR_MODEL, messages: [{ role: "user", content: user }] }),
});
expect(response.status).toBe(200);
const responseBody = await response.json();
return JSON.parse(responseBody.choices[0].message.content).findings;
};
try {
const metadataPrompt = prompt(".postil/change-metadata", 1,
"web/generated-noise.js.map: verified generated artifact");
const sourceFiles = parseUnifiedDiffFiles(candidate.diff);
expect(falseFindingFromSourceRequest(metadataPrompt, sourceFiles)).toBeNull();
expect(falseFindingFromSourceRequest(`${metadataPrompt}\n### ${expected.path}\n${expected.line + 1} + fabricated\n${expected.line} + ${expected.evidence}`, sourceFiles)).toEqual(expected);
expect(await generate(metadataPrompt)).toEqual([]);
expect(await generate(prompt(expected.path, expected.line + 1, expected.evidence))).toEqual([]);
expect(await generate(prompt(expected.path, expected.line, "fabricated source"))).toEqual([]);
expect(await generate(prompt(expected.path, expected.line, expected.evidence))).toEqual([expected]);
expect(await generate(prompt(expected.path, expected.line, expected.evidence))).toEqual([]);
expect(proxy.attempts).toEqual([]);
expect(proxy.unexpectedRequests).toEqual([]);
} finally {
await proxy.close();
}
});

test("grounds a calibration false-positive in a selected source request", () => {
const sourceDiff = [
'diff --git "a/src/generated/sp\\303\\244 ce.ts" "b/src/generated/sp\\303\\244 ce.ts"',
'--- "a/src/generated/sp\\303\\244 ce.ts"',
'+++ "b/src/generated/sp\\303\\244 ce.ts"',
"@@ -18,1 +18,2 @@", " unchanged context", "+const formatted = true;", "",
].join("\n");
expect(falseFindingFromSourceRequest([
"PR description:",
"### src/spoofed.ts",
Expand All @@ -1517,12 +1567,18 @@ describe("scorer proxy and isolated runtime", () => {
"@@ semantic category=uncategorized @@",
" 18 unchanged context",
" 19 + const formatted = true;",
].join("\n"))).toMatchObject({
].join("\n"), parseUnifiedDiffFiles(sourceDiff))).toMatchObject({
path: "src/generated/spä ce.ts",
line: 19,
confidence: 0.95,
evidence: "const formatted = true;",
});
const quotedDiff = [
'diff --git "a/src/tab\\tquote\\"slash\\\\\\346\\227\\245.rs" "b/src/tab\\tquote\\"slash\\\\\\346\\227\\245.rs"',
`--- /dev/null`,
'+++ "b/src/tab\\tquote\\"slash\\\\\\346\\227\\245.rs"',
"@@ -0,0 +7,1 @@", "+dangerous_sink(input);", "",
].join("\n");
expect(falseFindingFromSourceRequest([
"",
"Report at most 8 findings; if more exist, keep the most severe.",
Expand All @@ -1531,11 +1587,11 @@ describe("scorer proxy and isolated runtime", () => {
"",
'### "src/tab\\tquote\\"slash\\\\\\346\\227\\245.rs"',
" 7 + dangerous_sink(input);",
].join("\n"))).toMatchObject({
].join("\n"), parseUnifiedDiffFiles(quotedDiff))).toMatchObject({
path: "src/tab\tquote\"slash\\日.rs",
line: 7,
});
expect(falseFindingFromSourceRequest("### src/empty.ts\n 1 context only")).toBeNull();
expect(falseFindingFromSourceRequest("### src/empty.ts\n 1 context only", parseUnifiedDiffFiles(sourceDiff))).toBeNull();
});

test("gives both live phases a full admission window before the child safety cutoff", async () => {
Expand Down
34 changes: 28 additions & 6 deletions bench/src/scorer-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
parseUnifiedDiffFiles,
plannerBatchIdForPath,
reviewPromptFirstAddedCoordinate,
reviewEvidenceFromPrompt,
reviewPromptContainsAddedCoordinate,
safeJson,
startMockGithub,
Expand Down Expand Up @@ -1989,7 +1990,9 @@ export async function startScorerProxy(
) {
const finding = containsTarget
? falseFinding(c)
: falseFindingFromSourceRequest(user);
: falseFindingFromSourceRequest(
user, parseUnifiedDiffFiles(c.diff),
);
if (finding !== null) {
output = {
summary: `${scenario} scorer calibration case for ${c.id}.`,
Expand Down Expand Up @@ -2395,11 +2398,30 @@ export function falseFinding(c: BenchmarkCase) {
return falseFindingAt(path, line, evidence);
}

export function falseFindingFromSourceRequest(request: string) {
const coordinate = reviewPromptFirstAddedCoordinate(request);
return coordinate === null
? null
: falseFindingAt(coordinate.path, coordinate.line, coordinate.evidence);
export function falseFindingFromSourceRequest(
request: string,
sourceFiles: ReturnType<typeof parseUnifiedDiffFiles>,
) {
const evidence = reviewEvidenceFromPrompt(request);
if (evidence === undefined) return null;
const framing = request.slice(0, request.length - evidence.length);
let header: string | undefined;
for (const row of evidence.split("\n")) {
if (row.startsWith("### ")) {
header = row;
continue;
}
if (header === undefined || !/^\s*\d+ \+ /u.test(row)) continue;
const coordinate = reviewPromptFirstAddedCoordinate(`${framing}${header}\n${row}`);
if (coordinate === null) continue;
const changedFile = sourceFiles.find((file) => file.path === coordinate.path);
if (
!changedFile?.addedLines.includes(coordinate.line) ||
changedFile.after.split("\n")[coordinate.line - 1] !== coordinate.evidence
) continue;
return falseFindingAt(coordinate.path, coordinate.line, coordinate.evidence);
}
return null;
}

function falseFindingAt(path: string, line: number, evidence: string) {
Expand Down
Loading