@brainbase-labs/cli 0.25.0-eng1209.8 → 0.25.0-eng1209.9
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 +14 -7
- package/dist/index.js +84 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,21 +72,28 @@ synchronously. Schema v1 supports:
|
|
|
72
72
|
- `workspace_assertion`: file existence, absence, SHA-256, or content checks.
|
|
73
73
|
- `sandbox_command`: a bounded argv command rooted in the workspace or hidden
|
|
74
74
|
tests directory. Exit zero passes; any other exit code is a valid failed
|
|
75
|
-
verdict. Schema v1 allows
|
|
76
|
-
read-only assertions
|
|
75
|
+
verdict. Schema v1 allows up to 20 command evaluators and runs them after all
|
|
76
|
+
read-only assertions. Each command receives either the verified live workspace
|
|
77
|
+
for legacy read-only evaluation or its own isolated frozen workspace copy.
|
|
77
78
|
|
|
78
79
|
Valid failed verdicts still produce a successful evaluation phase. Invalid
|
|
79
|
-
specs, digest/path violations, missing environment,
|
|
80
|
-
and output-budget violations fail the phase.
|
|
80
|
+
specs, digest/path violations, missing environment, phase-budget exhaustion,
|
|
81
|
+
and output-budget violations fail the phase. Individual command launch failures,
|
|
82
|
+
timeouts, and forced terminations are recorded as errored evaluator results so
|
|
83
|
+
the remaining evaluators can still run. Results are written atomically
|
|
81
84
|
with mode `0600`, include the raw spec digest and checksummed evidence/log
|
|
82
85
|
input/output references with explicit `staging`, `workspace`, `tests`, or
|
|
83
86
|
`logs` roots, redact declared secret values from command logs, require the
|
|
84
87
|
result path to be `<logs_root>/result.json`, and treat a matching successful
|
|
85
88
|
phase result as authoritative on replay.
|
|
86
89
|
|
|
87
|
-
On any phase-level execution failure, MAS must tear down the task sandbox.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
On any phase-level execution failure, MAS must tear down the task sandbox.
|
|
91
|
+
Failed and timed-out commands clean up the process group and descendants the
|
|
92
|
+
CLI can observe. Successful evaluator commands receive the same cleanup so one
|
|
93
|
+
check cannot contaminate the next. Successful hydration setup commands may
|
|
94
|
+
intentionally leave services running for the agent turn; the runtime lifecycle
|
|
95
|
+
remains their final cleanup boundary. Isolated evaluator workspace copies omit
|
|
96
|
+
`.git` and unsafe absolute or escaping symlinks.
|
|
90
97
|
|
|
91
98
|
## Development
|
|
92
99
|
|
package/dist/index.js
CHANGED
|
@@ -36008,7 +36008,7 @@ function padStart(s, n) {
|
|
|
36008
36008
|
// package.json
|
|
36009
36009
|
var package_default = {
|
|
36010
36010
|
name: "@brainbase-labs/cli",
|
|
36011
|
-
version: "0.25.0-eng1209.
|
|
36011
|
+
version: "0.25.0-eng1209.9",
|
|
36012
36012
|
description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
|
|
36013
36013
|
type: "module",
|
|
36014
36014
|
bin: {
|
|
@@ -78487,7 +78487,8 @@ var MAX_ARCHIVE_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
|
|
|
78487
78487
|
var MAX_ARCHIVE_SCAN_BYTES = 2 * 1024 * 1024 * 1024;
|
|
78488
78488
|
var MAX_SANDBOX_COMMANDS = 20;
|
|
78489
78489
|
var MAX_CRITERIA_PER_EVALUATOR = 200;
|
|
78490
|
-
var MAX_CRITERIA_RESULT_BYTES = 1024 * 1024;
|
|
78490
|
+
var MAX_CRITERIA_RESULT_BYTES = 24 * 1024 * 1024;
|
|
78491
|
+
var MAX_CRITERIA_RESULT_PAYLOAD_BYTES = 20 * 1024 * 1024;
|
|
78491
78492
|
var MAX_CRITERION_EXPLANATION_LENGTH = 16384;
|
|
78492
78493
|
var MAX_CRITERION_EVIDENCE_IDS = 100;
|
|
78493
78494
|
var MAX_CRITERION_EVIDENCE_ID_LENGTH = 256;
|
|
@@ -80052,6 +80053,18 @@ function markedProcessPids(marker) {
|
|
|
80052
80053
|
return [];
|
|
80053
80054
|
}
|
|
80054
80055
|
function processExists(pid) {
|
|
80056
|
+
if (process.platform === "linux") {
|
|
80057
|
+
try {
|
|
80058
|
+
const stat = fs81.readFileSync(path88.join("/proc", String(pid), "stat"), "utf8");
|
|
80059
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
80060
|
+
const state = commandEnd >= 0 ? stat.slice(commandEnd + 2, commandEnd + 3) : "";
|
|
80061
|
+
if (state === "Z" || state === "X")
|
|
80062
|
+
return false;
|
|
80063
|
+
} catch (error2) {
|
|
80064
|
+
if (error2.code === "ENOENT")
|
|
80065
|
+
return false;
|
|
80066
|
+
}
|
|
80067
|
+
}
|
|
80055
80068
|
try {
|
|
80056
80069
|
process.kill(pid, 0);
|
|
80057
80070
|
return true;
|
|
@@ -80083,7 +80096,7 @@ async function terminateCommandProcesses(child, marker, observedDescendants) {
|
|
|
80083
80096
|
await new Promise((resolve) => setTimeout(resolve, COMMAND_PROCESS_POLL_MS));
|
|
80084
80097
|
}
|
|
80085
80098
|
}
|
|
80086
|
-
async function runCommand(command, root, spec, context,
|
|
80099
|
+
async function runCommand(command, root, spec, context, options = {}) {
|
|
80087
80100
|
const cwdRel = normalizedRootRelative(command.cwd);
|
|
80088
80101
|
assertNoSymlinkTraversal(root, cwdRel);
|
|
80089
80102
|
const cwd2 = path88.resolve(root, cwdRel);
|
|
@@ -80107,7 +80120,7 @@ async function runCommand(command, root, spec, context, additions = {}) {
|
|
|
80107
80120
|
const child = spawn4(command.argv[0], command.argv.slice(1), {
|
|
80108
80121
|
cwd: cwd2,
|
|
80109
80122
|
env: buildEnvironment(spec, command.secret_env, {
|
|
80110
|
-
...additions,
|
|
80123
|
+
...options.additions,
|
|
80111
80124
|
[COMMAND_PROCESS_MARKER_ENV]: commandMarker
|
|
80112
80125
|
}),
|
|
80113
80126
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -80163,13 +80176,23 @@ async function runCommand(command, root, spec, context, additions = {}) {
|
|
|
80163
80176
|
}
|
|
80164
80177
|
target.push(chunk2);
|
|
80165
80178
|
};
|
|
80179
|
+
const finish = (code, signal) => {
|
|
80180
|
+
if (observer)
|
|
80181
|
+
clearInterval(observer);
|
|
80182
|
+
if (code === null) {
|
|
80183
|
+
reject2(new BenchmarkCommandExecutionError("command_terminated", `command was terminated by ${signal ?? "an unknown signal"}: ${command.id}`, sanitizedOutput(stdout), sanitizedOutput(stderr), Date.now() - started));
|
|
80184
|
+
return;
|
|
80185
|
+
}
|
|
80186
|
+
resolve({
|
|
80187
|
+
exitCode: code,
|
|
80188
|
+
stdout: sanitizedOutput(stdout),
|
|
80189
|
+
stderr: sanitizedOutput(stderr),
|
|
80190
|
+
durationMs: Date.now() - started,
|
|
80191
|
+
redactions: [commandMarker]
|
|
80192
|
+
});
|
|
80193
|
+
};
|
|
80166
80194
|
child.stdout?.on("data", (chunk2) => capture(stdout, chunk2));
|
|
80167
80195
|
child.stderr?.on("data", (chunk2) => capture(stderr, chunk2));
|
|
80168
|
-
child.once("exit", () => {
|
|
80169
|
-
if (timer)
|
|
80170
|
-
clearTimeout(timer);
|
|
80171
|
-
cleanupProcesses();
|
|
80172
|
-
});
|
|
80173
80196
|
child.on("error", (error2) => {
|
|
80174
80197
|
fail(new BenchmarkCommandExecutionError("command_start_failed", `failed to start ${command.id}: ${error2.message}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
|
|
80175
80198
|
});
|
|
@@ -80180,24 +80203,21 @@ async function runCommand(command, root, spec, context, additions = {}) {
|
|
|
80180
80203
|
}
|
|
80181
80204
|
fail(new BenchmarkCommandExecutionError("command_timeout", `command timed out: ${command.id}`, Buffer.concat(stdout), Buffer.concat(stderr), Date.now() - started));
|
|
80182
80205
|
}, timeoutMs2);
|
|
80183
|
-
child.on("
|
|
80206
|
+
child.on("exit", (code, signal) => {
|
|
80184
80207
|
if (settled)
|
|
80185
80208
|
return;
|
|
80186
80209
|
settled = true;
|
|
80187
80210
|
clearTimeout(timer);
|
|
80188
|
-
|
|
80189
|
-
|
|
80190
|
-
|
|
80191
|
-
|
|
80192
|
-
|
|
80193
|
-
|
|
80194
|
-
|
|
80195
|
-
|
|
80196
|
-
|
|
80197
|
-
|
|
80198
|
-
redactions: [commandMarker]
|
|
80199
|
-
});
|
|
80200
|
-
}, reject2);
|
|
80211
|
+
if (options.descendantCleanup === "always" || code === null || code !== 0) {
|
|
80212
|
+
cleanupProcesses().then(() => finish(code, signal), reject2);
|
|
80213
|
+
} else {
|
|
80214
|
+
if (observer)
|
|
80215
|
+
clearInterval(observer);
|
|
80216
|
+
child.unref();
|
|
80217
|
+
child.stdout?.unref?.();
|
|
80218
|
+
child.stderr?.unref?.();
|
|
80219
|
+
finish(code, signal);
|
|
80220
|
+
}
|
|
80201
80221
|
});
|
|
80202
80222
|
});
|
|
80203
80223
|
}
|
|
@@ -80275,7 +80295,9 @@ async function executeHydrate(spec, context) {
|
|
|
80275
80295
|
assertBudget(context);
|
|
80276
80296
|
let result2;
|
|
80277
80297
|
try {
|
|
80278
|
-
result2 = await runCommand(command, spec.workspace_root, spec, context
|
|
80298
|
+
result2 = await runCommand(command, spec.workspace_root, spec, context, {
|
|
80299
|
+
descendantCleanup: "failure_only"
|
|
80300
|
+
});
|
|
80279
80301
|
} catch (error2) {
|
|
80280
80302
|
context.steps.push({
|
|
80281
80303
|
id: command.id,
|
|
@@ -80463,6 +80485,8 @@ function manifestDirectories(manifest, context) {
|
|
|
80463
80485
|
const directories = new Set;
|
|
80464
80486
|
for (const entry of manifest) {
|
|
80465
80487
|
assertBudget(context);
|
|
80488
|
+
if (entry.kind === "symlink")
|
|
80489
|
+
continue;
|
|
80466
80490
|
let current = path88.posix.dirname(entry.path);
|
|
80467
80491
|
while (current !== ".") {
|
|
80468
80492
|
assertBudget(context);
|
|
@@ -80480,9 +80504,8 @@ function candidateOutputFiles(output, manifest, context) {
|
|
|
80480
80504
|
assertBudget(context);
|
|
80481
80505
|
if (!matcher.test(entry.path))
|
|
80482
80506
|
continue;
|
|
80483
|
-
if (entry.kind === "symlink")
|
|
80484
|
-
|
|
80485
|
-
}
|
|
80507
|
+
if (entry.kind === "symlink")
|
|
80508
|
+
continue;
|
|
80486
80509
|
matched.push(entry);
|
|
80487
80510
|
}
|
|
80488
80511
|
return {
|
|
@@ -80490,12 +80513,6 @@ function candidateOutputFiles(output, manifest, context) {
|
|
|
80490
80513
|
files: matched
|
|
80491
80514
|
};
|
|
80492
80515
|
}
|
|
80493
|
-
for (const entry of manifest) {
|
|
80494
|
-
assertBudget(context);
|
|
80495
|
-
if (entry.kind === "symlink" && matcher.test(entry.path)) {
|
|
80496
|
-
throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
|
|
80497
|
-
}
|
|
80498
|
-
}
|
|
80499
80516
|
const directories = [];
|
|
80500
80517
|
for (const directory of manifestDirectories(manifest, context)) {
|
|
80501
80518
|
assertBudget(context);
|
|
@@ -80510,9 +80527,8 @@ function candidateOutputFiles(output, manifest, context) {
|
|
|
80510
80527
|
assertBudget(context);
|
|
80511
80528
|
if (!entry.path.startsWith(prefix))
|
|
80512
80529
|
continue;
|
|
80513
|
-
if (entry.kind === "symlink")
|
|
80514
|
-
|
|
80515
|
-
}
|
|
80530
|
+
if (entry.kind === "symlink")
|
|
80531
|
+
continue;
|
|
80516
80532
|
selected.set(entry.path, entry);
|
|
80517
80533
|
}
|
|
80518
80534
|
}
|
|
@@ -80558,7 +80574,7 @@ async function copyCandidateOutput(output, manifest, spec, context) {
|
|
|
80558
80574
|
}
|
|
80559
80575
|
return copied;
|
|
80560
80576
|
}
|
|
80561
|
-
async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root,
|
|
80577
|
+
async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.workspace_root, symlinkPolicy = "preserve") {
|
|
80562
80578
|
const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
|
|
80563
80579
|
fs81.chmodSync(destinationRoot, 448);
|
|
80564
80580
|
context.temporaryRoots.add(destinationRoot);
|
|
@@ -80572,12 +80588,9 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
|
|
|
80572
80588
|
if (target === null || Buffer.byteLength(target) !== frozenFile.size || sha256(target) !== frozenFile.sha256) {
|
|
80573
80589
|
throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
|
|
80574
80590
|
}
|
|
80575
|
-
if (!preserveUnsafeSymlinks && path88.isAbsolute(target)) {
|
|
80576
|
-
throw new BenchmarkPhaseError("unsafe_workspace_symlink", `sandbox evaluator workspace cannot reproduce an absolute symlink: ${frozenFile.path}`);
|
|
80577
|
-
}
|
|
80578
80591
|
const resolvedTarget = path88.resolve(path88.dirname(source), target);
|
|
80579
|
-
if (
|
|
80580
|
-
|
|
80592
|
+
if (symlinkPolicy === "contained_relative_only" && (path88.isAbsolute(target) || !isWithin(sourceRoot, resolvedTarget))) {
|
|
80593
|
+
continue;
|
|
80581
80594
|
}
|
|
80582
80595
|
fs81.mkdirSync(path88.dirname(destination), { recursive: true, mode: 448 });
|
|
80583
80596
|
fs81.symlinkSync(target, destination);
|
|
@@ -80591,11 +80604,15 @@ async function copyFrozenWorkspace(manifest, spec, context, sourceRoot = spec.wo
|
|
|
80591
80604
|
}
|
|
80592
80605
|
return destinationRoot;
|
|
80593
80606
|
}
|
|
80594
|
-
async function copyEvaluatorTests(spec, context) {
|
|
80607
|
+
async function copyEvaluatorTests(spec, evaluator, context) {
|
|
80595
80608
|
const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-tests-"));
|
|
80596
80609
|
fs81.chmodSync(destinationRoot, 448);
|
|
80597
80610
|
context.temporaryRoots.add(destinationRoot);
|
|
80598
|
-
const
|
|
80611
|
+
const sourceRoot = evaluatorTestsPath(evaluator, spec.tests_root);
|
|
80612
|
+
const relativeRoot = evaluator.tests_path ? normalizedRootRelative(evaluator.tests_path) : ".";
|
|
80613
|
+
const destinationStart = relativeRoot === "." ? destinationRoot : path88.resolve(destinationRoot, relativeRoot);
|
|
80614
|
+
fs81.mkdirSync(destinationStart, { recursive: true, mode: 448 });
|
|
80615
|
+
const stack = [{ source: sourceRoot, destination: destinationStart }];
|
|
80599
80616
|
while (stack.length > 0) {
|
|
80600
80617
|
assertBudget(context);
|
|
80601
80618
|
const current = stack.pop();
|
|
@@ -80657,7 +80674,15 @@ var CriterionResultSchema = exports_external.object({
|
|
|
80657
80674
|
});
|
|
80658
80675
|
var CriteriaResultFileSchema = exports_external.object({
|
|
80659
80676
|
criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
|
|
80660
|
-
}).strict()
|
|
80677
|
+
}).strict().superRefine((value, context) => {
|
|
80678
|
+
if (Buffer.byteLength(JSON.stringify(value)) > MAX_CRITERIA_RESULT_PAYLOAD_BYTES) {
|
|
80679
|
+
context.addIssue({
|
|
80680
|
+
code: exports_external.ZodIssueCode.custom,
|
|
80681
|
+
path: ["criteria"],
|
|
80682
|
+
message: "criterion result exceeds the 20 MiB aggregate payload limit"
|
|
80683
|
+
});
|
|
80684
|
+
}
|
|
80685
|
+
});
|
|
80661
80686
|
function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spec, additionalSecrets = []) {
|
|
80662
80687
|
let opened;
|
|
80663
80688
|
try {
|
|
@@ -80670,7 +80695,7 @@ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds, spe
|
|
|
80670
80695
|
}
|
|
80671
80696
|
try {
|
|
80672
80697
|
if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
|
|
80673
|
-
throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the
|
|
80698
|
+
throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 24 MiB file limit");
|
|
80674
80699
|
}
|
|
80675
80700
|
let raw;
|
|
80676
80701
|
try {
|
|
@@ -80766,9 +80791,12 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
|
|
|
80766
80791
|
cwd: ".",
|
|
80767
80792
|
secret_env: []
|
|
80768
80793
|
}, spec.tests_root, spec, context, {
|
|
80769
|
-
|
|
80770
|
-
|
|
80771
|
-
|
|
80794
|
+
additions: {
|
|
80795
|
+
BB_REGEX_INPUT: frozenEvidence.finalOutputPath,
|
|
80796
|
+
BB_REGEX_PATTERN_B64: Buffer.from(assertion.pattern).toString("base64"),
|
|
80797
|
+
BB_REGEX_FLAGS: assertion.flags
|
|
80798
|
+
},
|
|
80799
|
+
descendantCleanup: "always"
|
|
80772
80800
|
});
|
|
80773
80801
|
if (regexResult.exitCode === 2) {
|
|
80774
80802
|
throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
|
|
@@ -80867,12 +80895,12 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
|
|
|
80867
80895
|
let privateResultRoot;
|
|
80868
80896
|
const usesLiveWorkspace = evaluator.workspace_mode === "read_only";
|
|
80869
80897
|
try {
|
|
80870
|
-
const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot);
|
|
80898
|
+
const evaluatorWorkspace = usesLiveWorkspace ? spec.workspace_root : await copyFrozenWorkspace(manifest, spec, context, frozenWorkspaceRoot, "contained_relative_only");
|
|
80871
80899
|
if (!usesLiveWorkspace)
|
|
80872
80900
|
isolatedWorkspace = evaluatorWorkspace;
|
|
80873
80901
|
else
|
|
80874
80902
|
await verifyWorkspaceManifestUnchanged(manifest, spec, context);
|
|
80875
|
-
isolatedTestsRoot = await copyEvaluatorTests(spec, context);
|
|
80903
|
+
isolatedTestsRoot = await copyEvaluatorTests(spec, evaluator, context);
|
|
80876
80904
|
const testsPath = evaluatorTestsPath(evaluator, isolatedTestsRoot);
|
|
80877
80905
|
const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : isolatedTestsRoot;
|
|
80878
80906
|
const command = { id: evaluator.id, ...evaluator.command };
|
|
@@ -80891,7 +80919,10 @@ async function evaluateOne(evaluator, spec, manifest, frozenWorkspaceRoot, final
|
|
|
80891
80919
|
criterionResultPath = path88.join(privateResultRoot, "result.json");
|
|
80892
80920
|
environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
|
|
80893
80921
|
}
|
|
80894
|
-
const result2 = await runCommand(command, commandRoot, spec, context,
|
|
80922
|
+
const result2 = await runCommand(command, commandRoot, spec, context, {
|
|
80923
|
+
additions: environment,
|
|
80924
|
+
descendantCleanup: "always"
|
|
80925
|
+
});
|
|
80895
80926
|
const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
|
|
80896
80927
|
const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
|
|
80897
80928
|
let criterionResults;
|
|
@@ -81034,7 +81065,7 @@ async function executeEvaluate(spec, context) {
|
|
|
81034
81065
|
assertEvaluateOutputBudget(spec, context);
|
|
81035
81066
|
}
|
|
81036
81067
|
await verifyRecordsUnchanged(manifest, spec);
|
|
81037
|
-
const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root
|
|
81068
|
+
const frozenWorkspaceRoot = await copyFrozenWorkspace(manifest, spec, context, spec.workspace_root);
|
|
81038
81069
|
for (const reference of spec.references) {
|
|
81039
81070
|
const referenceOutputs = await copyMaterial(reference, spec.staging_root, spec.tests_root, "tests", false, context);
|
|
81040
81071
|
outputs.push(...referenceOutputs);
|