@tachikomagundam/abathur 0.2.6 → 0.2.7
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 +3 -3
- package/dist/cli.js +4 -0
- package/dist/commands/readjudicate.js +28 -0
- package/dist/commands/retract.js +53 -0
- package/dist/commands/tombstone.js +28 -15
- package/dist/core/evolve/run-rows.js +14 -0
- package/dist/core/ledger.js +2 -0
- package/dist/core/promote.js +61 -46
- package/dist/core/readjudicate.js +247 -0
- package/dist/core/retract.js +166 -0
- package/dist/core/worktree.js +1 -1
- package/dist/genomes/toy-smoke/init.mjs +1 -1
- package/dist/test/bundle.test.js +1 -1
- package/dist/test/fixture-loop.test.js +1 -1
- package/dist/test/fixtures-self.js +1 -1
- package/dist/test/fixtures-wt.js +1 -1
- package/dist/test/friction.test.js +3 -3
- package/dist/test/graft.test.js +1 -1
- package/dist/test/historian-run-scenario.test.js +2 -2
- package/dist/test/promote.test.js +222 -5
- package/dist/test/readjudicate.test.js +212 -0
- package/dist/test/reflect.test.js +1 -1
- package/dist/test/repopath-seams.test.js +1 -0
- package/dist/test/seam-gates.test.js +6 -0
- package/dist/test/self-snapshot.test.js +2 -2
- package/graders/historian/judge-poststage.mjs +8 -3
- package/package.json +1 -1
- package/plugin/abathur-command.md +3 -3
- package/plugin/abathur.ts +6 -6
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// re-adjudicate (core) tests. The mechanism under test: replay the CURRENT gate over
|
|
2
|
+
// archived generation rows and append a corrected verdict with provenance, files immutable.
|
|
3
|
+
//
|
|
4
|
+
// Coverage: verdict flip culled->nominated (synthetic), honest no-flip (culled stays
|
|
5
|
+
// culled), idempotence after append, promote-row refusal, missing-incumbent refusal,
|
|
6
|
+
// corrupt-archive notice (never a crash), and the historical anchor: a sandboxed COPY of
|
|
7
|
+
// the real historian archives must reproduce the c17 re-adjudication (NOMINATED,
|
|
8
|
+
// gain 0.2917) that the driver computed offline on 2026-09-26 — the engine's own replay
|
|
9
|
+
// of the incident that born this command.
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, copyFileSync } from "node:fs";
|
|
13
|
+
import { mkdtempSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { readjudicateGeneration } from "../core/readjudicate.js";
|
|
17
|
+
import { ledgerRecordSchema, LEDGER_KIND_GENERATION_COMPLETE, LEDGER_KIND_PROMOTE } from "../core/ledger.js";
|
|
18
|
+
import { decodeGenerationRecord } from "../core/evolve/run-rows.js";
|
|
19
|
+
const STATS = { halfWidth: 0.15, minEffect: 0.1, nReps: { initial: 2, max: 3 } };
|
|
20
|
+
const BUDGET = { maxCandidates: 1, maxModelCalls: 96, maxTokens: 25_000_000, maxWallS: 26_000 };
|
|
21
|
+
function entryFor(repo) {
|
|
22
|
+
return {
|
|
23
|
+
fingerprint: "f".repeat(16),
|
|
24
|
+
label: "test-genome",
|
|
25
|
+
registryFile: "/dev/null",
|
|
26
|
+
storedText: "",
|
|
27
|
+
spec: {
|
|
28
|
+
label: "test-genome",
|
|
29
|
+
repoPath: repo,
|
|
30
|
+
bench: {
|
|
31
|
+
type: "opencode-fixture-scenarios",
|
|
32
|
+
units: [
|
|
33
|
+
{ id: "scenario-19", split: "train", path: "scenarios/19.md" },
|
|
34
|
+
{ id: "scenario-13", split: "val", path: "scenarios/13.md" },
|
|
35
|
+
],
|
|
36
|
+
runCommand: "true",
|
|
37
|
+
graderCommand: "true",
|
|
38
|
+
timeoutS: 1800,
|
|
39
|
+
stats: STATS,
|
|
40
|
+
},
|
|
41
|
+
budget: BUDGET,
|
|
42
|
+
kernel: { immutableGlobs: ["scenarios/**"] },
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function row(o) {
|
|
47
|
+
const data = {
|
|
48
|
+
source: o.source,
|
|
49
|
+
headCommit: "1".repeat(40),
|
|
50
|
+
complete: true,
|
|
51
|
+
reps: o.units[0]?.scores.length ?? 0,
|
|
52
|
+
units: o.units.map((u) => ({ unitId: u.unitId, split: u.split, scores: u.scores, runIds: u.scores.map((_, i) => `r-${u.unitId}-${String(i)}`), failures: u.scores.filter((s) => s < 1).map((s) => `${u.unitId}: scored ${String(s)}`) })),
|
|
53
|
+
counters: { candidates: o.source === "candidate" ? 1 : 0, modelCalls: 10, tokens: 1000, wallS: 100 },
|
|
54
|
+
manifest: [],
|
|
55
|
+
benchProvenance: { benchType: "opencode-fixture-scenarios", versions: [{ bin: "opencode", version: "test" }] },
|
|
56
|
+
};
|
|
57
|
+
if (o.source === "candidate") {
|
|
58
|
+
data.candidateId = "mutate-live";
|
|
59
|
+
data.commitSha = "2".repeat(40);
|
|
60
|
+
data.treeSha = o.treeSha ?? "3".repeat(64);
|
|
61
|
+
}
|
|
62
|
+
if (o.verdict !== undefined)
|
|
63
|
+
data.verdict = o.verdict;
|
|
64
|
+
if (o.gain !== undefined)
|
|
65
|
+
data.gain = o.gain;
|
|
66
|
+
return `${JSON.stringify({ v: 1, ts: o.ts, kind: LEDGER_KIND_GENERATION_COMPLETE, genId: o.genId, runId: o.runId, data })}\n`;
|
|
67
|
+
}
|
|
68
|
+
function sandbox(t) {
|
|
69
|
+
const dir = mkdtempSync(path.join(tmpdir(), "readjud-"));
|
|
70
|
+
const state = path.join(dir, ".state", "abathur");
|
|
71
|
+
mkdirSync(state, { recursive: true });
|
|
72
|
+
writeFileSync(path.join(state, "ledger.jsonl"), "");
|
|
73
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
74
|
+
return { dir, state };
|
|
75
|
+
}
|
|
76
|
+
function cfg(t) {
|
|
77
|
+
const d = mkdtempSync(path.join(tmpdir(), "readjud-cfg-"));
|
|
78
|
+
t.after(() => rmSync(d, { recursive: true, force: true }));
|
|
79
|
+
return d;
|
|
80
|
+
}
|
|
81
|
+
function lastRows(stateDir) {
|
|
82
|
+
return readFileSync(path.join(stateDir, "ledger.jsonl"), "utf8")
|
|
83
|
+
.split("\n")
|
|
84
|
+
.filter((l) => l.trim().length > 0)
|
|
85
|
+
.map((l) => {
|
|
86
|
+
const parsed = ledgerRecordSchema.parse(JSON.parse(l));
|
|
87
|
+
return parsed;
|
|
88
|
+
})
|
|
89
|
+
.filter((r) => r.kind === LEDGER_KIND_GENERATION_COMPLETE)
|
|
90
|
+
.map((r) => {
|
|
91
|
+
try {
|
|
92
|
+
return decodeGenerationRecord(r);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
.filter((d) => d !== null);
|
|
99
|
+
}
|
|
100
|
+
test("flip: culled row re-judged nominated by the current gate; corrected row appended with provenance", (t) => {
|
|
101
|
+
const { dir, state } = sandbox(t);
|
|
102
|
+
// one rotated archive with the whole epoch (activation law: bank needs an archive)
|
|
103
|
+
const lines = [
|
|
104
|
+
// historical incumbent groups that price a small-ish sigma on both units
|
|
105
|
+
row({ genId: "g-old-a", runId: "r-old", ts: "2026-09-20T00:00:00.000Z", source: "incumbent", units: [{ unitId: "scenario-19", split: "train", scores: [0.875, 1, 0.875, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1, 1, 1] }] }),
|
|
106
|
+
// the epoch under re-adjudication
|
|
107
|
+
row({ genId: "g-epoch", runId: "run-1", ts: "2026-09-21T00:00:00.000Z", source: "incumbent", units: [{ unitId: "scenario-19", split: "train", scores: [0.75, 0.875] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }], verdict: undefined }),
|
|
108
|
+
row({ genId: "g-epoch-cand", runId: "run-1", ts: "2026-09-21T01:00:00.000Z", source: "candidate", units: [{ unitId: "scenario-19", split: "train", scores: [1, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }], verdict: "culled", gain: 0.1 }),
|
|
109
|
+
];
|
|
110
|
+
writeFileSync(path.join(state, "ledger.campaign1-2026-09-21.jsonl"), lines.join(""));
|
|
111
|
+
const out = readjudicateGeneration({ entry: entryFor(dir), configDir: cfg(t), genId: "g-epoch-cand", now: () => new Date("2026-09-22T00:00:00Z") });
|
|
112
|
+
assert.equal(out.appended, true, out.lines.join("\n"));
|
|
113
|
+
assert.equal(out.verdict, "nominated", out.lines.join("\n"));
|
|
114
|
+
const appended = lastRows(state).at(-1);
|
|
115
|
+
assert.ok(appended !== undefined);
|
|
116
|
+
assert.equal(appended.verdict, "nominated");
|
|
117
|
+
assert.ok(appended.readjudication !== undefined, "provenance must be present");
|
|
118
|
+
assert.equal(appended.readjudication.replacedVerdict, "culled");
|
|
119
|
+
assert.equal(appended.readjudication.gate, "acceptance");
|
|
120
|
+
assert.equal(appended.readjudication.sourceRows.candidate.file, "ledger.campaign1-2026-09-21.jsonl");
|
|
121
|
+
// archive untouched: still exactly 3 rows, original culled row intact
|
|
122
|
+
assert.equal(readFileSync(path.join(state, "ledger.campaign1-2026-09-21.jsonl"), "utf8").trim().split("\n").length, 3);
|
|
123
|
+
// idempotence: second pass sees the corrected last row -> no-op
|
|
124
|
+
const again = readjudicateGeneration({ entry: entryFor(dir), configDir: cfg(t), genId: "g-epoch-cand", now: () => new Date("2026-09-22T00:00:00Z") });
|
|
125
|
+
assert.equal(again.appended, false, again.lines.join("\n"));
|
|
126
|
+
assert.match(again.lines.join("\n"), /no-op/);
|
|
127
|
+
});
|
|
128
|
+
test("honest replay: a weak candidate stays culled — the tool re-judges, it never flips", (t) => {
|
|
129
|
+
const { dir, state } = sandbox(t);
|
|
130
|
+
const lines = [
|
|
131
|
+
row({ genId: "g-old-a", runId: "r-old", ts: "2026-09-20T00:00:00.000Z", source: "incumbent", units: [{ unitId: "scenario-19", split: "train", scores: [1, 1, 1, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1, 1, 1] }] }),
|
|
132
|
+
row({ genId: "g-epoch", runId: "run-1", ts: "2026-09-21T00:00:00.000Z", source: "incumbent", units: [{ unitId: "scenario-19", split: "train", scores: [1, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }] }),
|
|
133
|
+
row({ genId: "g-epoch-cand", runId: "run-1", ts: "2026-09-21T01:00:00.000Z", source: "candidate", units: [{ unitId: "scenario-19", split: "train", scores: [1, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }], verdict: "culled", gain: 0 }),
|
|
134
|
+
];
|
|
135
|
+
writeFileSync(path.join(state, "ledger.campaign1-2026-09-21.jsonl"), lines.join(""));
|
|
136
|
+
const out = readjudicateGeneration({ entry: entryFor(dir), configDir: cfg(t), genId: "g-epoch-cand", now: () => new Date() });
|
|
137
|
+
assert.equal(out.verdict, "culled", out.lines.join("\n"));
|
|
138
|
+
assert.equal(out.appended, false, "already-agreeing row must not append");
|
|
139
|
+
});
|
|
140
|
+
test("refusals: promoted gen, missing incumbent, unknown gen — all block before any append", (t) => {
|
|
141
|
+
const { dir, state } = sandbox(t);
|
|
142
|
+
const epoch = [
|
|
143
|
+
row({ genId: "g-epoch", runId: "run-9", ts: "2026-09-21T00:00:00.000Z", source: "incumbent", units: [{ unitId: "scenario-19", split: "train", scores: [0.75, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }] }),
|
|
144
|
+
row({ genId: "g-epoch-cand", runId: "run-9", ts: "2026-09-21T01:00:00.000Z", source: "candidate", units: [{ unitId: "scenario-19", split: "train", scores: [1, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }], verdict: "culled", gain: 0.1 }),
|
|
145
|
+
];
|
|
146
|
+
writeFileSync(path.join(state, "ledger.campaign1-2026-09-21.jsonl"), epoch.join(""));
|
|
147
|
+
// promoted epoch is untouchable
|
|
148
|
+
const promotedRun = "run-p";
|
|
149
|
+
writeFileSync(path.join(state, "ledger.campaign2-2026-09-22.jsonl"), epoch.join("") + `${JSON.stringify({ v: 1, ts: "2026-09-22T00:00:00.000Z", kind: LEDGER_KIND_PROMOTE, genId: "g-epoch-cand", runId: promotedRun, data: { actor: "cli", genId: "g-epoch-cand", from: null, to: "2".repeat(40) } })}\n`);
|
|
150
|
+
assert.throws(() => readjudicateGeneration({ entry: entryFor(dir), configDir: cfg(t), genId: "g-epoch-cand", now: () => new Date() }), (e) => /already promoted/.test(String(e.message ?? e)));
|
|
151
|
+
// unknown gen
|
|
152
|
+
assert.throws(() => readjudicateGeneration({ entry: entryFor(dir), configDir: cfg(t), genId: "g-nope", now: () => new Date() }), (e) => /no candidate generation_complete row/.test(String(e.message ?? e)));
|
|
153
|
+
// candidate without its incumbent: run-1 incumbent exists here, so cut a state with candidate only
|
|
154
|
+
const { dir: d2, state: s2 } = sandbox(t);
|
|
155
|
+
writeFileSync(path.join(s2, "ledger.campaign1-2026-09-21.jsonl"), row({ genId: "g-solo", runId: "run-s", ts: "2026-09-21T01:00:00.000Z", source: "candidate", units: [{ unitId: "scenario-19", split: "train", scores: [1, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }], verdict: "culled", gain: 0.1 }));
|
|
156
|
+
assert.throws(() => readjudicateGeneration({ entry: entryFor(d2), configDir: cfg(t), genId: "g-solo", now: () => new Date() }), (e) => /no incumbent baseline row/.test(String(e.message ?? e)));
|
|
157
|
+
});
|
|
158
|
+
test("corrupt archive lines are notices, never a crash, and never poison the replay", (t) => {
|
|
159
|
+
const { dir, state } = sandbox(t);
|
|
160
|
+
const good = [
|
|
161
|
+
row({ genId: "g-old-a", runId: "r-old", ts: "2026-09-20T00:00:00.000Z", source: "incumbent", units: [{ unitId: "scenario-19", split: "train", scores: [0.875, 1, 0.875, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1, 1, 1] }] }),
|
|
162
|
+
row({ genId: "g-epoch", runId: "run-1", ts: "2026-09-21T00:00:00.000Z", source: "incumbent", units: [{ unitId: "scenario-19", split: "train", scores: [0.75, 0.875] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }] }),
|
|
163
|
+
row({ genId: "g-epoch-cand", runId: "run-1", ts: "2026-09-21T01:00:00.000Z", source: "candidate", units: [{ unitId: "scenario-19", split: "train", scores: [1, 1] }, { unitId: "scenario-13", split: "val", scores: [1, 1] }], verdict: "culled", gain: 0.1 }),
|
|
164
|
+
];
|
|
165
|
+
writeFileSync(path.join(state, "ledger.campaign1-2026-09-21.jsonl"), `not json at all\n${good.join("")}{"v":9,"kind":"broken-schema"}\n`);
|
|
166
|
+
const out = readjudicateGeneration({ entry: entryFor(dir), configDir: cfg(t), genId: "g-epoch-cand", now: () => new Date("2026-09-22T00:00:00Z") });
|
|
167
|
+
assert.equal(out.verdict, "nominated", out.lines.join("\n"));
|
|
168
|
+
assert.ok(out.lines.some((l) => l.includes("not JSON")), "skip notice must surface");
|
|
169
|
+
});
|
|
170
|
+
// ------------------------------------------------------------------ historical anchor
|
|
171
|
+
// The incident replay: c17's real rows, judged first by the silently-downgraded engine
|
|
172
|
+
// (culled), then re-adjudicated offline to NOMINATED gain 0.2917 (start-work L141).
|
|
173
|
+
// A sandboxed copy of the real archives run through this command must reproduce that
|
|
174
|
+
// verdict with the same numbers — the engine re-judging its own history correctly.
|
|
175
|
+
// Skips (never fails) when the historian state files are absent (CI elsewhere).
|
|
176
|
+
test("anchor: real c17 archives replay to NOMINATED 0.2917 (copy, no mutation)", (t) => {
|
|
177
|
+
// located via env, never a hardcoded homedir (release hygiene); skipped wherever the
|
|
178
|
+
// historian repo is not wired — this anchor guards the OPERATOR's machine, CI has no history.
|
|
179
|
+
const hist = process.env["ABATHUR_HISTORIAN_REPO"];
|
|
180
|
+
if (hist === undefined || hist.length === 0) {
|
|
181
|
+
t.skip("ABATHUR_HISTORIAN_REPO unset — anchor needs the real archives");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const real = path.join(hist, ".state", "abathur");
|
|
185
|
+
let names;
|
|
186
|
+
try {
|
|
187
|
+
names = readdirSync(real);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
t.skip("historian .state/abathur not present on this machine");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const archives = names.filter((n) => n.startsWith("ledger.") && n.endsWith(".jsonl"));
|
|
194
|
+
if (!archives.some((n) => n.includes("campaign17"))) {
|
|
195
|
+
t.skip("campaign17 archive not present");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const { dir, state } = sandbox(t);
|
|
199
|
+
for (const n of archives)
|
|
200
|
+
copyFileSync(path.join(real, n), path.join(state, n));
|
|
201
|
+
writeFileSync(path.join(state, "ledger.jsonl"), ""); // copy of live would carry future rows — start empty
|
|
202
|
+
// spec: mirror the historian constitution's anchors exactly (stats/budget as registered)
|
|
203
|
+
const spec = entryFor(dir);
|
|
204
|
+
spec.spec.bench.stats = STATS;
|
|
205
|
+
const out = readjudicateGeneration({ entry: spec, configDir: cfg(t), genId: "g-20260925T150918Z-9969e4b6", now: () => new Date("2026-09-28T00:00:00Z") });
|
|
206
|
+
assert.equal(out.verdict, "nominated", out.lines.join("\n"));
|
|
207
|
+
const appended = lastRows(state).at(-1);
|
|
208
|
+
assert.ok(appended?.readjudication !== undefined);
|
|
209
|
+
assert.ok(Math.abs((appended.gain ?? 0) - 0.2916666666666667) < 1e-9, `gain ${String(appended.gain)} must match the offline replay 0.2917`);
|
|
210
|
+
assert.equal(appended.readjudication.replacedVerdict, "culled");
|
|
211
|
+
assert.equal(appended.readjudication.sourceRows.candidate.file, "ledger.campaign17-2026-09-25.jsonl");
|
|
212
|
+
});
|
|
@@ -130,7 +130,7 @@ async function sessionFixture(t) {
|
|
|
130
130
|
// plant the canary in the VAL unit and commit it, so it rides into worktrees
|
|
131
131
|
const sub = path.join(repo, "units", "sub.mjs");
|
|
132
132
|
writeFileSync(sub, `${readFileSync(sub, "utf8")}\n// ${CANARY}\n`, "utf8");
|
|
133
|
-
await gitIn(repo, "-c", "user.name=abathur", "-c", "user.email=
|
|
133
|
+
await gitIn(repo, "-c", "user.name=abathur", "-c", "user.email=agent@host.example", "commit", "-aqm", "plant val canary");
|
|
134
134
|
const spec = loadGenomeSpecFile(path.join(repo, "genome.jsonc"));
|
|
135
135
|
const env = {
|
|
136
136
|
XDG_CACHE_HOME: path.join(root, "xdg-cache"),
|
|
@@ -67,6 +67,12 @@ test("tombstone with env-literal repoPath: exported ⇒ blocked, no literal leak
|
|
|
67
67
|
assert.equal(r.status, 1, `stdout: ${r.stdout}\nstderr: ${r.stderr}`);
|
|
68
68
|
assertNoLiteralOrEnoent(r);
|
|
69
69
|
});
|
|
70
|
+
test("retract with env-literal repoPath: exported ⇒ blocked naming the RESOLVED repo path", async (t) => {
|
|
71
|
+
const f = await fixture(t, "seam-gate-retract");
|
|
72
|
+
const r = cli(f, ["retract", f.label, "g-nonexistent", "--reason", "seam test"], { [LIT_VAR]: f.repo });
|
|
73
|
+
assert.equal(r.status, 1, `stdout: ${r.stdout}\nstderr: ${r.stderr}`);
|
|
74
|
+
assertNoLiteralOrEnoent(r);
|
|
75
|
+
});
|
|
70
76
|
test("promote with env-literal repoPath: unset ⇒ clean exit 2 naming the variable", async (t) => {
|
|
71
77
|
const f = await fixture(t, "seam-gate-promote-unset");
|
|
72
78
|
const env = {};
|
|
@@ -283,8 +283,8 @@ function commitSurgery(fx, message, surgery) {
|
|
|
283
283
|
exec("git", ["-C", fx.harness.repo, "reset", "--hard", fx.head]);
|
|
284
284
|
surgery();
|
|
285
285
|
const repo = fx.harness.repo;
|
|
286
|
-
exec("git", ["-c", "user.name=abathur", "-c", "user.email=
|
|
287
|
-
exec("git", ["-c", "user.name=abathur", "-c", "user.email=
|
|
286
|
+
exec("git", ["-c", "user.name=abathur", "-c", "user.email=agent@host.example", "-C", repo, "add", "-A"]);
|
|
287
|
+
exec("git", ["-c", "user.name=abathur", "-c", "user.email=agent@host.example", "-C", repo, "commit", "-m", message]);
|
|
288
288
|
return headSha(repo);
|
|
289
289
|
}
|
|
290
290
|
function exec(bin, args) {
|
|
@@ -32,10 +32,14 @@ import { appendFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFile
|
|
|
32
32
|
import os from "node:os";
|
|
33
33
|
import path from "node:path";
|
|
34
34
|
|
|
35
|
+
// D7 zero-literals: the bee-judge bench lives outside the repo (campaign
|
|
36
|
+
// evidence dir); operators wire it via ABATHUR_JUDGE_BENCH. Unset => the
|
|
37
|
+
// judge stage fails closed BEFORE spawn and the grader fail-closes the legs.
|
|
38
|
+
const JUDGE_BENCH = process.env.ABATHUR_JUDGE_BENCH ?? null;
|
|
35
39
|
const DEFAULTS = {
|
|
36
|
-
bench: "
|
|
37
|
-
rubrics: ["R1-semantic", "R4-duty-v2", "R5-flavor"].map(
|
|
38
|
-
(r) =>
|
|
40
|
+
bench: JUDGE_BENCH ?? "",
|
|
41
|
+
rubrics: JUDGE_BENCH === null ? [] : ["R1-semantic", "R4-duty-v2", "R5-flavor"].map(
|
|
42
|
+
(r) => path.join(path.dirname(JUDGE_BENCH), "rubrics", `${r}.md`),
|
|
39
43
|
),
|
|
40
44
|
model: "local-qwen/qwen3.8-flash-next",
|
|
41
45
|
};
|
|
@@ -111,6 +115,7 @@ function foldMajority(row) {
|
|
|
111
115
|
const sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
112
116
|
|
|
113
117
|
const opts = parseArgs(process.argv.slice(2));
|
|
118
|
+
if (opts.bench === "") { console.error("judge-poststage: no bee-judge bench wired — set ABATHUR_JUDGE_BENCH (or pass --bench); grader fail-closes the judge legs without it."); process.exit(2); }
|
|
114
119
|
(async () => {
|
|
115
120
|
try {
|
|
116
121
|
const work = mkdtempSync(path.join(os.tmpdir(), "s17-judge-"));
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ top-level command (`genome`, `run`, `status`, `bundle`, `graft`, `self-eval`,
|
|
|
9
9
|
with `command` set to the first word and `extra` set to the remaining tokens,
|
|
10
10
|
then report the CLI exit code (0 ok / 1 blocked decision / 2 cannot-answer)
|
|
11
11
|
and the relevant lines of its output. If no request was given, call the tool
|
|
12
|
-
with `command: "--help"` and summarize the command list. `promote
|
|
13
|
-
`tombstone` cannot be called through the tool at all — the tool refuses them.
|
|
12
|
+
with `command: "--help"` and summarize the command list. `promote`,
|
|
13
|
+
`tombstone` and `retract` cannot be called through the tool at all — the tool refuses them.
|
|
14
14
|
They are human gates that belong to a terminal: if the user asks for one,
|
|
15
|
-
tell them to run `abathur promote …` / `abathur tombstone …` there.
|
|
15
|
+
tell them to run `abathur promote …` / `abathur tombstone …` / `abathur retract …` there.
|
package/plugin/abathur.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// abathur-opencode-plugin v0.2.
|
|
1
|
+
// abathur-opencode-plugin v0.2.7
|
|
2
2
|
// Official opencode plugin adapter for the abathur evolution harness.
|
|
3
3
|
// Registers ONE agent tool, `abathur`, that shells out to the abathur CLI —
|
|
4
4
|
// argv-only (node:child_process execFile, never a shell), top-level commands
|
|
@@ -29,7 +29,7 @@ const TIMEOUT_MS = 120_000;
|
|
|
29
29
|
const MAX_OUTPUT_BYTES = 64 * 1024;
|
|
30
30
|
|
|
31
31
|
/** The seven tool-reachable top-level commands plus --help. Anything else is refused locally.
|
|
32
|
-
* promote/tombstone are human gates and deliberately terminal-only (0.2.1): not reachable here. */
|
|
32
|
+
* promote/tombstone/retract are human gates and deliberately terminal-only (0.2.1/0.2.7): not reachable here. */
|
|
33
33
|
const ALLOWED_COMMANDS: readonly string[] = [
|
|
34
34
|
"genome",
|
|
35
35
|
"run",
|
|
@@ -133,10 +133,10 @@ top-level command (\`genome\`, \`run\`, \`status\`, \`bundle\`, \`graft\`, \`sel
|
|
|
133
133
|
with \`command\` set to the first word and \`extra\` set to the remaining tokens,
|
|
134
134
|
then report the CLI exit code (0 ok / 1 blocked decision / 2 cannot-answer)
|
|
135
135
|
and the relevant lines of its output. If no request was given, call the tool
|
|
136
|
-
with \`command: "--help"\` and summarize the command list. \`promote
|
|
137
|
-
\`tombstone\` cannot be called through the tool at all — the tool refuses them.
|
|
136
|
+
with \`command: "--help"\` and summarize the command list. \`promote\`,
|
|
137
|
+
\`tombstone\` and \`retract\` cannot be called through the tool at all — the tool refuses them.
|
|
138
138
|
They are human gates that belong to a terminal: if the user asks for one,
|
|
139
|
-
tell them to run \`abathur promote …\` / \`abathur tombstone …\` there.
|
|
139
|
+
tell them to run \`abathur promote …\` / \`abathur tombstone …\` / \`abathur retract …\` there.
|
|
140
140
|
`;
|
|
141
141
|
|
|
142
142
|
export default {
|
|
@@ -168,7 +168,7 @@ export default {
|
|
|
168
168
|
"(0 ok, 1 blocked decision, 2 cannot-answer). Honest privilege note: this " +
|
|
169
169
|
"tool carries bash-equivalent privilege — `run` and `genome` legitimately " +
|
|
170
170
|
"spawn mutator/engine binaries by design — so the allowlist limits typos " +
|
|
171
|
-
"and UX, not capability. `promote` and `
|
|
171
|
+
"and UX, not capability. `promote`, `tombstone` and `retract` are deliberately NOT " +
|
|
172
172
|
"reachable here: they are human gates, run in a terminal.",
|
|
173
173
|
args: {
|
|
174
174
|
command: tool.schema
|