@polderlabs/bizar 10.18.0 → 10.19.1

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 (39) hide show
  1. package/cli/bin.mjs +75 -2
  2. package/cli/commands/bench.mjs +360 -0
  3. package/cli/commands/explain-run.mjs +278 -0
  4. package/cli/commands/objective-scheduler.mjs +609 -0
  5. package/cli/commands/release-provenance.mjs +261 -0
  6. package/cli/commands/spec-list.mjs +225 -0
  7. package/cli/commands/verify-release.mjs +104 -0
  8. package/cli/cost-gate.mjs +323 -0
  9. package/config/claude/hooks/permission-request.mjs +176 -11
  10. package/package.json +1 -1
  11. package/packages/sdk/dist/autonomy/evidence-bundle.d.ts +10 -1
  12. package/packages/sdk/dist/autonomy/evidence-bundle.js +8 -0
  13. package/packages/sdk/dist/autonomy/index.d.ts +3 -3
  14. package/packages/sdk/dist/autonomy/index.js +3 -3
  15. package/packages/sdk/dist/autonomy/objective-run.d.ts +17 -0
  16. package/packages/sdk/dist/autonomy/objective-run.js +28 -0
  17. package/packages/sdk/dist/autonomy/outcome-record.d.ts +9 -0
  18. package/packages/sdk/dist/autonomy/outcome-record.js +8 -0
  19. package/packages/sdk/dist/bench/auto-reduction.d.ts +91 -0
  20. package/packages/sdk/dist/bench/auto-reduction.js +130 -0
  21. package/packages/sdk/dist/bench/efficiency.d.ts +174 -0
  22. package/packages/sdk/dist/bench/efficiency.js +202 -0
  23. package/packages/sdk/dist/bench/index.d.ts +15 -0
  24. package/packages/sdk/dist/bench/index.js +13 -0
  25. package/packages/sdk/dist/index.d.ts +2 -0
  26. package/packages/sdk/dist/index.js +4 -0
  27. package/packages/sdk/dist/release/index.d.ts +20 -0
  28. package/packages/sdk/dist/release/index.js +20 -0
  29. package/packages/sdk/dist/release/known-good-releases.d.ts +101 -0
  30. package/packages/sdk/dist/release/known-good-releases.js +200 -0
  31. package/packages/sdk/dist/release/provenance.d.ts +82 -0
  32. package/packages/sdk/dist/release/provenance.js +62 -0
  33. package/packages/sdk/dist/release/sbom.d.ts +77 -0
  34. package/packages/sdk/dist/release/sbom.js +114 -0
  35. package/packages/sdk/dist/release/signature.d.ts +84 -0
  36. package/packages/sdk/dist/release/signature.js +139 -0
  37. package/packages/sdk/dist/version.d.ts +1 -1
  38. package/packages/sdk/dist/version.js +1 -1
  39. package/packages/sdk/package.json +3 -2
package/cli/bin.mjs CHANGED
@@ -109,6 +109,10 @@ function showHelp() {
109
109
  restore Restore BizarHarness from a backup
110
110
  validate Validate the Bizar install
111
111
  setup-provider Configure a provider in ~/.claude/settings.json (since v6.2.2 installer doesn't touch providers)
112
+ release-provenance Generate SBOM + provenance + minisig for a release (audit #83)
113
+ verify-release Verify a release artifact set against the pinned allowlist
114
+ spec-list List SDK schemas, policy docs, and mirror sync status (audit #84)
115
+ bench Efficiency benchmarks + auto-fan-out rule (audit #85)
112
116
  team Run the office-manager orchestration agent
113
117
  subagent Run a named agent in read-only plan mode
114
118
  run Run Claude Code once (optionally --bg)
@@ -188,6 +192,9 @@ async function main() {
188
192
  // Pass --help to the command. Commands dispatched through util.mjs
189
193
  // (audit, init, export, doctor, backup, restore, etc.) don't have
190
194
  // their own cli/commands/<name>.mjs — they all live in util.mjs.
195
+ // Direct command modules (bench, release-provenance, verify-release,
196
+ // spec-list, …) have their own --help handling and accept
197
+ // run(cmdArgs); they fall through to the switch below.
191
198
  const UTIL_COMMANDS = new Set([
192
199
  'audit', 'init', 'export', 'test-gate',
193
200
  'doctor', 'repair', 'heads-up', 'backup', 'restore',
@@ -204,8 +211,6 @@ async function main() {
204
211
  const { runMigrate } = await import('./migrate.mjs');
205
212
  await runMigrate(['--help']);
206
213
  return;
207
- } else {
208
- mod = await importCommand(cmd);
209
214
  }
210
215
  if (mod && typeof mod.run === 'function') {
211
216
  await mod.run(cmd, cmdArgs, true);
@@ -447,6 +452,58 @@ async function main() {
447
452
  break;
448
453
  }
449
454
 
455
+ case 'release-provenance': {
456
+ const mod = await importCommand('release-provenance');
457
+ if (!mod) {
458
+ console.error(chalk.red(` ✗ Could not load release-provenance command module`));
459
+ process.exit(EXIT_ERROR);
460
+ return;
461
+ }
462
+ dbg('loaded command module:', 'release-provenance');
463
+ const code = await mod.run(cmdArgs);
464
+ if (typeof code === 'number') process.exit(code);
465
+ break;
466
+ }
467
+
468
+ case 'verify-release': {
469
+ const mod = await importCommand('verify-release');
470
+ if (!mod) {
471
+ console.error(chalk.red(` ✗ Could not load verify-release command module`));
472
+ process.exit(EXIT_ERROR);
473
+ return;
474
+ }
475
+ dbg('loaded command module:', 'verify-release');
476
+ const code = await mod.run(cmdArgs);
477
+ if (typeof code === 'number') process.exit(code);
478
+ break;
479
+ }
480
+
481
+ case 'spec-list': {
482
+ const mod = await importCommand('spec-list');
483
+ if (!mod) {
484
+ console.error(chalk.red(` ✗ Could not load spec-list command module`));
485
+ process.exit(EXIT_ERROR);
486
+ return;
487
+ }
488
+ dbg('loaded command module:', 'spec-list');
489
+ const code = await mod.run(cmdArgs);
490
+ if (typeof code === 'number') process.exit(code);
491
+ break;
492
+ }
493
+
494
+ case 'bench': {
495
+ const mod = await importCommand('bench');
496
+ if (!mod) {
497
+ console.error(chalk.red(` ✗ Could not load bench command module`));
498
+ process.exit(EXIT_ERROR);
499
+ return;
500
+ }
501
+ dbg('loaded command module:', 'bench');
502
+ const code = await mod.run(cmdArgs);
503
+ if (typeof code === 'number') process.exit(code);
504
+ break;
505
+ }
506
+
450
507
  case 'improve': {
451
508
  const mod = await importCommand('improve');
452
509
  if (!mod) {
@@ -521,6 +578,22 @@ async function main() {
521
578
  return;
522
579
  }
523
580
 
581
+ case 'explain-run': {
582
+ const mod = await importCommand('explain-run');
583
+ if (!mod) {
584
+ console.error(chalk.red(` ✗ Could not load explain-run command module`));
585
+ process.exit(EXIT_ERROR);
586
+ return;
587
+ }
588
+ dbg('loaded command module:', 'explain-run');
589
+ const found = await mod.run(cmd, cmdArgs, isHelpRequest);
590
+ if (found === false) {
591
+ console.error(chalk.red(` ✗ Usage: bizar explain-run <id> — run 'bizar explain-run --help'`));
592
+ process.exit(EXIT_USAGE);
593
+ }
594
+ break;
595
+ }
596
+
524
597
  default: {
525
598
  console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
526
599
  showHelp();
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli/commands/bench.mjs
4
+ *
5
+ * `bizar bench` — audit #85 (P2 efficiency benchmarks) operator surface.
6
+ *
7
+ * The SDK module at `packages/sdk/src/bench/efficiency.ts` is pure
8
+ * (no I/O, no env reads); this CLI is the thin operator wrapper that
9
+ * supplies the standard task profiles + auto-fan-out report and emits
10
+ * the JSON / human-readable output.
11
+ *
12
+ * Subcommands:
13
+ *
14
+ * bizar bench default: run all benchmarks
15
+ * bizar bench single-vs-multi compare fanOut=1 vs fanOut=4
16
+ * bizar bench sequential-vs-parallel compare serial vs parallel DAG
17
+ * bizar bench reviewers compare 0/1/2 reviewer gates
18
+ * bizar bench worktree compare worktree overhead vs saved conflict time
19
+ * bizar bench recommend-fan-out apply auto-fan-out rule to a context
20
+ * bizar bench --format=json|human output format (default: human)
21
+ * bizar bench --seed=N deterministic seed (default: 42)
22
+ *
23
+ * Why this exists: the audit calls out six required comparisons. We
24
+ * ship five as built-in subcommands; the sixth (research depth) is
25
+ * operator-configurable via `--tasks=<file.json>` and is intentionally
26
+ * left to workflow owners to specialize. The auto-fan-out report is
27
+ * the audit's "automatically reduce fan-out when coordination
28
+ * overhead exceeds expected benefit" rule, surfaced as a runnable
29
+ * command rather than buried in a router.
30
+ */
31
+
32
+ import { readFileSync } from 'node:fs';
33
+ import {
34
+ EFFICIENCY_BENCH_SCHEMA_VERSION,
35
+ AUTO_REDUCTION_SCHEMA_VERSION,
36
+ runBench,
37
+ compareConfigurations,
38
+ recommendFanOut,
39
+ } from '../../packages/sdk/dist/bench/index.js';
40
+
41
+ const REPO_ROOT = process.cwd();
42
+
43
+ /**
44
+ * Default synthetic workload: a 4-task chain where each task is
45
+ * roughly independent. Matches a typical "implement a feature" pass
46
+ * (research → design → implement → verify).
47
+ */
48
+ function defaultTasks() {
49
+ return [
50
+ { id: 'research', workMs: 4_000, costUsd: 12_000, successProb: 0.95, quality: 0.80 },
51
+ { id: 'design', workMs: 3_000, costUsd: 10_000, successProb: 0.90, quality: 0.85 },
52
+ { id: 'implement',workMs: 8_000, costUsd: 24_000, successProb: 0.85, quality: 0.75 },
53
+ { id: 'verify', workMs: 3_000, costUsd: 8_000, successProb: 0.92, quality: 0.88 },
54
+ ];
55
+ }
56
+
57
+ function loadTasks(args) {
58
+ const tasksFlag = args.find((a) => a.startsWith('--tasks='));
59
+ if (tasksFlag) {
60
+ const path = tasksFlag.slice('--tasks='.length);
61
+ const abs = path.startsWith('/') ? path : `${REPO_ROOT}/${path}`;
62
+ const raw = JSON.parse(readFileSync(abs, 'utf8'));
63
+ if (!Array.isArray(raw)) {
64
+ throw new TypeError('--tasks=<file.json> must contain a JSON array');
65
+ }
66
+ return raw;
67
+ }
68
+ return defaultTasks();
69
+ }
70
+
71
+ function seedOf(args) {
72
+ const sFlag = args.find((a) => a.startsWith('--seed='));
73
+ return sFlag ? Number(sFlag.slice('--seed='.length)) : 42;
74
+ }
75
+
76
+ function formatOf(args) {
77
+ const fFlag = args.find((a) => a.startsWith('--format='));
78
+ return fFlag ? fFlag.slice('--format='.length) : 'human';
79
+ }
80
+
81
+ /** Configurations for the single-vs-multi comparison. */
82
+ function configsSingleVsMulti() {
83
+ return [
84
+ { label: 'single-agent', fanOut: 1, reviewerCount: 1, parallel: false, coordinationOverheadRatio: 0.0 },
85
+ { label: 'multi-agent-par', fanOut: 4, reviewerCount: 1, parallel: true, coordinationOverheadRatio: 0.15 },
86
+ ];
87
+ }
88
+
89
+ function configsSequentialVsParallel() {
90
+ return [
91
+ { label: 'sequential', fanOut: 1, reviewerCount: 1, parallel: false, coordinationOverheadRatio: 0.0 },
92
+ { label: 'parallel-dag', fanOut: 4, reviewerCount: 1, parallel: true, coordinationOverheadRatio: 0.10 },
93
+ ];
94
+ }
95
+
96
+ function configsReviewers() {
97
+ return [
98
+ { label: 'no-reviewers', fanOut: 1, reviewerCount: 0, parallel: false, coordinationOverheadRatio: 0.0 },
99
+ { label: '1-reviewer', fanOut: 1, reviewerCount: 1, parallel: false, coordinationOverheadRatio: 0.0 },
100
+ { label: '2-reviewers', fanOut: 1, reviewerCount: 2, parallel: false, coordinationOverheadRatio: 0.0 },
101
+ ];
102
+ }
103
+
104
+ function configsWorktree() {
105
+ // 4 workers with and without worktree overhead.
106
+ return [
107
+ { label: 'no-worktree', fanOut: 4, reviewerCount: 1, parallel: true, coordinationOverheadRatio: 0.10 },
108
+ { label: 'with-worktree', fanOut: 4, reviewerCount: 1, parallel: true, coordinationOverheadRatio: 0.35 },
109
+ ];
110
+ }
111
+
112
+ function runComparison(tasks, configs, seed) {
113
+ const [a, b] = configs;
114
+ return compareConfigurations(tasks, a, b, seed);
115
+ }
116
+
117
+ function benchSingleVsMulti(tasks, seed) {
118
+ return runComparison(tasks, configsSingleVsMulti(), seed);
119
+ }
120
+
121
+ function benchSequentialVsParallel(tasks, seed) {
122
+ return runComparison(tasks, configsSequentialVsParallel(), seed);
123
+ }
124
+
125
+ function benchReviewers(tasks, seed) {
126
+ const configs = configsReviewers();
127
+ return configs.map((c) => runBench(tasks, c, seed));
128
+ }
129
+
130
+ function benchWorktree(tasks, seed) {
131
+ return runComparison(tasks, configsWorktree(), seed);
132
+ }
133
+
134
+ /**
135
+ * Render a single BenchResult as a fixed-width row.
136
+ */
137
+ function formatRow(r) {
138
+ const cost = Number.isFinite(r.costPerVerifiedUsd)
139
+ ? (r.costPerVerifiedUsd / 1_000_000).toFixed(4) + ' USD'
140
+ : '∞';
141
+ const wall = Number.isFinite(r.wallClockPerVerifiedMs)
142
+ ? r.wallClockPerVerifiedMs.toFixed(0) + ' ms'
143
+ : '∞';
144
+ return [
145
+ r.configLabel.padEnd(22),
146
+ `fanOut=${String(r.fanOut).padEnd(2)}`,
147
+ `reviewers=${r.reviewerCount}`,
148
+ `verified=${String(r.verifiedOutcomes).padEnd(2)}/${r.totalAttempts}`,
149
+ `cost/v=${cost.padStart(10)}`,
150
+ `wall/v=${wall.padStart(10)}`,
151
+ ].join(' ');
152
+ }
153
+
154
+ function renderHumanSingleVsMulti(comparison) {
155
+ const out = [];
156
+ out.push(' single-agent vs multi-agent (audit #85)');
157
+ out.push('');
158
+ out.push(' ' + formatRow(comparison.a));
159
+ out.push(' ' + formatRow(comparison.b));
160
+ out.push('');
161
+ out.push(
162
+ ` cost ratio (a/b): ${comparison.costRatioAOverB.toFixed(2)} wall ratio (a/b): ${comparison.wallClockRatioAOverB.toFixed(2)}`,
163
+ );
164
+ out.push(
165
+ ` winner by cost: ${comparison.costWinner} winner by wall-clock: ${comparison.wallClockWinner}`,
166
+ );
167
+ return out.join('\n');
168
+ }
169
+
170
+ function renderHumanSequentialVsParallel(comparison) {
171
+ const out = [];
172
+ out.push(' sequential vs parallel DAG (audit #85)');
173
+ out.push('');
174
+ out.push(' ' + formatRow(comparison.a));
175
+ out.push(' ' + formatRow(comparison.b));
176
+ out.push('');
177
+ out.push(
178
+ ` wall-clock speedup (a/b): ${comparison.wallClockRatioAOverB.toFixed(2)} (lower = sequential is faster)`,
179
+ );
180
+ return out.join('\n');
181
+ }
182
+
183
+ function renderHumanReviewers(results) {
184
+ const out = [];
185
+ out.push(' reviewer count vs verified outcomes (audit #85)');
186
+ out.push('');
187
+ for (const r of results) {
188
+ out.push(' ' + formatRow(r));
189
+ }
190
+ out.push('');
191
+ out.push(' expectation: 0 reviewers = cheapest, highest defect rate;');
192
+ out.push(' 2 reviewers = lowest defect rate, ~2× reviewer cost.');
193
+ return out.join('\n');
194
+ }
195
+
196
+ function renderHumanWorktree(comparison) {
197
+ const out = [];
198
+ out.push(' worktree overhead vs saved conflict time (audit #85)');
199
+ out.push('');
200
+ out.push(' ' + formatRow(comparison.a));
201
+ out.push(' ' + formatRow(comparison.b));
202
+ out.push('');
203
+ out.push(
204
+ ` observed overhead ratios: a=${comparison.a.observedOverheadRatio.toFixed(2)} b=${comparison.b.observedOverheadRatio.toFixed(2)}`,
205
+ );
206
+ out.push(
207
+ ` verdict: ${comparison.a.totalWallClockMs <= comparison.b.totalWallClockMs ? 'no-worktree' : 'with-worktree'} wins on wall-clock`,
208
+ );
209
+ return out.join('\n');
210
+ }
211
+
212
+ function renderHumanAutoReduction(decision, ctx) {
213
+ const out = [];
214
+ out.push(' recommend-fan-out (auto-reduction rule, audit #85)');
215
+ out.push('');
216
+ out.push(` context: fanOut=${ctx.currentFanOut} overheadRatio=${ctx.historicalCoordinationOverheadRatio} expectedBenefitRatio=${ctx.expectedBenefitRatio} defectEscapeRate=${ctx.historicalDefectEscapeRate}`);
217
+ out.push(` recommended fan-out: ${decision.recommendedFanOut}`);
218
+ out.push(` reason: ${decision.reason}`);
219
+ out.push(` overhead-vs-benefit: ${decision.overheadVsBenefitGap.toFixed(3)}`);
220
+ out.push(` reduced: ${decision.reduced}`);
221
+ out.push('');
222
+ out.push(' rule:');
223
+ out.push(' defectEscapeRate > 0.20 → verifier-cannot-certify (fan-out unchanged; widen reviewers)');
224
+ out.push(' overheadRatio >= benefit → reduce by one step (single-worker-fan-out at floor)');
225
+ out.push(' otherwise → keep-current');
226
+ return out.join('\n');
227
+ }
228
+
229
+ function parseFlags(args) {
230
+ const flags = {};
231
+ for (let i = 0; i < args.length; i++) {
232
+ const arg = args[i];
233
+ if (arg.startsWith('--')) {
234
+ const key = arg.slice(2);
235
+ const eq = key.indexOf('=');
236
+ if (eq >= 0) {
237
+ flags[key.slice(0, eq)] = key.slice(eq + 1);
238
+ } else {
239
+ const next = args[i + 1];
240
+ if (!next || next.startsWith('--')) flags[key] = true;
241
+ else { flags[key] = next; i++; }
242
+ }
243
+ }
244
+ }
245
+ return flags;
246
+ }
247
+
248
+ function parseAutoReductionFlags(args) {
249
+ // bizar bench recommend-fan-out --fan-out=N --overhead=R --benefit=R --escape=R
250
+ const flags = parseFlags(args);
251
+ const fanOut = Number(flags['fan-out'] ?? flags.fanOut ?? 4);
252
+ const overhead = Number(flags.overhead ?? 0.5);
253
+ const benefit = Number(flags.benefit ?? 0.6);
254
+ const escape = Number(flags.escape ?? 0.05);
255
+ return {
256
+ currentFanOut: fanOut,
257
+ historicalCoordinationOverheadRatio: overhead,
258
+ expectedBenefitRatio: benefit,
259
+ historicalDefectEscapeRate: escape,
260
+ };
261
+ }
262
+
263
+ export const USAGE = `
264
+ bizar bench — audit #85 efficiency benchmarks + auto-fan-out rule
265
+
266
+ Usage:
267
+ bizar bench [single-vs-multi | sequential-vs-parallel | reviewers | worktree]
268
+ [--format=json|human] [--seed=N] [--tasks=<file.json>]
269
+ bizar bench recommend-fan-out --fan-out=N --overhead=R --benefit=R --escape=R
270
+ [--format=json|human]
271
+
272
+ Subcommands (default: run all four):
273
+ single-vs-multi fanOut=1 sequential vs fanOut=4 parallel
274
+ sequential-vs-parallel serial 4-task chain vs parallel 4-task DAG
275
+ reviewers 0/1/2 reviewer gates on the same task set
276
+ worktree coordination overhead 0.10 vs 0.35
277
+
278
+ recommend-fan-out:
279
+ bizar bench recommend-fan-out --fan-out=4 --overhead=0.5 --benefit=0.6 --escape=0.05
280
+ Applies the audit's "automatically reduce fan-out when coordination
281
+ overhead exceeds expected benefit" rule.
282
+
283
+ Schema versions:
284
+ bench/efficiency.ts ${EFFICIENCY_BENCH_SCHEMA_VERSION}
285
+ bench/auto-reduction.ts ${AUTO_REDUCTION_SCHEMA_VERSION}
286
+ `;
287
+
288
+ export async function run(subargs) {
289
+ if (subargs.includes('--help') || subargs.includes('-h')) {
290
+ console.log(USAGE);
291
+ return 0;
292
+ }
293
+
294
+ const subcommand = subargs.find((a) => !a.startsWith('--'));
295
+
296
+ if (subcommand === 'recommend-fan-out') {
297
+ const ctx = parseAutoReductionFlags(subargs);
298
+ const decision = recommendFanOut(ctx);
299
+ if (formatOf(subargs) === 'json') {
300
+ console.log(JSON.stringify({ context: ctx, decision, schemaVersion: AUTO_REDUCTION_SCHEMA_VERSION }, null, 2));
301
+ } else {
302
+ console.log(renderHumanAutoReduction(decision, ctx));
303
+ }
304
+ return 0;
305
+ }
306
+
307
+ const tasks = loadTasks(subargs);
308
+ const seed = seedOf(subargs);
309
+ const format = formatOf(subargs);
310
+ const runOne = subcommand === undefined;
311
+ const want = (name) => runOne || subcommand === name;
312
+
313
+ const out = { schemaVersion: EFFICIENCY_BENCH_SCHEMA_VERSION, generatedAt: new Date().toISOString() };
314
+
315
+ if (want('single-vs-multi')) {
316
+ out.singleVsMulti = benchSingleVsMulti(tasks, seed);
317
+ }
318
+ if (want('sequential-vs-parallel')) {
319
+ out.sequentialVsParallel = benchSequentialVsParallel(tasks, seed);
320
+ }
321
+ if (want('reviewers')) {
322
+ out.reviewers = benchReviewers(tasks, seed);
323
+ }
324
+ if (want('worktree')) {
325
+ out.worktree = benchWorktree(tasks, seed);
326
+ }
327
+
328
+ if (Object.keys(out).length === 2) {
329
+ console.log(USAGE);
330
+ return 1;
331
+ }
332
+
333
+ if (format === 'json') {
334
+ console.log(JSON.stringify(out, null, 2));
335
+ } else {
336
+ console.log(`bizar bench — audit #85 (generated ${out.generatedAt}, seed=${seed})`);
337
+ console.log('');
338
+ if (out.singleVsMulti) {
339
+ console.log(renderHumanSingleVsMulti(out.singleVsMulti));
340
+ console.log('');
341
+ }
342
+ if (out.sequentialVsParallel) {
343
+ console.log(renderHumanSequentialVsParallel(out.sequentialVsParallel));
344
+ console.log('');
345
+ }
346
+ if (out.reviewers) {
347
+ console.log(renderHumanReviewers(out.reviewers));
348
+ console.log('');
349
+ }
350
+ if (out.worktree) {
351
+ console.log(renderHumanWorktree(out.worktree));
352
+ console.log('');
353
+ }
354
+ }
355
+ return 0;
356
+ }
357
+
358
+ if (import.meta.url === `file://${process.argv[1]}`) {
359
+ run(process.argv.slice(2)).then((code) => process.exit(code));
360
+ }