@forwardimpact/libharness 1.4.0 → 2.0.0
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/bin/fit-trace.js +10 -0
- package/package.json +1 -1
- package/src/benchmark/grade.js +222 -0
- package/src/benchmark/hidden-tests.js +180 -0
- package/src/benchmark/invariants.js +9 -10
- package/src/benchmark/judge.js +9 -6
- package/src/benchmark/report.js +141 -55
- package/src/benchmark/result.js +43 -8
- package/src/benchmark/runner.js +99 -109
- package/src/benchmark/task-family.js +139 -26
- package/src/benchmark/trace-split.js +73 -0
- package/src/benchmark/workdir.js +6 -1
- package/src/commands/assert.js +72 -0
- package/src/commands/benchmark-definition.js +6 -6
- package/src/commands/benchmark-grade.js +82 -0
- package/src/commands/benchmark-invariants.js +0 -73
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fit-benchmark grade` — run both check-row producers (the hidden test
|
|
3
|
+
* suite and the invariants script) against a post-run workdir directory and
|
|
4
|
+
* grade the merged rows with the same derivation the benchmark runner uses.
|
|
5
|
+
* No agent and no judge run, so authors validate a task's grading material
|
|
6
|
+
* against fixtures without paying for agent sessions; the process exit
|
|
7
|
+
* mirrors the graded verdict.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
import { validateGradeRecord } from "../benchmark/result.js";
|
|
13
|
+
import { runInvariants } from "../benchmark/invariants.js";
|
|
14
|
+
import { runHiddenTests } from "../benchmark/hidden-tests.js";
|
|
15
|
+
import { runProducersAndGrade } from "../benchmark/grade.js";
|
|
16
|
+
import { loadTaskFamily } from "../benchmark/task-family.js";
|
|
17
|
+
import { probeFreePort } from "../benchmark/workdir.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {import("@forwardimpact/libcli").InvocationContext} ctx
|
|
21
|
+
* @returns {Promise<{ok: true} | {ok: false, code: number, error: string}>}
|
|
22
|
+
*/
|
|
23
|
+
export async function runBenchmarkGradeCommand(ctx) {
|
|
24
|
+
const values = ctx.options;
|
|
25
|
+
const runtime = ctx.deps.runtime;
|
|
26
|
+
const familyInput = values.family;
|
|
27
|
+
if (!familyInput)
|
|
28
|
+
return { ok: false, code: 1, error: "--family is required" };
|
|
29
|
+
const taskId = values.task;
|
|
30
|
+
if (!taskId) return { ok: false, code: 1, error: "--task is required" };
|
|
31
|
+
const runDirArg = values["run-dir"];
|
|
32
|
+
if (!runDirArg) return { ok: false, code: 1, error: "--run-dir is required" };
|
|
33
|
+
|
|
34
|
+
const family = await loadTaskFamily(familyInput, runtime);
|
|
35
|
+
const task = family.tasks().find((t) => t.id === taskId);
|
|
36
|
+
if (!task)
|
|
37
|
+
return { ok: false, code: 1, error: `task not found in family: ${taskId}` };
|
|
38
|
+
|
|
39
|
+
const runDir = resolve(runDirArg);
|
|
40
|
+
const cwd = join(runDir, "cwd");
|
|
41
|
+
const port = await probeFreePort();
|
|
42
|
+
const cellCtx = { cwd, port, runDir, familyDir: family.rootPath };
|
|
43
|
+
|
|
44
|
+
const { invariants, hiddenRows, engineError, healthy, grade } =
|
|
45
|
+
await runProducersAndGrade(task, cellCtx, runtime, {
|
|
46
|
+
runInvariants,
|
|
47
|
+
runHiddenTests,
|
|
48
|
+
});
|
|
49
|
+
// Same effective-score rule as the runner, minus the judge (none runs
|
|
50
|
+
// here): an unhealthy grader or a failing gate zeroes the score, so a
|
|
51
|
+
// crashed hook can never mint marks from the rows it emitted before dying.
|
|
52
|
+
// Unlike a runner record — where `grade.score` stays the raw fraction and
|
|
53
|
+
// the zeroing lands on the top-level `score` — this record has no second
|
|
54
|
+
// field, so `grade.score` carries the effective value here.
|
|
55
|
+
if (grade.score !== undefined && !(healthy && grade.gatesPass)) {
|
|
56
|
+
grade.score = 0;
|
|
57
|
+
}
|
|
58
|
+
const record = {
|
|
59
|
+
taskId: task.id,
|
|
60
|
+
grade,
|
|
61
|
+
invariants,
|
|
62
|
+
...(task.tests && {
|
|
63
|
+
hiddenTests: {
|
|
64
|
+
details: hiddenRows,
|
|
65
|
+
...(engineError && { error: engineError.message }),
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
// Mirrors the script for diagnosis; the graded verdict drives the exit.
|
|
69
|
+
exitCode: invariants.exitCode,
|
|
70
|
+
};
|
|
71
|
+
validateGradeRecord(record);
|
|
72
|
+
|
|
73
|
+
const line = JSON.stringify(record) + "\n";
|
|
74
|
+
if (values.output) {
|
|
75
|
+
runtime.fsSync.writeFileSync(resolve(values.output), line);
|
|
76
|
+
} else {
|
|
77
|
+
runtime.proc.stdout.write(line);
|
|
78
|
+
}
|
|
79
|
+
return grade.verdict === "pass"
|
|
80
|
+
? { ok: true }
|
|
81
|
+
: { ok: false, code: 1, error: "" };
|
|
82
|
+
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `fit-benchmark invariants` — check a single task's invariants against a
|
|
3
|
-
* post-run workdir directory without invoking an agent. Useful for
|
|
4
|
-
* re-checking an agent's output against revised grading material.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { join, resolve } from "node:path";
|
|
8
|
-
import { createServer } from "node:net";
|
|
9
|
-
|
|
10
|
-
import { validateInvariantsRecord } from "../benchmark/result.js";
|
|
11
|
-
import { runInvariants } from "../benchmark/invariants.js";
|
|
12
|
-
import { loadTaskFamily } from "../benchmark/task-family.js";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {import("@forwardimpact/libcli").InvocationContext} ctx
|
|
16
|
-
* @returns {Promise<{ok: true} | {ok: false, code: number, error: string}>}
|
|
17
|
-
*/
|
|
18
|
-
export async function runBenchmarkInvariantsCommand(ctx) {
|
|
19
|
-
const values = ctx.options;
|
|
20
|
-
const runtime = ctx.deps.runtime;
|
|
21
|
-
const familyInput = values.family;
|
|
22
|
-
if (!familyInput)
|
|
23
|
-
return { ok: false, code: 1, error: "--family is required" };
|
|
24
|
-
const taskId = values.task;
|
|
25
|
-
if (!taskId) return { ok: false, code: 1, error: "--task is required" };
|
|
26
|
-
const runDirArg = values["run-dir"];
|
|
27
|
-
if (!runDirArg) return { ok: false, code: 1, error: "--run-dir is required" };
|
|
28
|
-
|
|
29
|
-
const family = await loadTaskFamily(familyInput, runtime);
|
|
30
|
-
const task = family.tasks().find((t) => t.id === taskId);
|
|
31
|
-
if (!task)
|
|
32
|
-
return { ok: false, code: 1, error: `task not found in family: ${taskId}` };
|
|
33
|
-
|
|
34
|
-
const runDir = resolve(runDirArg);
|
|
35
|
-
const cwd = join(runDir, "cwd");
|
|
36
|
-
const port = await allocatePort();
|
|
37
|
-
|
|
38
|
-
const invariants = await runInvariants(task, { cwd, port, runDir }, runtime);
|
|
39
|
-
const record = {
|
|
40
|
-
taskId: task.id,
|
|
41
|
-
invariants,
|
|
42
|
-
exitCode: invariants.exitCode,
|
|
43
|
-
};
|
|
44
|
-
validateInvariantsRecord(record);
|
|
45
|
-
|
|
46
|
-
const line = JSON.stringify(record) + "\n";
|
|
47
|
-
if (values.output) {
|
|
48
|
-
runtime.fsSync.writeFileSync(resolve(values.output), line);
|
|
49
|
-
} else {
|
|
50
|
-
runtime.proc.stdout.write(line);
|
|
51
|
-
}
|
|
52
|
-
return invariants.verdict === "pass"
|
|
53
|
-
? { ok: true }
|
|
54
|
-
: { ok: false, code: 1, error: "" };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function allocatePort() {
|
|
58
|
-
return new Promise((res, rej) => {
|
|
59
|
-
const server = createServer();
|
|
60
|
-
server.unref();
|
|
61
|
-
server.on("error", rej);
|
|
62
|
-
server.listen(0, "127.0.0.1", () => {
|
|
63
|
-
const addr = server.address();
|
|
64
|
-
if (!addr || typeof addr === "string") {
|
|
65
|
-
server.close();
|
|
66
|
-
rej(new Error("failed to allocate port"));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const port = addr.port;
|
|
70
|
-
server.close(() => res(port));
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
}
|