@polderlabs/bizar 10.23.8 → 10.23.10
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 +20 -21
- package/cli/commands/models.mjs +9 -26
- package/cli/commands/tier.mjs +1 -1
- package/cli/commands/upgrade-defaults.mjs +3 -6
- package/cli/provision.mjs +31 -28
- package/config/agents/model-assignment.mjs +16 -9
- package/config/claude/CLAUDE.md +20 -21
- package/config/claude/agents/office-manager.md +21 -15
- package/config/claude/agents/planner.md +2 -1
- package/config/claude/hooks/README.md +1 -1
- package/config/claude/hooks/agent-model-guard.mjs +27 -4
- package/config/claude/hooks/sessionstart-model-sync.mjs +4 -15
- package/config/claude/hooks/sessionstart-prime.mjs +4 -4
- package/config/claude/hooks/worker-suggest.mjs +6 -5
- package/config/claude/hooks/workflow-route-guard.mjs +17 -14
- package/config/workflows/bizar-implement.js +28 -5
- package/config/workflows/lib/dispatch.js +7 -5
- package/package.json +3 -4
- package/packages/sdk/dist/mcp/server.js +1 -1
- package/packages/sdk/dist/version.d.ts +1 -1
- package/packages/sdk/dist/version.js +1 -1
- package/packages/sdk/package.json +1 -1
- package/config/claude/model-router.json +0 -131
package/AGENTS.md
CHANGED
|
@@ -58,21 +58,18 @@ still deny prohibited actions and escalate externally visible or irreversible
|
|
|
58
58
|
actions with `permissionDecision: "ask"`; that escalation list is the
|
|
59
59
|
authoritative floor, not a starting point.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
change
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Do not add unnecessary phases or duplicate workers. For work that needs 3+
|
|
68
|
-
long-lived workers with bounded cross-talk,
|
|
69
|
-
Mike invokes a workflow that fans out as a native agent team; the team is
|
|
61
|
+
Mike selects the coordination mode after bounded read-only orientation and one
|
|
62
|
+
user clarification checkpoint: direct work only for an unmistakably tiny
|
|
63
|
+
single-target copy/style/format edit; one isolated Agent for a clear bounded
|
|
64
|
+
change; a native workflow for repeatable phased work; parallel Agents for
|
|
65
|
+
disjoint scopes; and an Agent team for 3+ sustained roles that genuinely need
|
|
66
|
+
cross-talk. Do not add unnecessary phases or duplicate workers. The team is
|
|
70
67
|
host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
|
|
71
|
-
Anthropic's docs `team_name` is deprecated and ignored.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
Anthropic's docs `team_name` is deprecated and ignored. Every editing dispatch
|
|
69
|
+
uses an explicit configured model and call-level `isolation: "worktree"`.
|
|
70
|
+
When two or more subtasks have non-overlapping writable scopes and no data
|
|
71
|
+
dependency, dispatch them concurrently; serialize only dependencies and
|
|
72
|
+
integration.
|
|
76
73
|
|
|
77
74
|
Agent roles are model-agnostic. Mike selects the cheapest sufficient enabled
|
|
78
75
|
configured-tier model for each dispatch, with explicit user picks taking
|
|
@@ -136,11 +133,13 @@ The autonomy and approval policy above governs this execution model. The project
|
|
|
136
133
|
Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
|
|
137
134
|
The installer sets Claude Code's global `agent` setting to Mike's frontmatter
|
|
138
135
|
name (`mike`),
|
|
139
|
-
and
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
and the routing hook supplies the adaptive coordination policy. For non-tiny
|
|
137
|
+
work, Mike first gathers only bounded read-only context, asks one concise
|
|
138
|
+
clarification question that names the inferred outcome and proposed mode, then
|
|
139
|
+
continues autonomously after the answer. A native workflow is one available
|
|
140
|
+
mode, not a universal gate; Mike may select an isolated Agent, parallel Agents,
|
|
141
|
+
or an Agent team when that better fits the work. Mike owns integration and
|
|
142
|
+
final verification.
|
|
144
143
|
A Bizar custom agent already executing its assigned role does not recursively
|
|
145
144
|
dispatch itself.
|
|
146
145
|
|
|
@@ -152,8 +151,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
|
|
|
152
151
|
documentation is unavailable or ambiguous, inspect authoritative source code
|
|
153
152
|
and report the evidence gap.
|
|
154
153
|
|
|
155
|
-
For workflow
|
|
156
|
-
a known risk
|
|
154
|
+
For a workflow or team, `office-manager` uses only phases and members that
|
|
155
|
+
reduce a known risk:
|
|
157
156
|
|
|
158
157
|
1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
|
|
159
158
|
2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
|
package/cli/commands/models.mjs
CHANGED
|
@@ -716,9 +716,8 @@ export function classifyKind(modelId) {
|
|
|
716
716
|
// in-code hardcoded list — adding or removing a blocked provider is a
|
|
717
717
|
// single JSON edit.
|
|
718
718
|
//
|
|
719
|
-
//
|
|
720
|
-
//
|
|
721
|
-
// "no providers disabled" without deleting the legacy mirror). Whitespace
|
|
719
|
+
// Global-only read: the Bizar path (`~/.config/bizar/config/claude/model-router.json`)
|
|
720
|
+
// is the sole operator policy source. Whitespace
|
|
722
721
|
// trim + lowercase normalization happens at read time so operators may
|
|
723
722
|
// write `" Anthropic "` in JSON and still match `anthropic/...` model
|
|
724
723
|
// ids. The comparison itself is a case-sensitive prefix filter against
|
|
@@ -758,44 +757,28 @@ function extractDisabledProviders(router) {
|
|
|
758
757
|
|
|
759
758
|
/**
|
|
760
759
|
* Read the operator's `disabledProviders` list from the model-router
|
|
761
|
-
* config.
|
|
762
|
-
*
|
|
763
|
-
* is the fallback. Both paths are normalized (trim + lowercase) at read.
|
|
760
|
+
* config. The Bizar global path is the only source. It is normalized (trim +
|
|
761
|
+
* lowercase) at read.
|
|
764
762
|
*
|
|
765
763
|
* Pure function over the filesystem; returns an empty array when neither
|
|
766
|
-
* file exists,
|
|
764
|
+
* file exists, lacks the key, or cannot be read.
|
|
767
765
|
*
|
|
768
|
-
* @param {{ routerPath?: string
|
|
766
|
+
* @param {{ routerPath?: string }} [opts]
|
|
769
767
|
* - `routerPath` defaults to the Bizar home path
|
|
770
768
|
* (`~/.config/bizar/config/claude/model-router.json`).
|
|
771
|
-
* - `legacyPath` defaults to the Claude Code mirror
|
|
772
|
-
* (`~/.claude/model-router.json`).
|
|
773
769
|
* @returns {string[]} normalized disabled-provider prefixes
|
|
774
770
|
*/
|
|
775
|
-
export function readDisabledProviders({ routerPath
|
|
771
|
+
export function readDisabledProviders({ routerPath } = {}) {
|
|
776
772
|
const bizarPath = routerPath || resolveGlobalModelRouter();
|
|
777
|
-
const fallPath = legacyPath || join(resolveClaudeConfigDir(), 'model-router.json');
|
|
778
773
|
if (existsSync(bizarPath)) {
|
|
779
774
|
try {
|
|
780
775
|
const parsed = JSON.parse(readFileSync(bizarPath, 'utf8'));
|
|
781
776
|
const extracted = extractDisabledProviders(parsed);
|
|
782
|
-
//
|
|
783
|
-
// when explicitly `[]` (operators may pin "no providers disabled"
|
|
784
|
-
// without deleting the legacy mirror). Missing key still falls back.
|
|
777
|
+
// An explicit empty list is a valid global opt-out policy.
|
|
785
778
|
if (Array.isArray(parsed && typeof parsed === 'object' ? parsed.disabledProviders : undefined)) {
|
|
786
779
|
return extracted;
|
|
787
780
|
}
|
|
788
|
-
} catch {
|
|
789
|
-
// Corrupt Bizar file — fall through to the legacy mirror.
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
if (existsSync(fallPath)) {
|
|
793
|
-
try {
|
|
794
|
-
const parsed = JSON.parse(readFileSync(fallPath, 'utf8'));
|
|
795
|
-
return extractDisabledProviders(parsed);
|
|
796
|
-
} catch {
|
|
797
|
-
return [];
|
|
798
|
-
}
|
|
781
|
+
} catch { return []; }
|
|
799
782
|
}
|
|
800
783
|
return [];
|
|
801
784
|
}
|
package/cli/commands/tier.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `bizar tier` — automatic task-based model selection.
|
|
6
6
|
*
|
|
7
7
|
* Replays the same tier resolution the @mike orchestrator uses:
|
|
8
|
-
* 1. Read the role default from
|
|
8
|
+
* 1. Read the role default from the global Bizar model router.
|
|
9
9
|
* 2. Adjust the tier from current task risk and complexity.
|
|
10
10
|
* 3. Print ordered candidates; dispatch uses a candidate only when live
|
|
11
11
|
* discovery proves it, otherwise Agent omits model and inherits the session.
|
|
@@ -42,13 +42,11 @@ const CLAUDE_DIR = resolveClaudeConfigDir();
|
|
|
42
42
|
const SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json');
|
|
43
43
|
// 10.22.0 / Phase 4 spirit-of-constraint: the install model id comes
|
|
44
44
|
// from the operator's `userSelected.models[0]`, not a hardcoded literal.
|
|
45
|
-
//
|
|
46
|
-
// the Bizar path wins when present; the Claude Code mirror is fallback.
|
|
45
|
+
// The Bizar global router is the sole model-policy source.
|
|
47
46
|
const BIZAR_ROUTER_PATH = resolveGlobalModelRouter();
|
|
48
|
-
const LEGACY_ROUTER_PATH = join(CLAUDE_DIR, 'model-router.json');
|
|
49
47
|
|
|
50
48
|
function readConfiguredModels() {
|
|
51
|
-
for (const path of [BIZAR_ROUTER_PATH
|
|
49
|
+
for (const path of [BIZAR_ROUTER_PATH]) {
|
|
52
50
|
if (!existsSync(path)) continue;
|
|
53
51
|
try {
|
|
54
52
|
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -58,8 +56,7 @@ function readConfiguredModels() {
|
|
|
58
56
|
if (list.length > 0) return list;
|
|
59
57
|
const fallback = configuredFallbackModels(parsed);
|
|
60
58
|
if (fallback.length > 0) return fallback;
|
|
61
|
-
//
|
|
62
|
-
// honour that intent (do NOT fall through to the legacy mirror).
|
|
59
|
+
// An explicit empty selection is an intentional global policy.
|
|
63
60
|
if (path === BIZAR_ROUTER_PATH && parsed && typeof parsed === 'object'
|
|
64
61
|
&& Array.isArray(parsed.userSelected?.models)) {
|
|
65
62
|
return [];
|
package/cli/provision.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
import { homedir } from 'node:os';
|
|
32
32
|
import { dirname, join, resolve, sep } from 'node:path';
|
|
33
33
|
import { fileURLToPath } from 'node:url';
|
|
34
|
-
import { resolveBizarHome } from './config-paths.mjs';
|
|
34
|
+
import { resolveBizarHome, resolveGlobalModelRouter } from './config-paths.mjs';
|
|
35
35
|
import {
|
|
36
36
|
buildClaudeModelOverrides,
|
|
37
37
|
configuredEnabledModels,
|
|
@@ -465,9 +465,20 @@ export async function syncAgentFiles({ dryRun = false, force = false } = {}) {
|
|
|
465
465
|
return { ok: true, message: `${copied} agent(s) synced (${skipped} kept)${sharedCopied ? `, ${sharedCopied} _shared/*.md` : ''}${tail}`, copied, skipped, pruned };
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
// The dynamic model router
|
|
469
|
-
//
|
|
470
|
-
|
|
468
|
+
// The dynamic model router is operator-owned state. It is created only under
|
|
469
|
+
// Bizar's global config root; a repository can never supply a runtime model.
|
|
470
|
+
|
|
471
|
+
export const EMPTY_GLOBAL_MODEL_ROUTER = {
|
|
472
|
+
$schema: 'https://bizar.dev/schema/model-router.v3.json',
|
|
473
|
+
version: '13.0.0',
|
|
474
|
+
endpoint: null,
|
|
475
|
+
disabledProviders: ['anthropic'],
|
|
476
|
+
userSelected: { models: [], lastUpdated: null, source: 'default-empty', tierHints: {} },
|
|
477
|
+
gateway: { required: false, endpoint: null, availabilityProbe: '/models', discoveryTimeoutMs: 3000, unavailableBehavior: 'configured-tier-fallback' },
|
|
478
|
+
tiers: Object.fromEntries(['premium', 'high', 'mid-design', 'default', 'mid', 'budget'].map((name) => [name, { models: [] }])),
|
|
479
|
+
roleDefaults: { mike: 'premium', paul: 'premium', carl: 'premium', karen: 'high', linda: 'high', ria: 'mid-design', greg: 'default', steve: 'default', oscar: 'mid', todd: 'mid', susan: 'mid', pam: 'budget', brenda: 'budget', janet: 'budget', kevin: 'budget', brad: 'mid-design' },
|
|
480
|
+
policies: { mainOrchestrator: 'mike', selectionOwner: 'orchestrator', dispatchModelOverride: 'required-configured-candidate', unknownAgent: 'configured-tier-fallback', discoveryFailure: 'configured-tier-fallback', unavailableModel: 'configured-tier-fallback', silentProviderSubstitution: false, retryModelAliases: false, snapshotDecisions: true, maxDispatchModelAttempts: 1 },
|
|
481
|
+
};
|
|
471
482
|
|
|
472
483
|
export function isBizarManagedModelRouter(value) {
|
|
473
484
|
return Boolean(
|
|
@@ -479,16 +490,14 @@ export function isBizarManagedModelRouter(value) {
|
|
|
479
490
|
}
|
|
480
491
|
|
|
481
492
|
export async function syncModelRouter({ dryRun = false, force = false } = {}) {
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (existsSync(dest) && !force && !isBizarManagedModelRouter(readJsonSafe(dest, null))) {
|
|
486
|
-
return { ok: true, message: `${dest} is user-managed — pass --force to overwrite`, preserved: true };
|
|
493
|
+
const dest = resolveGlobalModelRouter();
|
|
494
|
+
if (existsSync(dest)) {
|
|
495
|
+
return { ok: true, message: `${dest} is operator-managed — preserved`, preserved: true };
|
|
487
496
|
}
|
|
488
|
-
if (dryRun) return { ok: true, message: `[dry-run] would
|
|
489
|
-
ensureDir(
|
|
490
|
-
|
|
491
|
-
return { ok: true, message: `model-router.json → ${dest}`, path: dest };
|
|
497
|
+
if (dryRun) return { ok: true, message: `[dry-run] would create global model router at ${dest}` };
|
|
498
|
+
ensureDir(dirname(dest));
|
|
499
|
+
writeFileSync(dest, `${JSON.stringify(EMPTY_GLOBAL_MODEL_ROUTER, null, 2)}\n`);
|
|
500
|
+
return { ok: true, message: `global model-router.json → ${dest}`, path: dest };
|
|
492
501
|
}
|
|
493
502
|
|
|
494
503
|
export async function syncSkillFiles({ dryRun = false, force = false } = {}) {
|
|
@@ -949,16 +958,12 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
|
949
958
|
// wins; otherwise the first enabled configured tier candidate is used.
|
|
950
959
|
// This prevents Claude Code from falling through to an unconfigured
|
|
951
960
|
// provider default when `userSelected.models` is intentionally empty.
|
|
952
|
-
//
|
|
953
|
-
|
|
954
|
-
// `cli/commands/upgrade-defaults.mjs#readUserSelectedModels`.
|
|
955
|
-
const bizarRouterPath = process.env.BIZAR_MODEL_ROUTER_CONFIG?.trim()
|
|
956
|
-
|| join(BIZAR_HOME(), 'config', 'claude', 'model-router.json');
|
|
957
|
-
const legacyRouterPath = join(CLAUDE_DIR, 'model-router.json');
|
|
961
|
+
// The Bizar global router is the sole source of model policy and picks.
|
|
962
|
+
const bizarRouterPath = resolveGlobalModelRouter();
|
|
958
963
|
let installModel;
|
|
959
964
|
let installContextTokens;
|
|
960
965
|
let installModels = [];
|
|
961
|
-
for (const p of [bizarRouterPath
|
|
966
|
+
for (const p of [bizarRouterPath]) {
|
|
962
967
|
if (!existsSync(p)) continue;
|
|
963
968
|
const parsed = readJsonSafe(p, null);
|
|
964
969
|
if (!parsed || typeof parsed !== 'object') continue;
|
|
@@ -966,8 +971,6 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
|
|
|
966
971
|
installModels = configuredEnabledModels(parsed);
|
|
967
972
|
installContextTokens = installModel ? configuredModelContextTokens(parsed, installModel) : undefined;
|
|
968
973
|
if (installModel) break;
|
|
969
|
-
// The Bizar path is authoritative even when it has no enabled candidates.
|
|
970
|
-
if (p === bizarRouterPath) break;
|
|
971
974
|
}
|
|
972
975
|
if (installModel) {
|
|
973
976
|
bizarSettings.model = installModel;
|
|
@@ -1177,16 +1180,16 @@ export async function runProvision(opts = {}) {
|
|
|
1177
1180
|
await runStep('Installing git hooks', () => installGitHooks({ dryRun }));
|
|
1178
1181
|
await runStep('Building SDK', () => buildSdk({ dryRun }));
|
|
1179
1182
|
|
|
1183
|
+
section('Ensuring global model-router.json');
|
|
1184
|
+
const routerStep = await syncModelRouter({ dryRun, force });
|
|
1185
|
+
if (routerStep.ok) logOk(routerStep.message); else logErr(routerStep.message);
|
|
1186
|
+
stepResults.push({ label: 'model-router', ...routerStep });
|
|
1187
|
+
|
|
1180
1188
|
section('Writing settings.json');
|
|
1181
1189
|
const settingsStep = writeClaudeSettings({ dryRun, force });
|
|
1182
1190
|
if (settingsStep.ok) logOk(settingsStep.message); else logErr(settingsStep.message);
|
|
1183
1191
|
stepResults.push({ label: 'settings.json', ...settingsStep });
|
|
1184
1192
|
|
|
1185
|
-
section('Syncing model-router.json');
|
|
1186
|
-
const routerStep = await syncModelRouter({ dryRun, force });
|
|
1187
|
-
if (routerStep.ok) logOk(routerStep.message); else logErr(routerStep.message);
|
|
1188
|
-
stepResults.push({ label: 'model-router', ...routerStep });
|
|
1189
|
-
|
|
1190
1193
|
section('MCP server (bizar)');
|
|
1191
1194
|
const mcpStep = setupMcpServer({ dryRun });
|
|
1192
1195
|
if (mcpStep.ok) logOk(mcpStep.message); else logWarn(mcpStep.message);
|
|
@@ -1221,7 +1224,7 @@ export async function runProvision(opts = {}) {
|
|
|
1221
1224
|
// back to a clear hint to run `bizar models`.
|
|
1222
1225
|
let premiumPick = null;
|
|
1223
1226
|
try {
|
|
1224
|
-
const routerPath =
|
|
1227
|
+
const routerPath = resolveGlobalModelRouter();
|
|
1225
1228
|
const router = JSON.parse(readFileSync(routerPath, 'utf8'));
|
|
1226
1229
|
const picks = Array.isArray(router?.userSelected?.tierHints?.premium)
|
|
1227
1230
|
? router.userSelected.tierHints.premium.filter((id) => typeof id === 'string' && id)
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createHash } from 'node:crypto';
|
|
10
10
|
import { readFileSync } from 'node:fs';
|
|
11
|
-
import {
|
|
12
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { resolveGlobalModelRouter } from '../../cli/config-paths.mjs';
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
export
|
|
13
|
+
/** Resolve the single operator-owned router at call time, never from a repo. */
|
|
14
|
+
export function defaultModelRouterPath() {
|
|
15
|
+
return resolveGlobalModelRouter();
|
|
16
|
+
}
|
|
16
17
|
|
|
17
18
|
function fail(code, message) {
|
|
18
19
|
const error = new Error(message);
|
|
@@ -39,9 +40,6 @@ function assertDynamicRegistry(registry) {
|
|
|
39
40
|
if (!registry.tiers || typeof registry.tiers !== 'object' || Object.keys(registry.tiers).length === 0) {
|
|
40
41
|
fail('MODEL_REGISTRY_INVALID', 'The model router must define at least one dynamic tier.');
|
|
41
42
|
}
|
|
42
|
-
for (const [name, tier] of Object.entries(registry.tiers)) {
|
|
43
|
-
if (normalizeModelIds(tier?.models).length === 0) fail('MODEL_REGISTRY_INVALID', `Tier ${name} has no candidate models.`);
|
|
44
|
-
}
|
|
45
43
|
const policies = registry.policies || {};
|
|
46
44
|
if (policies.selectionOwner !== 'orchestrator') fail('MODEL_POLICY_INVALID', 'The orchestrator must own model selection.');
|
|
47
45
|
if (!['configured-tier-fallback', 'inherit-session'].includes(policies.discoveryFailure)
|
|
@@ -54,7 +52,7 @@ function assertDynamicRegistry(registry) {
|
|
|
54
52
|
return registry;
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
export function loadModelRouter(path =
|
|
55
|
+
export function loadModelRouter(path = defaultModelRouterPath()) {
|
|
58
56
|
try {
|
|
59
57
|
return assertDynamicRegistry(JSON.parse(readFileSync(path, 'utf8')));
|
|
60
58
|
} catch (error) {
|
|
@@ -73,7 +71,16 @@ export function resolveDispatchModel({
|
|
|
73
71
|
const definition = registry.tiers?.[chosenTier];
|
|
74
72
|
if (!definition) fail('UNKNOWN_TIER', `Unknown model tier ${chosenTier}.`);
|
|
75
73
|
const disabled = new Set(normalizeModelIds(registry.disabledProviders).map((prefix) => prefix.toLowerCase()));
|
|
76
|
-
const
|
|
74
|
+
const selected = normalizeModelIds(registry.userSelected?.models)
|
|
75
|
+
.filter((candidate) => ![...disabled].some((prefix) => candidate.toLowerCase().startsWith(prefix)));
|
|
76
|
+
const hints = registry.userSelected?.tierHints && typeof registry.userSelected.tierHints === 'object'
|
|
77
|
+
? registry.userSelected.tierHints
|
|
78
|
+
: {};
|
|
79
|
+
// The operator's picker is authoritative. A matching tier hint narrows the
|
|
80
|
+
// pool; otherwise all enabled picks remain eligible rather than silently
|
|
81
|
+
// falling back to repository-supplied candidates.
|
|
82
|
+
const hinted = selected.filter((candidate) => hints[candidate] === chosenTier);
|
|
83
|
+
const candidates = (hinted.length > 0 ? hinted : selected.length > 0 ? selected : normalizeModelIds(definition.models))
|
|
77
84
|
.filter((candidate) => ![...disabled].some((prefix) => candidate.toLowerCase().startsWith(prefix)));
|
|
78
85
|
if (candidates.length === 0) fail('NO_ENABLED_TIER_MODEL', `Tier ${chosenTier} has no enabled candidate models.`);
|
|
79
86
|
const available = availableModelIds == null ? null : new Set(normalizeModelIds(availableModelIds));
|
package/config/claude/CLAUDE.md
CHANGED
|
@@ -69,21 +69,18 @@ still deny prohibited actions and escalate externally visible or irreversible
|
|
|
69
69
|
actions with `permissionDecision: "ask"`; that escalation list is the
|
|
70
70
|
authoritative floor, not a starting point.
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
change
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Do not add unnecessary phases or duplicate workers. For work that needs 3+
|
|
79
|
-
long-lived workers with bounded cross-talk,
|
|
80
|
-
Mike invokes a workflow that fans out as a native agent team; the team is
|
|
72
|
+
Mike selects the coordination mode after bounded read-only orientation and one
|
|
73
|
+
user clarification checkpoint: direct work only for an unmistakably tiny
|
|
74
|
+
single-target copy/style/format edit; one isolated Agent for a clear bounded
|
|
75
|
+
change; a native workflow for repeatable phased work; parallel Agents for
|
|
76
|
+
disjoint scopes; and an Agent team for 3+ sustained roles that genuinely need
|
|
77
|
+
cross-talk. Do not add unnecessary phases or duplicate workers. The team is
|
|
81
78
|
host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
|
|
82
|
-
Anthropic's docs `team_name` is deprecated and ignored.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
Anthropic's docs `team_name` is deprecated and ignored. Every editing dispatch
|
|
80
|
+
uses an explicit configured model and call-level `isolation: "worktree"`.
|
|
81
|
+
When two or more subtasks have non-overlapping writable scopes and no data
|
|
82
|
+
dependency, dispatch them concurrently; serialize only dependencies and
|
|
83
|
+
integration.
|
|
87
84
|
|
|
88
85
|
Agent roles are model-agnostic. Mike selects the cheapest sufficient enabled
|
|
89
86
|
configured-tier model for each dispatch, with explicit user picks taking
|
|
@@ -147,11 +144,13 @@ The autonomy and approval policy above governs this execution model. The project
|
|
|
147
144
|
Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
|
|
148
145
|
The installer sets Claude Code's global `agent` setting to Mike's frontmatter
|
|
149
146
|
name (`mike`),
|
|
150
|
-
and
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
and the routing hook supplies the adaptive coordination policy. For non-tiny
|
|
148
|
+
work, Mike first gathers only bounded read-only context, asks one concise
|
|
149
|
+
clarification question that names the inferred outcome and proposed mode, then
|
|
150
|
+
continues autonomously after the answer. A native workflow is one available
|
|
151
|
+
mode, not a universal gate; Mike may select an isolated Agent, parallel Agents,
|
|
152
|
+
or an Agent team when that better fits the work. Mike owns integration and
|
|
153
|
+
final verification.
|
|
155
154
|
A Bizar custom agent already executing its assigned role does not recursively
|
|
156
155
|
dispatch itself.
|
|
157
156
|
|
|
@@ -163,8 +162,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
|
|
|
163
162
|
documentation is unavailable or ambiguous, inspect authoritative source code
|
|
164
163
|
and report the evidence gap.
|
|
165
164
|
|
|
166
|
-
For workflow
|
|
167
|
-
a known risk
|
|
165
|
+
For a workflow or team, `office-manager` uses only phases and members that
|
|
166
|
+
reduce a known risk:
|
|
168
167
|
|
|
169
168
|
1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
|
|
170
169
|
2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mike
|
|
3
|
-
description: Mike —
|
|
4
|
-
tools: Workflow, Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill
|
|
3
|
+
description: Mike — adaptive orchestrator that selects the lightest safe coordination mode.
|
|
4
|
+
tools: Workflow, Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill, AskUserQuestion
|
|
5
5
|
skills:
|
|
6
6
|
- i-have-adhd
|
|
7
7
|
---
|
|
@@ -12,24 +12,29 @@ Follow `_shared/AGENT_BASELINE.md`. You own the user outcome, integration, and
|
|
|
12
12
|
final verification. Direct execution is a narrow exception; workflows are the
|
|
13
13
|
default for meaningful work.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Orient, clarify, then select the coordination mode
|
|
16
16
|
|
|
17
17
|
| Shape | Signals | Execution |
|
|
18
18
|
|---|---|---|
|
|
19
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
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
| Single isolated worker | one bounded implementation after scope is clear | dispatch one worktree-isolated Agent with an explicit Bizar model; integrate and verify |
|
|
21
|
+
| Native workflow | repeatable diagnosis, research, review, or an implementation needing visible phase barriers | invoke the matching Bizar workflow with explicit Bizar routing |
|
|
22
|
+
| Agent team | three or more sustained, independent roles need bounded cross-talk or coordinated handoff | use the native Agent-team capability; writers use worktrees and explicit Bizar models |
|
|
23
|
+
| Parallel agents | two disjoint writable scopes with no cross-talk needed | dispatch concurrently with explicit models and worktree isolation |
|
|
24
|
+
|
|
25
|
+
For every non-tiny request, first make only enough read-only inspection to
|
|
26
|
+
understand the repository boundary and current constraints. Then ask the user
|
|
27
|
+
one concise clarification checkpoint: state the inferred outcome, the material
|
|
28
|
+
choice or risk, and the proposed coordination mode. Wait for the answer before
|
|
29
|
+
writing, dispatching editors, creating branches, or running tests. If the user
|
|
30
|
+
explicitly says to proceed without questions, record that choice and continue.
|
|
31
|
+
After the answer, work autonomously until the requested outcome and verification
|
|
32
|
+
are complete. Research current official docs only for external or
|
|
28
33
|
version-sensitive claims. Inspect installed skills before hard or specialized
|
|
29
34
|
work; if stuck with no match, search skills.sh and review the candidate before
|
|
30
35
|
proposing installation.
|
|
31
36
|
|
|
32
|
-
Before every Workflow call, read the global Bizar model router and construct a
|
|
37
|
+
Before every Workflow, Agent, or Agent-team call, read the global Bizar model router and construct a
|
|
33
38
|
small `args.routing` object whose `default`, `medium`, and `high` values are
|
|
34
39
|
explicit enabled configured model IDs (user picks win; otherwise use enabled
|
|
35
40
|
tier candidates). Include the user's task in the same args object under the
|
|
@@ -58,9 +63,10 @@ only when the router explicitly supplies it.
|
|
|
58
63
|
|
|
59
64
|
## Worktree Discipline and integration
|
|
60
65
|
|
|
61
|
-
Every editing subagent call uses call-level `isolation: "worktree"`.
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
Every editing subagent call uses call-level `isolation: "worktree"`. Use teams
|
|
67
|
+
only when collaboration changes the result; do not manufacture a team or a
|
|
68
|
+
workflow for a simple isolated task. Parallel writers receive disjoint file
|
|
69
|
+
ownership and sibling scopes. Read-only research stays foreground. When a writer finishes, merge its queued branch with
|
|
64
70
|
`bizar worktree-merge`; report conflicts instead of guessing. The integration
|
|
65
71
|
branch runs final tests once after all required results are incorporated.
|
|
66
72
|
Worktree branches use `wt/<agent_type>-<short-task-id>`.
|
|
@@ -43,7 +43,8 @@ Every plan you produce MUST follow this shape. The user (or Mike, or any downstr
|
|
|
43
43
|
|
|
44
44
|
## Subagent Model Selection
|
|
45
45
|
|
|
46
|
-
When you delegate implementation, you recommend the model tier. Read
|
|
46
|
+
When you delegate implementation, you recommend the model tier. Read the
|
|
47
|
+
operator-global Bizar router (`$BIZAR_HOME/config/claude/model-router.json`) to confirm.
|
|
47
48
|
|
|
48
49
|
| Task shape | Route to | Tier |
|
|
49
50
|
|---|---|---|
|
|
@@ -11,7 +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 |
|
|
14
|
+
| `workflow-route-guard.mjs` | UserPromptSubmit, PreToolUse, PostToolUse | records adaptive Bizar routing state without blocking Mike's selected coordination mode; workflow success clears the pending route record |
|
|
15
15
|
| `thinking-route.mjs` | UserPromptSubmit | slash and mental-model routing |
|
|
16
16
|
| `telemetry.mjs` | SessionStart/UserPromptSubmit | local correlation and rejection categories |
|
|
17
17
|
| `sessionstart-prime.mjs` | SessionStart | bounded project and handoff context |
|
|
@@ -44,9 +44,11 @@
|
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
46
|
import { readFileSync } from 'node:fs';
|
|
47
|
+
import { join } from 'node:path';
|
|
47
48
|
import { pathToFileURL } from 'node:url';
|
|
48
49
|
|
|
49
50
|
import { loadModelRouter } from '../../../config/agents/model-assignment.mjs';
|
|
51
|
+
import { resolveClaudeConfigDir } from '../../../cli/config-paths.mjs';
|
|
50
52
|
|
|
51
53
|
function deny(reason) {
|
|
52
54
|
return {
|
|
@@ -152,6 +154,17 @@ function readFailoverBlock(toolInput) {
|
|
|
152
154
|
};
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
function readConfiguredParentModel(options = {}) {
|
|
158
|
+
if (typeof options.parentModel === 'string') return options.parentModel.trim();
|
|
159
|
+
const settingsPath = options.settingsPath || join(resolveClaudeConfigDir(), 'settings.json');
|
|
160
|
+
try {
|
|
161
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
162
|
+
return typeof settings?.model === 'string' ? settings.model.trim() : '';
|
|
163
|
+
} catch {
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
155
168
|
export async function guardAgentModel(input, options = {}) {
|
|
156
169
|
if (!input || typeof input !== 'object') return {};
|
|
157
170
|
if (input.hook_event_name !== 'PreToolUse' || input.tool_name !== 'Agent') return {};
|
|
@@ -160,10 +173,6 @@ export async function guardAgentModel(input, options = {}) {
|
|
|
160
173
|
const failoverBlock = readFailoverBlock(toolInput);
|
|
161
174
|
const hasFailoverContract = Boolean(failoverBlock.routingDecisionId) && Boolean(failoverBlock.fallback);
|
|
162
175
|
|
|
163
|
-
if (!requested || requested === 'inherit') {
|
|
164
|
-
return deny('Bizar Agent dispatch blocked: every Agent call requires an explicit enabled model from `bizar models`; session/provider inheritance is prohibited.');
|
|
165
|
-
}
|
|
166
|
-
|
|
167
176
|
let registry;
|
|
168
177
|
try {
|
|
169
178
|
registry = options.registry || loadModelRouter(options.routerPath);
|
|
@@ -181,6 +190,20 @@ export async function guardAgentModel(input, options = {}) {
|
|
|
181
190
|
const allowed = configuredModels(registry);
|
|
182
191
|
const userPicks = userSelectedModels(registry);
|
|
183
192
|
|
|
193
|
+
// The native Agent tool rejects arbitrary gateway IDs in its `model` enum.
|
|
194
|
+
// Inheritance is safe only when the auditable Bizar selection equals the
|
|
195
|
+
// actual global Claude parent model and is a selected, enabled user pick.
|
|
196
|
+
if (!requested || requested === 'inherit') {
|
|
197
|
+
const configured = typeof toolInput.additionalContext?.bizarConfiguredModel === 'string'
|
|
198
|
+
? toolInput.additionalContext.bizarConfiguredModel.trim()
|
|
199
|
+
: '';
|
|
200
|
+
const parent = readConfiguredParentModel(options);
|
|
201
|
+
if (!configured || configured !== parent || !userPicks.has(configured)) {
|
|
202
|
+
return deny('Bizar Agent dispatch blocked: native Agent inheritance requires the global Claude parent model to exactly match an enabled Bizar user selection. Run `bizar models` and restart Claude Code.');
|
|
203
|
+
}
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
|
|
184
207
|
// F-185 contract: when the orchestrator passes both `routingDecisionId`
|
|
185
208
|
// and `fallback`, validate the fallback against the userSelected pool
|
|
186
209
|
// but skip the live-discovery re-probe. The fallback's eligibility was
|
|
@@ -61,16 +61,12 @@ function readRouterPath() {
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* 10.22.0 / Phase 4: read the operator's `disabledProviders: string[]`
|
|
64
|
-
* list
|
|
65
|
-
*
|
|
66
|
-
* when both exist (even with explicit `[]`); the legacy
|
|
67
|
-
* `~/.claude/model-router.json` mirror is the fallback. Whitespace +
|
|
68
|
-
* lowercase normalization happens here. Returns `[]` on any failure —
|
|
64
|
+
* list from the sole global Bizar router. Whitespace + lowercase
|
|
65
|
+
* normalization happens here. Returns `[]` on any failure —
|
|
69
66
|
* the hook is advisory and must NEVER block session start.
|
|
70
67
|
*/
|
|
71
68
|
function readDisabledProviders() {
|
|
72
69
|
const bizarPath = readRouterPath();
|
|
73
|
-
const legacyPath = join(resolveClaudeConfigDir(), 'model-router.json');
|
|
74
70
|
const extract = (parsed) => {
|
|
75
71
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
|
|
76
72
|
if (!Array.isArray(parsed.disabledProviders)) return null;
|
|
@@ -85,18 +81,12 @@ function readDisabledProviders() {
|
|
|
85
81
|
};
|
|
86
82
|
if (existsSync(bizarPath)) {
|
|
87
83
|
const parsed = readJsonIfObject(bizarPath);
|
|
88
|
-
//
|
|
89
|
-
// when explicit `[]`. Missing key still falls back to the legacy.
|
|
84
|
+
// An explicit empty global list is a valid operator policy.
|
|
90
85
|
if (parsed && Array.isArray(parsed.disabledProviders)) {
|
|
91
86
|
const extracted = extract(parsed);
|
|
92
87
|
if (extracted !== null) return extracted;
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
|
-
const legacy = readJsonIfObject(legacyPath);
|
|
96
|
-
if (legacy && Array.isArray(legacy.disabledProviders)) {
|
|
97
|
-
const extracted = extract(legacy);
|
|
98
|
-
if (extracted !== null) return extracted;
|
|
99
|
-
}
|
|
100
90
|
return [];
|
|
101
91
|
}
|
|
102
92
|
|
|
@@ -193,8 +183,7 @@ function syncOnce() {
|
|
|
193
183
|
: {};
|
|
194
184
|
// 10.22.0 / Phase 4: filter the operator's disabled-provider ids out
|
|
195
185
|
// of the SessionStart re-apply so Claude Code's `/model` picker never
|
|
196
|
-
// surfaces e.g. `anthropic/*` after a session restart.
|
|
197
|
-
// matches `cli/commands/models.mjs#readDisabledProviders`.
|
|
186
|
+
// surfaces e.g. `anthropic/*` after a session restart.
|
|
198
187
|
const disabled = readDisabledProviders();
|
|
199
188
|
const selectedIds = filterDisabled(
|
|
200
189
|
models.filter((id) => typeof id === 'string' && id.trim()),
|
|
@@ -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.
|
|
189
|
+
lines.push('- You are @mike. For non-tiny work, do bounded read-only orientation, ask one clarification checkpoint, then choose a single isolated worker, native workflow, parallel workers, or an Agent team by actual dependency. Use worktrees for editors and explicit Bizar models for every Agent.');
|
|
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
|
|
195
|
+
'- First move: read PROGRESS.md and feature_list.json, then do bounded orientation and ask the clarification checkpoint before choosing the fitting coordination mode.',
|
|
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
|
|
206
|
+
lines.push('- You are @mike: continue the active coordination plan; adapt it when new evidence changes the fit.');
|
|
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 continue the active
|
|
213
|
+
lines.push('- You are @mike: restore state, then continue the active coordination plan and adapt it if the evidence changed.');
|
|
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}.`);
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Runs on every user prompt. Only unmistakably tiny, single-scope edits take
|
|
8
8
|
* a cheap fast path. Every other request is routed into a native Bizar
|
|
9
|
-
*
|
|
9
|
+
* adaptive coordination mode selected by Mike after bounded orientation and a
|
|
10
|
+
* clarification checkpoint.
|
|
10
11
|
*
|
|
11
12
|
* Uses import.meta.url + dynamic import() to resolve the sibling CLI module so
|
|
12
13
|
* the hook works regardless of install path (fixes ERR_MODULE_NOT_FOUND after
|
|
@@ -56,10 +57,10 @@ const FAST_ROUTE_POLICY = [
|
|
|
56
57
|
].join('\n');
|
|
57
58
|
|
|
58
59
|
const ROUTE_POLICY = [
|
|
59
|
-
'
|
|
60
|
-
'- If this is the primary session, you ARE @mike.
|
|
61
|
-
'-
|
|
62
|
-
'-
|
|
60
|
+
'Adaptive Bizar routing policy:',
|
|
61
|
+
'- If this is the primary session, you ARE @mike. First do only bounded read-only orientation. Then ask one concise clarification question describing the inferred outcome, the material choice/risk, and your proposed coordination mode. Wait for the answer before edits, branches, tests, or editor dispatch. If the user explicitly waives questions, continue autonomously.',
|
|
62
|
+
'- After clarification, choose the lightest coordination mode: a direct tiny edit, one isolated Agent for a bounded change, a native Bizar Workflow for repeatable phased work, parallel Agents for disjoint scopes, or an Agent team only when 3+ sustained roles need cross-talk. Do not force a workflow or team when it adds no value.',
|
|
63
|
+
'- Every Agent or team member receives an explicit enabled configured Bizar model; use no provider alias, inheritance, or default. Every editing worker uses call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch concurrently; otherwise use one owner.',
|
|
63
64
|
'- 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
65
|
'- 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
66
|
'- If you are already running as a Bizar custom agent, follow your assigned role and do not recursively dispatch yourself.',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
2
|
+
/** Preserve a mutation barrier while Mike selects an adaptive coordination mode. */
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -13,6 +13,9 @@ import { parseGitCommands } from './git-command-parser.mjs';
|
|
|
13
13
|
const READ_ONLY_GIT_SUBCOMMANDS = new Set([
|
|
14
14
|
'diff', 'log', 'ls-files', 'rev-parse', 'show', 'status',
|
|
15
15
|
]);
|
|
16
|
+
const READ_ONLY_BRANCH_OPTIONS = new Set([
|
|
17
|
+
'--all', '--list', '--remotes', '--show-current', '--verbose', '-a', '-r', '-v', '-vv',
|
|
18
|
+
]);
|
|
16
19
|
const WORKFLOW_SUCCESS = new Set(['completed', 'dry', 'ready-for-integration', 'succeeded', 'success']);
|
|
17
20
|
const WORKFLOW_FAILURE = new Set(['blocked', 'budget-exhausted', 'cancelled', 'canceled', 'error', 'failed', 'failure']);
|
|
18
21
|
|
|
@@ -22,10 +25,17 @@ export function isReadOnlyShellInspection(command) {
|
|
|
22
25
|
|
|
23
26
|
return source.split(/\s*&&\s*/).every((segment) => {
|
|
24
27
|
const trimmed = segment.trim();
|
|
25
|
-
|
|
28
|
+
// These commands can inspect a newly opened project but cannot mutate it.
|
|
29
|
+
// Permit them before workflow launch so the primary can choose the right
|
|
30
|
+
// native workflow instead of deadlocking on its standard orientation step.
|
|
31
|
+
if (/^(?:echo|ls)(?:\s|$)/.test(trimmed) || trimmed === 'pwd') return true;
|
|
26
32
|
if (!/^git(?:\s|$)/.test(trimmed) || /(?:^|\s)--(?:output|ext-diff)(?:=|\s|$)/.test(trimmed)) return false;
|
|
27
33
|
const parsed = parseGitCommands(trimmed);
|
|
28
|
-
|
|
34
|
+
if (parsed.length !== 1) return false;
|
|
35
|
+
const [git] = parsed;
|
|
36
|
+
if (READ_ONLY_GIT_SUBCOMMANDS.has(git.subcommand)) return true;
|
|
37
|
+
return git.subcommand === 'branch'
|
|
38
|
+
&& git.args.every((argument) => READ_ONLY_BRANCH_OPTIONS.has(argument));
|
|
29
39
|
});
|
|
30
40
|
}
|
|
31
41
|
|
|
@@ -77,17 +87,10 @@ process.stdin.on('end', () => {
|
|
|
77
87
|
return;
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
hookEventName: 'PreToolUse',
|
|
85
|
-
permissionDecision: 'deny',
|
|
86
|
-
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.`,
|
|
87
|
-
},
|
|
88
|
-
})}\n`);
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
90
|
+
// Coordination is an orchestrator decision, not a hard-coded tool gate.
|
|
91
|
+
// Instructions require orientation + clarification before mutation; Agent
|
|
92
|
+
// calls are independently protected by agent-model-guard.mjs. Keeping this
|
|
93
|
+
// hook advisory avoids trapping valid single-worker and Agent-team plans.
|
|
91
94
|
} catch (error) {
|
|
92
95
|
process.stderr.write(`[bizar.workflow-route] ${error?.message || String(error)}\n`);
|
|
93
96
|
}
|
|
@@ -3,7 +3,10 @@ export const meta = {
|
|
|
3
3
|
description: 'Implement one bounded change in one worktree, or run explicitly supplied disjoint lanes concurrently',
|
|
4
4
|
whenToUse: 'Use when implementation scope is understood and external research or root-cause discovery is unnecessary.',
|
|
5
5
|
phases: [
|
|
6
|
-
{ title: '
|
|
6
|
+
{ title: 'Scope', detail: 'Independently confirm the local implementation boundary and risks' },
|
|
7
|
+
{ title: 'Plan', detail: 'Turn the bounded objective into owned edit lanes and checks' },
|
|
8
|
+
{ title: 'Implement', detail: 'Run isolated writers for the approved lanes' },
|
|
9
|
+
{ title: 'Review', detail: 'Independently verify each implementation result before integration' },
|
|
7
10
|
],
|
|
8
11
|
}
|
|
9
12
|
|
|
@@ -52,9 +55,20 @@ const TOPIC = typeof args === 'string'
|
|
|
52
55
|
: JSON.stringify(args || {})
|
|
53
56
|
const SCOPE = (args && Array.isArray(args.scope)) ? args.scope : []
|
|
54
57
|
const suppliedLanes = (args && Array.isArray(args.lanes)) ? args.lanes : []
|
|
58
|
+
const fallbackLanes = [{ name: 'bounded-change', scope: SCOPE, task: TOPIC }]
|
|
59
|
+
|
|
60
|
+
phase('Scope')
|
|
61
|
+
const scopeEvidence = await parallel([
|
|
62
|
+
() => dispatchAgent(agent, 'scope-researcher', `Confirm the smallest repository-local implementation boundary for "${TOPIC}". Identify existing code, tests, configuration, and reusable utilities. Do not edit.`, { role: 'research-analyst', risk: 'medium', capabilities: ['structured-output', 'reasoning'], label: 'scope-repository', phase: 'Scope' }),
|
|
63
|
+
() => dispatchAgent(agent, 'scope-critic', `Independently challenge the assumed scope for "${TOPIC}". Identify hidden integration points, ownership conflicts, and the minimum regression evidence needed. Do not edit.`, { role: 'adversarial', risk: 'high', capabilities: ['structured-output', 'reasoning'], label: 'scope-risk', phase: 'Scope' }),
|
|
64
|
+
])
|
|
65
|
+
const scopeSummary = scopeEvidence.map((entry) => JSON.stringify(entry ?? '')).join('\n').slice(0, 4000)
|
|
66
|
+
|
|
67
|
+
phase('Plan')
|
|
68
|
+
const plan = await dispatchAgent(agent, 'bounded-planner', `Produce a minimal reversible plan for "${TOPIC}". Return disjoint edit lanes with a single owner for shared files, plus the smallest proving tests. Do not edit.\n${barrierRef({ phase: 'Scope', label: 'scope-evidence', summary: scopeSummary, payload: scopeEvidence }).promptBlock}`, { role: 'architect', risk: 'medium', capabilities: ['structured-output', 'reasoning', 'architecture'], label: 'plan', phase: 'Plan' })
|
|
55
69
|
const lanes = (suppliedLanes.length > 0
|
|
56
70
|
? suppliedLanes
|
|
57
|
-
:
|
|
71
|
+
: (Array.isArray(plan?.lanes) && plan.lanes.length > 0 ? plan.lanes : fallbackLanes)
|
|
58
72
|
).slice(0, 6)
|
|
59
73
|
|
|
60
74
|
phase('Implement')
|
|
@@ -70,14 +84,23 @@ const implementations = lanes.length === 1
|
|
|
70
84
|
: await parallel(lanes.map((lane, index) => () => runLane(lane, index)))
|
|
71
85
|
const completed = implementations.filter(Boolean)
|
|
72
86
|
|
|
73
|
-
if (completed.length === 0) {
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
if (completed.length === 0) return { status: 'blocked', reason: 'No implementation lane completed successfully.', scopeEvidence, plan, lanes }
|
|
88
|
+
|
|
89
|
+
phase('Review')
|
|
90
|
+
const reviews = await parallel(completed.map((implementation, index) => () => dispatchAgent(
|
|
91
|
+
agent,
|
|
92
|
+
`implementation-reviewer-${index + 1}`,
|
|
93
|
+
`Review implementation lane "${lanes[index]?.name || index + 1}" for "${TOPIC}". Verify scope, correctness, regression evidence, and integration assumptions. Report only actionable defects and required checks; do not edit.\n${barrierRef({ phase: 'Implement', label: `implement:${index + 1}:${lanes[index]?.name || 'bounded-change'}`, summary: typeof implementation === 'string' ? implementation.slice(0, 400) : 'implementation artifact', payload: implementation }).promptBlock}`,
|
|
94
|
+
{ role: 'adversarial', risk: 'high', capabilities: ['structured-output', 'reasoning'], label: `review:${index + 1}:${lanes[index]?.name || 'bounded-change'}`, phase: 'Review' },
|
|
95
|
+
)))
|
|
76
96
|
|
|
77
97
|
return {
|
|
78
98
|
status: 'ready-for-integration',
|
|
79
99
|
topic: TOPIC,
|
|
100
|
+
scopeEvidence,
|
|
101
|
+
plan,
|
|
80
102
|
lanes,
|
|
81
103
|
implementations: completed,
|
|
104
|
+
reviews: reviews.filter(Boolean),
|
|
82
105
|
next: 'Merge queued worktrees and run integration verification in the primary session.',
|
|
83
106
|
}
|
|
@@ -793,15 +793,17 @@ export function classifyDispatchOutcome(result, error, startMs) {
|
|
|
793
793
|
}
|
|
794
794
|
|
|
795
795
|
/**
|
|
796
|
-
* Build the augmented agent-call payload.
|
|
797
|
-
*
|
|
798
|
-
*
|
|
799
|
-
* selector step fired.
|
|
796
|
+
* Build the augmented agent-call payload. The native Agent model field cannot
|
|
797
|
+
* carry arbitrary gateway IDs, so the selected Bizar ID is audit context and
|
|
798
|
+
* the guard permits inheritance only from an identical configured parent.
|
|
800
799
|
*/
|
|
801
800
|
export function augmentPayload(opts, decision, agentName) {
|
|
802
801
|
return {
|
|
803
802
|
...opts,
|
|
804
|
-
|
|
803
|
+
additionalContext: {
|
|
804
|
+
...(opts.additionalContext && typeof opts.additionalContext === 'object' ? opts.additionalContext : {}),
|
|
805
|
+
bizarConfiguredModel: decision.modelId ?? null,
|
|
806
|
+
},
|
|
805
807
|
routingDecisionId: decision.routingDecisionId,
|
|
806
808
|
tier: decision.tier,
|
|
807
809
|
selectorReason: decision.reason,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polderlabs/bizar",
|
|
3
|
-
"version": "10.23.
|
|
3
|
+
"version": "10.23.10",
|
|
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": {
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"config/claude/hooks/*.mjs",
|
|
26
26
|
"config/claude/hooks/*.sh",
|
|
27
27
|
"config/claude/hooks/README.md",
|
|
28
|
-
"config/claude/model-router.json",
|
|
29
28
|
"config/claude/settings.json",
|
|
30
29
|
"scripts/git-hooks/commit-msg",
|
|
31
30
|
"scripts/git-hooks/pre-commit",
|
|
@@ -35,11 +34,11 @@
|
|
|
35
34
|
],
|
|
36
35
|
"scripts": {
|
|
37
36
|
"typecheck": "tsc --noEmit",
|
|
38
|
-
"build:sdk": "node scripts/build-sdk.mjs",
|
|
37
|
+
"build:sdk": "node scripts/with-sdk-dist-lock.mjs node scripts/build-sdk.mjs",
|
|
39
38
|
"test:sdk": "vitest run --root packages/sdk",
|
|
40
39
|
"test:sdk:watch": "vitest --root packages/sdk",
|
|
41
40
|
"test:node": "node scripts/run-node-tests.mjs",
|
|
42
|
-
"test": "
|
|
41
|
+
"test": "node scripts/with-sdk-dist-lock.mjs node scripts/run-test-with-sdk-build.mjs",
|
|
43
42
|
"build": "npm run build:sdk",
|
|
44
43
|
"prepack": "npm run build",
|
|
45
44
|
"prepublishOnly": "npm run build",
|
|
@@ -385,7 +385,7 @@ const bizarAuditTool = defineTool("bizar_audit", "Wrapper around `bizar audit --
|
|
|
385
385
|
}
|
|
386
386
|
}, { readOnlyHint: true });
|
|
387
387
|
// `bizar model list --json` — gateway model inventory.
|
|
388
|
-
const bizarModelListTool = defineTool("bizar_model_list", "Wrapper around `bizar models --json`. Returns ONLY the models the user has explicitly enabled via `bizar models`
|
|
388
|
+
const bizarModelListTool = defineTool("bizar_model_list", "Wrapper around `bizar models --json`. Returns ONLY the models the user has explicitly enabled via `bizar models` in the global Bizar router (`$BIZAR_HOME/config/claude/model-router.json#userSelected`). Live discovery is filtered out by default to avoid surfacing dozens of unrelated models. An empty list means no agent dispatch is permitted until the operator configures models.", {}, async () => {
|
|
389
389
|
try {
|
|
390
390
|
// Prefer the new `bizar models` surface; fall back to the legacy
|
|
391
391
|
// `bizar model list` alias for older installs. The CLI is responsible
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://bizar.dev/schema/model-router.v3.json",
|
|
3
|
-
"version": "13.0.0",
|
|
4
|
-
"endpoint": null,
|
|
5
|
-
"disabledProviders": ["anthropic"],
|
|
6
|
-
"comment": "Mike selects the cheapest sufficient enabled configured model per dispatch. Explicit user-selected models take precedence; configured dynamic tiers are the fallback pool. The orchestrator always supplies an Agent model and fails closed when no enabled configured candidate exists, so Claude Code cannot select an unconfigured provider default. The gateway endpoint is intentionally null in the shipped config — operators MUST configure it via the `ANTHROPIC_BASE_URL` or `BIZAR_MODEL_ROUTER_URL` environment variable. Bizar is provider-agnostic and ships no default provider. `disabledProviders` (top-level, optional) is a case-insensitive-at-write, case-sensitive-at-filter list of provider prefixes whose model ids are stripped from every read site (CLI sync, picker, settings.json sync, SessionStart hook, Agent model guard). Defaults to `[]`; ships with `[\"anthropic\"]` so a fresh install never dispatches `anthropic/*` ids even if a gateway reports them.",
|
|
7
|
-
"userSelected": {
|
|
8
|
-
"models": [],
|
|
9
|
-
"lastUpdated": null,
|
|
10
|
-
"source": "default-empty",
|
|
11
|
-
"tierHints": {}
|
|
12
|
-
},
|
|
13
|
-
"gateway": {
|
|
14
|
-
"required": false,
|
|
15
|
-
"endpoint": null,
|
|
16
|
-
"availabilityProbe": "/models",
|
|
17
|
-
"discoveryTimeoutMs": 3000,
|
|
18
|
-
"unavailableBehavior": "configured-tier-fallback",
|
|
19
|
-
"documentation": "https://code.claude.com/docs/en/llm-gateway"
|
|
20
|
-
},
|
|
21
|
-
"tiers": {
|
|
22
|
-
"premium": {
|
|
23
|
-
"models": [
|
|
24
|
-
"qct/qwen3.8-max-preview",
|
|
25
|
-
"codex/gpt-5.6-sol",
|
|
26
|
-
"codex/gpt-5.6-luna"
|
|
27
|
-
],
|
|
28
|
-
"purpose": "Architecture, high-risk planning, adversarial verification, and last-resort debugging where mistakes are expensive.",
|
|
29
|
-
"effort": "high"
|
|
30
|
-
},
|
|
31
|
-
"high": {
|
|
32
|
-
"models": [
|
|
33
|
-
"codex/gpt-5.6-sol",
|
|
34
|
-
"qct/qwen3.8-max-preview"
|
|
35
|
-
],
|
|
36
|
-
"purpose": "Complex implementation, security review, and cross-cutting reasoning.",
|
|
37
|
-
"effort": "high"
|
|
38
|
-
},
|
|
39
|
-
"mid-design": {
|
|
40
|
-
"models": [
|
|
41
|
-
"codex/gpt-5.6-luna",
|
|
42
|
-
"minimax/MiniMax-M3"
|
|
43
|
-
],
|
|
44
|
-
"purpose": "UI/UX design and product-quality implementation where visual judgment matters.",
|
|
45
|
-
"effort": "medium"
|
|
46
|
-
},
|
|
47
|
-
"default": {
|
|
48
|
-
"models": [
|
|
49
|
-
"minimax/MiniMax-M3",
|
|
50
|
-
"minimax/MiniMax-M2.7"
|
|
51
|
-
],
|
|
52
|
-
"purpose": "Broad research, git operations, and ordinary engineering work.",
|
|
53
|
-
"effort": "medium"
|
|
54
|
-
},
|
|
55
|
-
"mid": {
|
|
56
|
-
"models": [
|
|
57
|
-
"minimax/MiniMax-M2.7",
|
|
58
|
-
"minimax/MiniMax-M2.7-highspeed",
|
|
59
|
-
"qct/deepseek-v4-pro",
|
|
60
|
-
"openrouter/nvidia/nemotron-3-ultra-550b-a55b:free"
|
|
61
|
-
],
|
|
62
|
-
"purpose": "Bounded technical analysis, code search, and moderate implementation.",
|
|
63
|
-
"effort": "medium"
|
|
64
|
-
},
|
|
65
|
-
"budget": {
|
|
66
|
-
"models": [
|
|
67
|
-
"glm/glm-5.3-flash",
|
|
68
|
-
"minimax/MiniMax-M2.7-highspeed"
|
|
69
|
-
],
|
|
70
|
-
"purpose": "Routine deterministic edits, targeted clarification, and scripted verification.",
|
|
71
|
-
"effort": "low"
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"roleDefaults": {
|
|
75
|
-
"mike": "premium",
|
|
76
|
-
"paul": "premium",
|
|
77
|
-
"carl": "premium",
|
|
78
|
-
"karen": "high",
|
|
79
|
-
"linda": "high",
|
|
80
|
-
"ria": "mid-design",
|
|
81
|
-
"greg": "default",
|
|
82
|
-
"steve": "default",
|
|
83
|
-
"oscar": "mid",
|
|
84
|
-
"todd": "mid",
|
|
85
|
-
"susan": "mid",
|
|
86
|
-
"pam": "budget",
|
|
87
|
-
"brenda": "budget",
|
|
88
|
-
"janet": "budget",
|
|
89
|
-
"kevin": "budget",
|
|
90
|
-
"brad": "mid-design"
|
|
91
|
-
},
|
|
92
|
-
"complexityRules": [
|
|
93
|
-
{
|
|
94
|
-
"when": "irreversible, security-sensitive, architectural, or previously failed twice",
|
|
95
|
-
"tier": "premium"
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"when": "cross-cutting implementation or adversarial review",
|
|
99
|
-
"tier": "high"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"when": "UI or product design judgment dominates",
|
|
103
|
-
"tier": "mid-design"
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
"when": "bounded implementation or codebase research",
|
|
107
|
-
"tier": "mid"
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"when": "deterministic, mechanical, or clarification-only",
|
|
111
|
-
"tier": "budget"
|
|
112
|
-
}
|
|
113
|
-
],
|
|
114
|
-
"policies": {
|
|
115
|
-
"mainOrchestrator": "mike",
|
|
116
|
-
"selectionOwner": "orchestrator",
|
|
117
|
-
"dispatchModelOverride": "required-configured-candidate",
|
|
118
|
-
"unknownAgent": "configured-tier-fallback",
|
|
119
|
-
"discoveryFailure": "configured-tier-fallback",
|
|
120
|
-
"unavailableModel": "configured-tier-fallback",
|
|
121
|
-
"silentProviderSubstitution": false,
|
|
122
|
-
"retryModelAliases": false,
|
|
123
|
-
"snapshotDecisions": true,
|
|
124
|
-
"maxDispatchModelAttempts": 1
|
|
125
|
-
},
|
|
126
|
-
"costControls": {
|
|
127
|
-
"maxConcurrentAgents": 10,
|
|
128
|
-
"maxPremiumAgentsPerWorkflow": 3,
|
|
129
|
-
"preferInheritanceWhenDiscoveryUnavailable": false
|
|
130
|
-
}
|
|
131
|
-
}
|