/** * Print every issue of a specific category across an entire run, with the * row id, node id, and reason. Generic version of inspect-contrast-hits * — used for spot-checking any single detector's true-positive vs * false-positive rate against a real corpus. */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { detectAllIssues, parseModelOutput, type Issue, type IssueCategory, } from '@zseven-w/pen-ai-skills'; import { applyToFreshDoc } from './apply'; interface JsonlRow { promptId: string; category: string; difficulty: string; variant: string; rawOutput: string; } async function main(): Promise { const runId = process.argv[2]; const wantCat = process.argv[3] as IssueCategory | undefined; if (!runId || !wantCat) { console.error('usage: bun run scripts/ab-corpus/inspect-issue-category.ts '); process.exit(1); } const path = join(import.meta.dir, 'runs', runId, 'scores.jsonl'); const rows: JsonlRow[] = readFileSync(path, 'utf-8') .split('\n') .filter(Boolean) .map((l) => JSON.parse(l)); for (const r of rows) { const parsed = parseModelOutput(r.rawOutput); if (parsed.kind === 'garbage') continue; const result = await applyToFreshDoc(parsed); if (!result.ok || !result.doc) continue; const root = result.doc.children?.[0] ?? null; if (!root) continue; const issues: Issue[] = detectAllIssues(root, result.doc).filter((i) => i.category === wantCat); if (issues.length === 0) continue; console.log( `\n=== ${r.promptId} [${r.category}/${r.difficulty}/${r.variant}] (${issues.length} hits) ===`, ); for (const issue of issues) { console.log(` ${issue.nodeId.padEnd(50)} ${issue.reason}`); } } } main().catch((e) => { console.error(e); process.exit(1); });