@hawkeyexl/manni 4.2.0 → 4.3.1
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/README.md +2 -2
- package/dist/batch-A5HZKYIW.js +18 -0
- package/dist/batch-A5HZKYIW.js.map +1 -0
- package/dist/calibrate-S5K6QHUY.js +13 -0
- package/dist/calibrate-S5K6QHUY.js.map +1 -0
- package/dist/capture-OZEZDABQ.js +15 -0
- package/dist/capture-OZEZDABQ.js.map +1 -0
- package/dist/chunk-3R23C5DT.js +120 -0
- package/dist/chunk-3R23C5DT.js.map +1 -0
- package/dist/chunk-5HNHEUD5.js +174 -0
- package/dist/chunk-5HNHEUD5.js.map +1 -0
- package/dist/chunk-CFDUZRM2.js +1941 -0
- package/dist/chunk-CFDUZRM2.js.map +1 -0
- package/dist/chunk-G5CM2VGQ.js +297 -0
- package/dist/chunk-G5CM2VGQ.js.map +1 -0
- package/dist/chunk-HAPTXWU2.js +379 -0
- package/dist/chunk-HAPTXWU2.js.map +1 -0
- package/dist/chunk-IOYXPBCL.js +37 -0
- package/dist/chunk-IOYXPBCL.js.map +1 -0
- package/dist/chunk-KJR7OVDX.js +4447 -0
- package/dist/chunk-KJR7OVDX.js.map +1 -0
- package/dist/chunk-KTSJ2ZHA.js +40 -0
- package/dist/chunk-KTSJ2ZHA.js.map +1 -0
- package/dist/{chunk-BNHQWIAE.js → chunk-UBMMXS2Y.js} +2 -2
- package/dist/{chunk-UYDRDFYF.js → chunk-V4KUVE47.js} +1943 -909
- package/dist/chunk-V4KUVE47.js.map +1 -0
- package/dist/{chunk-TH565O2N.js → chunk-ZQBTK3DO.js} +898 -278
- package/dist/chunk-ZQBTK3DO.js.map +1 -0
- package/dist/cli.js +437 -47
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +3 -2
- package/dist/docmeta.js.map +1 -1
- package/dist/index.d.ts +3873 -795
- package/dist/index.js +292 -4
- package/dist/index.js.map +1 -1
- package/dist/picker-6HRMETE7.js +12 -0
- package/dist/picker-6HRMETE7.js.map +1 -0
- package/package.json +3 -1
- package/plugin/tracevals/.claude-plugin/plugin.json +9 -0
- package/plugin/tracevals/hooks/hooks.json +17 -0
- package/dist/chunk-TH565O2N.js.map +0 -1
- package/dist/chunk-UYDRDFYF.js.map +0 -1
- /package/dist/{chunk-BNHQWIAE.js.map → chunk-UBMMXS2Y.js.map} +0 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertProviderSelection,
|
|
4
|
+
renderCalibration,
|
|
5
|
+
resolveBatchTraces,
|
|
6
|
+
runBatch,
|
|
7
|
+
selectProvider
|
|
8
|
+
} from "./chunk-KJR7OVDX.js";
|
|
9
|
+
import {
|
|
10
|
+
discoverConfig,
|
|
11
|
+
labelKey,
|
|
12
|
+
loadLabels
|
|
13
|
+
} from "./chunk-CFDUZRM2.js";
|
|
14
|
+
import {
|
|
15
|
+
TracevalsError
|
|
16
|
+
} from "./chunk-3R23C5DT.js";
|
|
17
|
+
|
|
18
|
+
// src/tracevals/commands/calibrate.ts
|
|
19
|
+
import { writeFile } from "node:fs/promises";
|
|
20
|
+
import { basename, resolve } from "node:path";
|
|
21
|
+
|
|
22
|
+
// src/tracevals/calibrate/score.ts
|
|
23
|
+
import { computeConsensus, zoneFor } from "@hawkeyexl/inference";
|
|
24
|
+
function classify(expected, actual) {
|
|
25
|
+
if (actual === "skipped") return "skipped";
|
|
26
|
+
if (actual === "error") return "error";
|
|
27
|
+
if (actual === expected) return "agree";
|
|
28
|
+
if (expected === "fail" && actual === "pass") return "false-pass";
|
|
29
|
+
if (expected === "pass" && actual === "fail") return "false-fail";
|
|
30
|
+
if (actual === "needs-review") return "review";
|
|
31
|
+
return "missed-review";
|
|
32
|
+
}
|
|
33
|
+
function joinLabels(outcomes, labels) {
|
|
34
|
+
const byType = /* @__PURE__ */ new Map();
|
|
35
|
+
const byName = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const outcome of outcomes) {
|
|
37
|
+
if (!("report" in outcome)) continue;
|
|
38
|
+
for (const result of outcome.report.evalResults) {
|
|
39
|
+
byType.set(
|
|
40
|
+
labelKey(
|
|
41
|
+
outcome.file,
|
|
42
|
+
result.artifactName,
|
|
43
|
+
result.evalName,
|
|
44
|
+
result.artifactType
|
|
45
|
+
),
|
|
46
|
+
result
|
|
47
|
+
);
|
|
48
|
+
const key = labelKey(outcome.file, result.artifactName, result.evalName);
|
|
49
|
+
const seen = byName.get(key);
|
|
50
|
+
if (seen === void 0) byName.set(key, [result]);
|
|
51
|
+
else seen.push(result);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const joined = [];
|
|
55
|
+
const unmatched = [];
|
|
56
|
+
const ambiguous = [];
|
|
57
|
+
for (const label of labels) {
|
|
58
|
+
if (label.type !== void 0) {
|
|
59
|
+
const result = byType.get(
|
|
60
|
+
labelKey(label.traceFile, label.artifact, label.eval, label.type)
|
|
61
|
+
);
|
|
62
|
+
if (result === void 0) unmatched.push(label);
|
|
63
|
+
else joined.push({ label, result });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const candidates = byName.get(labelKey(label.traceFile, label.artifact, label.eval)) ?? [];
|
|
67
|
+
const kinds = new Set(candidates.map((r) => r.artifactType));
|
|
68
|
+
if (candidates.length === 0) unmatched.push(label);
|
|
69
|
+
else if (kinds.size > 1) ambiguous.push({ label, candidates });
|
|
70
|
+
else joined.push({ label, result: candidates[0] });
|
|
71
|
+
}
|
|
72
|
+
return { joined, unmatched, ambiguous };
|
|
73
|
+
}
|
|
74
|
+
var summarise = (consensus) => ({
|
|
75
|
+
votes: consensus.votes,
|
|
76
|
+
agreement: consensus.agreement,
|
|
77
|
+
meanConfidence: consensus.meanConfidence,
|
|
78
|
+
runs: consensus.runs.length
|
|
79
|
+
});
|
|
80
|
+
function rescore(result, setting) {
|
|
81
|
+
const consensus = result.consensus;
|
|
82
|
+
if (consensus === void 0) {
|
|
83
|
+
return { outcome: result.outcome, insufficient: false };
|
|
84
|
+
}
|
|
85
|
+
const runs = consensus.runs;
|
|
86
|
+
if (runs.length < setting.ensembleRuns) {
|
|
87
|
+
return { outcome: result.outcome, insufficient: true };
|
|
88
|
+
}
|
|
89
|
+
const slice = runs.slice(0, setting.ensembleRuns);
|
|
90
|
+
const base = computeConsensus(slice);
|
|
91
|
+
const zone = zoneFor(base, setting.zones);
|
|
92
|
+
return {
|
|
93
|
+
outcome: zone === "auto-pass" ? "pass" : zone === "auto-fail" ? "fail" : "needs-review",
|
|
94
|
+
insufficient: false,
|
|
95
|
+
consensus: {
|
|
96
|
+
votes: base.votes,
|
|
97
|
+
agreement: base.agreement,
|
|
98
|
+
meanConfidence: base.meanConfidence,
|
|
99
|
+
runs: slice.length
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
var emptyCounts = () => ({
|
|
104
|
+
labels: 0,
|
|
105
|
+
scored: 0,
|
|
106
|
+
agree: 0,
|
|
107
|
+
falsePass: 0,
|
|
108
|
+
falseFail: 0,
|
|
109
|
+
review: 0,
|
|
110
|
+
missedReview: 0,
|
|
111
|
+
errored: 0,
|
|
112
|
+
skipped: 0,
|
|
113
|
+
reviewVolume: 0,
|
|
114
|
+
insufficient: 0,
|
|
115
|
+
agreement: null
|
|
116
|
+
});
|
|
117
|
+
function score(input, setting) {
|
|
118
|
+
const counts = emptyCounts();
|
|
119
|
+
const disagreements = [];
|
|
120
|
+
const unscored = [];
|
|
121
|
+
for (const result of input.all) {
|
|
122
|
+
const outcome = setting === void 0 ? result.outcome : rescore(result, setting).outcome;
|
|
123
|
+
if (outcome === "needs-review") counts.reviewVolume += 1;
|
|
124
|
+
}
|
|
125
|
+
for (const { label, result } of input.joined) {
|
|
126
|
+
const scored = setting === void 0 ? { outcome: result.outcome, insufficient: false } : rescore(result, setting);
|
|
127
|
+
if (scored.insufficient) counts.insufficient += 1;
|
|
128
|
+
const kind = classify(label.expected, scored.outcome);
|
|
129
|
+
counts.labels += 1;
|
|
130
|
+
const row = {
|
|
131
|
+
trace: label.traceFile,
|
|
132
|
+
artifactType: result.artifactType,
|
|
133
|
+
artifactName: result.artifactName,
|
|
134
|
+
evalName: result.evalName,
|
|
135
|
+
grader: result.grader,
|
|
136
|
+
expected: label.expected,
|
|
137
|
+
actual: scored.outcome,
|
|
138
|
+
kind,
|
|
139
|
+
...label.note !== void 0 ? { note: label.note } : {},
|
|
140
|
+
...result.skipReason !== void 0 ? { skipReason: result.skipReason } : {},
|
|
141
|
+
// At the setting being scored, falling back to the run's own numbers
|
|
142
|
+
// when nothing was re-scored.
|
|
143
|
+
...(() => {
|
|
144
|
+
const c = scored.consensus ?? (result.consensus !== void 0 ? summarise(result.consensus) : void 0);
|
|
145
|
+
return c !== void 0 ? { consensus: c } : {};
|
|
146
|
+
})()
|
|
147
|
+
};
|
|
148
|
+
switch (kind) {
|
|
149
|
+
case "skipped":
|
|
150
|
+
counts.skipped += 1;
|
|
151
|
+
unscored.push(row);
|
|
152
|
+
break;
|
|
153
|
+
case "agree":
|
|
154
|
+
counts.agree += 1;
|
|
155
|
+
counts.scored += 1;
|
|
156
|
+
break;
|
|
157
|
+
case "false-pass":
|
|
158
|
+
counts.falsePass += 1;
|
|
159
|
+
counts.scored += 1;
|
|
160
|
+
disagreements.push(row);
|
|
161
|
+
break;
|
|
162
|
+
case "false-fail":
|
|
163
|
+
counts.falseFail += 1;
|
|
164
|
+
counts.scored += 1;
|
|
165
|
+
disagreements.push(row);
|
|
166
|
+
break;
|
|
167
|
+
case "review":
|
|
168
|
+
counts.review += 1;
|
|
169
|
+
counts.scored += 1;
|
|
170
|
+
disagreements.push(row);
|
|
171
|
+
break;
|
|
172
|
+
case "missed-review":
|
|
173
|
+
counts.missedReview += 1;
|
|
174
|
+
counts.scored += 1;
|
|
175
|
+
disagreements.push(row);
|
|
176
|
+
break;
|
|
177
|
+
case "error":
|
|
178
|
+
counts.errored += 1;
|
|
179
|
+
counts.scored += 1;
|
|
180
|
+
disagreements.push(row);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
counts.agreement = counts.scored > 0 ? counts.agree / counts.scored : null;
|
|
185
|
+
const byRow = (a, b) => {
|
|
186
|
+
const ka = `${a.kind} ${a.trace} ${a.artifactName} ${a.evalName}`;
|
|
187
|
+
const kb = `${b.kind} ${b.trace} ${b.artifactName} ${b.evalName}`;
|
|
188
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
189
|
+
};
|
|
190
|
+
disagreements.sort(byRow);
|
|
191
|
+
unscored.sort(byRow);
|
|
192
|
+
return { counts, disagreements, unscored };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/tracevals/commands/calibrate.ts
|
|
196
|
+
function sweepDepth(grid, baseline) {
|
|
197
|
+
return Math.max(baseline, ...grid);
|
|
198
|
+
}
|
|
199
|
+
async function runCalibrate(options) {
|
|
200
|
+
const start = Date.now();
|
|
201
|
+
const { config, dir: configDir } = await discoverConfig(
|
|
202
|
+
options.configDir ?? process.cwd(),
|
|
203
|
+
{
|
|
204
|
+
...options.config === void 0 ? {} : { configPath: options.config },
|
|
205
|
+
...options.noConfig === void 0 ? {} : { noConfig: options.noConfig }
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
assertProviderSelection(
|
|
209
|
+
selectProvider(config, {
|
|
210
|
+
...options.provider !== void 0 ? { provider: options.provider } : {},
|
|
211
|
+
...options.model !== void 0 ? { model: options.model } : {},
|
|
212
|
+
...options.local !== void 0 ? { local: options.local } : {}
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
const labelsFile = options.labels !== void 0 ? resolve(options.labels) : resolve(configDir, config.calibrate.labels);
|
|
216
|
+
const labels = await loadLabels(labelsFile);
|
|
217
|
+
const corpus = (await resolveBatchTraces(options)).map((f) => resolve(f));
|
|
218
|
+
assertLabelsInCorpus(labels, corpus, labelsFile);
|
|
219
|
+
const baseline = {
|
|
220
|
+
ensembleRuns: options.runs ?? config.judge.ensembleRuns,
|
|
221
|
+
zones: config.judge.zones
|
|
222
|
+
};
|
|
223
|
+
const grid = config.calibrate.sweep;
|
|
224
|
+
const depth = options.sweep === true ? sweepDepth(grid.ensembleRuns, baseline.ensembleRuns) : baseline.ensembleRuns;
|
|
225
|
+
const batch = await runBatch({
|
|
226
|
+
...options,
|
|
227
|
+
traces: corpus,
|
|
228
|
+
allProjects: void 0,
|
|
229
|
+
newerThan: void 0,
|
|
230
|
+
limit: void 0,
|
|
231
|
+
runs: depth,
|
|
232
|
+
// The calibration report is the deliverable; rendering the batch as well
|
|
233
|
+
// would write the wrong thing to --output.
|
|
234
|
+
format: "json",
|
|
235
|
+
output: void 0
|
|
236
|
+
});
|
|
237
|
+
const all = [];
|
|
238
|
+
for (const outcome of batch.outcomes) {
|
|
239
|
+
if ("report" in outcome) all.push(...outcome.report.evalResults);
|
|
240
|
+
}
|
|
241
|
+
const { joined, unmatched, ambiguous } = joinLabels(batch.outcomes, labels);
|
|
242
|
+
assertNoUnmatched(unmatched, batch, labelsFile);
|
|
243
|
+
assertNoAmbiguous(ambiguous, labelsFile);
|
|
244
|
+
const scored = score(
|
|
245
|
+
{ joined, all },
|
|
246
|
+
options.sweep === true ? baseline : void 0
|
|
247
|
+
);
|
|
248
|
+
const sweep = options.sweep === true ? buildSweep({ joined, all }, baseline, grid) : void 0;
|
|
249
|
+
const gates = buildGates(scored.counts, {
|
|
250
|
+
falsePass: options.maxFalsePass ?? config.calibrate.maxFalsePass,
|
|
251
|
+
falseFail: options.maxFalseFail ?? config.calibrate.maxFalseFail,
|
|
252
|
+
review: options.maxReview ?? config.calibrate.maxReview
|
|
253
|
+
});
|
|
254
|
+
const warnings = [...batch.report.warnings];
|
|
255
|
+
if (scored.counts.insufficient > 0) {
|
|
256
|
+
warnings.push(
|
|
257
|
+
`${scored.counts.insufficient} labelled eval(s) had fewer cached runs than the setting asks for and were left at their reported outcome`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const measuredNothing = scored.counts.scored === 0;
|
|
261
|
+
if (measuredNothing) {
|
|
262
|
+
warnings.push(
|
|
263
|
+
`no labelled eval produced evidence: ${scored.counts.labels} label(s) all joined to a skipped result, so agreement and every --max-* threshold are computed over an empty denominator`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const report = {
|
|
267
|
+
labelsFile,
|
|
268
|
+
corpus,
|
|
269
|
+
setting: baseline,
|
|
270
|
+
counts: scored.counts,
|
|
271
|
+
disagreements: scored.disagreements,
|
|
272
|
+
unscored: scored.unscored,
|
|
273
|
+
...sweep !== void 0 ? { sweep } : {},
|
|
274
|
+
gates,
|
|
275
|
+
batch: batch.report,
|
|
276
|
+
warnings,
|
|
277
|
+
// A calibration run is a measurement, not a gate: disagreement is the
|
|
278
|
+
// finding, not a failure. Exit 1 only when a threshold was asked for and
|
|
279
|
+
// missed, or when the measurement itself is incomplete — a trace lost, the
|
|
280
|
+
// shared judge budget cut the corpus short, or nothing scored at all. An
|
|
281
|
+
// incomplete number presented as a clean one is worse than no number.
|
|
282
|
+
exitCode: gates.some((g) => g.exceeded) || batch.report.summary.tracesErrored > 0 || batch.report.budget !== void 0 || measuredNothing ? 1 : 0,
|
|
283
|
+
turns: batch.report.turns,
|
|
284
|
+
durationMs: Date.now() - start
|
|
285
|
+
};
|
|
286
|
+
const rendered = renderCalibration(report, options.format ?? "pretty", {
|
|
287
|
+
color: options.color === true
|
|
288
|
+
});
|
|
289
|
+
if (options.output) await writeFile(options.output, rendered, "utf-8");
|
|
290
|
+
return { report, rendered };
|
|
291
|
+
}
|
|
292
|
+
function assertLabelsInCorpus(labels, corpus, labelsFile) {
|
|
293
|
+
const known = new Set(corpus);
|
|
294
|
+
const missing = [...new Set(labels.filter((l) => !known.has(l.traceFile)).map((l) => l.trace))];
|
|
295
|
+
if (missing.length === 0) return;
|
|
296
|
+
throw new TracevalsError(
|
|
297
|
+
`${labelsFile}: label(s) name trace(s) not in the corpus: ${missing.join(", ")}. The corpus is: ${corpus.map((f) => basename(f)).join(", ")}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
function assertNoUnmatched(unmatched, batch, labelsFile) {
|
|
301
|
+
if (unmatched.length === 0) return;
|
|
302
|
+
const lines = unmatched.map((label) => {
|
|
303
|
+
const outcome = batch.outcomes.find((o) => o.file === label.traceFile);
|
|
304
|
+
const results = outcome?.report?.evalResults ?? [];
|
|
305
|
+
const sameArtifact = results.filter(
|
|
306
|
+
(r) => r.artifactName === label.artifact
|
|
307
|
+
);
|
|
308
|
+
const hint = sameArtifact.length > 0 ? `that trace evaluated ${sameArtifact.map((r) => r.evalName).sort().join(", ")} for "${label.artifact}"` : `that trace resolved no artifact named "${label.artifact}" \u2014 it evaluated ${[...new Set(results.map((r) => r.artifactName))].sort().join(", ") || "nothing"}`;
|
|
309
|
+
return ` ${label.artifact} \u203A ${label.eval} on ${basename(label.traceFile)} (${hint})`;
|
|
310
|
+
});
|
|
311
|
+
throw new TracevalsError(
|
|
312
|
+
`${labelsFile}: label(s) matched no eval result:
|
|
313
|
+
${lines.join("\n")}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function assertNoAmbiguous(ambiguous, labelsFile) {
|
|
317
|
+
if (ambiguous.length === 0) return;
|
|
318
|
+
const lines = ambiguous.map(({ label, candidates }) => {
|
|
319
|
+
const kinds = [...new Set(candidates.map((r) => r.artifactType))].sort();
|
|
320
|
+
return ` ${label.artifact} \u203A ${label.eval} on ${basename(label.traceFile)} matches ${kinds.join(" and ")} \u2014 add \`type:\` to say which`;
|
|
321
|
+
});
|
|
322
|
+
throw new TracevalsError(
|
|
323
|
+
`${labelsFile}: label(s) name an artifact two kinds answer:
|
|
324
|
+
${lines.join("\n")}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
function buildGates(counts, limits) {
|
|
328
|
+
const gates = [];
|
|
329
|
+
const add = (name, limit, actual) => {
|
|
330
|
+
if (limit === void 0) return;
|
|
331
|
+
gates.push({ name, limit, actual, exceeded: actual > limit });
|
|
332
|
+
};
|
|
333
|
+
add("falsePass", limits.falsePass, counts.falsePass);
|
|
334
|
+
add("falseFail", limits.falseFail, counts.falseFail);
|
|
335
|
+
add("review", limits.review, counts.reviewVolume);
|
|
336
|
+
return gates;
|
|
337
|
+
}
|
|
338
|
+
function buildSweep(input, baseline, grid) {
|
|
339
|
+
const cell = (axis, value, setting) => ({
|
|
340
|
+
axis,
|
|
341
|
+
value,
|
|
342
|
+
setting,
|
|
343
|
+
counts: score(input, setting).counts
|
|
344
|
+
});
|
|
345
|
+
const cells = [
|
|
346
|
+
cell("baseline", baseline.ensembleRuns, baseline)
|
|
347
|
+
];
|
|
348
|
+
for (const runs of [...grid.ensembleRuns].sort((a, b) => a - b)) {
|
|
349
|
+
cells.push(
|
|
350
|
+
cell("ensembleRuns", runs, { ensembleRuns: runs, zones: baseline.zones })
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
for (const autoPass of [...grid.autoPass].sort((a, b) => a - b)) {
|
|
354
|
+
cells.push(
|
|
355
|
+
cell("zones.autoPass", autoPass, {
|
|
356
|
+
ensembleRuns: baseline.ensembleRuns,
|
|
357
|
+
zones: { autoPass, autoFail: baseline.zones.autoFail }
|
|
358
|
+
})
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
for (const autoFail of [...grid.autoFail].sort((a, b) => a - b)) {
|
|
362
|
+
cells.push(
|
|
363
|
+
cell("zones.autoFail", autoFail, {
|
|
364
|
+
ensembleRuns: baseline.ensembleRuns,
|
|
365
|
+
zones: { autoPass: baseline.zones.autoPass, autoFail }
|
|
366
|
+
})
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
return cells;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export {
|
|
373
|
+
classify,
|
|
374
|
+
joinLabels,
|
|
375
|
+
rescore,
|
|
376
|
+
score,
|
|
377
|
+
runCalibrate
|
|
378
|
+
};
|
|
379
|
+
//# sourceMappingURL=chunk-HAPTXWU2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracevals/commands/calibrate.ts","../src/tracevals/calibrate/score.ts"],"sourcesContent":["/**\n * `manni tracevals calibrate <traces...> --labels <file>` (ADR 01022).\n *\n * The tool could report what it decided; it could not report whether it was\n * right. This command runs the batch (ADR 01018), joins the results against a\n * human's answers, and reports the three numbers the calibration page used to\n * ask people to count by hand — false passes, false fails, and review volume —\n * plus which eval disagreed.\n *\n * `--sweep` costs no inference beyond the one run it already made: a judged\n * eval's consensus carries the whole `JudgeRun[]`, so a different zone\n * threshold or a smaller ensemble is arithmetic over verdicts already on disk.\n * Read-only throughout — labels, traces, and artifacts are never written.\n */\nimport { writeFile } from \"node:fs/promises\";\nimport { basename, resolve } from \"node:path\";\nimport { discoverConfig } from \"../core/config.js\";\nimport { assertProviderSelection, selectProvider } from \"../judge/provider.js\";\nimport { renderCalibration, type ReportFormat } from \"../reporters/index.js\";\nimport { TracevalsError, type EvalResult } from \"../types.js\";\nimport { loadLabels, type EvalLabel } from \"../calibrate/labels.js\";\nimport { joinLabels, score, type AmbiguousLabel } from \"../calibrate/score.js\";\nimport type {\n CalibrationGate,\n CalibrationReport,\n JudgeSetting,\n SweepCell,\n} from \"../calibrate/types.js\";\nimport {\n resolveBatchTraces,\n runBatch,\n type BatchCommandOptions,\n} from \"./batch.js\";\n\nexport interface CalibrateCommandOptions extends BatchCommandOptions {\n /** Overrides `calibrate.labels`; resolved against the working directory. */\n labels?: string;\n /** Re-score the corpus across the configured grid. */\n sweep?: boolean;\n /** Thresholds; undefined defers to the config, which defaults to no gate. */\n maxFalsePass?: number;\n maxFalseFail?: number;\n maxReview?: number;\n}\n\nexport interface CalibrateCommandResult {\n report: CalibrationReport;\n rendered: string;\n}\n\n/** Largest ensemble the sweep needs cached, so every cell can be re-scored. */\nfunction sweepDepth(grid: number[], baseline: number): number {\n return Math.max(baseline, ...grid);\n}\n\nexport async function runCalibrate(\n options: CalibrateCommandOptions,\n): Promise<CalibrateCommandResult> {\n const start = Date.now();\n const { config, dir: configDir } = await discoverConfig(\n options.configDir ?? process.cwd(),\n {\n ...(options.config === undefined ? {} : { configPath: options.config }),\n ...(options.noConfig === undefined ? {} : { noConfig: options.noConfig }),\n },\n );\n\n // Before the labels file is read: a contradictory `--local --provider`, an\n // unknown provider name, or a model with no provider to own it is a usage\n // error, and a usage error must not arrive behind an unrelated one about a\n // file the run would never have needed.\n assertProviderSelection(\n selectProvider(config, {\n ...(options.provider !== undefined ? { provider: options.provider } : {}),\n ...(options.model !== undefined ? { model: options.model } : {}),\n ...(options.local !== undefined ? { local: options.local } : {}),\n }),\n );\n\n // A flag overrides the config rather than bypassing it (CLAUDE.md,\n // \"Config <-> CLI flags\"). An explicit --labels is a working-directory path,\n // the way every other path argument on the CLI is; the config default is a\n // project path and resolves against the config file's directory.\n const labelsFile =\n options.labels !== undefined\n ? resolve(options.labels)\n : resolve(configDir, config.calibrate.labels);\n const labels = await loadLabels(labelsFile);\n\n // Resolve the corpus once and hand the list to the batch, so the membership\n // check below cannot disagree with what actually ran.\n const corpus = (await resolveBatchTraces(options)).map((f) => resolve(f));\n assertLabelsInCorpus(labels, corpus, labelsFile);\n\n const baseline: JudgeSetting = {\n ensembleRuns: options.runs ?? config.judge.ensembleRuns,\n zones: config.judge.zones,\n };\n const grid = config.calibrate.sweep;\n // With --sweep the corpus is judged once at the deepest ensemble the grid\n // asks for, and every cell — the baseline included — is re-scored from those\n // runs. Judging at the configured depth instead would make the larger cells\n // unscoreable, and judging per cell would multiply the bill by the grid.\n const depth =\n options.sweep === true\n ? sweepDepth(grid.ensembleRuns, baseline.ensembleRuns)\n : baseline.ensembleRuns;\n\n const batch = await runBatch({\n ...options,\n traces: corpus,\n allProjects: undefined,\n newerThan: undefined,\n limit: undefined,\n runs: depth,\n // The calibration report is the deliverable; rendering the batch as well\n // would write the wrong thing to --output.\n format: \"json\",\n output: undefined,\n });\n\n const all: EvalResult[] = [];\n for (const outcome of batch.outcomes) {\n if (\"report\" in outcome) all.push(...outcome.report.evalResults);\n }\n const { joined, unmatched, ambiguous } = joinLabels(batch.outcomes, labels);\n assertNoUnmatched(unmatched, batch, labelsFile);\n assertNoAmbiguous(ambiguous, labelsFile);\n\n // Without a sweep the reported outcomes are used verbatim, so the headline\n // numbers are exactly the verdicts `run` produced. With one, the corpus was\n // judged deeper than the configured ensemble, so the baseline row has to be\n // re-scored back down to it.\n const scored = score(\n { joined, all },\n options.sweep === true ? baseline : undefined,\n );\n\n const sweep =\n options.sweep === true\n ? buildSweep({ joined, all }, baseline, grid)\n : undefined;\n\n const gates = buildGates(scored.counts, {\n falsePass: options.maxFalsePass ?? config.calibrate.maxFalsePass,\n falseFail: options.maxFalseFail ?? config.calibrate.maxFalseFail,\n review: options.maxReview ?? config.calibrate.maxReview,\n });\n\n const warnings = [...batch.report.warnings];\n if (scored.counts.insufficient > 0) {\n warnings.push(\n `${scored.counts.insufficient} labelled eval(s) had fewer cached runs than the setting asks for and were left at their reported outcome`,\n );\n }\n\n // A measurement that measured nothing must not report a pass.\n //\n // Every label joining to a `skipped` result — `--deterministic-only` over\n // ai-graded labels, a budget exhausted on the first trace — leaves `scored`\n // at 0, `agreement` at null, and each threshold reading \"0 of at most N, not\n // exceeded\". The gates are not lying; the denominator behind them is empty.\n // Saying so is the report's job, and exit 0 would say the opposite.\n const measuredNothing = scored.counts.scored === 0;\n if (measuredNothing) {\n warnings.push(\n `no labelled eval produced evidence: ${scored.counts.labels} label(s) all joined to a skipped result, ` +\n `so agreement and every --max-* threshold are computed over an empty denominator`,\n );\n }\n\n const report: CalibrationReport = {\n labelsFile,\n corpus,\n setting: baseline,\n counts: scored.counts,\n disagreements: scored.disagreements,\n unscored: scored.unscored,\n ...(sweep !== undefined ? { sweep } : {}),\n gates,\n batch: batch.report,\n warnings,\n // A calibration run is a measurement, not a gate: disagreement is the\n // finding, not a failure. Exit 1 only when a threshold was asked for and\n // missed, or when the measurement itself is incomplete — a trace lost, the\n // shared judge budget cut the corpus short, or nothing scored at all. An\n // incomplete number presented as a clean one is worse than no number.\n exitCode:\n gates.some((g) => g.exceeded) ||\n batch.report.summary.tracesErrored > 0 ||\n batch.report.budget !== undefined ||\n measuredNothing\n ? 1\n : 0,\n turns: batch.report.turns,\n durationMs: Date.now() - start,\n };\n\n const rendered = renderCalibration(report, options.format ?? \"pretty\", {\n color: options.color === true,\n });\n if (options.output) await writeFile(options.output, rendered, \"utf-8\");\n return { report, rendered };\n}\n\n/**\n * Every label must name a trace this run evaluates. Checked before the batch,\n * because the alternative is paying for a corpus and then being told the file\n * was misspelled.\n */\nfunction assertLabelsInCorpus(\n labels: EvalLabel[],\n corpus: string[],\n labelsFile: string,\n): void {\n const known = new Set(corpus);\n const missing = [...new Set(labels.filter((l) => !known.has(l.traceFile)).map((l) => l.trace))];\n if (missing.length === 0) return;\n throw new TracevalsError(\n `${labelsFile}: label(s) name trace(s) not in the corpus: ${missing.join(\", \")}. ` +\n `The corpus is: ${corpus.map((f) => basename(f)).join(\", \")}`,\n );\n}\n\n/**\n * A label that matched nothing is an error, not a quiet omission.\n *\n * It has to be: a typo'd eval id contributes to no count, so the report would\n * read as a cleaner corpus than it is — the same silent-nothing failure the\n * empty-selector rule exists to prevent (ADR 01018).\n */\nfunction assertNoUnmatched(\n unmatched: EvalLabel[],\n batch: { outcomes: { file: string; report?: { evalResults: EvalResult[] } }[] },\n labelsFile: string,\n): void {\n if (unmatched.length === 0) return;\n const lines = unmatched.map((label) => {\n const outcome = batch.outcomes.find((o) => o.file === label.traceFile);\n const results = outcome?.report?.evalResults ?? [];\n const sameArtifact = results.filter(\n (r) => r.artifactName === label.artifact,\n );\n const hint =\n sameArtifact.length > 0\n ? `that trace evaluated ${sameArtifact.map((r) => r.evalName).sort().join(\", \")} for \"${label.artifact}\"`\n : `that trace resolved no artifact named \"${label.artifact}\" — it evaluated ${\n [...new Set(results.map((r) => r.artifactName))].sort().join(\", \") ||\n \"nothing\"\n }`;\n return ` ${label.artifact} › ${label.eval} on ${basename(label.traceFile)} (${hint})`;\n });\n throw new TracevalsError(\n `${labelsFile}: label(s) matched no eval result:\\n${lines.join(\"\\n\")}`,\n );\n}\n\n/**\n * A label that omits `type:` over a name two artifact kinds both answer cannot\n * be resolved, and must not be guessed. Binding it to whichever result was\n * indexed last would score agreement against a verdict belonging to a\n * different artifact — the report would read as measured when it is wrong,\n * which is the one thing this command exists not to do.\n *\n * Operational, like `assertNoUnmatched`: the labels file is under-specified,\n * so this is exit 2 (bad input), not exit 1 (a check failed).\n */\nfunction assertNoAmbiguous(\n ambiguous: AmbiguousLabel[],\n labelsFile: string,\n): void {\n if (ambiguous.length === 0) return;\n const lines = ambiguous.map(({ label, candidates }) => {\n const kinds = [...new Set(candidates.map((r) => r.artifactType))].sort();\n return ` ${label.artifact} › ${label.eval} on ${basename(label.traceFile)} matches ${kinds.join(\" and \")} — add \\`type:\\` to say which`;\n });\n throw new TracevalsError(\n `${labelsFile}: label(s) name an artifact two kinds answer:\\n${lines.join(\"\\n\")}`,\n );\n}\n\nfunction buildGates(\n counts: { falsePass: number; falseFail: number; reviewVolume: number },\n limits: { falsePass?: number; falseFail?: number; review?: number },\n): CalibrationGate[] {\n const gates: CalibrationGate[] = [];\n const add = (\n name: CalibrationGate[\"name\"],\n limit: number | undefined,\n actual: number,\n ): void => {\n if (limit === undefined) return;\n gates.push({ name, limit, actual, exceeded: actual > limit });\n };\n add(\"falsePass\", limits.falsePass, counts.falsePass);\n add(\"falseFail\", limits.falseFail, counts.falseFail);\n add(\"review\", limits.review, counts.reviewVolume);\n return gates;\n}\n\n/**\n * One axis at a time, every other knob held at its configured value.\n *\n * A full cross-product would be dozens of rows and would answer a question\n * nobody asked; calibration is \"move one knob, re-run, recount\", and the\n * per-axis shape is what makes a row readable as *the effect of that knob*.\n */\nfunction buildSweep(\n input: Parameters<typeof score>[0],\n baseline: JudgeSetting,\n grid: { ensembleRuns: number[]; autoPass: number[]; autoFail: number[] },\n): SweepCell[] {\n const cell = (\n axis: SweepCell[\"axis\"],\n value: number,\n setting: JudgeSetting,\n ): SweepCell => ({\n axis,\n value,\n setting,\n counts: score(input, setting).counts,\n });\n\n const cells: SweepCell[] = [\n cell(\"baseline\", baseline.ensembleRuns, baseline),\n ];\n for (const runs of [...grid.ensembleRuns].sort((a, b) => a - b)) {\n cells.push(\n cell(\"ensembleRuns\", runs, { ensembleRuns: runs, zones: baseline.zones }),\n );\n }\n for (const autoPass of [...grid.autoPass].sort((a, b) => a - b)) {\n cells.push(\n cell(\"zones.autoPass\", autoPass, {\n ensembleRuns: baseline.ensembleRuns,\n zones: { autoPass, autoFail: baseline.zones.autoFail },\n }),\n );\n }\n for (const autoFail of [...grid.autoFail].sort((a, b) => a - b)) {\n cells.push(\n cell(\"zones.autoFail\", autoFail, {\n ensembleRuns: baseline.ensembleRuns,\n zones: { autoPass: baseline.zones.autoPass, autoFail },\n }),\n );\n }\n return cells;\n}\n\nexport type { ReportFormat };\n","/**\n * Joining labels to results, and re-scoring cached verdicts (ADR 01022).\n *\n * Everything here is pure: results and labels in, counts out. That is what\n * makes a sweep free. A judged eval's `ConsensusResult` carries the whole\n * `JudgeRun[]` it was built from, so a different zone threshold — or a smaller\n * ensemble — is a fresh call to `computeConsensus`/`zoneFor` over runs the\n * corpus already produced, not a fresh call to a provider.\n */\nimport { computeConsensus, zoneFor } from \"@hawkeyexl/inference\";\nimport type { BatchOutcome } from \"../aggregate.js\";\nimport type { EvalResult, Outcome } from \"../types.js\";\nimport { labelKey, type EvalLabel, type LabelOutcome } from \"./labels.js\";\nimport type {\n CalibrationCounts,\n Disagreement,\n JoinKind,\n JudgeSetting,\n} from \"./types.js\";\n\n/**\n * How one label met one result, over the full (expected × actual) grid.\n *\n * The order matters. `skipped` and `error` are tested *first*, so a labelled\n * eval that produced no evidence can never be counted as agreement — the same\n * rule that keeps an errored judge run from becoming a silent pass.\n */\nexport function classify(expected: LabelOutcome, actual: Outcome): JoinKind {\n if (actual === \"skipped\") return \"skipped\";\n if (actual === \"error\") return \"error\";\n if (actual === expected) return \"agree\";\n if (expected === \"fail\" && actual === \"pass\") return \"false-pass\";\n if (expected === \"pass\" && actual === \"fail\") return \"false-fail\";\n if (actual === \"needs-review\") return \"review\";\n return \"missed-review\";\n}\n\nexport interface JoinedLabel {\n label: EvalLabel;\n result: EvalResult;\n}\n\nexport interface JoinResult {\n joined: JoinedLabel[];\n /**\n * Labels that matched no result. Never silently dropped: a typo'd eval id\n * would otherwise deflate every count on the report by one.\n */\n unmatched: EvalLabel[];\n /**\n * Labels that omit `type:` over a name two artifact kinds both answer. Never\n * bound to one of them: a skill and the slash command that types it can share\n * a name in one trace, and picking whichever was indexed last would measure\n * agreement against a verdict belonging to something else — a false pass or\n * false fail reported with total confidence.\n */\n ambiguous: AmbiguousLabel[];\n}\n\n/** An untyped label and every result its name matched. */\nexport interface AmbiguousLabel {\n label: EvalLabel;\n candidates: EvalResult[];\n}\n\n/**\n * Match each label to the result its trace produced. The key is\n * `(trace file, artifact name, eval id)`, with the artifact *type* joining too\n * when the label states one — needed only for a skill and an agent sharing a\n * name, which is legal and does happen.\n */\nexport function joinLabels(\n outcomes: BatchOutcome[],\n labels: EvalLabel[],\n): JoinResult {\n // Typed keys are unique by construction. Untyped ones are not: two artifact\n // kinds may answer the same name, so keep every candidate rather than\n // letting the last one indexed overwrite the rest.\n const byType = new Map<string, EvalResult>();\n const byName = new Map<string, EvalResult[]>();\n for (const outcome of outcomes) {\n if (!(\"report\" in outcome)) continue;\n for (const result of outcome.report.evalResults) {\n byType.set(\n labelKey(\n outcome.file,\n result.artifactName,\n result.evalName,\n result.artifactType,\n ),\n result,\n );\n const key = labelKey(outcome.file, result.artifactName, result.evalName);\n const seen = byName.get(key);\n if (seen === undefined) byName.set(key, [result]);\n else seen.push(result);\n }\n }\n\n const joined: JoinedLabel[] = [];\n const unmatched: EvalLabel[] = [];\n const ambiguous: AmbiguousLabel[] = [];\n for (const label of labels) {\n if (label.type !== undefined) {\n const result = byType.get(\n labelKey(label.traceFile, label.artifact, label.eval, label.type),\n );\n if (result === undefined) unmatched.push(label);\n else joined.push({ label, result });\n continue;\n }\n const candidates =\n byName.get(labelKey(label.traceFile, label.artifact, label.eval)) ?? [];\n const kinds = new Set(candidates.map((r) => r.artifactType));\n if (candidates.length === 0) unmatched.push(label);\n else if (kinds.size > 1) ambiguous.push({ label, candidates });\n else joined.push({ label, result: candidates[0] as EvalResult });\n }\n return { joined, unmatched, ambiguous };\n}\n\nexport interface Rescored {\n outcome: Outcome;\n /** The cache held fewer runs than the setting asks for. */\n insufficient: boolean;\n /**\n * The arithmetic **at this setting**, not at the setting the corpus was\n * judged with. A sweep judges deeper than most of its cells, so reporting\n * the full cached ensemble beside a re-scored verdict would show a reader\n * evidence that does not add up to the outcome next to it.\n */\n consensus?: Disagreement[\"consensus\"];\n}\n\n/** The numbers a reader needs from a consensus, without the run bodies. */\nconst summarise = (\n consensus: NonNullable<EvalResult[\"consensus\"]>,\n): Disagreement[\"consensus\"] => ({\n votes: consensus.votes,\n agreement: consensus.agreement,\n meanConfidence: consensus.meanConfidence,\n runs: consensus.runs.length,\n});\n\n/**\n * What this result would have been at a different setting.\n *\n * A result with no `consensus` — deterministic, `human`, skipped, errored — is\n * returned untouched: zones and ensemble size do not reach it, and pretending\n * otherwise would make a sweep look like it moved things it cannot move.\n *\n * A smaller `ensembleRuns` re-scores the **first k** of the cached runs. That\n * is a sub-sample of the ensemble that was actually drawn, not a fresh k-run\n * ensemble; at temperature 0 the runs are independent samples of one prompt,\n * so it is an honest estimate, and the alternative is paying per grid cell.\n */\nexport function rescore(result: EvalResult, setting: JudgeSetting): Rescored {\n const consensus = result.consensus;\n if (consensus === undefined) {\n return { outcome: result.outcome, insufficient: false };\n }\n const runs = consensus.runs;\n if (runs.length < setting.ensembleRuns) {\n // Never scale up by guessing. Report the setting as unreachable and leave\n // the row at what it actually was.\n return { outcome: result.outcome, insufficient: true };\n }\n const slice = runs.slice(0, setting.ensembleRuns);\n const base = computeConsensus(slice);\n const zone = zoneFor(base, setting.zones);\n return {\n outcome:\n zone === \"auto-pass\" ? \"pass\" : zone === \"auto-fail\" ? \"fail\" : \"needs-review\",\n insufficient: false,\n consensus: {\n votes: base.votes,\n agreement: base.agreement,\n meanConfidence: base.meanConfidence,\n runs: slice.length,\n },\n };\n}\n\nconst emptyCounts = (): CalibrationCounts => ({\n labels: 0,\n scored: 0,\n agree: 0,\n falsePass: 0,\n falseFail: 0,\n review: 0,\n missedReview: 0,\n errored: 0,\n skipped: 0,\n reviewVolume: 0,\n insufficient: 0,\n agreement: null,\n});\n\nexport interface ScoreInput {\n joined: JoinedLabel[];\n /** Every eval result in the corpus, for the corpus-wide review volume. */\n all: EvalResult[];\n}\n\nexport interface ScoreResult {\n counts: CalibrationCounts;\n disagreements: Disagreement[];\n unscored: Disagreement[];\n}\n\n/**\n * Score the joined corpus, optionally at a setting other than the one it ran\n * at. Omit `setting` and the reported outcomes are used verbatim, so the\n * headline numbers of a plain `calibrate` are exactly the verdicts `run`\n * would have produced — no re-derivation standing between the two commands.\n */\nexport function score(input: ScoreInput, setting?: JudgeSetting): ScoreResult {\n const counts = emptyCounts();\n const disagreements: Disagreement[] = [];\n const unscored: Disagreement[] = [];\n\n for (const result of input.all) {\n const outcome =\n setting === undefined ? result.outcome : rescore(result, setting).outcome;\n if (outcome === \"needs-review\") counts.reviewVolume += 1;\n }\n\n for (const { label, result } of input.joined) {\n const scored: Rescored =\n setting === undefined\n ? { outcome: result.outcome, insufficient: false }\n : rescore(result, setting);\n if (scored.insufficient) counts.insufficient += 1;\n const kind = classify(label.expected, scored.outcome);\n counts.labels += 1;\n\n const row: Disagreement = {\n trace: label.traceFile,\n artifactType: result.artifactType,\n artifactName: result.artifactName,\n evalName: result.evalName,\n grader: result.grader,\n expected: label.expected,\n actual: scored.outcome,\n kind,\n ...(label.note !== undefined ? { note: label.note } : {}),\n ...(result.skipReason !== undefined\n ? { skipReason: result.skipReason }\n : {}),\n // At the setting being scored, falling back to the run's own numbers\n // when nothing was re-scored.\n ...(() => {\n const c =\n scored.consensus ??\n (result.consensus !== undefined ? summarise(result.consensus) : undefined);\n return c !== undefined ? { consensus: c } : {};\n })(),\n };\n\n switch (kind) {\n case \"skipped\":\n counts.skipped += 1;\n unscored.push(row);\n break;\n case \"agree\":\n counts.agree += 1;\n counts.scored += 1;\n break;\n case \"false-pass\":\n counts.falsePass += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"false-fail\":\n counts.falseFail += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"review\":\n counts.review += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"missed-review\":\n counts.missedReview += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n case \"error\":\n counts.errored += 1;\n counts.scored += 1;\n disagreements.push(row);\n break;\n }\n }\n\n counts.agreement = counts.scored > 0 ? counts.agree / counts.scored : null;\n // Byte comparison, so the ubuntu and windows legs order a report the same\n // way and two runs over one corpus stay diffable.\n const byRow = (a: Disagreement, b: Disagreement): number => {\n const ka = `${a.kind} ${a.trace} ${a.artifactName} ${a.evalName}`;\n const kb = `${b.kind} ${b.trace} ${b.artifactName} ${b.evalName}`;\n return ka < kb ? -1 : ka > kb ? 1 : 0;\n };\n disagreements.sort(byRow);\n unscored.sort(byRow);\n return { counts, disagreements, unscored };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/shared/errors.ts
|
|
9
|
+
var ToolError = class extends Error {
|
|
10
|
+
/** clig.dev: 2 is operational/usage; 1 is reserved for findings. */
|
|
11
|
+
exitCode = 2;
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "ToolError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function errorMessage(err) {
|
|
18
|
+
if (err instanceof Error) return err.message;
|
|
19
|
+
if (typeof err === "string") return err;
|
|
20
|
+
if (typeof err === "number" || typeof err === "bigint") return String(err);
|
|
21
|
+
if (typeof err === "object" && err !== null && "message" in err && typeof err.message === "string") {
|
|
22
|
+
return err.message;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const json = JSON.stringify(err);
|
|
26
|
+
if (json !== void 0) return json;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return String(err);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
__export,
|
|
34
|
+
ToolError,
|
|
35
|
+
errorMessage
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-IOYXPBCL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/errors.ts"],"sourcesContent":["/**\n * The error every tool under the umbrella throws for an operational failure.\n *\n * Each tool keeps its own subclass (`DocmetaError`, …) so its tests and\n * library callers can match on it. The base is what the shared bin runner\n * matches on: a `ToolError` is reported as one line and exit 2, anything else\n * is a bug and keeps its stack trace.\n */\nexport class ToolError extends Error {\n /** clig.dev: 2 is operational/usage; 1 is reserved for findings. */\n readonly exitCode: number = 2;\n\n constructor(message: string) {\n super(message);\n this.name = \"ToolError\";\n }\n}\n\n/**\n * The message a caught value carries, whatever was thrown.\n *\n * JavaScript can throw anything, so `(err as Error).message` is `undefined`\n * for a thrown string, number or `null`, and \"undefined\" is what the user\n * reads. An Error, or anything with a string `message`, gives that message.\n * A string is its own message, and so is a number or a bigint, as `String()`\n * writes it: JSON would turn `NaN` and the infinities into `null`. Anything\n * else gives its JSON text, or its `String()` form when it will not\n * serialise.\n */\nexport function errorMessage(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === \"string\") return err;\n // Before the JSON step, which writes NaN and the infinities as \"null\" and\n // throws on a bigint.\n if (typeof err === \"number\" || typeof err === \"bigint\") return String(err);\n if (\n typeof err === \"object\" &&\n err !== null &&\n \"message\" in err &&\n typeof err.message === \"string\"\n ) {\n return err.message;\n }\n try {\n // Typed as returning a string, but `undefined`, a function or a symbol\n // gives `undefined`.\n const json = JSON.stringify(err) as string | undefined;\n if (json !== undefined) return json;\n } catch {\n // A cycle, or a bigint inside an object: fall through to the one form\n // that always exists.\n }\n return String(err);\n}\n"],"mappings":";;;;;;;;AAQO,IAAM,YAAN,cAAwB,MAAM;AAAA;AAAA,EAE1B,WAAmB;AAAA,EAE5B,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAaO,SAAS,aAAa,KAAsB;AACjD,MAAI,eAAe,MAAO,QAAO,IAAI;AACrC,MAAI,OAAO,QAAQ,SAAU,QAAO;AAGpC,MAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,SAAU,QAAO,OAAO,GAAG;AACzE,MACE,OAAO,QAAQ,YACf,QAAQ,QACR,aAAa,OACb,OAAO,IAAI,YAAY,UACvB;AACA,WAAO,IAAI;AAAA,EACb;AACA,MAAI;AAGF,UAAM,OAAO,KAAK,UAAU,GAAG;AAC/B,QAAI,SAAS,OAAW,QAAO;AAAA,EACjC,QAAQ;AAAA,EAGR;AACA,SAAO,OAAO,GAAG;AACnB;","names":[]}
|