Watch
0
0
Fork
You've already forked hyperhive
0

review classifier: a setting makes the PR review list only the failed rules

failures_only (off by default, also for a stored document without it) makes the
review the classifier posts on a PR list just the rows that failed their rule,
or say that no check failed on an approval. A review with no clean verdict and
a try-out keep every row. The classifier settings form gets a checkbox for it.
This commit is contained in:
iris 2026-11-11 00:55:46 +01:00 • committed by mara
commit cf5746188a
3 changed files with 138 additions and 9 deletions

View file

@ -15,6 +15,7 @@ import { Button } from "../../ui/button/Button.js";
import { FieldGrid, FieldGridFull } from "../../ui/field-grid/FieldGrid.js";
import { Hint } from "../../ui/hint/Hint.js";
import { Disclosure } from "../../ui/disclosure/Disclosure.js";
import { CheckboxField } from "../../ui/checkbox-field/CheckboxField.js";
import { SelectField } from "../../ui/select-field/SelectField.js";
import { TextField } from "../../ui/text-field/TextField.js";
import {
@ -187,6 +188,12 @@ export function ClassifierSettings() {
pattern="[0-9]+"
onInput={(maxRules) => edit({ ...draft, maxRules })}
/>
<CheckboxField
id="classifier-failures-only"
label="PR reviews list only failed rules"
checked={draft.failuresOnly}
onChange={(failuresOnly) => edit({ ...draft, failuresOnly })}
/>
</FieldGrid>
{draft.runtime === "acp" ? (
<fieldset class="ui-classifier-settings-group">

View file

@ -35,6 +35,8 @@ export interface ClassifierSettingsBody {
max_concurrent_reviews: number;
/** The most classifications (rules) one run carries. */
max_classifications: number;
/** Whether a posted PR review lists only the rules that failed. */
failures_only: boolean;
runtime: Runtime;
/** The provider record. It stays stored while the runtime is `api-key`. */
acp: AcpSettings | null;
@ -48,6 +50,8 @@ export interface SettingsDraft {
max: string;
/** The most classifications one run carries. */
maxRules: string;
/** Whether a posted PR review lists only the failed rules. */
failuresOnly: boolean;
runtime: Runtime;
acpUrl: string;
acpModel: string;
@ -70,6 +74,7 @@ export function draftOf(s: ClassifierSettingsBody): SettingsDraft {
effort: s.effort ?? "",
max: String(s.max_concurrent_reviews),
maxRules: String(s.max_classifications),
failuresOnly: s.failures_only,
runtime: s.runtime,
acpUrl: acp?.base_url ?? "",
acpModel: acp?.model ?? "",
@ -173,6 +178,7 @@ export function toSettings(
effort: d.effort === "" ? null : (d.effort as Effort),
max_concurrent_reviews: maxValue(d.max) ?? MIN_REVIEWS,
max_classifications: rulesValue(d.maxRules) ?? MIN_RULES,
failures_only: d.failuresOnly,
runtime: d.runtime,
};
return d.runtime === "acp" ? { ...body, acp: toAcp(d) } : body;
@ -190,6 +196,7 @@ export function changed(
d.effort !== now.effort ||
d.max.trim() !== now.max ||
d.maxRules.trim() !== now.maxRules ||
d.failuresOnly !== now.failuresOnly ||
d.runtime !== now.runtime;
if (base) return true;
// The provider fields only save on the ACP runtime.

View file

@ -20,6 +20,7 @@ const apiKey = {
effort: null,
max_concurrent_reviews: 2,
max_classifications: 10,
failures_only: false,
runtime: "api-key",
acp: null,
};
@ -60,6 +61,7 @@ test("the API key request leaves the provider out, so the stored one stays", ()
effort: null,
max_concurrent_reviews: 2,
max_classifications: 10,
failures_only: false,
runtime: "api-key",
});
assert.equal("acp" in body, false);
@ -166,3 +168,11 @@ test("under ACP the summary names the provider's model, not the claude one", ()
"acp · glm · 2 at once · up to 10 rules",
);
});
test("the failures-only switch round-trips and counts as a change", () => {
const on = { ...draftOf(apiKey), failuresOnly: true };
assert.equal(draftOf(apiKey).failuresOnly, false);
assert.equal(toSettings(on).failures_only, true);
assert.equal(changed(on, apiKey), true);
assert.equal(changed(draftOf(apiKey), apiKey), false);
});