@polderlabs/bizar 10.23.4 → 10.23.6

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/AGENTS.md CHANGED
@@ -59,11 +59,13 @@ actions with `permissionDecision: "ask"`; that escalation list is the
59
59
  authoritative floor, not a starting point.
60
60
 
61
61
  Native dynamic workflows under `config/workflows/` and `~/.claude/workflows/`
62
- are the primary dispatch mechanism for shaped work (research / implement /
63
- debug / review). Small, deterministic, repository-local tasks go directly to
64
- one isolated worker: inspect, make the smallest change, and run the smallest
65
- proving check. Do not add research, planning, review, or duplicate workers to
66
- such tasks. For work that needs 3+ long-lived workers with bounded cross-talk,
62
+ are the required dispatch mechanism for every primary request except an
63
+ unmistakably tiny single-target copy/style/format edit with no behavior or test
64
+ change. Mike may execute that narrow exception directly. All other work enters
65
+ the matching research / implement / debug / review workflow and uses at least
66
+ one explicitly modeled editing subagent with call-level worktree isolation.
67
+ Do not add unnecessary phases or duplicate workers. For work that needs 3+
68
+ long-lived workers with bounded cross-talk,
67
69
  Mike invokes a workflow that fans out as a native agent team; the team is
68
70
  host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
69
71
  Anthropic's docs `team_name` is deprecated and ignored. When two or more
@@ -132,10 +134,15 @@ guess-and-try remains prohibited.
132
134
  The autonomy and approval policy above governs this execution model. The project defaults to `acceptEdits`; eligible operators may opt into Claude Code Auto mode.
133
135
 
134
136
  Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
135
- Mike handles small, deterministic repository-local changes directly and uses
136
- the smallest proving check. It delegates when worktree isolation, specialist
137
- expertise, or real parallelism materially helps. A Bizar custom agent already
138
- executing its assigned role does not recursively dispatch itself.
137
+ The installer sets Claude Code's global `agent` setting to Mike's frontmatter
138
+ name (`mike`),
139
+ and a session-scoped routing guard requires a successful native Workflow before
140
+ substantive primary-session mutation. Read-only inspection remains available.
141
+ Mike directly executes only the tiny edit exception above. Every other request
142
+ must invoke a native Bizar workflow before mutation; the workflow dispatches
143
+ worktree-isolated subagents while Mike owns integration and final verification.
144
+ A Bizar custom agent already executing its assigned role does not recursively
145
+ dispatch itself.
139
146
 
140
147
  Every shipped agent has `WebSearch` access. Before proposing, explaining,
141
148
  troubleshooting, or implementing behavior from an external API, library,
@@ -145,8 +152,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
145
152
  documentation is unavailable or ambiguous, inspect authoritative source code
146
153
  and report the evidence gap.
147
154
 
148
- For shaped requests, `office-manager` uses only the phases that reduce a known
149
- risk; direct tasks skip this pipeline:
155
+ For workflow-routed requests, `office-manager` uses only the phases that reduce
156
+ a known risk; only the tiny edit exception skips this pipeline:
150
157
 
151
158
  1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
152
159
  2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
package/cli/bin.mjs CHANGED
@@ -113,7 +113,7 @@ function showHelp() {
113
113
  backup Create / list / verify / delete backups of BizarHarness state
114
114
  restore Restore BizarHarness from a backup
115
115
  validate Validate the Bizar install
116
- setup-provider Configure a provider in ~/.claude/settings.json (since v6.2.2 installer doesn't touch providers)
116
+ setup-provider Configure the global provider used by Bizar and Claude Code
117
117
  release-provenance Generate SBOM + provenance + minisig for a release (audit #83)
118
118
  verify-release Verify a release artifact set against the pinned allowlist
119
119
  spec-list List SDK schemas, policy docs, and mirror sync status (audit #84)
@@ -56,7 +56,7 @@ export async function runClaudeTeam(args = []) {
56
56
  console.error(chalk.red(` ✗ Missing ${router}; run bizar install`));
57
57
  return 1;
58
58
  }
59
- return spawnClaude(['-p', '--name', name, '--agent', 'office-manager', mission, ...rest]);
59
+ return spawnClaude(['-p', '--name', name, '--agent', 'mike', mission, ...rest]);
60
60
  }
61
61
 
62
62
  export async function runClaudeSubagent(args = []) {
@@ -80,7 +80,9 @@ export async function runClaudeRun(args = []) {
80
80
  console.log(' Usage: bizar run [--bg] <prompt>');
81
81
  return 2;
82
82
  }
83
- return spawnClaude(background ? ['--bg', '-p', prompt] : ['-p', prompt]);
83
+ return spawnClaude(background
84
+ ? ['--bg', '-p', '--agent', 'mike', prompt]
85
+ : ['-p', '--agent', 'mike', prompt]);
84
86
  }
85
87
 
86
88
  export async function run(name, args, isHelpRequest) {
@@ -19,6 +19,7 @@ const PRETOOL_SAFETY_LEAVES = new Set([
19
19
  'pretooluse-editwrite', 'path-ownership-guard',
20
20
  'pretooluse-bash', 'git-workflow-guard',
21
21
  'agent-model-guard',
22
+ 'workflow-route-guard',
22
23
  ]);
23
24
 
24
25
  export const HOOK_PROGRAMS = Object.freeze({
@@ -47,6 +48,7 @@ export const HOOK_PROGRAMS = Object.freeze({
47
48
  'team-lifecycle': 'team-lifecycle.mjs',
48
49
  'verify-deliverables': 'verify-deliverables.mjs',
49
50
  'worker-suggest': 'worker-suggest.mjs',
51
+ 'workflow-route-guard': 'workflow-route-guard.mjs',
50
52
  'worktree-archive': 'worktree-archive.mjs',
51
53
  'worktree-bootstrap': 'worktree-bootstrap.mjs',
52
54
  });
@@ -55,6 +57,7 @@ export const EVENT_CHAINS = Object.freeze({
55
57
  'user-prompt-submit': Object.freeze([
56
58
  'control-inbox',
57
59
  'keyword-router',
60
+ 'workflow-route-guard',
58
61
  'worker-suggest',
59
62
  'thinking-route',
60
63
  'telemetry',
@@ -230,16 +233,17 @@ export function selectEventChain(eventKey, input = '') {
230
233
 
231
234
  if (eventKey === 'pre-tool-use') {
232
235
  if (/^(Write|Edit|MultiEdit)$/.test(toolName)) {
233
- return ['pretooluse-editwrite', 'path-ownership-guard'];
236
+ return ['workflow-route-guard', 'pretooluse-editwrite', 'path-ownership-guard'];
234
237
  }
235
238
  if (toolName === 'Bash') {
236
- return ['pretooluse-bash', 'git-workflow-guard'];
239
+ return ['workflow-route-guard', 'pretooluse-bash', 'git-workflow-guard'];
237
240
  }
238
- if (toolName === 'Agent') return ['agent-model-guard'];
241
+ if (toolName === 'Agent') return ['workflow-route-guard', 'agent-model-guard'];
239
242
  return [];
240
243
  }
241
244
 
242
245
  if (eventKey === 'post-tool-use') {
246
+ if (toolName === 'Workflow') return ['workflow-route-guard'];
243
247
  if (/^(Write|Edit|MultiEdit)$/.test(toolName)) return ['posttooluse-editwrite'];
244
248
  if (toolName === 'Bash') return [];
245
249
  return [];
@@ -29,7 +29,8 @@ export function showInstallHelp() {
29
29
  from the repo. Preserves ~/.config/bizar/
30
30
  login state. Combine with --yes to skip prompts.
31
31
  bizar install --deep Alias for --force (clean-install semantics)
32
- bizar install --yes Assume yes for any non-destructive prompt
32
+ bizar install --yes Non-interactive install (CI/script friendly)
33
+ bizar install --non-interactive Alias for --yes
33
34
  bizar install --help Show this help
34
35
 
35
36
  Description:
@@ -59,8 +60,13 @@ export function showInstallHelp() {
59
60
  4. Registers the Bizar MCP server in ~/.claude/settings.json.
60
61
  5. Wires Claude Code lifecycle hooks (SessionStart / PreToolUse /
61
62
  PostToolUse / UserPromptSubmit) under ~/.claude/hooks/.
62
- 6. Runs 'bizar doctor' as a post-install health check.
63
- No API key collection, no interactive prompts.
63
+ 6. In a terminal, confirms the install and securely asks for a provider
64
+ URL and key only when they are not already configured.
65
+ 7. Runs 'bizar doctor' as a post-install health check.
66
+
67
+ Provider settings are global (~/.claude/settings.json), so they work from
68
+ every project. Key input is hidden. Use --yes or --non-interactive to skip
69
+ all prompts; missing provider values then produce actionable guidance.
64
70
  `);
65
71
  }
66
72
 
@@ -2,7 +2,7 @@
2
2
  * Configure Claude Code's provider environment in settings.json.
3
3
  *
4
4
  * Bizar does not maintain a parallel provider registry. Claude Code reads
5
- * ANTHROPIC_BASE_URL, ANTHROPIC_API_KEY, and ANTHROPIC_MODEL directly.
5
+ * ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, and ANTHROPIC_MODEL directly.
6
6
  */
7
7
  import chalk from 'chalk';
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
@@ -41,10 +41,16 @@ export function parseProviderArgs(args = []) {
41
41
 
42
42
  export function updateProviderSettings(current, options) {
43
43
  const next = { ...current, env: { ...(current.env || {}) } };
44
- if (options.gateway !== undefined) next.env.ANTHROPIC_BASE_URL = options.gateway;
45
- if (options.key !== undefined) next.env.ANTHROPIC_API_KEY = options.key;
44
+ if (options.gateway !== undefined) {
45
+ next.env.ANTHROPIC_BASE_URL = options.gateway;
46
+ next.env.BIZAR_MODEL_ROUTER_URL = options.gateway;
47
+ }
48
+ if (options.key !== undefined) next.env.ANTHROPIC_AUTH_TOKEN = options.key;
46
49
  if (options.model !== undefined) next.env.ANTHROPIC_MODEL = options.model;
47
- if (options.removeKey) delete next.env.ANTHROPIC_API_KEY;
50
+ if (options.removeKey) {
51
+ delete next.env.ANTHROPIC_AUTH_TOKEN;
52
+ delete next.env.ANTHROPIC_API_KEY;
53
+ }
48
54
  return next;
49
55
  }
50
56
 
@@ -81,9 +87,9 @@ export async function runSetupProvider(args = []) {
81
87
 
82
88
  if (options.list) {
83
89
  console.log(` Settings: ${path}`);
84
- console.log(` Gateway: ${current.env?.ANTHROPIC_BASE_URL || '(Anthropic default)'}`);
90
+ console.log(` Gateway: ${current.env?.ANTHROPIC_BASE_URL || current.env?.BIZAR_MODEL_ROUTER_URL || '(Anthropic default)'}`);
85
91
  console.log(` Model: ${current.env?.ANTHROPIC_MODEL || '(Claude Code default)'}`);
86
- console.log(` API key: ${redact(current.env?.ANTHROPIC_API_KEY)}`);
92
+ console.log(` API key: ${redact(current.env?.ANTHROPIC_AUTH_TOKEN || current.env?.ANTHROPIC_API_KEY)}`);
87
93
  return { ok: true, path, settings: current };
88
94
  }
89
95
 
@@ -82,6 +82,8 @@ export const REQUIRED_HOOKS = [
82
82
  'thinking-route.mjs',
83
83
  'verify-deliverables.mjs',
84
84
  'worker-suggest.mjs',
85
+ 'workflow-route-guard.mjs',
86
+ 'workflow-route-state.mjs',
85
87
  'worktree-archive.mjs',
86
88
  'worktree-bootstrap.mjs',
87
89
  ];
@@ -9,6 +9,7 @@ import { runProvision, forceCleanInstall, clearSavedEnv } from '../provision.mjs
9
9
  import { runDoctor } from '../doctor.mjs';
10
10
  import { showBanner, sectionHeading } from './banner.mjs';
11
11
  import { printInstallLocations } from './paths.mjs';
12
+ import { runInteractiveSetup } from './interactive-setup.mjs';
12
13
 
13
14
  /**
14
15
  * Thin orchestrator entry point.
@@ -43,6 +44,15 @@ export async function runInstaller(opts = {}) {
43
44
  showBanner();
44
45
  printInstallLocations({ dryRun, force });
45
46
 
47
+ // A normal TTY install is a guided setup. Automation remains prompt-free
48
+ // via --yes / --non-interactive, and update runs never request credentials.
49
+ let interactive = null;
50
+ if (mode !== 'update' && !dryRun) {
51
+ interactive = await runInteractiveSetup({ enabled: !yes });
52
+ if (!interactive.ok) return { ok: false, interactive };
53
+ if (interactive.cancelled) return { ok: true, cancelled: true, interactive };
54
+ }
55
+
46
56
  // F-183 — pre-provision wipe for forced installs. Force-clean is what
47
57
  // makes `--force` actually a clean install: dirs under ~/.claude/ are
48
58
  // wiped (BIZAR_HOME and third-party state preserved), settings.json
@@ -94,5 +104,5 @@ export async function runInstaller(opts = {}) {
94
104
  }
95
105
  }
96
106
 
97
- return { ...provisionResult, clean };
98
- }
107
+ return { ...provisionResult, clean, interactive };
108
+ }
@@ -0,0 +1,131 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { Writable } from 'node:stream';
5
+ import { createInterface } from 'node:readline/promises';
6
+
7
+ export function providerSettingsPath(env = process.env) {
8
+ const root = env.CLAUDE_CONFIG_DIR?.trim()
9
+ || join(env.HOME?.trim() || homedir(), '.claude');
10
+ return join(root, 'settings.json');
11
+ }
12
+
13
+ export function readProviderSettings(path = providerSettingsPath()) {
14
+ if (!existsSync(path)) return {};
15
+ return JSON.parse(readFileSync(path, 'utf8'));
16
+ }
17
+
18
+ export function detectProviderConfiguration({ env = process.env, settings = {} } = {}) {
19
+ const settingsEnv = settings?.env && typeof settings.env === 'object' ? settings.env : {};
20
+ const url = env.BIZAR_MODEL_ROUTER_URL?.trim()
21
+ || env.ANTHROPIC_BASE_URL?.trim()
22
+ || settingsEnv.BIZAR_MODEL_ROUTER_URL?.trim()
23
+ || settingsEnv.ANTHROPIC_BASE_URL?.trim()
24
+ || '';
25
+ const key = env.ANTHROPIC_AUTH_TOKEN?.trim()
26
+ || env.ANTHROPIC_API_KEY?.trim()
27
+ || settingsEnv.ANTHROPIC_AUTH_TOKEN?.trim()
28
+ || settingsEnv.ANTHROPIC_API_KEY?.trim()
29
+ || '';
30
+ return { url, key, missing: [...(!url ? ['url'] : []), ...(!key ? ['key'] : [])] };
31
+ }
32
+
33
+ export function isValidProviderUrl(value) {
34
+ try {
35
+ const parsed = new URL(value);
36
+ return parsed.protocol === 'https:' || parsed.protocol === 'http:';
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ export async function askLine(prompt, { input = process.stdin, output = process.stdout } = {}) {
43
+ const rl = createInterface({ input, output });
44
+ try {
45
+ return await rl.question(prompt);
46
+ } finally {
47
+ rl.close();
48
+ }
49
+ }
50
+
51
+ /** Read a secret through readline without forwarding its terminal redraws. */
52
+ export async function askSecret(prompt, { input = process.stdin, output = process.stdout } = {}) {
53
+ const muted = new Writable({
54
+ write(_chunk, _encoding, callback) { callback(); },
55
+ });
56
+ const rl = createInterface({ input, output: muted, terminal: true });
57
+ output.write(prompt);
58
+ try {
59
+ return await rl.question('');
60
+ } finally {
61
+ rl.close();
62
+ output.write('\n');
63
+ }
64
+ }
65
+
66
+ function writeLine(output, value = '') {
67
+ output.write(`${value}\n`);
68
+ }
69
+
70
+ /**
71
+ * Guided install preflight. Credentials are placed only in the current
72
+ * process; the provisioner's settings writer persists them globally with the
73
+ * rest of the install. Injected question functions keep the policy testable.
74
+ */
75
+ export async function runInteractiveSetup({
76
+ env = process.env,
77
+ input = process.stdin,
78
+ output = process.stdout,
79
+ enabled = true,
80
+ readSettings = readProviderSettings,
81
+ askText = askLine,
82
+ askHidden = askSecret,
83
+ } = {}) {
84
+ let settings;
85
+ try {
86
+ settings = readSettings(providerSettingsPath(env));
87
+ } catch (error) {
88
+ return { ok: false, error: `Cannot read global Claude settings: ${error.message}` };
89
+ }
90
+
91
+ const detected = detectProviderConfiguration({ env, settings });
92
+ const interactive = enabled && input.isTTY === true && output.isTTY === true;
93
+ if (!interactive) {
94
+ if (detected.missing.length > 0) {
95
+ writeLine(output, ` ! Provider ${detected.missing.join(' and ')} not detected; set ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN or run \`bizar setup-provider\`.`);
96
+ }
97
+ return { ok: true, interactive: false, configured: detected.missing.length === 0, missing: detected.missing };
98
+ }
99
+
100
+ writeLine(output, '');
101
+ writeLine(output, ' Interactive setup');
102
+ const confirmation = (await askText(' Continue with the installation? [Y/n] ', { input, output })).trim().toLowerCase();
103
+ if (confirmation === 'n' || confirmation === 'no') {
104
+ writeLine(output, ' Installation cancelled.');
105
+ return { ok: true, interactive: true, cancelled: true, configured: detected.missing.length === 0 };
106
+ }
107
+
108
+ let url = detected.url;
109
+ let key = detected.key;
110
+ if (url) writeLine(output, ` ✓ Provider URL detected: ${url}`);
111
+ while (!url) {
112
+ const answer = (await askText(' Provider URL (for example https://gateway.example/v1): ', { input, output })).trim();
113
+ if (!isValidProviderUrl(answer)) {
114
+ writeLine(output, ' ! Enter a valid http:// or https:// URL.');
115
+ continue;
116
+ }
117
+ url = answer.replace(/\/+$/, '');
118
+ }
119
+
120
+ if (key) writeLine(output, ' ✓ Provider key detected (hidden)');
121
+ while (!key) {
122
+ key = (await askHidden(' Provider API key (input hidden): ', { input, output })).trim();
123
+ if (!key) writeLine(output, ' ! Provider key cannot be empty.');
124
+ }
125
+
126
+ env.ANTHROPIC_BASE_URL = url;
127
+ env.BIZAR_MODEL_ROUTER_URL = url;
128
+ env.ANTHROPIC_AUTH_TOKEN = key;
129
+ writeLine(output, ' ✓ Provider configuration ready; the key will be stored in global Claude settings.');
130
+ return { ok: true, interactive: true, cancelled: false, configured: true, missing: detected.missing };
131
+ }
package/cli/provision.mjs CHANGED
@@ -608,6 +608,8 @@ const LEGACY_BIZAR_HOOK_FILES = new Set([
608
608
  'thinking-route.mjs',
609
609
  'verify-deliverables.mjs',
610
610
  'worker-suggest.mjs',
611
+ 'workflow-route-guard.mjs',
612
+ 'workflow-route-state.mjs',
611
613
  'worktree-bootstrap.mjs',
612
614
  ]);
613
615
 
@@ -835,7 +837,7 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
835
837
  const hook = (name, timeout = 15) => resolveHookCommand(name, timeout);
836
838
 
837
839
  // The shipped settings template (`config/claude/settings.json`) is the
838
- // source of truth for defaultMode, worktree, enableWorkflows, etc. We
840
+ // source of truth for the main agent, worktree, workflows, etc. We
839
841
  // overlay Bizar-owned keys (mcpServers, hooks, env) on top so the installer
840
842
  // honors user preferences without having to fork the template here.
841
843
  // 10.22.0 / Phase 4 spirit-of-constraint fix: `model` and
@@ -848,6 +850,10 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
848
850
  const bizarSettings = {
849
851
  ...shipped,
850
852
  $schema: shipped.$schema || 'https://json.schemastore.org/claude-code-settings.json',
853
+ // Bizar must own the primary thread globally. A routing hook can add
854
+ // context, but only the agent setting applies Mike's system prompt and
855
+ // tool surface (including Workflow) to ordinary `claude` launches.
856
+ agent: 'mike',
851
857
  mcpServers: {
852
858
  bizar: {
853
859
  type: 'stdio',
@@ -892,6 +898,8 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
892
898
  cleanupPeriodDays: 7,
893
899
  },
894
900
  enableWorkflows: shipped.enableWorkflows !== undefined ? shipped.enableWorkflows : true,
901
+ disableWorkflows: false,
902
+ workflowSizeGuideline: shipped.workflowSizeGuideline || 'small',
895
903
  alwaysThinkingEnabled: shipped.alwaysThinkingEnabled !== undefined ? shipped.alwaysThinkingEnabled : true,
896
904
  autoDreamEnabled: shipped.autoDreamEnabled !== undefined ? shipped.autoDreamEnabled : true,
897
905
  showThinkingSummaries: shipped.showThinkingSummaries !== undefined ? shipped.showThinkingSummaries : true,
@@ -990,11 +998,15 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
990
998
  merged.autoMode = existing.autoMode || bizarSettings.autoMode;
991
999
  merged.attribution = existing.attribution || bizarSettings.attribution;
992
1000
  merged.worktree = { ...(bizarSettings.worktree || {}), ...(existing.worktree || {}) };
993
- for (const key of ['enableWorkflows', 'alwaysThinkingEnabled', 'autoDreamEnabled', 'showThinkingSummaries']) {
1001
+ for (const key of ['enableWorkflows', 'disableWorkflows', 'workflowSizeGuideline', 'alwaysThinkingEnabled', 'autoDreamEnabled', 'showThinkingSummaries']) {
994
1002
  if (merged[key] === undefined) merged[key] = bizarSettings[key];
995
1003
  }
996
1004
  }
997
1005
 
1006
+ // The installed harness owns the default main-thread role. Operators can
1007
+ // still override it for one session with `claude --agent <name>`.
1008
+ merged.agent = 'mike';
1009
+
998
1010
  // `model` is Bizar-owned routing policy, so refresh it on both ordinary and
999
1011
  // forced provision runs. Do not erase an existing operator model if a
1000
1012
  // malformed router has no enabled candidate; dispatch will fail closed.
@@ -70,11 +70,13 @@ actions with `permissionDecision: "ask"`; that escalation list is the
70
70
  authoritative floor, not a starting point.
71
71
 
72
72
  Native dynamic workflows under `config/workflows/` and `~/.claude/workflows/`
73
- are the primary dispatch mechanism for shaped work (research / implement /
74
- debug / review). Small, deterministic, repository-local tasks go directly to
75
- one isolated worker: inspect, make the smallest change, and run the smallest
76
- proving check. Do not add research, planning, review, or duplicate workers to
77
- such tasks. For work that needs 3+ long-lived workers with bounded cross-talk,
73
+ are the required dispatch mechanism for every primary request except an
74
+ unmistakably tiny single-target copy/style/format edit with no behavior or test
75
+ change. Mike may execute that narrow exception directly. All other work enters
76
+ the matching research / implement / debug / review workflow and uses at least
77
+ one explicitly modeled editing subagent with call-level worktree isolation.
78
+ Do not add unnecessary phases or duplicate workers. For work that needs 3+
79
+ long-lived workers with bounded cross-talk,
78
80
  Mike invokes a workflow that fans out as a native agent team; the team is
79
81
  host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
80
82
  Anthropic's docs `team_name` is deprecated and ignored. When two or more
@@ -143,10 +145,15 @@ guess-and-try remains prohibited.
143
145
  The autonomy and approval policy above governs this execution model. The project defaults to `acceptEdits`; eligible operators may opt into Claude Code Auto mode.
144
146
 
145
147
  Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
146
- Mike handles small, deterministic repository-local changes directly and uses
147
- the smallest proving check. It delegates when worktree isolation, specialist
148
- expertise, or real parallelism materially helps. A Bizar custom agent already
149
- executing its assigned role does not recursively dispatch itself.
148
+ The installer sets Claude Code's global `agent` setting to Mike's frontmatter
149
+ name (`mike`),
150
+ and a session-scoped routing guard requires a successful native Workflow before
151
+ substantive primary-session mutation. Read-only inspection remains available.
152
+ Mike directly executes only the tiny edit exception above. Every other request
153
+ must invoke a native Bizar workflow before mutation; the workflow dispatches
154
+ worktree-isolated subagents while Mike owns integration and final verification.
155
+ A Bizar custom agent already executing its assigned role does not recursively
156
+ dispatch itself.
150
157
 
151
158
  Every shipped agent has `WebSearch` access. Before proposing, explaining,
152
159
  troubleshooting, or implementing behavior from an external API, library,
@@ -156,8 +163,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
156
163
  documentation is unavailable or ambiguous, inspect authoritative source code
157
164
  and report the evidence gap.
158
165
 
159
- For shaped requests, `office-manager` uses only the phases that reduce a known
160
- risk; direct tasks skip this pipeline:
166
+ For workflow-routed requests, `office-manager` uses only the phases that reduce
167
+ a known risk; only the tiny edit exception skips this pipeline:
161
168
 
162
169
  1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
163
170
  2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
@@ -24,7 +24,12 @@ Exact authority is required for pushes, PR mutations, releases, publication, dep
24
24
 
25
25
  ## 5. Agent coordination
26
26
 
27
- Every primary request enters through `@mike`. Mike handles small deterministic local work directly. Delegate only when isolation, expertise, or real parallelism helps; use only risk-reducing phases. A Bizar subagent never recursively dispatches itself.
27
+ Every primary request enters through `@mike`. Mike directly handles only an
28
+ unmistakably tiny single-target copy/style/format edit with no behavior or test
29
+ change. Every other request enters a matching native Bizar workflow, which
30
+ dispatches at least one explicitly modeled worktree-isolated implementation
31
+ subagent. Use only risk-reducing phases and parallelize only genuinely disjoint
32
+ scopes. A Bizar subagent never recursively dispatches itself.
28
33
 
29
34
  Agent prompts name ownership, deliverable, validation, sibling awareness, and escalation. Editing Agent calls use `isolation: "worktree"`; independent writers run concurrently. The leader consumes terminal results, merges queued branches, and verifies integration.
30
35
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: mike
3
- description: Mike — adaptive primary orchestrator for direct fixes and coordinated agent work.
4
- tools: Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill
3
+ description: Mike — workflow-first orchestrator with a tiny direct-edit exception.
4
+ tools: Workflow, Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill
5
5
  skills:
6
6
  - i-have-adhd
7
7
  ---
@@ -9,22 +9,25 @@ skills:
9
9
  # Mike — adaptive primary orchestrator
10
10
 
11
11
  Follow `_shared/AGENT_BASELINE.md`. You own the user outcome, integration, and
12
- final verification. Choose the lightest workflow that proves the result.
12
+ final verification. Direct execution is a narrow exception; workflows are the
13
+ default for meaningful work.
13
14
 
14
- ## Route once
15
+ ## Route, then reassess if scope expands
15
16
 
16
17
  | Shape | Signals | Execution |
17
18
  |---|---|---|
18
- | Direct | small known local bug, style/spacing/text change, bounded lookup, one or two files | inspect, edit, run the smallest proving check yourself; no plan, research, or subagent ceremony |
19
- | Isolated | bounded implementation where worktree isolation materially helps | one `@brenda` call with `isolation: "worktree"`, then merge and verify |
20
- | Parallel | two or more independent writable scopes with no data dependency | one concurrent Agent batch; every writer gets a disjoint scope and `isolation: "worktree"` |
21
- | Shaped | uncertain root cause, architecture/security, external/version-sensitive behavior, or interacting components | use the matching native research/debug/implement workflow; parallelize independent lanes and serialize only dependencies |
19
+ | Tiny direct | one obvious copy, typo, comment, whitespace, or single style-token edit; one target; no behavior or test change | inspect, make the micro-edit, run the smallest proving check yourself |
20
+ | Bounded workflow | known non-trivial implementation, including a logical bug or any behavioral change | invoke `bizar-implement`; it dispatches at least one editing worker with `isolation: "worktree"`; merge and verify |
21
+ | Debug workflow | failing behavior, unclear cause, regression, or interacting state | invoke `bizar-debug`; keep diagnosis and fix evidence separate |
22
+ | Research/shaped | external/version-sensitive behavior, architecture/security, broad review, or interacting components | invoke `bizar-research` or the matching `ultracode*` workflow; parallelize independent lanes and serialize dependencies |
22
23
 
23
- Do not expand a direct task because tools are available. Do not compress a
24
- shaped task merely to avoid coordination. Research current official docs only
25
- for external or version-sensitive claims. Inspect installed skills before hard
26
- or specialized work; if stuck with no match, search skills.sh and review the
27
- candidate before proposing installation.
24
+ If a request could reasonably require a regression test, touch multiple files,
25
+ or needs inspection to discover its scope, it is not tiny: invoke a workflow
26
+ before editing. The primary session does not substitute an ad-hoc Agent call
27
+ for the workflow. Research current official docs only for external or
28
+ version-sensitive claims. Inspect installed skills before hard or specialized
29
+ work; if stuck with no match, search skills.sh and review the candidate before
30
+ proposing installation.
28
31
 
29
32
  ## Models
30
33
 
@@ -11,16 +11,16 @@ Run `$ARGUMENTS` directly in this session. Do NOT delegate to subagents.
11
11
 
12
12
  ## Mechanism
13
13
 
14
- The `worker-suggest` hook recognizes `/quick` directly and skips orchestration
15
- for this command only. Legacy `.bizar/.quick-once` sentinels are consumed and
16
- deleted on their first prompt, so they cannot disable routing for a session.
14
+ The `worker-suggest` hook recognizes `/quick` directly, but honors the bypass
15
+ only when the argument still qualifies as an unmistakably tiny edit. Legacy
16
+ `.bizar/.quick-once` sentinels are consumed on their first prompt and cannot
17
+ bypass routing for substantive work.
17
18
 
18
19
  ## What this is for
19
20
 
20
21
  - One-line file edits
21
22
  - Quick lookups ("find X", "show me Y")
22
- - Mechanical renames
23
- - Single-tool invocations
23
+ - One-token style adjustments
24
24
 
25
25
  ## What this is NOT for
26
26
 
@@ -14,10 +14,13 @@ bizar setup-provider --remove-key
14
14
  ```
15
15
 
16
16
  The command edits only `env.ANTHROPIC_BASE_URL`,
17
- `env.ANTHROPIC_API_KEY`, and `env.ANTHROPIC_MODEL` in
17
+ `env.BIZAR_MODEL_ROUTER_URL`, `env.ANTHROPIC_AUTH_TOKEN`, and
18
+ `env.ANTHROPIC_MODEL` in
18
19
  `~/.claude/settings.json` (or `$CLAUDE_CONFIG_DIR/settings.json`).
19
20
  All unrelated settings are preserved. It rejects invalid existing JSON
20
21
  and never prints a full API key.
21
22
 
22
23
  With no arguments it shows help; it does not guess credentials or query
23
- an untrusted model catalog.
24
+ an untrusted model catalog. A normal `bizar install` provides the safer
25
+ interactive path with hidden key entry; this command remains useful for
26
+ automation and explicit changes.
@@ -11,6 +11,7 @@ All hooks read Claude Code JSON from stdin and emit either no decision, addition
11
11
  | `simplify-guard.mjs` | PostToolUse Skill + PreToolUse Bash | require one `/simplify` per commit attempt |
12
12
  | `posttooluse-editwrite.mjs` | PostToolUse writes | local telemetry and test reminder |
13
13
  | `worker-suggest.mjs` | UserPromptSubmit | ranked skill/agent suggestions |
14
+ | `workflow-route-guard.mjs` | UserPromptSubmit, PreToolUse, PostToolUse | requires a proven successful native workflow before substantive primary mutation; permits narrowly whitelisted, redirect-free Git inspection |
14
15
  | `thinking-route.mjs` | UserPromptSubmit | slash and mental-model routing |
15
16
  | `telemetry.mjs` | SessionStart/UserPromptSubmit | local correlation and rejection categories |
16
17
  | `sessionstart-prime.mjs` | SessionStart | bounded project and handoff context |
@@ -186,13 +186,13 @@ function startupBriefing(cwd, featureBrief, recentCommits, projectLine, progress
186
186
  }
187
187
  }
188
188
  if (progressLast) lines.push(`- Progress: ${progressLast}.`);
189
- lines.push('- You are @mike. Direct small known local fixes; use one isolated worker when useful; use parallel worktrees only for independent scopes; shaped work gets only risk-reducing phases.');
189
+ lines.push('- You are @mike. Only an unmistakably tiny single-target copy/style/format edit is direct; every other change enters the matching native workflow before mutation. Use one isolated writer by default and parallel worktrees only for independent scopes.');
190
190
  lines.push('- External/version-sensitive work requires current official docs via WebSearch/WebFetch. Use relevant installed skills; apply i-have-adhd to user output. WIP=1.');
191
191
  lines.push('- TaskCompleted/SubagentStop/<task-notification> is terminal: consume <result>, mark done/failed, merge queued work, continue the objective.');
192
192
  // Default-first-stop hint when nothing is active yet.
193
193
  if (featureBrief && featureBrief.active.length === 0) {
194
194
  lines.push(
195
- '- First move: read PROGRESS.md and feature_list.json; then choose direct, isolated, parallel, or shaped execution once.',
195
+ '- First move: read PROGRESS.md and feature_list.json; then choose the matching workflow, except for an unmistakably tiny direct edit.',
196
196
  );
197
197
  }
198
198
  return lines.join('\n');
@@ -203,14 +203,14 @@ function clearBriefing(cwd, recentCommits, progressLast) {
203
203
  if (progressLast) lines.push(`- Progress: ${progressLast}.`);
204
204
  if (recentCommits.length > 0) lines.push(`- Last commit: ${recentCommits[0]}.`);
205
205
  lines.push('- Context preserved in same repo / cwd — only the model turn was reset.');
206
- lines.push('- You are @mike: continue with the lightest execution shape that proves the result.');
206
+ lines.push('- You are @mike: continue through the active workflow; only an unmistakably tiny edit may stay direct.');
207
207
  lines.push('- First move: continue from where the model left off; no need to reread project files.');
208
208
  return lines.join('\n');
209
209
  }
210
210
 
211
211
  function resumeBriefing(cwd, state) {
212
212
  const lines = ['Bizar SessionStart (resume):'];
213
- lines.push('- You are @mike: restore state, then use direct work or bounded delegation based on actual complexity.');
213
+ lines.push('- You are @mike: restore state, then continue the active workflow; only an unmistakably tiny edit may stay direct.');
214
214
  if (state) {
215
215
  if (state.activeFeature) lines.push(`- Last active feature: ${state.activeFeature}.`);
216
216
  if (state.reason) lines.push(`- Last session ended with: ${state.reason}.`);
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * Bizar Background Workers — UserPromptSubmit hook.
6
6
  *
7
- * Runs on every user prompt. Small, repository-local requests take a cheap
8
- * fast path without loading the worker/learning modules. Larger requests get
9
- * routing and specialized suggestions from cli/worker-dispatcher.mjs.
7
+ * Runs on every user prompt. Only unmistakably tiny, single-scope edits take
8
+ * a cheap fast path. Every other request is routed into a native Bizar
9
+ * workflow that owns subagent dispatch.
10
10
  *
11
11
  * Uses import.meta.url + dynamic import() to resolve the sibling CLI module so
12
12
  * the hook works regardless of install path (fixes ERR_MODULE_NOT_FOUND after
@@ -37,6 +37,7 @@
37
37
  import { dirname, join } from 'node:path';
38
38
  import { existsSync, unlinkSync } from 'node:fs';
39
39
  import { fileURLToPath } from 'node:url';
40
+ import { isTinyDirectTask } from './workflow-route-state.mjs';
40
41
 
41
42
  const __dirname = dirname(fileURLToPath(import.meta.url));
42
43
 
@@ -46,17 +47,22 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
46
47
  * normal routing path. Keeping the classifier lexical makes it free on the
47
48
  * UserPromptSubmit hot path and easy to explain to the model.
48
49
  */
49
- function isFastLocalTask(prompt) {
50
- if (prompt.length > 500 || prompt.split('\n').length > 3) return false;
51
- if (!/\b(fix|correct|rename|remove|delete|format|typo|style|padding|margin|color|spacing|align|change|update)\b/i.test(prompt)) return false;
52
- return !/\b(api|sdk|library|framework|dependency|version|migration|architecture|security|auth|credential|deploy|publish|release|database|workflow|agent team|hook|performance|benchmark|all files|every file|failing|failure|error|crash|root cause|regression)\b/i.test(prompt);
53
- }
50
+ const isFastLocalTask = isTinyDirectTask;
54
51
 
55
52
  const FAST_ROUTE_POLICY = [
56
- 'Bizar fast path:',
57
- '- This is a small, bounded, repository-local request. The primary orchestrator may execute it directly; otherwise dispatch exactly one @brenda worker with call-level `isolation: "worktree"`. Do not research, plan, or review first.',
58
- '- Inspect the named/local code, make the smallest reversible change, add or adjust only the directly relevant regression test when behavior changes, and run the smallest proving check. Use current official documentation only if the change touches an external or version-sensitive API.',
59
- '- Do not fan out duplicate analysis. Parallelize only independent file scopes; a single-file fix stays single-worker to avoid worktree and merge overhead.',
53
+ 'Bizar tiny direct path:',
54
+ '- Direct execution is allowed only because this request is an unmistakably tiny, single-scope copy/style/format edit. Inspect the exact target, make one minimal reversible edit, and run the smallest proving check. Do not plan, research, or dispatch a subagent.',
55
+ '- If inspection reveals behavioral logic, more than one target, ambiguity, a required test change, or any interaction beyond the named micro-edit, stop the direct path and invoke the matching native Bizar workflow before editing further.',
56
+ ].join('\n');
57
+
58
+ const ROUTE_POLICY = [
59
+ 'Workflow-required Bizar routing policy:',
60
+ '- If this is the primary session, you ARE @mike. Do not implement this request directly in the primary session. Before any edit or mutation, invoke the matching native Bizar workflow; the primary owns routing, integration, and final verification.',
61
+ '- Use bizar-implement for known bounded changes, bizar-debug for bugs needing diagnosis, bizar-research for external or uncertain implementation context, and ultracode / ultracode-research / ultracode-review for broad, high-risk, or review-heavy objectives. Use only phases that reduce a concrete risk.',
62
+ '- The workflow must dispatch at least one implementation subagent with an explicit configured model and call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch them concurrently; otherwise use one isolated writer. Never create duplicate workers merely to satisfy fan-out.',
63
+ '- Consume terminal agent results, merge queued worktrees with bizar worktree-merge, and run integration checks in the primary session. A subagent may not recursively dispatch itself.',
64
+ '- Do NOT execute any tool you do not have. If a tool you need is missing from your tools list, dispatch to a subagent that has it — do not pretend you have it.',
65
+ '- If you are already running as a Bizar custom agent, follow your assigned role and do not recursively dispatch yourself.',
60
66
  ].join('\n');
61
67
 
62
68
  let raw = '';
@@ -72,7 +78,7 @@ process.stdin.on('end', async () => {
72
78
 
73
79
  const prompt = String(input.prompt ?? input.user_prompt ?? '').trim();
74
80
 
75
- if (input.task_notification || /<task-notification\b[\s\S]*<result\b/i.test(prompt)) {
81
+ if (input.task_notification || /^<task-notification\b[\s\S]*<result\b[\s\S]*<\/task-notification>\s*$/i.test(prompt)) {
76
82
  process.stdout.write(JSON.stringify({
77
83
  hookSpecificOutput: {
78
84
  hookEventName: 'UserPromptSubmit',
@@ -83,7 +89,9 @@ process.stdin.on('end', async () => {
83
89
  }
84
90
 
85
91
  if (/^\/quick(?:\s|$)/i.test(prompt)) {
86
- process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: '' } }) + '\n');
92
+ const quickTask = prompt.replace(/^\/quick(?:\s+|$)/i, '').trim();
93
+ const context = !quickTask || isFastLocalTask(quickTask) ? FAST_ROUTE_POLICY : ROUTE_POLICY;
94
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: context } }) + '\n');
87
95
  return;
88
96
  }
89
97
 
@@ -93,10 +101,11 @@ process.stdin.on('end', async () => {
93
101
  const quickSentinel = join(input.cwd || process.cwd(), '.bizar', '.quick-once');
94
102
  if (existsSync(quickSentinel)) {
95
103
  try { unlinkSync(quickSentinel); } catch { /* best-effort one-shot cleanup */ }
104
+ const context = isFastLocalTask(prompt) ? FAST_ROUTE_POLICY : ROUTE_POLICY;
96
105
  process.stdout.write(JSON.stringify({
97
106
  hookSpecificOutput: {
98
107
  hookEventName: 'UserPromptSubmit',
99
- additionalContext: '',
108
+ additionalContext: context,
100
109
  },
101
110
  }) + '\n');
102
111
  process.exit(0);
@@ -130,15 +139,6 @@ process.stdin.on('end', async () => {
130
139
  return;
131
140
  }
132
141
 
133
- const routePolicy = [
134
- 'Adaptive Bizar routing policy:',
135
- '- If this is the primary session, you ARE @mike. Execute small deterministic repository work directly, or use the Agent tool for one @brenda worktree worker when isolation helps. Do not add research, planning, review, or a second worker unless the task needs it.',
136
- '- For a known, multi-file change, first split only genuinely disjoint edit scopes and dispatch those writers concurrently with call-level `isolation: "worktree"`. A monolithic scope gets one writer, not artificial parallelism.',
137
- '- Use research and planning only when external/version-sensitive behavior, an unclear root cause, an architectural decision, or interacting scopes make them decision-reducing. When required, run independent research in parallel with repository inspection.',
138
- '- Do NOT execute any tool you do not have. If a tool you need is missing from your tools list, dispatch to a subagent that has it — do not pretend you have it.',
139
- '- If you are already running as a Bizar custom agent, follow your assigned role and do not recursively dispatch yourself.',
140
- ].join('\n');
141
-
142
142
  let dispatch;
143
143
  let recordSuggestion;
144
144
  try {
@@ -150,7 +150,7 @@ process.stdin.on('end', async () => {
150
150
  process.stdout.write(JSON.stringify({
151
151
  hookSpecificOutput: {
152
152
  hookEventName: 'UserPromptSubmit',
153
- additionalContext: routePolicy,
153
+ additionalContext: ROUTE_POLICY,
154
154
  },
155
155
  }) + '\n');
156
156
  process.exit(0);
@@ -167,7 +167,7 @@ process.stdin.on('end', async () => {
167
167
  process.stdout.write(JSON.stringify({
168
168
  hookSpecificOutput: {
169
169
  hookEventName: 'UserPromptSubmit',
170
- additionalContext: routePolicy,
170
+ additionalContext: ROUTE_POLICY,
171
171
  },
172
172
  }) + '\n');
173
173
  process.exit(0);
@@ -204,7 +204,7 @@ process.stdin.on('end', async () => {
204
204
 
205
205
  // Build additionalContext for the model so Bizar routing is mandatory even
206
206
  // when no specialized worker pattern matches.
207
- let note = routePolicy;
207
+ let note = ROUTE_POLICY;
208
208
  if (suggestions.length > 0) {
209
209
  const lines = suggestions.map((s) => {
210
210
  const skillPart = s.skill ? `, skill=${s.skill}` : '';
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /** Enforce native-workflow entry before substantive primary-session mutation. */
3
+ 'use strict';
4
+
5
+ import {
6
+ clearWorkflowRequired,
7
+ markWorkflowRequired,
8
+ promptRequiresWorkflow,
9
+ workflowRequired,
10
+ } from './workflow-route-state.mjs';
11
+ import { parseGitCommands } from './git-command-parser.mjs';
12
+
13
+ const READ_ONLY_GIT_SUBCOMMANDS = new Set([
14
+ 'diff', 'log', 'ls-files', 'rev-parse', 'show', 'status',
15
+ ]);
16
+ const WORKFLOW_SUCCESS = new Set(['completed', 'dry', 'ready-for-integration', 'succeeded', 'success']);
17
+ const WORKFLOW_FAILURE = new Set(['blocked', 'budget-exhausted', 'cancelled', 'canceled', 'error', 'failed', 'failure']);
18
+
19
+ export function isReadOnlyShellInspection(command) {
20
+ const source = String(command || '').trim();
21
+ if (!source || /[\n\r;|<>`]/.test(source) || /\$\(|(?:^|\s)&(?:\s|$)/.test(source)) return false;
22
+
23
+ return source.split(/\s*&&\s*/).every((segment) => {
24
+ const trimmed = segment.trim();
25
+ if (/^echo(?:\s|$)/.test(trimmed)) return true;
26
+ if (!/^git(?:\s|$)/.test(trimmed) || /(?:^|\s)--(?:output|ext-diff)(?:=|\s|$)/.test(trimmed)) return false;
27
+ const parsed = parseGitCommands(trimmed);
28
+ return parsed.length === 1 && READ_ONLY_GIT_SUBCOMMANDS.has(parsed[0].subcommand);
29
+ });
30
+ }
31
+
32
+ export function workflowCompletedSuccessfully(input) {
33
+ const root = input?.tool_response ?? input?.tool_result ?? input?.toolUseResult;
34
+ if (!root || typeof root !== 'object') return false;
35
+ const statuses = [];
36
+ const visit = (value, depth = 0) => {
37
+ if (!value || typeof value !== 'object' || depth > 3) return;
38
+ if (typeof value.status === 'string') statuses.push(value.status.toLowerCase());
39
+ for (const key of ['result', 'workflow', 'data']) visit(value[key], depth + 1);
40
+ };
41
+ visit(root);
42
+ if (root.is_error === true || root.error || statuses.some((status) => WORKFLOW_FAILURE.has(status))) return false;
43
+ return statuses.some((status) => WORKFLOW_SUCCESS.has(status));
44
+ }
45
+
46
+ let raw = '';
47
+ process.stdin.setEncoding('utf8');
48
+ process.stdin.on('data', (chunk) => { raw += chunk; });
49
+ process.stdin.on('end', () => {
50
+ let input = {};
51
+ try { input = JSON.parse(raw || '{}'); } catch { input = {}; }
52
+
53
+ const event = String(input.hook_event_name || '');
54
+ const sessionId = String(input.session_id || '');
55
+ const toolName = String(input.tool_name || '');
56
+
57
+ try {
58
+ if (event === 'UserPromptSubmit') {
59
+ if (promptRequiresWorkflow(input)) markWorkflowRequired(input);
60
+ else clearWorkflowRequired(sessionId);
61
+ process.stdout.write('{}\n');
62
+ return;
63
+ }
64
+
65
+ if (event === 'PostToolUse' && toolName === 'Workflow') {
66
+ if (workflowCompletedSuccessfully(input)) clearWorkflowRequired(sessionId);
67
+ process.stdout.write('{}\n');
68
+ return;
69
+ }
70
+
71
+ if (event !== 'PreToolUse' || input.agent_id || !workflowRequired(sessionId)) {
72
+ process.stdout.write('{}\n');
73
+ return;
74
+ }
75
+
76
+ const readOnlyBash = toolName === 'Bash' && isReadOnlyShellInspection(input.tool_input?.command);
77
+ if (!readOnlyBash && /^(?:Edit|Write|MultiEdit|Bash|Agent)$/.test(toolName)) {
78
+ process.stdout.write(`${JSON.stringify({
79
+ hookSpecificOutput: {
80
+ hookEventName: 'PreToolUse',
81
+ permissionDecision: 'deny',
82
+ permissionDecisionReason: `Bizar workflow routing guard: ${toolName} is unavailable in the primary session until the required native Workflow runs successfully. Invoke bizar-implement, bizar-debug, bizar-research, or the matching ultracode workflow first.`,
83
+ },
84
+ })}\n`);
85
+ return;
86
+ }
87
+ } catch (error) {
88
+ process.stderr.write(`[bizar.workflow-route] ${error?.message || String(error)}\n`);
89
+ }
90
+
91
+ process.stdout.write('{}\n');
92
+ });
@@ -0,0 +1,71 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ const MAX_AGE_MS = 24 * 60 * 60 * 1000;
6
+
7
+ export function isTinyDirectTask(prompt) {
8
+ const text = String(prompt || '').trim();
9
+ if (text.length === 0 || text.length > 240 || text.split('\n').length > 1) return false;
10
+ if (!/^(?:please\s+)?(?:fix|correct|format|adjust|change|update)\b/i.test(text)) return false;
11
+ if (/[?]\s*$/.test(text) || /\b(?:do\s+not|don't|never)\b/i.test(text)) return false;
12
+ if (!/\b(typo|spelling|punctuation|comment|copy|wording|whitespace|formatting|indentation|padding|margin|spacing|color|colour|alignment?)\b/i.test(text)) return false;
13
+ return !/\b(and|also|then|plus|both|across|multiple|several|throughout|entire|everywhere|sitewide|app-wide|components?|files?|api|sdk|library|framework|dependency|version|migration|architecture|security|auth|credential|deploy|publish|release|database|workflow|agent|hook|performance|benchmark|all files|every file|failing|failure|error|crash|root cause|regression|test|tests|logic|handling|parser|parsing|algorithm|calculation|rendering?|generation|tokenizer|lexer)\b/i.test(text);
14
+ }
15
+
16
+ function safeSessionId(value) {
17
+ return String(value || '').replace(/[^A-Za-z0-9_-]/g, '').slice(0, 80);
18
+ }
19
+
20
+ function stateDir(env = process.env) {
21
+ const root = env.BIZAR_HOME || join(env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'bizar');
22
+ return join(root, 'workflow-routing');
23
+ }
24
+
25
+ export function routeStatePath(sessionId, env = process.env) {
26
+ const safe = safeSessionId(sessionId);
27
+ return safe ? join(stateDir(env), `${safe}.json`) : '';
28
+ }
29
+
30
+ export function markWorkflowRequired(input, env = process.env) {
31
+ const path = routeStatePath(input?.session_id, env);
32
+ if (!path) return false;
33
+ const dir = stateDir(env);
34
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
35
+ const tmp = `${path}.${process.pid}.tmp`;
36
+ writeFileSync(tmp, `${JSON.stringify({ required: true, createdAt: Date.now(), cwd: String(input?.cwd || '') })}\n`, { mode: 0o600 });
37
+ renameSync(tmp, path);
38
+ return true;
39
+ }
40
+
41
+ export function clearWorkflowRequired(sessionId, env = process.env) {
42
+ const path = routeStatePath(sessionId, env);
43
+ if (!path) return false;
44
+ rmSync(path, { force: true });
45
+ return true;
46
+ }
47
+
48
+ export function workflowRequired(sessionId, env = process.env, now = Date.now()) {
49
+ const path = routeStatePath(sessionId, env);
50
+ if (!path || !existsSync(path)) return false;
51
+ try {
52
+ const state = JSON.parse(readFileSync(path, 'utf8'));
53
+ if (state?.required !== true || !Number.isFinite(state.createdAt) || now - state.createdAt > MAX_AGE_MS) {
54
+ rmSync(path, { force: true });
55
+ return false;
56
+ }
57
+ return true;
58
+ } catch {
59
+ rmSync(path, { force: true });
60
+ return false;
61
+ }
62
+ }
63
+
64
+ export function promptRequiresWorkflow(input) {
65
+ const prompt = String(input?.prompt ?? input?.user_prompt ?? '').trim();
66
+ if (!prompt) return false;
67
+ if (input?.task_notification || /^<task-notification\b[\s\S]*<result\b[\s\S]*<\/task-notification>\s*$/i.test(prompt)) return false;
68
+ const quick = prompt.match(/^\/quick(?:\s+([\s\S]*))?$/i);
69
+ if (quick) return Boolean(quick[1]?.trim()) && !isTinyDirectTask(quick[1]);
70
+ return !isTinyDirectTask(prompt);
71
+ }
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "agent": "mike",
3
4
  "mcpServers": {
4
5
  "bizar": {
5
6
  "command": "npx",
@@ -65,6 +66,8 @@
65
66
  "askUserQuestionTimeout": "5m",
66
67
  "showThinkingSummaries": true,
67
68
  "enableWorkflows": true,
69
+ "disableWorkflows": false,
70
+ "workflowSizeGuideline": "small",
68
71
  "disableAutoCompact": false,
69
72
  "autoDreamEnabled": true,
70
73
  "env": {
@@ -82,7 +82,7 @@ if (!accepted) {
82
82
  }
83
83
 
84
84
  phase('Fix')
85
- const fix = await dispatchAgent(agent, 'fix-author', `Produce the smallest fix + regression test for bug ${BUG_ID} based on the accepted hypothesis. Do not commit, push, publish, or deploy.\n${barrierRef({ runId: RUN_ID, phase: 'Hypothesis', label: 'hypothesis:initial', summary: accepted.hypothesis?.cause ? accepted.hypothesis.cause.slice(0, 200) : 'accepted hypothesis' }).promptBlock}`, { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'fix', phase: 'Fix' })
85
+ const fix = await dispatchAgent(agent, 'fix-author', `Implement the smallest fix + regression test for bug ${BUG_ID} based on the accepted hypothesis. Edit and test in your isolated worktree. Do not commit, push, publish, or deploy.\n${barrierRef({ runId: RUN_ID, phase: 'Hypothesis', label: 'hypothesis:initial', summary: accepted.hypothesis?.cause ? accepted.hypothesis.cause.slice(0, 200) : 'accepted hypothesis' }).promptBlock}`, { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'fix', phase: 'Fix', isolation: 'worktree' })
86
86
  writeArtifact({ runId: RUN_ID, phase: 'Fix', label: 'fix', payload: fix, summary: typeof fix === 'string' ? fix.slice(0, 200) : 'fix proposed', role: 'implementer' })
87
87
 
88
88
  phase('Verify')
@@ -1,16 +1,11 @@
1
- import { randomUUID } from 'node:crypto'
2
- import { dispatchAgent, writeArtifact, barrierRef } from './lib/dispatch.js'
1
+ import { dispatchAgent } from './lib/dispatch.js'
3
2
 
4
3
  export const meta = {
5
4
  name: 'bizar-implement',
6
- description: 'Run disjoint implementation lanes concurrently, barrier-merge their results, and synthesize an integration report without sequential pipeline stages',
7
- whenToUse: 'Use when the scope is already understood, lanes can be drawn up front, and a single barrier agent can reconcile the work before final synthesis. Parallel-only no sequential pipeline stages.',
5
+ description: 'Implement one bounded change in one worktree, or run explicitly supplied disjoint lanes concurrently',
6
+ whenToUse: 'Use when implementation scope is understood and external research or root-cause discovery is unnecessary.',
8
7
  phases: [
9
- { title: 'Scope', detail: 'Extract disjoint lanes from the supplied scope' },
10
- { title: 'Implement', detail: 'Run lanes concurrently in worktrees' },
11
- { title: 'Barrier', detail: 'A single agent reconciles all lane outputs' },
12
- { title: 'Verify', detail: 'A single agent re-checks the barrier plan against the scope' },
13
- { title: 'Synthesis', detail: 'A single integration agent produces the final report' },
8
+ { title: 'Implement', detail: 'Run one isolated writer, or explicit disjoint writers concurrently' },
14
9
  ],
15
10
  }
16
11
 
@@ -20,79 +15,33 @@ const TOPIC = typeof args === 'string'
20
15
  ? args.topic
21
16
  : JSON.stringify(args || {})
22
17
  const SCOPE = (args && Array.isArray(args.scope)) ? args.scope : []
23
-
24
- // Phase B (v10.21.0) artifact-on-disk barriers: one runId per workflow
25
- // invocation. Used by every writeArtifact() + barrierRef() in this script.
26
- const RUN_ID = randomUUID()
27
-
28
- const LANES = {
29
- type: 'object',
30
- required: ['lanes'],
31
- properties: {
32
- lanes: {
33
- type: 'array',
34
- items: {
35
- type: 'object',
36
- required: ['name', 'scope', 'task'],
37
- properties: {
38
- name: { type: 'string' },
39
- scope: { type: 'array', items: { type: 'string' } },
40
- task: { type: 'string' },
41
- },
42
- },
43
- },
44
- },
45
- }
46
-
47
- phase('Scope')
48
- const scoped = await dispatchAgent(agent, 'scope-extractor', `Extract 2-6 disjoint edit lanes for: ${TOPIC}\nProvided scope: ${SCOPE.length ? SCOPE.join(', ') : '(none supplied)'}\nEach lane owns a non-overlapping file scope. Shared root/config/lock files must have one owner. Return lanes with name/scope/task.`, { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'scope-extract', phase: 'Scope', schema: LANES })
49
- if (!scoped || !Array.isArray(scoped.lanes) || scoped.lanes.length === 0) {
50
- return { status: 'blocked', reason: 'Scope agent produced no lanes.' }
51
- }
52
- // Phase B: persist the scope artifact for the next barrier agent.
53
- const scopeSummary = `scope lanes: ${scoped.lanes.map((l) => l.name).join(', ')}`
54
- writeArtifact({ runId: RUN_ID, phase: 'Scope', label: 'barrier', payload: scoped, summary: scopeSummary, role: 'implementer' })
55
- const lanes = scoped.lanes.slice(0, 6)
56
- if (scoped.lanes.length > lanes.length) {
57
- log(`Bounded implementation to 6 of ${scoped.lanes.length} lanes.`)
58
- }
18
+ const suppliedLanes = (args && Array.isArray(args.lanes)) ? args.lanes : []
19
+ const lanes = (suppliedLanes.length > 0
20
+ ? suppliedLanes
21
+ : [{ name: 'bounded-change', scope: SCOPE, task: TOPIC }]
22
+ ).slice(0, 6)
59
23
 
60
24
  phase('Implement')
61
- const implementations = (await parallel(
62
- lanes.map((lane, index) => () => dispatchAgent(
63
- agent,
64
- `lane-implementer-${index + 1}`,
65
- `Implement this owned lane for the topic "${TOPIC}".\n${barrierRef({ runId: RUN_ID, phase: 'Scope', label: 'barrier', summary: `lane ${lane.name}: ${lane.task.slice(0, 120)}` }).promptBlock}\nDo not edit outside the listed scope. Do not revert sibling work. Add regression tests and run the smallest relevant checks. Return changed files, commands, exact results, and blockers. Do not commit, push, publish, or deploy.`,
66
- { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: `implement:${index + 1}:${lane.name}`, phase: 'Implement', isolation: 'worktree' },
67
- )),
68
- )).filter(Boolean)
69
- if (implementations.length === 0) {
70
- return { status: 'blocked', reason: 'No implementation lane completed successfully.', scope: scoped }
71
- }
72
- // Phase B: persist each implementation artifact.
73
- for (let i = 0; i < implementations.length; i++) {
74
- const lane = lanes[i];
75
- const label = `implement:${i + 1}:${lane.name}`;
76
- const summary = `lane ${lane.name} files: ${(implementations[i]?.files || []).slice(0, 5).join(', ')}`;
77
- writeArtifact({ runId: RUN_ID, phase: 'Implement', label, payload: implementations[i], summary, role: 'implementer' });
25
+ const runLane = (lane, index) => dispatchAgent(
26
+ agent,
27
+ `lane-implementer-${index + 1}`,
28
+ `Implement this owned lane for the topic "${TOPIC}".\nLane: ${lane.name || `lane-${index + 1}`}\nTask: ${lane.task || TOPIC}\nWritable scope: ${Array.isArray(lane.scope) && lane.scope.length ? lane.scope.join(', ') : 'discover the smallest necessary scope, then keep it bounded'}\nDo not edit outside the lane's necessary scope. Do not revert sibling work. Add a regression test when behavior changes and run the smallest proving checks. Return changed files, commands, exact results, and blockers. Do not commit, push, publish, or deploy.`,
29
+ { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: `implement:${index + 1}:${lane.name || 'bounded-change'}`, phase: 'Implement', isolation: 'worktree' },
30
+ )
31
+
32
+ const implementations = lanes.length === 1
33
+ ? [await runLane(lanes[0], 0)]
34
+ : await parallel(lanes.map((lane, index) => () => runLane(lane, index)))
35
+ const completed = implementations.filter(Boolean)
36
+
37
+ if (completed.length === 0) {
38
+ return { status: 'blocked', reason: 'No implementation lane completed successfully.', lanes }
78
39
  }
79
40
 
80
- phase('Barrier')
81
- const merge = await dispatchAgent(agent, 'barrier-merger', `Reconcile the lane outputs for topic "${TOPIC}" into one MERGE plan. Identify conflicts between worktrees, exact integration order, shared-file ownership, and any human approvals required.\n${barrierRef({ runId: RUN_ID, phase: 'Implement', label: 'implement:summary', summary: `${implementations.length} lanes complete across ${lanes.length} planned` }).promptBlock}`, { role: 'implementer', risk: 'high', capabilities: ['structured-output', 'reasoning', 'architecture'], label: 'barrier-merge', phase: 'Barrier' })
82
- writeArtifact({ runId: RUN_ID, phase: 'Barrier', label: 'barrier', payload: merge, summary: typeof merge === 'string' ? merge.slice(0, 200) : `barrier merge complete`, role: 'implementer' })
83
-
84
- phase('Verify')
85
- const verify = await dispatchAgent(agent, 'barrier-verifier', `Re-check this MERGE plan against the original scope for topic "${TOPIC}". Reject it if any lane output is missing, any conflict is unresolved, or any test gate is unbounded. Return the verified plan plus the exact gating tests.\n${barrierRef({ runId: RUN_ID, phase: 'Barrier', label: 'barrier', summary: `verify against scope: ${SCOPE.length} scope items` }).promptBlock}`, { role: 'adversarial', risk: 'high', capabilities: ['structured-output', 'reasoning'], label: 'barrier-verify', phase: 'Verify' })
86
-
87
- phase('Synthesis')
88
- const synthesis = await dispatchAgent(agent, 'integration-reporter', `Produce the final integration report for topic "${TOPIC}". State exact integration order, remaining gates, evidence commands to run, and any required human approvals. Do not claim success without fresh command evidence.\n${barrierRef({ runId: RUN_ID, phase: 'Barrier', label: 'barrier', summary: `merge plan: ${typeof merge === 'string' ? merge.slice(0, 120) : 'complex'}` }).promptBlock}\n${barrierRef({ runId: RUN_ID, phase: 'Verify', label: 'barrier-verify', summary: typeof verify === 'string' ? verify.slice(0, 120) : 'verified' }).promptBlock}`, { role: 'implementer', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'integration-report', phase: 'Synthesis' })
89
-
90
41
  return {
91
42
  status: 'ready-for-integration',
92
43
  topic: TOPIC,
93
44
  lanes,
94
- implementations,
95
- barrier: merge,
96
- verify,
97
- synthesis,
45
+ implementations: completed,
46
+ next: 'Merge queued worktrees and run integration verification in the primary session.',
98
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "10.23.4",
3
+ "version": "10.23.6",
4
4
  "description": "Autonomous, human-in-the-loop multi-agent harness for Claude Code with guarded workflows, typed SDK primitives, and MCP tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export declare const SDK_VERSION: "10.23.4";
4
+ export declare const SDK_VERSION: "10.23.6";
5
5
  //# sourceMappingURL=version.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * SDK version constant. Keep synchronized with the workspace package versions.
3
3
  */
4
- export const SDK_VERSION = "10.23.4";
4
+ export const SDK_VERSION = "10.23.6";
5
5
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar-sdk",
3
- "version": "10.23.4",
3
+ "version": "10.23.6",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",