@forwardimpact/libharness 3.0.1 → 3.0.2
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/package.json +1 -1
- package/src/benchmark/hidden-tests.js +49 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/libharness",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Autonomous agent team harness — coordinate a lead and participant agents in one async session, with eval, benchmark, and trace tooling to prove the changes worked.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orchestration",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hidden-test engine — runs a task's `tests/` overlay against the post-run
|
|
3
3
|
* agent CWD. The engine stages each file at its mirrored path. It runs each
|
|
4
|
-
* check with `
|
|
4
|
+
* check with `bun test`. It converts the exit status into one check row.
|
|
5
5
|
* It restores the tree, so the judge sees the workdir exactly as the agent
|
|
6
6
|
* left it.
|
|
7
7
|
*
|
|
@@ -69,7 +69,25 @@ async function runOneCheck(task, ctx, runtime, timeoutMs, check) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* Reads a child pipe to a string. Returns what it read when the stream tears
|
|
73
|
+
* down mid-read, because a pipe that closes as the child exits is a race, not
|
|
74
|
+
* a check failure. The caller reads the exit code for the verdict.
|
|
75
|
+
*
|
|
76
|
+
* @param {import("node:stream").Readable} stream - Child stdout or stderr.
|
|
77
|
+
* @returns {Promise<string>} Everything read before the stream ended.
|
|
78
|
+
*/
|
|
79
|
+
async function drainQuietly(stream) {
|
|
80
|
+
let out = "";
|
|
81
|
+
try {
|
|
82
|
+
for await (const chunk of stream) out += chunk.toString();
|
|
83
|
+
} catch {
|
|
84
|
+
// The stream closed under us. Keep what arrived.
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Spawn `bun test <staged path>` from the agent CWD under the hook env
|
|
73
91
|
* and map the exit status onto one row. The clock timer SIGKILLs a child
|
|
74
92
|
* that outlives the per-check budget. The row then fails with a timeout
|
|
75
93
|
* message.
|
|
@@ -83,27 +101,40 @@ async function spawnCheck(task, ctx, runtime, timeoutMs, check) {
|
|
|
83
101
|
hooksDir: task.paths.hooks,
|
|
84
102
|
familyDir: ctx.familyDir,
|
|
85
103
|
});
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
// `bun test` sets no test-context variable that a nested run inherits, so
|
|
105
|
+
// a child reports its own exit status even when the harness itself runs
|
|
106
|
+
// under a test runner. That removes the `NODE_TEST_CONTEXT` scrub the
|
|
107
|
+
// `node --test` engine needed to stop a failing check minting a passing
|
|
108
|
+
// row. The "fractional score" grade test is the standing guard: it fails
|
|
109
|
+
// if a failing check ever reports success again.
|
|
110
|
+
//
|
|
111
|
+
// Pass the ABSOLUTE staged path. `bun test` reads its argument as a
|
|
112
|
+
// substring filter over discovered paths, not as one file, so the relative
|
|
113
|
+
// `app/test/x.test.js` also matches an agent-authored `sub/app/test/
|
|
114
|
+
// x.test.js` and folds that file's result into this check's row. An
|
|
115
|
+
// absolute path cannot be a substring of a deeper path, so it selects
|
|
116
|
+
// exactly the staged file. One `*.test.js` stays one check.
|
|
117
|
+
const child = runtime.subprocess.spawn(
|
|
118
|
+
"bun",
|
|
119
|
+
["test", join(ctx.cwd, check.stagePath)],
|
|
120
|
+
{
|
|
121
|
+
cwd: ctx.cwd,
|
|
122
|
+
env,
|
|
123
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
124
|
+
},
|
|
125
|
+
);
|
|
95
126
|
let timedOut = false;
|
|
96
127
|
const timer = runtime.clock.setTimeout(() => {
|
|
97
128
|
timedOut = true;
|
|
98
129
|
child.kill("SIGKILL");
|
|
99
130
|
}, timeoutMs);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
// Both pipes drain only so a chatty child never blocks on a full pipe.
|
|
132
|
+
// A child that exits while its pipe is still open makes the async iterator
|
|
133
|
+
// reject with ERR_STREAM_PREMATURE_CLOSE on some runtimes. That teardown
|
|
134
|
+
// race is not a check result, so it must not throw out of the check. The
|
|
135
|
+
// exit code below is the verdict.
|
|
136
|
+
const drainStdout = drainQuietly(child.stdout);
|
|
137
|
+
const stderr = await drainQuietly(child.stderr);
|
|
107
138
|
await drainStdout;
|
|
108
139
|
const exit = await child.exitCode;
|
|
109
140
|
runtime.clock.clearTimeout(timer);
|