@brainbase-labs/cli 0.24.0 → 0.25.0-eng1209.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.
Files changed (2) hide show
  1. package/dist/index.js +422 -44
  2. package/package.json +1 -1
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.24.0",
36011
+ version: "0.25.0-eng1209.2",
36012
36012
  description: "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
36013
36013
  type: "module",
36014
36014
  bin: {
@@ -78485,6 +78485,13 @@ var MAX_TRAJECTORY_BYTES = 100 * 1024 * 1024;
78485
78485
  var MAX_REMOTE_INPUT_BYTES = 2 * 1024 * 1024 * 1024;
78486
78486
  var MAX_ARCHIVE_EXTRACTED_BYTES = 2 * 1024 * 1024 * 1024;
78487
78487
  var MAX_ARCHIVE_SCAN_BYTES = 2 * 1024 * 1024 * 1024;
78488
+ var MAX_SANDBOX_COMMANDS = 20;
78489
+ var MAX_CRITERIA_PER_EVALUATOR = 100;
78490
+ var MAX_CRITERIA_RESULT_BYTES = 1024 * 1024;
78491
+ var MAX_CRITERION_EXPLANATION_LENGTH = 16384;
78492
+ var MAX_CRITERION_EVIDENCE_IDS = 100;
78493
+ var MAX_CRITERION_EVIDENCE_ID_LENGTH = 256;
78494
+ var MAX_ALLOWED_EVIDENCE_IDS = 1e4;
78488
78495
  var RESERVED_WORKSPACE_PATHS = new Set([
78489
78496
  ".brainbase",
78490
78497
  ".git",
@@ -78512,6 +78519,17 @@ var NonSecretEnvironmentValueSchema = exports_external.object({
78512
78519
  sensitive: exports_external.literal(false)
78513
78520
  }).strict();
78514
78521
  var RootRelativePathSchema = exports_external.string().min(1).max(1024);
78522
+ var CandidateOutputPatternSchema = RootRelativePathSchema.refine((value) => {
78523
+ if (value.includes("\\") || /[\[\]{}]/.test(value))
78524
+ return false;
78525
+ try {
78526
+ return safeRelPath(value) === value;
78527
+ } catch {
78528
+ return false;
78529
+ }
78530
+ }, {
78531
+ message: "must be a normalized safe relative glob using only *, ?, and ** wildcards"
78532
+ });
78515
78533
  var ArchivePathSchema = RootRelativePathSchema.refine((value) => {
78516
78534
  try {
78517
78535
  return safeRelPath(value) === value;
@@ -78618,7 +78636,11 @@ var EvaluatorSchema = exports_external.discriminatedUnion("type", [
78618
78636
  ...EvaluatorBase,
78619
78637
  type: exports_external.literal("sandbox_command"),
78620
78638
  command: CommandSchema.omit({ id: true }),
78621
- root: exports_external.enum(["workspace", "tests"]).default("tests")
78639
+ root: exports_external.enum(["workspace", "tests"]).default("tests"),
78640
+ criterion_keys: exports_external.array(IdSchema).min(1).max(MAX_CRITERIA_PER_EVALUATOR).optional(),
78641
+ allowed_evidence_ids: exports_external.array(exports_external.string().min(1).max(MAX_CRITERION_EVIDENCE_ID_LENGTH)).max(MAX_ALLOWED_EVIDENCE_IDS).optional(),
78642
+ tests_path: ArchivePathSchema.optional(),
78643
+ workspace_mode: exports_external.enum(["read_only", "isolated_copy"]).default("read_only")
78622
78644
  }).strict()
78623
78645
  ]);
78624
78646
  var EvidenceFileSchema = exports_external.object({
@@ -78638,6 +78660,37 @@ var HydrateSpecSchema = BaseSpecSchema.extend({
78638
78660
  materials: exports_external.array(MaterialSchema).max(1e4).default([]),
78639
78661
  setup_commands: exports_external.array(CommandSchema).max(128).default([])
78640
78662
  }).strict();
78663
+ var CandidateOutputSchema = exports_external.object({
78664
+ id: IdSchema,
78665
+ pattern: CandidateOutputPatternSchema,
78666
+ kind: exports_external.enum(["file", "directory"]),
78667
+ required: exports_external.boolean(),
78668
+ min_matches: exports_external.number().int().min(0).max(1e5),
78669
+ max_matches: exports_external.number().int().min(1).max(1e5),
78670
+ max_total_bytes: exports_external.number().int().min(0).max(2 * 1024 * 1024 * 1024)
78671
+ }).strict().superRefine((value, context) => {
78672
+ if (value.min_matches > value.max_matches) {
78673
+ context.addIssue({
78674
+ code: exports_external.ZodIssueCode.custom,
78675
+ path: ["min_matches"],
78676
+ message: "min_matches cannot exceed max_matches"
78677
+ });
78678
+ }
78679
+ if (value.required && value.min_matches === 0) {
78680
+ context.addIssue({
78681
+ code: exports_external.ZodIssueCode.custom,
78682
+ path: ["min_matches"],
78683
+ message: "required candidate outputs must require at least one match"
78684
+ });
78685
+ }
78686
+ if (!value.required && value.min_matches > 0) {
78687
+ context.addIssue({
78688
+ code: exports_external.ZodIssueCode.custom,
78689
+ path: ["min_matches"],
78690
+ message: "optional candidate outputs must allow zero matches"
78691
+ });
78692
+ }
78693
+ });
78641
78694
  var EvaluateSpecSchema = BaseSpecSchema.extend({
78642
78695
  phase: exports_external.literal("evaluate"),
78643
78696
  tests_root: AbsolutePathSchema,
@@ -78648,6 +78701,7 @@ var EvaluateSpecSchema = BaseSpecSchema.extend({
78648
78701
  references: exports_external.array(MaterialSchema).max(1e4).default([]),
78649
78702
  evaluators: exports_external.array(EvaluatorSchema).min(1).max(1000),
78650
78703
  candidate_artifacts: exports_external.array(RootRelativePathSchema).max(1000).default([]),
78704
+ candidate_outputs: exports_external.array(CandidateOutputSchema).max(1000).default([]),
78651
78705
  capture_workspace_archive: exports_external.boolean().default(false),
78652
78706
  workspace_limits: exports_external.object({
78653
78707
  max_file_count: exports_external.number().int().min(1).max(1e6).default(1e5),
@@ -78727,7 +78781,8 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78727
78781
  { items: value.setup_commands, path: "setup_commands" }
78728
78782
  ] : [
78729
78783
  { items: value.references, path: "references" },
78730
- { items: value.evaluators, path: "evaluators" }
78784
+ { items: value.evaluators, path: "evaluators" },
78785
+ { items: value.candidate_outputs, path: "candidate_outputs" }
78731
78786
  ];
78732
78787
  for (const { items, path: issuePath } of uniqueLists) {
78733
78788
  const ids = items.map((item) => item.id);
@@ -78739,6 +78794,39 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78739
78794
  });
78740
78795
  }
78741
78796
  }
78797
+ if (value.phase === "evaluate") {
78798
+ value.evaluators.forEach((evaluator, index) => {
78799
+ if (evaluator.type === "sandbox_command" && evaluator.criterion_keys && new Set(evaluator.criterion_keys).size !== evaluator.criterion_keys.length) {
78800
+ context.addIssue({
78801
+ code: exports_external.ZodIssueCode.custom,
78802
+ path: ["evaluators", index, "criterion_keys"],
78803
+ message: "criterion_keys must be unique"
78804
+ });
78805
+ }
78806
+ if (evaluator.type === "sandbox_command" && evaluator.criterion_keys && evaluator.allowed_evidence_ids === undefined) {
78807
+ context.addIssue({
78808
+ code: exports_external.ZodIssueCode.custom,
78809
+ path: ["evaluators", index, "allowed_evidence_ids"],
78810
+ message: "structured criteria require allowed_evidence_ids"
78811
+ });
78812
+ }
78813
+ if (evaluator.type === "sandbox_command" && evaluator.allowed_evidence_ids && new Set(evaluator.allowed_evidence_ids).size !== evaluator.allowed_evidence_ids.length) {
78814
+ context.addIssue({
78815
+ code: exports_external.ZodIssueCode.custom,
78816
+ path: ["evaluators", index, "allowed_evidence_ids"],
78817
+ message: "allowed_evidence_ids must be unique"
78818
+ });
78819
+ }
78820
+ });
78821
+ const candidateOutputBytes = value.candidate_outputs.reduce((total, output) => total + output.max_total_bytes, 0);
78822
+ if (candidateOutputBytes > value.workspace_limits.max_total_bytes) {
78823
+ context.addIssue({
78824
+ code: exports_external.ZodIssueCode.custom,
78825
+ path: ["candidate_outputs"],
78826
+ message: "candidate output byte limits exceed the workspace byte limit"
78827
+ });
78828
+ }
78829
+ }
78742
78830
  const materials = value.phase === "hydrate" ? value.materials : value.references;
78743
78831
  const archiveOutputBytes = materials.reduce((total, material) => total + (material.kind === "archive_file" ? material.file_size_bytes : 0), 0);
78744
78832
  if (archiveOutputBytes > MAX_ARCHIVE_EXTRACTED_BYTES) {
@@ -78748,11 +78836,11 @@ var BenchmarkSpecSchema = exports_external.discriminatedUnion("phase", [
78748
78836
  message: "archive-file materials exceed the aggregate extracted byte limit"
78749
78837
  });
78750
78838
  }
78751
- if (value.phase === "evaluate" && value.evaluators.filter((evaluator) => evaluator.type === "sandbox_command").length > 1) {
78839
+ if (value.phase === "evaluate" && value.evaluators.filter((evaluator) => evaluator.type === "sandbox_command").length > MAX_SANDBOX_COMMANDS) {
78752
78840
  context.addIssue({
78753
78841
  code: exports_external.ZodIssueCode.custom,
78754
78842
  path: ["evaluators"],
78755
- message: "schema version 1 supports at most one sandbox_command evaluator"
78843
+ message: `schema version 1 supports at most ${MAX_SANDBOX_COMMANDS} sandbox_command evaluators`
78756
78844
  });
78757
78845
  }
78758
78846
  });
@@ -78763,7 +78851,12 @@ var BENCHMARK_CAPABILITIES = {
78763
78851
  phases: ["hydrate", "evaluate"],
78764
78852
  features: [
78765
78853
  "remote_input_references_v1",
78766
- "archive_file_materials_v1"
78854
+ "archive_file_materials_v1",
78855
+ "structured_criterion_results_v1",
78856
+ "multiple_sandbox_commands_v1",
78857
+ "sandbox_command_workspace_modes_v1",
78858
+ "candidate_outputs_v1",
78859
+ "criterion_evidence_allowlist_v1"
78767
78860
  ],
78768
78861
  evaluator_types: [
78769
78862
  "output_assertion",
@@ -78774,7 +78867,8 @@ var BENCHMARK_CAPABILITIES = {
78774
78867
  limits: {
78775
78868
  max_secret_bindings: 100,
78776
78869
  max_evaluators: 1000,
78777
- max_sandbox_commands: 1
78870
+ max_sandbox_commands: MAX_SANDBOX_COMMANDS,
78871
+ max_criteria_per_evaluator: MAX_CRITERIA_PER_EVALUATOR
78778
78872
  }
78779
78873
  };
78780
78874
 
@@ -80126,6 +80220,234 @@ async function workspaceManifest(spec, context) {
80126
80220
  }
80127
80221
  return records.sort((a3, b4) => a3.path.localeCompare(b4.path));
80128
80222
  }
80223
+ function candidateGlob(pattern) {
80224
+ let source = "^";
80225
+ for (let index = 0;index < pattern.length; index += 1) {
80226
+ const char = pattern[index];
80227
+ if (char === "*") {
80228
+ if (pattern[index + 1] === "*") {
80229
+ if (pattern[index + 2] === "/") {
80230
+ source += "(?:.*/)?";
80231
+ index += 2;
80232
+ } else {
80233
+ source += ".*";
80234
+ index += 1;
80235
+ }
80236
+ } else {
80237
+ source += "[^/]*";
80238
+ }
80239
+ continue;
80240
+ }
80241
+ if (char === "?") {
80242
+ source += "[^/]";
80243
+ continue;
80244
+ }
80245
+ source += char.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
80246
+ }
80247
+ return new RegExp(`${source}$`);
80248
+ }
80249
+ function manifestDirectories(manifest, context) {
80250
+ const directories = new Set;
80251
+ for (const entry of manifest) {
80252
+ assertBudget(context);
80253
+ let current = path88.posix.dirname(entry.path);
80254
+ while (current !== ".") {
80255
+ assertBudget(context);
80256
+ directories.add(current);
80257
+ current = path88.posix.dirname(current);
80258
+ }
80259
+ }
80260
+ return [...directories].sort();
80261
+ }
80262
+ function candidateOutputFiles(output, manifest, context) {
80263
+ const matcher = candidateGlob(output.pattern);
80264
+ if (output.kind === "file") {
80265
+ const matched = [];
80266
+ for (const entry of manifest) {
80267
+ assertBudget(context);
80268
+ if (!matcher.test(entry.path))
80269
+ continue;
80270
+ if (entry.kind === "symlink") {
80271
+ throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80272
+ }
80273
+ matched.push(entry);
80274
+ }
80275
+ return {
80276
+ matchedCount: matched.length,
80277
+ files: matched
80278
+ };
80279
+ }
80280
+ for (const entry of manifest) {
80281
+ assertBudget(context);
80282
+ if (entry.kind === "symlink" && matcher.test(entry.path)) {
80283
+ throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} matches a symlink`);
80284
+ }
80285
+ }
80286
+ const directories = [];
80287
+ for (const directory of manifestDirectories(manifest, context)) {
80288
+ assertBudget(context);
80289
+ if (matcher.test(directory))
80290
+ directories.push(directory);
80291
+ }
80292
+ const selected = new Map;
80293
+ for (const directory of directories) {
80294
+ assertBudget(context);
80295
+ const prefix = `${directory}/`;
80296
+ for (const entry of manifest) {
80297
+ assertBudget(context);
80298
+ if (!entry.path.startsWith(prefix))
80299
+ continue;
80300
+ if (entry.kind === "symlink") {
80301
+ throw new BenchmarkPhaseError("unsafe_path", `candidate output ${output.id} contains a symlink`);
80302
+ }
80303
+ selected.set(entry.path, entry);
80304
+ }
80305
+ }
80306
+ return {
80307
+ matchedCount: directories.length,
80308
+ files: [...selected.values()].sort((left, right) => left.path.localeCompare(right.path))
80309
+ };
80310
+ }
80311
+ async function copyCandidateOutput(output, manifest, spec, context) {
80312
+ const selected = candidateOutputFiles(output, manifest, context);
80313
+ if (!output.required && selected.matchedCount === 0)
80314
+ return [];
80315
+ const minimumMatches = output.required ? Math.max(1, output.min_matches) : output.min_matches;
80316
+ if (selected.matchedCount < minimumMatches || selected.matchedCount > output.max_matches) {
80317
+ throw new BenchmarkPhaseError("candidate_output_match_count", `candidate output ${output.id} matched ${selected.matchedCount} ${output.kind}s`, {
80318
+ actual: selected.matchedCount,
80319
+ min_matches: minimumMatches,
80320
+ max_matches: output.max_matches
80321
+ });
80322
+ }
80323
+ let totalBytes = 0;
80324
+ for (const file of selected.files) {
80325
+ assertBudget(context);
80326
+ totalBytes += file.size;
80327
+ }
80328
+ if (totalBytes > output.max_total_bytes) {
80329
+ throw new BenchmarkPhaseError("candidate_output_size_exceeded", `candidate output ${output.id} exceeds its byte limit`, { actual: totalBytes, max_total_bytes: output.max_total_bytes });
80330
+ }
80331
+ const copied = [];
80332
+ for (const frozenFile of selected.files) {
80333
+ assertBudget(context);
80334
+ const source = path88.resolve(spec.workspace_root, safeRelPath(frozenFile.path));
80335
+ const destination = path88.resolve(spec.logs_root, "candidate-outputs", output.id, safeRelPath(frozenFile.path));
80336
+ if (fs81.existsSync(destination)) {
80337
+ throw new BenchmarkPhaseError("destination_conflict", `candidate output destination already exists: ${output.id}/${frozenFile.path}`);
80338
+ }
80339
+ await atomicCopy(source, destination, frozenFile.mode, spec.workspace_root);
80340
+ const record3 = await recordFile(spec.logs_root, destination, "logs");
80341
+ if (record3.sha256 !== frozenFile.sha256 || record3.size !== frozenFile.size || record3.mode !== frozenFile.mode) {
80342
+ throw new BenchmarkPhaseError("evidence_tampered", `candidate output changed after the workspace freeze: ${frozenFile.path}`);
80343
+ }
80344
+ copied.push(record3);
80345
+ }
80346
+ return copied;
80347
+ }
80348
+ async function copyFrozenWorkspace(manifest, spec, context) {
80349
+ for (const entry of manifest) {
80350
+ assertBudget(context);
80351
+ if (entry.kind === "symlink") {
80352
+ throw new BenchmarkPhaseError("unsupported_workspace_symlink", `sandbox evaluator workspace cannot safely reproduce symlink: ${entry.path}`);
80353
+ }
80354
+ }
80355
+ const destinationRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-workspace-"));
80356
+ fs81.chmodSync(destinationRoot, 448);
80357
+ context.temporaryRoots.add(destinationRoot);
80358
+ for (const frozenFile of manifest) {
80359
+ assertBudget(context);
80360
+ const source = path88.resolve(spec.workspace_root, safeRelPath(frozenFile.path));
80361
+ const destination = path88.resolve(destinationRoot, safeRelPath(frozenFile.path));
80362
+ await atomicCopy(source, destination, frozenFile.mode, spec.workspace_root);
80363
+ const copied = await recordFile(destinationRoot, destination, "workspace");
80364
+ if (copied.sha256 !== frozenFile.sha256 || copied.size !== frozenFile.size || copied.mode !== frozenFile.mode) {
80365
+ throw new BenchmarkPhaseError("evidence_tampered", `workspace changed after it was frozen: ${frozenFile.path}`);
80366
+ }
80367
+ }
80368
+ return destinationRoot;
80369
+ }
80370
+ function evaluatorTestsPath(evaluator, spec) {
80371
+ if (!evaluator.tests_path)
80372
+ return spec.tests_root;
80373
+ const relative = normalizedRootRelative(evaluator.tests_path);
80374
+ assertNoSymlinkTraversal(spec.tests_root, relative);
80375
+ const candidate = path88.resolve(spec.tests_root, relative);
80376
+ if (!isWithin(spec.tests_root, candidate) || !fs81.existsSync(candidate)) {
80377
+ throw new BenchmarkPhaseError("invalid_tests_path", `evaluator tests_path does not exist: ${evaluator.tests_path}`);
80378
+ }
80379
+ const stat = fs81.lstatSync(candidate);
80380
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
80381
+ throw new BenchmarkPhaseError("invalid_tests_path", `evaluator tests_path must be a directory: ${evaluator.tests_path}`);
80382
+ }
80383
+ return candidate;
80384
+ }
80385
+ var CriterionResultSchema = exports_external.object({
80386
+ criterion_key: IdSchema,
80387
+ outcome: exports_external.enum(["satisfied", "not_satisfied", "not_applicable"]),
80388
+ explanation: exports_external.string().min(1).max(MAX_CRITERION_EXPLANATION_LENGTH),
80389
+ evidence_ids: exports_external.array(exports_external.string().min(1).max(MAX_CRITERION_EVIDENCE_ID_LENGTH)).max(MAX_CRITERION_EVIDENCE_IDS)
80390
+ }).strict().superRefine((value, context) => {
80391
+ if (new Set(value.evidence_ids).size !== value.evidence_ids.length) {
80392
+ context.addIssue({
80393
+ code: exports_external.ZodIssueCode.custom,
80394
+ path: ["evidence_ids"],
80395
+ message: "evidence_ids must be unique"
80396
+ });
80397
+ }
80398
+ });
80399
+ var CriteriaResultFileSchema = exports_external.object({
80400
+ criteria: exports_external.array(CriterionResultSchema).max(MAX_CRITERIA_PER_EVALUATOR)
80401
+ }).strict();
80402
+ function readCriterionResults(resultPath, criterionKeys, allowedEvidenceIds) {
80403
+ let opened;
80404
+ try {
80405
+ opened = openRegularFileNoFollow(resultPath, "criterion result", path88.dirname(resultPath));
80406
+ } catch (error2) {
80407
+ if (error2.code === "ENOENT") {
80408
+ throw new BenchmarkPhaseError("missing_criterion_result", "sandbox evaluator did not write its criterion result");
80409
+ }
80410
+ throw error2;
80411
+ }
80412
+ try {
80413
+ if (opened.stat.size > MAX_CRITERIA_RESULT_BYTES) {
80414
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result exceeds the 1 MiB limit");
80415
+ }
80416
+ let raw;
80417
+ try {
80418
+ raw = JSON.parse(readDescriptor(opened.fd).toString("utf8"));
80419
+ } catch {
80420
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result is not valid JSON");
80421
+ }
80422
+ let parsed;
80423
+ try {
80424
+ parsed = CriteriaResultFileSchema.parse(raw);
80425
+ } catch (error2) {
80426
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result does not match the required schema", error2 instanceof exports_external.ZodError ? error2.issues : undefined);
80427
+ }
80428
+ const byKey = new Map;
80429
+ for (const criterion of parsed.criteria) {
80430
+ if (byKey.has(criterion.criterion_key)) {
80431
+ throw new BenchmarkPhaseError("invalid_criterion_result", `criterion result is duplicated: ${criterion.criterion_key}`);
80432
+ }
80433
+ byKey.set(criterion.criterion_key, criterion);
80434
+ }
80435
+ const expected = new Set(criterionKeys);
80436
+ const extra = [...byKey.keys()].filter((key2) => !expected.has(key2));
80437
+ const missing = criterionKeys.filter((key2) => !byKey.has(key2));
80438
+ if (extra.length > 0 || missing.length > 0) {
80439
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result keys do not match the evaluator plan", { missing, extra });
80440
+ }
80441
+ const allowedEvidence = new Set(allowedEvidenceIds);
80442
+ const unknownEvidenceIds = [...new Set(parsed.criteria.flatMap((criterion) => criterion.evidence_ids))].filter((evidenceId) => !allowedEvidence.has(evidenceId));
80443
+ if (unknownEvidenceIds.length > 0) {
80444
+ throw new BenchmarkPhaseError("invalid_criterion_result", "criterion result cites evidence outside the evaluator plan", { unknown_evidence_ids: unknownEvidenceIds });
80445
+ }
80446
+ return criterionKeys.map((key2) => byKey.get(key2));
80447
+ } finally {
80448
+ fs81.closeSync(opened.fd);
80449
+ }
80450
+ }
80129
80451
  function trajectoryEvents(value) {
80130
80452
  if (Array.isArray(value))
80131
80453
  return value;
@@ -80144,7 +80466,7 @@ function eventType(event) {
80144
80466
  }
80145
80467
  return;
80146
80468
  }
80147
- async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvidence, context) {
80469
+ async function evaluateOne(evaluator, spec, manifest, finalOutput, trajectory, frozenEvidence, context) {
80148
80470
  const started = Date.now();
80149
80471
  const base2 = {
80150
80472
  id: evaluator.id,
@@ -80156,11 +80478,11 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80156
80478
  };
80157
80479
  if (evaluator.type === "output_assertion") {
80158
80480
  const assertion = evaluator.assertion;
80159
- let verdict2 = false;
80481
+ let verdict = false;
80160
80482
  if (assertion.operator === "exact")
80161
- verdict2 = finalOutput === assertion.expected;
80483
+ verdict = finalOutput === assertion.expected;
80162
80484
  if (assertion.operator === "contains")
80163
- verdict2 = finalOutput.includes(assertion.expected);
80485
+ verdict = finalOutput.includes(assertion.expected);
80164
80486
  if (assertion.operator === "regex") {
80165
80487
  const regexResult = await runCommand({
80166
80488
  id: `${evaluator.id}.regex`,
@@ -80186,17 +80508,17 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80186
80508
  if (regexResult.exitCode === 2) {
80187
80509
  throw new BenchmarkPhaseError("invalid_evaluator", `invalid regex in evaluator: ${evaluator.id}`);
80188
80510
  }
80189
- verdict2 = regexResult.exitCode === 0;
80511
+ verdict = regexResult.exitCode === 0;
80190
80512
  }
80191
- return { ...base2, status: verdict2 ? "passed" : "failed", verdict: verdict2, duration_ms: Date.now() - started };
80513
+ return { ...base2, status: verdict ? "passed" : "failed", verdict, duration_ms: Date.now() - started };
80192
80514
  }
80193
80515
  if (evaluator.type === "trajectory_assertion") {
80194
80516
  const count = trajectory.filter((event) => eventType(event) === evaluator.event_type).length;
80195
- const verdict2 = count >= evaluator.min_count && (evaluator.max_count === undefined || count <= evaluator.max_count);
80517
+ const verdict = count >= evaluator.min_count && (evaluator.max_count === undefined || count <= evaluator.max_count);
80196
80518
  return {
80197
80519
  ...base2,
80198
- status: verdict2 ? "passed" : "failed",
80199
- verdict: verdict2,
80520
+ status: verdict ? "passed" : "failed",
80521
+ verdict,
80200
80522
  duration_ms: Date.now() - started,
80201
80523
  details: { count, min_count: evaluator.min_count, max_count: evaluator.max_count }
80202
80524
  };
@@ -80217,16 +80539,16 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80217
80539
  throw new BenchmarkPhaseError("unsafe_path", `workspace assertion cannot target a symlink: ${relative}`);
80218
80540
  }
80219
80541
  const exists2 = stat !== null;
80220
- let verdict2 = false;
80542
+ let verdict = false;
80221
80543
  if (evaluator.assertion.operator === "exists")
80222
- verdict2 = exists2;
80544
+ verdict = exists2;
80223
80545
  if (evaluator.assertion.operator === "not_exists")
80224
- verdict2 = !exists2;
80546
+ verdict = !exists2;
80225
80547
  if (evaluator.assertion.operator === "sha256") {
80226
80548
  if (stat?.isFile()) {
80227
80549
  const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, spec.workspace_root);
80228
80550
  try {
80229
- verdict2 = await sha256OfDescriptor(opened.fd) === evaluator.assertion.expected;
80551
+ verdict = await sha256OfDescriptor(opened.fd) === evaluator.assertion.expected;
80230
80552
  } finally {
80231
80553
  fs81.closeSync(opened.fd);
80232
80554
  }
@@ -80236,35 +80558,78 @@ async function evaluateOne(evaluator, spec, finalOutput, trajectory, frozenEvide
80236
80558
  if (stat?.isFile()) {
80237
80559
  const opened = openRegularFileNoFollow(candidate, `workspace assertion ${relative}`, spec.workspace_root);
80238
80560
  try {
80239
- verdict2 = readDescriptor(opened.fd).toString("utf8").includes(evaluator.assertion.expected);
80561
+ verdict = readDescriptor(opened.fd).toString("utf8").includes(evaluator.assertion.expected);
80240
80562
  } finally {
80241
80563
  fs81.closeSync(opened.fd);
80242
80564
  }
80243
80565
  }
80244
80566
  }
80245
- return { ...base2, status: verdict2 ? "passed" : "failed", verdict: verdict2, duration_ms: Date.now() - started };
80567
+ return { ...base2, status: verdict ? "passed" : "failed", verdict, duration_ms: Date.now() - started };
80568
+ }
80569
+ let isolatedWorkspace;
80570
+ let privateResultRoot;
80571
+ try {
80572
+ const evaluatorWorkspace = await copyFrozenWorkspace(manifest, spec, context);
80573
+ isolatedWorkspace = evaluatorWorkspace;
80574
+ const testsPath = evaluatorTestsPath(evaluator, spec);
80575
+ const commandRoot = evaluator.root === "workspace" ? evaluatorWorkspace : spec.tests_root;
80576
+ const command = { id: evaluator.id, ...evaluator.command };
80577
+ const environment = {
80578
+ BRAINBASE_BENCHMARK_WORKSPACE: evaluatorWorkspace,
80579
+ BRAINBASE_BENCHMARK_TESTS: testsPath,
80580
+ BRAINBASE_BENCHMARK_LOGS: spec.logs_root,
80581
+ BRAINBASE_BENCHMARK_FINAL_OUTPUT: frozenEvidence.finalOutputPath,
80582
+ BRAINBASE_BENCHMARK_TRAJECTORY: frozenEvidence.trajectoryPath
80583
+ };
80584
+ let criterionResultPath;
80585
+ if (evaluator.criterion_keys) {
80586
+ privateResultRoot = fs81.mkdtempSync(path88.join(os16.tmpdir(), "brainbase-benchmark-criteria-"));
80587
+ fs81.chmodSync(privateResultRoot, 448);
80588
+ context.temporaryRoots.add(privateResultRoot);
80589
+ criterionResultPath = path88.join(privateResultRoot, "result.json");
80590
+ environment.BRAINBASE_BENCHMARK_CRITERIA_RESULT = criterionResultPath;
80591
+ }
80592
+ const result2 = await runCommand(command, commandRoot, spec, context, environment);
80593
+ const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80594
+ const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80595
+ let criterionResults;
80596
+ try {
80597
+ criterionResults = evaluator.criterion_keys && criterionResultPath ? readCriterionResults(criterionResultPath, evaluator.criterion_keys, evaluator.allowed_evidence_ids ?? []) : undefined;
80598
+ } catch (error2) {
80599
+ const normalized = stableError(error2);
80600
+ return {
80601
+ ...base2,
80602
+ status: "errored",
80603
+ verdict: null,
80604
+ duration_ms: result2.durationMs,
80605
+ details: {
80606
+ exit_code: result2.exitCode,
80607
+ error_code: normalized?.code ?? "invalid_criterion_result",
80608
+ error_message: normalized?.message ?? "criterion result validation failed"
80609
+ },
80610
+ stdout,
80611
+ stderr
80612
+ };
80613
+ }
80614
+ const verdict = result2.exitCode === 0;
80615
+ return {
80616
+ ...base2,
80617
+ status: verdict ? "passed" : "failed",
80618
+ verdict,
80619
+ duration_ms: result2.durationMs,
80620
+ details: { exit_code: result2.exitCode },
80621
+ stdout,
80622
+ stderr,
80623
+ ...criterionResults ? { criterion_results: criterionResults } : {}
80624
+ };
80625
+ } finally {
80626
+ for (const temporary of [privateResultRoot, isolatedWorkspace]) {
80627
+ if (!temporary)
80628
+ continue;
80629
+ fs81.rmSync(temporary, { recursive: true, force: true });
80630
+ context.temporaryRoots.delete(temporary);
80631
+ }
80246
80632
  }
80247
- const commandRoot = evaluator.root === "workspace" ? spec.workspace_root : spec.tests_root;
80248
- const command = { id: evaluator.id, ...evaluator.command };
80249
- const result2 = await runCommand(command, commandRoot, spec, context, {
80250
- BRAINBASE_BENCHMARK_WORKSPACE: spec.workspace_root,
80251
- BRAINBASE_BENCHMARK_TESTS: spec.tests_root,
80252
- BRAINBASE_BENCHMARK_LOGS: spec.logs_root,
80253
- BRAINBASE_BENCHMARK_FINAL_OUTPUT: frozenEvidence.finalOutputPath,
80254
- BRAINBASE_BENCHMARK_TRAJECTORY: frozenEvidence.trajectoryPath
80255
- });
80256
- const stdout = await writeLog(spec.logs_root, `${evaluator.id}.stdout.log`, result2.stdout, spec);
80257
- const stderr = await writeLog(spec.logs_root, `${evaluator.id}.stderr.log`, result2.stderr, spec);
80258
- const verdict = result2.exitCode === 0;
80259
- return {
80260
- ...base2,
80261
- status: verdict ? "passed" : "failed",
80262
- verdict,
80263
- duration_ms: result2.durationMs,
80264
- details: { exit_code: result2.exitCode },
80265
- stdout,
80266
- stderr
80267
- };
80268
80633
  }
80269
80634
  async function executeEvaluate(spec, context) {
80270
80635
  await downloadInputReference(spec.staging_root, spec.evidence.final_output, context);
@@ -80336,6 +80701,11 @@ async function executeEvaluate(spec, context) {
80336
80701
  outputs.push(artifact);
80337
80702
  context.outputs.push(artifact);
80338
80703
  }
80704
+ for (const candidateOutput of spec.candidate_outputs) {
80705
+ const copied = await copyCandidateOutput(candidateOutput, manifest, spec, context);
80706
+ outputs.push(...copied);
80707
+ context.outputs.push(...copied);
80708
+ }
80339
80709
  if (spec.capture_workspace_archive) {
80340
80710
  const regularFiles = manifest.filter((entry) => entry.kind !== "symlink").map((entry) => entry.path);
80341
80711
  const archive = path88.join(spec.logs_root, "candidate-workspace.tar.gz");
@@ -80363,8 +80733,9 @@ async function executeEvaluate(spec, context) {
80363
80733
  for (const evaluator of executionOrder) {
80364
80734
  assertBudget(context);
80365
80735
  const started = Date.now();
80736
+ let requiredResultError;
80366
80737
  try {
80367
- const evaluated = await evaluateOne(evaluator, spec, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
80738
+ const evaluated = await evaluateOne(evaluator, spec, manifest, finalOutput.buffer.toString("utf8"), trajectory, { finalOutputPath, trajectoryPath }, context);
80368
80739
  assertBudget(context);
80369
80740
  evaluators.push(evaluated);
80370
80741
  context.evaluators.push(evaluated);
@@ -80376,6 +80747,11 @@ async function executeEvaluate(spec, context) {
80376
80747
  outputs.push(evaluated.stderr);
80377
80748
  context.outputs.push(evaluated.stderr);
80378
80749
  }
80750
+ if (evaluated.status === "errored" && evaluator.required) {
80751
+ const errorCode = typeof evaluated.details?.error_code === "string" ? evaluated.details.error_code : "evaluator_execution_failed";
80752
+ const errorMessage2 = typeof evaluated.details?.error_message === "string" ? evaluated.details.error_message : "required evaluator execution failed";
80753
+ requiredResultError = new BenchmarkPhaseError(errorCode, errorMessage2);
80754
+ }
80379
80755
  } catch (error2) {
80380
80756
  const normalized = stableError(error2);
80381
80757
  const errored = {
@@ -80399,6 +80775,8 @@ async function executeEvaluate(spec, context) {
80399
80775
  throw error2;
80400
80776
  assertBudget(context);
80401
80777
  }
80778
+ if (requiredResultError)
80779
+ throw requiredResultError;
80402
80780
  }
80403
80781
  await verifyRecordsUnchanged([...context.inputs, ...context.outputs.slice(0, frozenOutputCount)], spec);
80404
80782
  if (!verifyOwnedDirectory(spec.tests_root, "tests", spec) || !verifyOwnedDirectory(spec.logs_root, "logs", spec)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainbase-labs/cli",
3
- "version": "0.24.0",
3
+ "version": "0.25.0-eng1209.2",
4
4
  "description": "Pack, share, and install agent templates across harnesses (Claude Code, Codex, ...).",
5
5
  "type": "module",
6
6
  "bin": {