@skill-harness/core 0.22.0 → 0.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/grade.d.ts +4 -2
- package/dist/grade.js +14 -6
- package/dist/regate.js +7 -6
- package/dist/regrade.d.ts +5 -0
- package/dist/regrade.js +57 -19
- package/dist/results.d.ts +2 -0
- package/dist/results.js +1 -1
- package/dist/run.js +1 -1
- package/package.json +1 -1
- package/schemas/results-v2.schema.json +1 -0
- package/schemas/results-v3.schema.json +1 -0
package/dist/grade.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export declare function judgeResemblesSubject(judge: ModelRef, subject: ModelRef
|
|
|
38
38
|
export interface GradeResult extends ParsedVerdict {
|
|
39
39
|
raw: string;
|
|
40
40
|
criteria: CriterionVote[];
|
|
41
|
+
/** One identical-input retry after missing criterion votes. Absent on the first attempt. */
|
|
42
|
+
judgeRetries?: 1;
|
|
41
43
|
/** Judge misfire: the overall verdict disagrees with AND(per-item grades). Recorded, never auto-passed; blocks SHIP until re-judged or overridden. */
|
|
42
44
|
suspect: boolean;
|
|
43
45
|
}
|
|
@@ -50,10 +52,10 @@ export interface GradeResult extends ParsedVerdict {
|
|
|
50
52
|
*/
|
|
51
53
|
export declare function detectMisfire(raw: string, verdict: Verdict): boolean;
|
|
52
54
|
/** Drive the judge for one transcript and parse the result. */
|
|
53
|
-
export declare function gradeTranscript(adapter: HarnessAdapter, judge: ModelRef, prompt: string, cwd: string): Promise<GradeResult>;
|
|
55
|
+
export declare function gradeTranscript(adapter: HarnessAdapter, judge: ModelRef, prompt: string, cwd: string, expectedCriteria?: number): Promise<GradeResult>;
|
|
54
56
|
/**
|
|
55
57
|
* Grade a transcript in a fresh, isolated, throwaway workspace — never the
|
|
56
58
|
* subject's scenario dir — so the judge can't ingest repo context the subject
|
|
57
59
|
* left behind (matters for provider CLIs that read cwd, including Pi).
|
|
58
60
|
*/
|
|
59
|
-
export declare function judgeInWorkspace(adapter: HarnessAdapter, judge: ModelRef, prompt: string, specDir: string): Promise<GradeResult>;
|
|
61
|
+
export declare function judgeInWorkspace(adapter: HarnessAdapter, judge: ModelRef, prompt: string, specDir: string, expectedCriteria?: number): Promise<GradeResult>;
|
package/dist/grade.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createWorkspace } from "./workspace.js";
|
|
2
|
-
import { parseCriterionVotes } from "./results.js";
|
|
2
|
+
import { parseCriterionVotes, completeCriterionVotes } from "./results.js";
|
|
3
3
|
/** The heading runSeeded writes above the staged diff. Gating on this exact string is what keeps the guidance honest — see below. */
|
|
4
4
|
export const STAGED_DIFF_HEADING = "=== STAGED DIFF ===";
|
|
5
5
|
/**
|
|
@@ -123,8 +123,16 @@ export function detectMisfire(raw, verdict) {
|
|
|
123
123
|
// Non-global twin of REASON_RE: matchAll needs /g, a single .match() must not have it.
|
|
124
124
|
const REASON_LINE_RE = /^\s*\**\s*REASON\**\s*:\s*\**\s*(.*)$/im;
|
|
125
125
|
/** Drive the judge for one transcript and parse the result. */
|
|
126
|
-
export async function gradeTranscript(adapter, judge, prompt, cwd) {
|
|
127
|
-
const
|
|
126
|
+
export async function gradeTranscript(adapter, judge, prompt, cwd, expectedCriteria) {
|
|
127
|
+
const request = { model: judge, prompt, cwd };
|
|
128
|
+
let raw = await adapter.judge(request);
|
|
129
|
+
let criteria = parseCriterionVotes(raw);
|
|
130
|
+
let judgeRetries;
|
|
131
|
+
if (expectedCriteria !== undefined && completeCriterionVotes(criteria, expectedCriteria).some(vote => vote.verdict === "ERROR")) {
|
|
132
|
+
raw = await adapter.judge(request);
|
|
133
|
+
criteria = parseCriterionVotes(raw);
|
|
134
|
+
judgeRetries = 1;
|
|
135
|
+
}
|
|
128
136
|
const parsed = parseVerdict(raw);
|
|
129
137
|
// On a parse failure, surface what the judge actually emitted (e.g. a provider
|
|
130
138
|
// error) rather than a generic message — otherwise the cause is invisible.
|
|
@@ -134,17 +142,17 @@ export async function gradeTranscript(adapter, judge, prompt, cwd) {
|
|
|
134
142
|
parsed.reason = `judge unparseable: ${snippet}`;
|
|
135
143
|
}
|
|
136
144
|
const suspect = detectMisfire(raw, parsed.verdict);
|
|
137
|
-
return { ...parsed, raw, suspect, criteria:
|
|
145
|
+
return { ...parsed, raw, suspect, criteria, ...(judgeRetries ? { judgeRetries } : {}) };
|
|
138
146
|
}
|
|
139
147
|
/**
|
|
140
148
|
* Grade a transcript in a fresh, isolated, throwaway workspace — never the
|
|
141
149
|
* subject's scenario dir — so the judge can't ingest repo context the subject
|
|
142
150
|
* left behind (matters for provider CLIs that read cwd, including Pi).
|
|
143
151
|
*/
|
|
144
|
-
export async function judgeInWorkspace(adapter, judge, prompt, specDir) {
|
|
152
|
+
export async function judgeInWorkspace(adapter, judge, prompt, specDir, expectedCriteria) {
|
|
145
153
|
const ws = createWorkspace("none", { specDir });
|
|
146
154
|
try {
|
|
147
|
-
return await gradeTranscript(adapter, judge, prompt, ws.cwd);
|
|
155
|
+
return await gradeTranscript(adapter, judge, prompt, ws.cwd, expectedCriteria);
|
|
148
156
|
}
|
|
149
157
|
finally {
|
|
150
158
|
ws.cleanup();
|
package/dist/regate.js
CHANGED
|
@@ -290,12 +290,13 @@ export async function regateRun(opts) {
|
|
|
290
290
|
}
|
|
291
291
|
// The gate blocked this rep before, so no judgement of it exists anywhere.
|
|
292
292
|
const transcript = readFileSync(tPath, "utf8");
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
const judged = await judgeOneRep({
|
|
294
|
+
runDir: opts.runDir, spec: opts.spec, scenario, transcript,
|
|
295
|
+
adapter: opts.adapter, judge: opts.judge, specDir: opts.specDir,
|
|
296
|
+
mode, rep, now, rejudge: true,
|
|
297
|
+
});
|
|
298
|
+
outcomes.push({ ...judged, objective });
|
|
299
|
+
judgeCalls += judged.metrics?.judge_calls ?? 0;
|
|
299
300
|
judgedHere++;
|
|
300
301
|
}
|
|
301
302
|
const threshold = effectiveThreshold(rec, scenario);
|
package/dist/regrade.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ export interface RegradeOptions {
|
|
|
34
34
|
mode?: string;
|
|
35
35
|
/** Recorded repetition count; when supplied, missing/extra rep artifacts fail before judge calls. */
|
|
36
36
|
expectedReps?: number;
|
|
37
|
+
/** Select only repetitions with retained ERROR criterion votes. */
|
|
38
|
+
onlyUnparsed?: boolean;
|
|
39
|
+
prior?: ScenarioResult;
|
|
37
40
|
now?: () => string;
|
|
38
41
|
}
|
|
39
42
|
/** Judge one saved transcript: writes the judge-raw artifact, emits a `judge-verdict` journal event (plus `misfire-flag` when the verdict is suspect), and returns the outcome. */
|
|
@@ -72,6 +75,8 @@ export interface RegradeRunOptions {
|
|
|
72
75
|
* cleanly, so spending judge calls on them buys nothing.
|
|
73
76
|
*/
|
|
74
77
|
onlySuspect?: boolean;
|
|
78
|
+
/** Rejudge only retained repetitions containing ERROR criterion votes, with the recorded judge. */
|
|
79
|
+
onlyUnparsed?: boolean;
|
|
75
80
|
}
|
|
76
81
|
/**
|
|
77
82
|
* Re-judge every scenario in a run dir that has a transcript for the run's own
|
package/dist/regrade.js
CHANGED
|
@@ -56,18 +56,18 @@ export async function judgeOneRep(opts) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
const prompt = buildJudgePrompt({ skill: spec.skill, persona: spec.judge_persona, scenario, transcript });
|
|
59
|
-
const g = await judgeInWorkspace(adapter, judge, prompt, specDir);
|
|
59
|
+
const g = await judgeInWorkspace(adapter, judge, prompt, specDir, scenario.checklist.length);
|
|
60
60
|
writeFileSync(judgeRawPath(runDir, scenario.id, mode, rep), g.raw, "utf8");
|
|
61
61
|
appendJournal(runDir, { event: "judge-verdict", ts: now(), id: scenario.id, verdict: g.verdict, reason: g.reason, suspect: g.suspect, ...repField });
|
|
62
62
|
if (g.suspect)
|
|
63
63
|
appendJournal(runDir, { event: "misfire-flag", ts: now(), id: scenario.id, reason: g.reason, ...repField });
|
|
64
64
|
return {
|
|
65
65
|
verdict: g.verdict, reason: g.reason, suspect: g.suspect,
|
|
66
|
-
judgment: { ordinal: 1, judge: { ...judge }, verdict: g.verdict, reason: g.reason, suspect: g.suspect, criteria: completeCriterionVotes(g.criteria, scenario.checklist.length) },
|
|
66
|
+
judgment: { ordinal: 1, judge: { ...judge }, verdict: g.verdict, reason: g.reason, suspect: g.suspect, criteria: completeCriterionVotes(g.criteria, scenario.checklist.length), ...(g.judgeRetries ? { judgeRetries: g.judgeRetries } : {}) },
|
|
67
67
|
metrics: {
|
|
68
68
|
wall_time_ms: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
69
|
-
judge_calls: 1,
|
|
70
|
-
judge_rejudge_calls: opts.rejudge ? 1 : 0,
|
|
69
|
+
judge_calls: 1 + (g.judgeRetries ?? 0),
|
|
70
|
+
judge_rejudge_calls: opts.rejudge ? 1 + (g.judgeRetries ?? 0) : 0,
|
|
71
71
|
},
|
|
72
72
|
};
|
|
73
73
|
}
|
|
@@ -81,18 +81,30 @@ export async function judgeOneRep(opts) {
|
|
|
81
81
|
export async function regradeScenario(opts) {
|
|
82
82
|
const now = opts.now ?? (() => new Date().toISOString());
|
|
83
83
|
const mode = opts.mode ?? "green";
|
|
84
|
-
|
|
84
|
+
let files = findTranscriptFiles(opts.runDir, opts.scenario.id, mode);
|
|
85
|
+
if (opts.onlyUnparsed)
|
|
86
|
+
files = files.filter(file => hasUnparsedVotes(opts.prior?.rep_judgments?.find(panel => panel.repetition === (repIndexOf(file) ?? 0))));
|
|
85
87
|
if (files.length === 0)
|
|
86
88
|
throw new Error(`no ${mode} transcripts for ${opts.scenario.id} in ${opts.runDir}`);
|
|
87
89
|
const expected = opts.expectedReps ?? files.length;
|
|
88
|
-
const expectedIndices =
|
|
90
|
+
const expectedIndices = opts.onlyUnparsed
|
|
91
|
+
? opts.prior.rep_judgments.filter(hasUnparsedVotes).map(panel => expected === 1 ? null : panel.repetition).sort((a, b) => (a ?? -1) - (b ?? -1))
|
|
92
|
+
: expected === 1 ? [null] : Array.from({ length: expected }, (_, index) => index);
|
|
89
93
|
const actualIndices = files.map((file) => repIndexOf(file)).sort((a, b) => (a ?? -1) - (b ?? -1));
|
|
90
|
-
if (files.length !==
|
|
94
|
+
if (files.length !== expectedIndices.length || JSON.stringify(actualIndices) !== JSON.stringify(expectedIndices)) {
|
|
91
95
|
throw new Error(`${opts.scenario.id}: transcript artifacts are incomplete for ${expected} recorded rep(s) — re-run instead of grading a smaller repetition set`);
|
|
92
96
|
}
|
|
93
97
|
const repCount = expected;
|
|
94
98
|
const outcomes = [];
|
|
95
|
-
for (
|
|
99
|
+
for (let repetition = 0; repetition < expected; repetition++) {
|
|
100
|
+
const panel = opts.prior?.rep_judgments?.find(panel => panel.repetition === repetition);
|
|
101
|
+
if (opts.onlyUnparsed && !hasUnparsedVotes(panel)) {
|
|
102
|
+
if (!panel)
|
|
103
|
+
throw new Error(`missing retained judgments for ${opts.scenario.id}#${repetition}`);
|
|
104
|
+
outcomes.push({ verdict: panel.recorded_verdict, reason: panel.judgments[0]?.reason ?? "", suspect: panel.judgments.length > 0 && !panel.judgments.some(judgment => !judgment.suspect && (judgment.verdict === "PASS" || judgment.verdict === "FAIL")), judgment: panel.judgments[0], objective: panel.objective });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const file = files.find(file => (repIndexOf(file) ?? 0) === repetition);
|
|
96
108
|
const rep = repIndexOf(file) ?? undefined;
|
|
97
109
|
const transcript = readFileSync(join(opts.runDir, file), "utf8");
|
|
98
110
|
outcomes.push(await judgeOneRep({
|
|
@@ -100,7 +112,16 @@ export async function regradeScenario(opts) {
|
|
|
100
112
|
adapter: opts.adapter, judge: opts.judge, specDir: opts.specDir, mode, rep, now, rejudge: true,
|
|
101
113
|
}));
|
|
102
114
|
}
|
|
103
|
-
|
|
115
|
+
const result = outcomesToResult(opts.scenario.id, outcomes, repCount, opts.threshold);
|
|
116
|
+
if (opts.onlyUnparsed)
|
|
117
|
+
result.rep_judgments = result.rep_judgments?.map(panel => {
|
|
118
|
+
const prior = opts.prior.rep_judgments.find(prior => prior.repetition === panel.repetition);
|
|
119
|
+
return hasUnparsedVotes(prior) ? panel : prior;
|
|
120
|
+
});
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function hasUnparsedVotes(panel) {
|
|
124
|
+
return panel?.judgments.some(judgment => judgment.criteria?.some(vote => vote.verdict === "ERROR")) ?? false;
|
|
104
125
|
}
|
|
105
126
|
/**
|
|
106
127
|
* Re-judge every scenario in a run dir that has a transcript for the run's own
|
|
@@ -114,17 +135,21 @@ export async function regradeScenario(opts) {
|
|
|
114
135
|
* ResultsFile. Shared by `cmdGrade` and the pi-extension's `judge` command.
|
|
115
136
|
*/
|
|
116
137
|
export async function regradeRun(opts) {
|
|
117
|
-
const { runDir, spec, adapter,
|
|
138
|
+
const { runDir, spec, adapter, specDir } = opts;
|
|
118
139
|
const now = opts.now ?? (() => new Date().toISOString());
|
|
119
140
|
const prev = existsSync(join(runDir, "results.yaml")) ? readResults(runDir) : null;
|
|
141
|
+
if (opts.onlyUnparsed && !prev)
|
|
142
|
+
throw new Error(`--unparsed-only needs a prior results.yaml in ${runDir}`);
|
|
143
|
+
if (opts.onlyUnparsed && opts.onlySuspect)
|
|
144
|
+
throw new Error("--unparsed-only cannot be combined with --suspect-only");
|
|
145
|
+
const judge = opts.onlyUnparsed ? prev.judge : opts.judge;
|
|
120
146
|
// Carried across a re-judge, per field, because the right answer differs:
|
|
121
147
|
// - `override`/`note` — the author's, never the judge's to discard.
|
|
122
148
|
// - `objective` — a re-judge does NOT re-evaluate trace gates (that is
|
|
123
149
|
// `regate`), so the recorded evidence still describes this run. Dropping it
|
|
124
150
|
// silently downgraded a gated scenario to "no assertions declared".
|
|
125
|
-
// `adjudication` is
|
|
126
|
-
//
|
|
127
|
-
// than none.
|
|
151
|
+
// `adjudication` is dropped when its repetition is rejudged. A selective
|
|
152
|
+
// repair of a different repetition carries that untouched evidence instead.
|
|
128
153
|
// The whole prior result per id — `rebuildScenarioResult` decides, field by
|
|
129
154
|
// field, what survives. Passing a hand-picked subset here is how fields got
|
|
130
155
|
// dropped before.
|
|
@@ -148,6 +173,8 @@ export async function regradeRun(opts) {
|
|
|
148
173
|
.filter((s) => s.suspect || s.judge_verdict === "JUDGE-AMBIGUOUS")
|
|
149
174
|
.map((s) => s.id);
|
|
150
175
|
}
|
|
176
|
+
if (opts.onlyUnparsed)
|
|
177
|
+
targets = recorded.filter(s => s.rep_judgments?.some(hasUnparsedVotes)).map(s => s.id);
|
|
151
178
|
// Schema-3 delivery is a prerequisite for behavioral judging. A later `grade`
|
|
152
179
|
// cannot turn an undelivered or unauthenticated transcript into product evidence.
|
|
153
180
|
if (prev?.schema === 3) {
|
|
@@ -161,6 +188,9 @@ export async function regradeRun(opts) {
|
|
|
161
188
|
const completeTranscripts = (record) => {
|
|
162
189
|
const expected = record.reps ?? 1;
|
|
163
190
|
const files = findTranscriptFiles(runDir, record.id, mode);
|
|
191
|
+
if (opts.onlyUnparsed) {
|
|
192
|
+
return record.rep_judgments?.length === expected && record.rep_judgments.every(panel => !hasUnparsedVotes(panel) || files.some(file => (repIndexOf(file) ?? 0) === panel.repetition)) || false;
|
|
193
|
+
}
|
|
164
194
|
const expectedIndices = expected === 1 ? [null] : Array.from({ length: expected }, (_, index) => index);
|
|
165
195
|
const actualIndices = files.map((file) => repIndexOf(file)).sort((a, b) => (a ?? -1) - (b ?? -1));
|
|
166
196
|
return files.length === expected && JSON.stringify(actualIndices) === JSON.stringify(expectedIndices);
|
|
@@ -173,6 +203,14 @@ export async function regradeRun(opts) {
|
|
|
173
203
|
if (missing.length > 0) {
|
|
174
204
|
throw new Error(`cannot re-grade ${missing.join(", ")} in ${runDir} (transcript missing or scenario no longer in the spec) — re-run instead of grading`);
|
|
175
205
|
}
|
|
206
|
+
if (opts.onlyUnparsed) {
|
|
207
|
+
const drifted = targets.filter(id => {
|
|
208
|
+
const count = recordedById.get(id).criterion_count;
|
|
209
|
+
return count !== undefined && count !== specById.get(id).checklist.length;
|
|
210
|
+
});
|
|
211
|
+
if (drifted.length)
|
|
212
|
+
throw new Error(`criterion count changed for ${drifted.join(", ")} — use full grade instead of --unparsed-only`);
|
|
213
|
+
}
|
|
176
214
|
const targetSet = new Set(targets);
|
|
177
215
|
const scenarioResults = [];
|
|
178
216
|
for (const rec of recorded) {
|
|
@@ -186,17 +224,17 @@ export async function regradeRun(opts) {
|
|
|
186
224
|
const threshold = effectiveThreshold(prevScenario, scenario);
|
|
187
225
|
const rr = await regradeScenario({
|
|
188
226
|
runDir, spec, scenario, adapter, judge, specDir, threshold, mode, now,
|
|
189
|
-
expectedReps: prevScenario?.reps ?? 1,
|
|
227
|
+
expectedReps: prevScenario?.reps ?? 1, onlyUnparsed: opts.onlyUnparsed, prior: prevScenario,
|
|
190
228
|
});
|
|
191
229
|
const carry = overrides.get(id);
|
|
192
230
|
if (prev?.schema === 3)
|
|
193
231
|
rr.criterion_count = scenario.checklist.length;
|
|
194
232
|
rr.metrics = mergeScenarioMetrics(carry?.metrics, rr.metrics);
|
|
195
233
|
rr.rep_judgments = carryRepObjectives(rr.rep_judgments, carry?.rep_judgments);
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
scenarioResults.push(rebuildScenarioResult(rr, carry, { objective: "carry", adjudication: "drop" }));
|
|
234
|
+
// Gates still describe the original subject execution. An adjudication of
|
|
235
|
+
// an untouched repetition also survives, bounded by rebuild's conflict guards.
|
|
236
|
+
const carryAdjudication = opts.onlyUnparsed && carry?.adjudication && !hasUnparsedVotes(carry.rep_judgments?.find(panel => panel.repetition === (carry.adjudication.repetition ?? 0)));
|
|
237
|
+
scenarioResults.push(rebuildScenarioResult(rr, carry, { objective: "carry", adjudication: carryAdjudication ? "carry" : "drop" }));
|
|
200
238
|
}
|
|
201
239
|
const ctx = scoreContextFor({ mode, partial: prev?.partial }, spec);
|
|
202
240
|
const results = writeResults(runDir, {
|
|
@@ -229,7 +267,7 @@ export async function regradeRun(opts) {
|
|
|
229
267
|
// say so. Doctrine narrowed 0.4.0, from "recorded hashes stay" to "recorded
|
|
230
268
|
// *stimulus* hashes stay" — see refreshRubricHashes.
|
|
231
269
|
partial: prev?.partial,
|
|
232
|
-
source_hashes: refreshRubricHashes(prev?.source_hashes, spec, targets),
|
|
270
|
+
source_hashes: opts.onlyUnparsed ? prev?.source_hashes : refreshRubricHashes(prev?.source_hashes, spec, targets),
|
|
233
271
|
source_hash_roots: prev?.source_hash_roots,
|
|
234
272
|
scenarios: scenarioResults,
|
|
235
273
|
}, ctx);
|
package/dist/results.d.ts
CHANGED
|
@@ -99,6 +99,8 @@ export interface Judgment {
|
|
|
99
99
|
suspect: boolean;
|
|
100
100
|
/** Every numbered rubric-item vote parsed from this exact judge response. Required in schema v3. */
|
|
101
101
|
criteria?: CriterionVote[];
|
|
102
|
+
/** One retry with identical judge input after unparseable criterion votes. */
|
|
103
|
+
judgeRetries?: 1;
|
|
102
104
|
}
|
|
103
105
|
export interface AdjudicationResult {
|
|
104
106
|
/** The saved transcript repetition judged by every panel member (first rep by policy). */
|
package/dist/results.js
CHANGED
|
@@ -613,7 +613,7 @@ export function rebuildScenarioResult(fresh, prior, policy) {
|
|
|
613
613
|
const objective = pick(policy.objective, freshObjective, prior?.objective);
|
|
614
614
|
const pickedAdjudication = pick(policy.adjudication, freshAdjudication, prior?.adjudication);
|
|
615
615
|
const conflictsWithFreshEvidence = Boolean(pickedAdjudication?.verdict && (objective?.status === "FAIL" || objective?.status === "ERROR" || objective?.status === "NOT-MEASURED" ||
|
|
616
|
-
(pass_threshold === 1 && (reps ?? 1) > 1 && judge_verdict !== "PASS")));
|
|
616
|
+
(pickedAdjudication.verdict === "PASS" && pass_threshold === 1 && (reps ?? 1) > 1 && judge_verdict !== "PASS")));
|
|
617
617
|
// A settled cell-level panel cannot overrule newly evaluated objective gates,
|
|
618
618
|
// nor can one transcript erase a failed repetition under an all-clean policy.
|
|
619
619
|
const adjudication = conflictsWithFreshEvidence && pickedAdjudication
|
package/dist/run.js
CHANGED
|
@@ -521,7 +521,7 @@ async function runRep(scenario, rep, repCount, ctx) {
|
|
|
521
521
|
reason = o.reason;
|
|
522
522
|
suspect = o.suspect;
|
|
523
523
|
judgment = o.judgment; // judgeOneRep already journaled (verdict + misfire)
|
|
524
|
-
judgeCalls =
|
|
524
|
+
judgeCalls = o.metrics?.judge_calls ?? 0;
|
|
525
525
|
}
|
|
526
526
|
log(` → ${scenario.id}${repCount > 1 ? `#${rep}` : ""} ${verdict}${reason ? `: ${reason}` : ""}${suspect ? " ⚠ suspect" : ""}`);
|
|
527
527
|
const subject = mergeTraces(traces)?.metrics;
|
package/package.json
CHANGED