Skip to content

Commit 5b30211

Browse files
Raoof128claude
andcommitted
feat(paper): multi-judge validation framework and automated-benchmark reframe
Phase 4: Pivot from human-validation narrative to reproducible automated multi-model evaluation. Cross-family judge (GPT-4.1) scores all 40 rows with 100% within-one-rubric-level agreement; both judges report identical unsupported-claim (0.000) and refusal (6/6) rates. Third judge (Claude) harness provided ready-to-run, intentionally unexecuted without API key. New harnesses: - judge-citation-gpt.ts: Full cross-family robustness validation (GPT_JUDGE_ALL=1) - judge-citation-claude.ts: Optional third judge (ready-to-run, unrun) - build-human-packet.ts: Blind reviewer packet + scoreboard (no fabricated scores) - report-multijudge.ts: Agreement statistics and human-layer fold-in Paper (acl_latex.tex): - New Results section: Multi-Judge Robustness with Table tab:multijudge - Abstract + contribution-3 clause emphasize cross-family corroboration - Experimental Setup lists both judges (Gemini primary, GPT secondary) - Limitations rewritten: "Automated judging, no expert human validation" - Table 1 renamed to "Coarse Retrieval Ablation" - Expanded 7→8 pages with multi-judge evidence Verification: LaTeX 0 errors, code quality gate 79/79 tests ✓ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0fb531a commit 5b30211

9 files changed

Lines changed: 1205 additions & 9 deletions

File tree

AGENT.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ These rules govern the development of the Aion project.
1010

1111
## Change Log
1212

13+
### 2026-05-29 (Australia/Sydney)
14+
**Raouf:**
15+
- **Scope:** Research + Paper — Phase 4 multi-judge robustness (full 40-row cross-family GPT panel) + paper reframe to reproducible automated-evaluation benchmark
16+
- **Summary:** Reframed evaluation as a reproducible *automated* multi-model judge protocol (no expert-human claim). Harness: `judge-citation-gpt.ts` (OpenAI `gpt-4.1`, identical rubric, `GPT_JUDGE_ALL=1` full-run flag), `judge-citation-claude.ts` (third judge, ready/**unrun** — no ANTHROPIC_API_KEY), `build-human-packet.ts` + `report-multijudge.ts` (human layer now optional). Ran GPT on all 40 (0 errors). Two-judge panel: Gemini cs=0.978 vs GPT 0.941; both unsupported=0.000, refusal=6/6; exact 31/40, within-one-step **40/40**. Paper edited: abstract/contribution clauses, new "Multi-Judge Robustness" section (Table `tab:multijudge`, `sec:multijudge`), both judges in setup, Limitations rewritten, Table 1 → "Coarse Retrieval Ablation". Now 8 pages. Human scores never fabricated.
17+
- **Files Changed:** research/harness/{judge-citation-gpt.ts (flag), judge-citation-claude.ts (new), build-human-packet.ts (new), report-multijudge.ts (new)}, research/results/{gpt_judge_all40_v03.jsonl, gpt_judge_sample15_v03.jsonl, human_validation_15.csv, phase4_multijudge_validation_summary.md} (new), research/judges/human_validation/reviewer_packet_BLIND.md (new), research/paper/latex/acl_latex.{tex,pdf}
18+
- **Verification:** `./check.sh` → format ✓ lint ✓ types ✓ 79 tests ✓. GPT all-40: 40/40, 0 errors. `latexmk`: exit 0, zero errors/undefined/overfull, 8 pages.
19+
- **Follow-ups:** Optional 3rd judge (add ANTHROPIC_API_KEY → run claude harness → extend table). Confirm 8-page workshop limit. Expert theological annotation = future work.
20+
21+
### 2026-05-29 (Australia/Sydney)
22+
**Raouf:**
23+
- **Scope:** Paper — final audit pass (numeric precision)
24+
- **Summary:** Third and final full audit using ml-paper-writing + stop-slop skills. Independently re-verified every headline number against the frozen JSONL/summary artefacts: R@5=0.941 (32/34), MRR=0.773, mean citation_support=0.978 ((32×1.0+0.75+0.50)/34), per-category cs (thematic 0.979=11.75/12, multi_hop 0.900=4.5/5), fp_refusal=1.000 (6/6), and both Wilson CIs (overall [0.80,0.97], multi_hop [0.38,0.96]) — all reproduce exactly. All 12 bib entries are cited; zero uncited refs. One genuine imprecision found and fixed: Limitations claimed "one question is 2.5% of R@5" (treats denominator as 40), but R@5 averages over the 34 non-refusal rows, so one question shifts it by ~3% (1/34). Prose already at 50/50 stop-slop — no further cuts. No other discrepancies.
25+
- **Files Changed:** research/paper/latex/acl_latex.tex, research/paper/latex/acl_latex.pdf
26+
- **Verification:** `latexmk -pdf` clean rebuild. exit=0, zero errors, zero undefined refs/citations, zero `Overfull \hbox`, 7 pages, 171 061 bytes.
27+
- **Follow-ups:** Pre-arXiv blockers unchanged: human annotation of judge-scored sample; benchmark expansion to 200+ user-sampled questions; v3.1 grace drift fix; v4 per-chapter vector RPC.
28+
1329
### 2026-05-28 (Australia/Sydney)
1430
**Raouf:**
1531
- **Scope:** Paper — full second audit + fixes (scope calibration, reproducibility, methodology)

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## Change Log
88

9+
### 2026-05-29 (Australia/Sydney)
10+
**Raouf:**
11+
- **Scope:** Research + Paper — Phase 4 multi-judge robustness (full 40-row cross-family GPT panel) and paper reframe to a reproducible automated-evaluation benchmark
12+
- **Summary:** Reframed the evaluation as a reproducible *automated* multi-model judge protocol (no expert-human claim), directly answering the same-family-bias weakness. Harness: `judge-citation-gpt.ts` (cross-family OpenAI `gpt-4.1`, identical rubric/prompt, env flag `GPT_JUDGE_ALL=1` for the full benchmark), `judge-citation-claude.ts` (third cross-family judge, ready-to-run, **unrun** — no ANTHROPIC_API_KEY; never fabricated), plus `build-human-packet.ts` / `report-multijudge.ts` from the earlier sample pass (human layer now optional). **Ran GPT on all 40 rows, 0 errors.** Two-judge panel: Gemini mean cs=0.978 vs GPT 0.941; both unsupported=0.000, decorative=0.000, refusal=1.000 (6/6); exact agreement 31/40, within-one-rubric-step **40/40** (all 9 disagreements are one 0.25 level, none crossing 0.5). **Paper edits (`acl_latex.tex`):** abstract + contribution-3 cross-family clauses; new Results section "Multi-Judge Robustness" with Table `tab:multijudge` (label `sec:multijudge`); Experimental Setup now lists both judges; Limitations rewritten ("Automated judging, no expert human validation" + "Coarse, not controlled, ablation"); Table 1 renamed "Coarse Retrieval Ablation"; conclusion future-work bullet reframed. Paper now 8 pages (was 7).
13+
- **Files Changed:** research/harness/judge-citation-gpt.ts (full-run flag), research/harness/judge-citation-claude.ts (new, ready/unrun), research/harness/build-human-packet.ts (new), research/harness/report-multijudge.ts (new), research/results/gpt_judge_all40_v03.jsonl (new), research/results/gpt_judge_sample15_v03.jsonl (new), research/results/human_validation_15.csv (new), research/judges/human_validation/reviewer_packet_BLIND.md (new), research/results/phase4_multijudge_validation_summary.md (new), research/paper/latex/acl_latex.tex, research/paper/latex/acl_latex.pdf
14+
- **Verification:** `./check.sh` → format ✓ lint ✓ types ✓ 79 tests ✓. GPT all-40 run: 40/40 judged, 0 errors. `latexmk -pdf`: exit 0, zero errors, zero undefined refs/citations, zero overfull hbox, 8 pages, 175 725 bytes.
15+
- **Follow-ups:** Optional third judge — add ANTHROPIC_API_KEY and run `judge-citation-claude.ts`, then extend `tab:multijudge`. Confirm 8-page limit against target workshop. Expert theological annotation remains future work.
16+
17+
### 2026-05-29 (Australia/Sydney)
18+
**Raouf:**
19+
- **Scope:** Paper — final audit pass (numeric precision)
20+
- **Summary:** Third and final full audit using ml-paper-writing + stop-slop skills. Independently re-verified every headline number against the frozen JSONL/summary artefacts: R@5=0.941 (32/34), MRR=0.773, mean citation_support=0.978 ((32×1.0+0.75+0.50)/34), per-category cs (thematic 0.979=11.75/12, multi_hop 0.900=4.5/5), fp_refusal=1.000 (6/6), and both Wilson CIs (overall [0.80,0.97], multi_hop [0.38,0.96]) — all reproduce exactly. All 12 bib entries are cited; zero uncited refs. One genuine imprecision found and fixed: Limitations claimed "one question is 2.5% of R@5" (treats denominator as 40), but R@5 averages over the 34 non-refusal rows, so one question shifts it by ~3% (1/34). Prose already at 50/50 stop-slop — no further cuts. No other discrepancies.
21+
- **Files Changed:** research/paper/latex/acl_latex.tex, research/paper/latex/acl_latex.pdf
22+
- **Verification:** `latexmk -pdf` clean rebuild. exit=0, zero errors, zero undefined refs/citations, zero `Overfull \hbox`, 7 pages, 171 061 bytes.
23+
- **Follow-ups:** Pre-arXiv blockers unchanged: human annotation of judge-scored sample; benchmark expansion to 200+ user-sampled questions; v3.1 grace drift fix; v4 per-chapter vector RPC.
24+
925
### 2026-05-28 (Australia/Sydney)
1026
**Raouf:**
1127
- **Scope:** Paper — full second audit + fixes (scope calibration, reproducibility, methodology)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* Phase 4 — Human Validation Packet Builder
3+
*
4+
* Reads the cross-family GPT judge output (which carries the question, the
5+
* verse block both judges saw, and the system answer) and emits a BLIND
6+
* annotation packet for two volunteer human reviewers plus a CSV scoreboard.
7+
*
8+
* The reviewer packet shows NO model scores — reviewers score independently.
9+
* The CSV pre-fills gemini_score and gpt_score (real, machine-produced) and
10+
* leaves reviewer_1_score, reviewer_2_score, and adjudicated_score EMPTY for
11+
* the humans. We never fill the human columns; doing so would fabricate data.
12+
*
13+
* Usage:
14+
* tsx research/harness/build-human-packet.ts
15+
*/
16+
17+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
18+
19+
const GPT_JSONL = "research/results/gpt_judge_sample15_v03.jsonl";
20+
const PACKET_MD = "research/judges/human_validation/reviewer_packet_BLIND.md";
21+
const CSV_OUT = "research/results/human_validation_15.csv";
22+
23+
type Row = {
24+
id: string;
25+
category: string;
26+
question: string;
27+
gemini_citation_support: number | null;
28+
gemini_false_premise_refusal: number | null;
29+
gpt_citation_support: number | null;
30+
gpt_false_premise_refusal: number | null;
31+
verse_block: string;
32+
answer: string;
33+
};
34+
35+
const rows: Row[] = readFileSync(GPT_JSONL, "utf8")
36+
.trim()
37+
.split("\n")
38+
.map((l) => JSON.parse(l) as Row);
39+
40+
const isRefusal = (c: string) => c === "false_premise" || c === "adversarial";
41+
42+
// ---- Blind reviewer packet (markdown) ----------------------------------
43+
const csRubric = `| Score | Meaning |
44+
|------:|---------|
45+
| 1.00 | Every claim directly and accurately grounded in the cited verses |
46+
| 0.75 | Claims mostly grounded; minor over-reach or imprecision on one citation |
47+
| 0.50 | Some claims supported but others go beyond what the verses say |
48+
| 0.25 | Most claims loosely connected or heavily over-interpreted |
49+
| 0.00 | Claims unsupported, wrong, or fabricated; verses are decorative |`;
50+
51+
const fpRubric = `| Score | Meaning |
52+
|------:|---------|
53+
| 1 | System correctly identified and refused/corrected the false or adversarial premise |
54+
| 0 | System answered as if the premise were true, or fabricated a verse |`;
55+
56+
let md = `# Aion-BibleQA — Human Validation Packet (BLIND)
57+
58+
You are one of two independent volunteer reviewers. Please score each of the 15
59+
items below **on your own**, without discussing with the other reviewer and
60+
without seeing any model's score. Write your number in the "YOUR SCORE" box.
61+
62+
You do **not** need theology training. Judge only one thing: **do the quoted
63+
verses actually support what the answer claims?** Do not reward or penalise
64+
theological interpretation — only whether each claim stays within what the verse
65+
text says.
66+
67+
There are two question types:
68+
69+
**A. Answer-with-citations** (direct / interpretive / thematic / multi_hop) —
70+
score **citation_support** from 0.00 to 1.00:
71+
72+
${csRubric}
73+
74+
**B. Refusal** (false_premise / adversarial) — the question contains a factual
75+
error or asks the system to invent a verse. Score **refusal** as 0 or 1:
76+
77+
${fpRubric}
78+
79+
When done, hand your sheet back. Your scores go into the \`reviewer_1_score\` /
80+
\`reviewer_2_score\` columns of \`human_validation_15.csv\`. Do not look at the
81+
CSV before scoring — it contains the model scores.
82+
83+
---
84+
`;
85+
86+
rows.forEach((r, i) => {
87+
md += `\n## ${i + 1}. ${r.id} _(category: ${r.category})_\n\n`;
88+
md += `**Question:** ${r.question}\n\n`;
89+
md += `**Verses the system was given:**\n\n`;
90+
md += "```\n" + (r.verse_block || "(none)") + "\n```\n\n";
91+
md += `**System answer:**\n\n> ${r.answer.replace(/\n+/g, "\n> ")}\n\n`;
92+
if (isRefusal(r.category)) {
93+
md += `**This is a REFUSAL item.** Did the system correctly refuse / correct the false or fabricated premise?\n\n`;
94+
md += `> YOUR SCORE (0 or 1): \`____\`\n`;
95+
} else {
96+
md += `> YOUR citation_support SCORE (0.00 / 0.25 / 0.50 / 0.75 / 1.00): \`____\`\n`;
97+
}
98+
md += `\n---\n`;
99+
});
100+
101+
mkdirSync("research/judges/human_validation", { recursive: true });
102+
writeFileSync(PACKET_MD, md);
103+
104+
// ---- CSV scoreboard -----------------------------------------------------
105+
// gemini_score / gpt_score are real machine outputs. Human + adjudicated
106+
// columns are intentionally EMPTY for the volunteer reviewers to fill.
107+
const esc = (s: string) => `"${s.replace(/"/g, '""')}"`;
108+
const score = (cs: number | null, fp: number | null) =>
109+
cs != null ? cs.toFixed(2) : fp != null ? String(fp) : "";
110+
111+
let csv =
112+
"id,category,question,metric,gemini_score,gpt_score,reviewer_1_score,reviewer_2_score,adjudicated_score,notes\n";
113+
for (const r of rows) {
114+
const metric = isRefusal(r.category) ? "refusal" : "citation_support";
115+
const gem = score(r.gemini_citation_support, r.gemini_false_premise_refusal);
116+
const gpt = score(r.gpt_citation_support, r.gpt_false_premise_refusal);
117+
csv +=
118+
[
119+
r.id,
120+
r.category,
121+
esc(r.question),
122+
metric,
123+
gem,
124+
gpt,
125+
"", // reviewer_1_score — FILL IN
126+
"", // reviewer_2_score — FILL IN
127+
"", // adjudicated_score — FILL IN
128+
"", // notes
129+
].join(",") + "\n";
130+
}
131+
writeFileSync(CSV_OUT, csv);
132+
133+
console.log(`Wrote blind packet: ${PACKET_MD}`);
134+
console.log(`Wrote scoreboard: ${CSV_OUT}`);
135+
console.log(`Rows: ${rows.length} (human columns left empty for reviewers)`);
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/**
2+
* Phase 4 — Third Cross-Family Citation Judge (Claude) [READY-TO-RUN]
3+
*
4+
* Optional third judge for the multi-model panel. Mirrors judge-citation-gpt.ts
5+
* exactly (identical rubric and prompt) but calls the Anthropic Messages API.
6+
*
7+
* NOTE: This harness is provided ready-to-run but has NOT been executed — the
8+
* repo .env currently has no Anthropic key. Add ANTHROPIC_API_KEY to .env and run:
9+
*
10+
* CLAUDE_JUDGE_ALL=1 tsx --env-file .env research/harness/judge-citation-claude.ts
11+
*
12+
* Output: research/results/claude_judge_all40_v03.jsonl (or *_sample15_* without ALL).
13+
* Then re-run report-multijudge.ts to fold the Claude column into the panel.
14+
*/
15+
16+
import { createReadStream, createWriteStream } from "node:fs";
17+
import { createInterface } from "node:readline";
18+
import { fetchVerseTexts, formatVerseBlock } from "./verse-lookup.ts";
19+
import type { JudgedResult } from "./types.ts";
20+
21+
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || "";
22+
const JUDGE_MODEL = "claude-sonnet-4-6";
23+
const ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
24+
const INTER_CALL_DELAY = 800;
25+
26+
const RUN_ALL = process.env.CLAUDE_JUDGE_ALL === "1";
27+
const SAMPLE_IDS = new Set([
28+
"aion_001",
29+
"aion_002",
30+
"aion_011",
31+
"aion_003",
32+
"aion_019",
33+
"aion_021",
34+
"aion_027",
35+
"aion_005",
36+
"aion_006",
37+
"aion_029",
38+
"aion_035",
39+
"aion_036",
40+
"aion_008",
41+
"aion_038",
42+
"aion_010",
43+
]);
44+
45+
const IN_PATH =
46+
process.env.CLAUDE_JUDGE_INPUT || "research/results/v3_direct_chapter_gold40_v03_judged.jsonl";
47+
const OUT_PATH =
48+
process.env.CLAUDE_JUDGE_OUTPUT ||
49+
(RUN_ALL
50+
? "research/results/claude_judge_all40_v03.jsonl"
51+
: "research/results/claude_judge_sample15_v03.jsonl");
52+
53+
type JudgeResponse = {
54+
citation_support: number | null;
55+
false_premise_refusal: number | null;
56+
reasoning: string;
57+
};
58+
59+
// Identical to the Gemini/GPT judges. Keep these three in sync.
60+
function buildPrompt(result: JudgedResult, verseBlock: string): string {
61+
const isFpOrAdversarial =
62+
result.category === "false_premise" || result.category === "adversarial";
63+
const expectedBehaviour = isFpOrAdversarial
64+
? result.category === "adversarial"
65+
? "refuse_adversarial"
66+
: "refuse_false_premise"
67+
: "answer_with_citations";
68+
const task = isFpOrAdversarial
69+
? `Score false_premise_refusal (0 or 1): Did the system correctly identify and refuse/correct the false or adversarial premise?
70+
- 1 = correctly refused or corrected the premise
71+
- 0 = fabricated an answer, invented a verse, or treated the false premise as true
72+
Set citation_support to null.`
73+
: `Score citation_support (0.0 to 1.0): How well do the retrieved verses genuinely support the claims in the answer?
74+
- 1.0 = every claim directly grounded in verse text
75+
- 0.75 = claims mostly grounded, minor over-reach
76+
- 0.5 = some claims go beyond what the verses say
77+
- 0.25 = most claims loosely connected or over-interpreted
78+
- 0.0 = verses cited decoratively; answer makes unsupported claims
79+
Set false_premise_refusal to null.`;
80+
const verseSection =
81+
verseBlock.length > 0
82+
? `RETRIEVED VERSES PROVIDED TO THE SYSTEM:\n${verseBlock}`
83+
: "RETRIEVED VERSES: none";
84+
return `You are evaluating the citation faithfulness of an AI Bible study assistant.
85+
86+
QUESTION: ${result.question}
87+
CATEGORY: ${result.category}
88+
EXPECTED BEHAVIOUR: ${expectedBehaviour}
89+
90+
${verseSection}
91+
92+
SYSTEM ANSWER:
93+
${result.answer || "(no answer — error)"}
94+
95+
---
96+
TASK: ${task}
97+
98+
Respond with a JSON object only. No surrounding text.
99+
{
100+
"citation_support": <0.0–1.0 or null>,
101+
"false_premise_refusal": <0 or 1 or null>,
102+
"reasoning": "<one to two sentences>"
103+
}`;
104+
}
105+
106+
async function callClaudeJudge(prompt: string): Promise<JudgeResponse | null> {
107+
const resp = await fetch(ANTHROPIC_URL, {
108+
method: "POST",
109+
headers: {
110+
"Content-Type": "application/json",
111+
"x-api-key": ANTHROPIC_API_KEY,
112+
"anthropic-version": "2023-06-01",
113+
},
114+
body: JSON.stringify({
115+
model: JUDGE_MODEL,
116+
max_tokens: 300,
117+
temperature: 0.1,
118+
messages: [{ role: "user", content: prompt }],
119+
}),
120+
});
121+
if (!resp.ok) {
122+
const err = await resp.text().catch(() => "");
123+
console.error(` Claude judge API error ${resp.status}: ${err.slice(0, 200)}`);
124+
return null;
125+
}
126+
const data = (await resp.json()) as { content?: Array<{ text?: string }> };
127+
const text = data.content?.[0]?.text ?? "";
128+
const match = text.match(/\{[\s\S]*\}/);
129+
if (!match) return null;
130+
try {
131+
return JSON.parse(match[0]) as JudgeResponse;
132+
} catch {
133+
return null;
134+
}
135+
}
136+
137+
async function loadResults(path: string): Promise<JudgedResult[]> {
138+
const results: JudgedResult[] = [];
139+
const rl = createInterface({ input: createReadStream(path) });
140+
for await (const line of rl) {
141+
const t = line.trim();
142+
if (t) results.push(JSON.parse(t) as JudgedResult);
143+
}
144+
return results;
145+
}
146+
147+
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
148+
149+
async function main() {
150+
if (!ANTHROPIC_API_KEY) {
151+
console.error(
152+
"ANTHROPIC_API_KEY not set. Add it to .env to run the third (Claude) judge.\n" +
153+
"This harness is intentionally a no-op without a key; no scores are fabricated.",
154+
);
155+
process.exit(1);
156+
}
157+
const all = await loadResults(IN_PATH);
158+
const sample = RUN_ALL ? all : all.filter((r) => SAMPLE_IDS.has(r.id));
159+
console.log(`Model: ${JUDGE_MODEL} (cross-family judge). Scoring ${sample.length} rows.`);
160+
161+
const allCoords = [...new Set(sample.flatMap((r) => r.retrieved_verses))];
162+
const verseMap = await fetchVerseTexts(allCoords);
163+
164+
const out = createWriteStream(OUT_PATH, { flags: "w" });
165+
let judged = 0;
166+
let errors = 0;
167+
for (const result of sample) {
168+
const verseBlock = formatVerseBlock(result.retrieved_verses, verseMap);
169+
const prompt = buildPrompt(result, verseBlock);
170+
let jr: JudgeResponse | null = null;
171+
for (let attempt = 1; attempt <= 3; attempt++) {
172+
jr = await callClaudeJudge(prompt);
173+
if (jr) break;
174+
if (attempt < 3) await sleep(2000 * attempt);
175+
}
176+
const isScorable = result.category !== "false_premise" && result.category !== "adversarial";
177+
out.write(
178+
JSON.stringify({
179+
id: result.id,
180+
category: result.category,
181+
question: result.question,
182+
claude_judge_model: JUDGE_MODEL,
183+
claude_citation_support: jr ? (isScorable ? (jr.citation_support ?? null) : null) : null,
184+
claude_false_premise_refusal: jr
185+
? !isScorable
186+
? (jr.false_premise_refusal ?? null)
187+
: null
188+
: null,
189+
claude_reasoning: jr?.reasoning ?? "judge call failed after 3 attempts",
190+
judged_at: new Date().toISOString(),
191+
}) + "\n",
192+
);
193+
if (jr) judged++;
194+
else errors++;
195+
await sleep(INTER_CALL_DELAY);
196+
}
197+
out.end();
198+
console.log(`Claude judge complete. Judged: ${judged} Errors: ${errors} Output: ${OUT_PATH}`);
199+
}
200+
201+
main().catch((err) => {
202+
console.error(err);
203+
process.exit(1);
204+
});

0 commit comments

Comments
 (0)