@polderlabs/bizar 10.13.0 → 10.15.0
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/.claude-plugin/plugin.json +18 -18
- package/AGENTS.md +14 -8
- package/cli/commands/hook.mjs +8 -1
- package/cli/commands/model.mjs +2 -2
- package/cli/commands/tier.mjs +151 -0
- package/cli/commands/tools.mjs +124 -0
- package/cli/commands/upgrade-defaults.mjs +139 -0
- package/cli/commands/workflow.mjs +9 -2
- package/cli/core/workflow-state.mjs +20 -15
- package/cli/provision.mjs +76 -49
- package/cli/worker-dispatcher.mjs +1 -1
- package/config/AGENTS.md +1 -1
- package/config/agents/model-assignment.mjs +69 -109
- package/config/claude/CLAUDE.md +137 -0
- package/{.claude → config/claude}/agents/brand-designer.md +0 -1
- package/{.claude → config/claude}/agents/debug-specialist.md +0 -1
- package/{.claude → config/claude}/agents/exec-assistant.md +0 -1
- package/{.claude → config/claude}/agents/help-desk.md +0 -1
- package/{.claude → config/claude}/agents/it-lead.md +0 -1
- package/{.claude → config/claude}/agents/knowledge-manager.md +0 -1
- package/{.claude → config/claude}/agents/office-coordinator.md +0 -1
- package/{.claude → config/claude}/agents/office-greeter.md +0 -1
- package/{.claude → config/claude}/agents/office-manager.md +27 -14
- package/{.claude → config/claude}/agents/planner.md +0 -1
- package/{.claude → config/claude}/agents/principal-engineer.md +0 -1
- package/{.claude → config/claude}/agents/qa-reviewer.md +0 -1
- package/{.claude → config/claude}/agents/research-analyst.md +0 -1
- package/{.claude → config/claude}/agents/senior-engineer.md +0 -1
- package/{.claude → config/claude}/agents/support-tech.md +0 -1
- package/{.claude → config/claude}/agents/ui-designer.md +0 -1
- package/config/claude/commands/backup.md +19 -0
- package/config/claude/commands/browser.md +19 -0
- package/config/claude/commands/doctor.md +17 -0
- package/config/claude/commands/migrate.md +18 -0
- package/config/claude/commands/picker.md +20 -0
- package/config/claude/commands/rca.md +19 -0
- package/config/claude/commands/repair.md +16 -0
- package/config/claude/commands/restore.md +20 -0
- package/{.claude → config/claude}/commands/team.md +4 -1
- package/config/claude/commands/tier.md +32 -0
- package/config/claude/commands/tools.md +31 -0
- package/config/claude/commands/ultracode.md +8 -0
- package/config/claude/commands/update.md +15 -0
- package/config/claude/commands/upgrade-defaults.md +36 -0
- package/{.claude → config/claude}/commands/use-default.md +6 -6
- package/{.claude → config/claude}/commands/use-premium.md +13 -13
- package/config/claude/hooks/agent-model-guard.mjs +74 -0
- package/{.claude → config/claude}/hooks/control-inbox.mjs +1 -1
- package/{.claude → config/claude}/hooks/path-ownership-guard.mjs +1 -1
- package/{.claude → config/claude}/hooks/sessionstart-prime.mjs +3 -0
- package/config/claude/hooks/team-lifecycle.mjs +44 -0
- package/{.claude → config/claude}/hooks/verify-deliverables.mjs +1 -1
- package/{.claude → config/claude}/hooks/worker-suggest.mjs +3 -2
- package/config/claude/model-router.json +121 -0
- package/{.claude → config/claude}/settings.json +45 -6
- package/config/skills/bizar/SKILL.md +1 -1
- package/config/skills/harness-engineering/SKILL.md +1 -1
- package/config/skills/ultracode/SKILL.md +15 -0
- package/config/workflows/ultracode-research.js +42 -0
- package/config/workflows/ultracode-review.js +52 -0
- package/config/workflows/ultracode.js +77 -0
- package/package.json +10 -8
- package/packages/sdk/dist/router/agent-model-registry.d.ts +57 -81
- package/packages/sdk/dist/router/agent-model-registry.js +71 -217
- 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/.claude/hooks/agent-model-guard.mjs +0 -153
- package/.claude/model-router.json +0 -179
- /package/{.claude → config/claude}/agents/_shared/AGENT_BASELINE.md +0 -0
- /package/{.claude → config/claude}/agents/_shared/CLAUDE_TOOLS.md +0 -0
- /package/{.claude → config/claude}/agents/_shared/SKILLS.md +0 -0
- /package/{.claude → config/claude}/commands/audit.md +0 -0
- /package/{.claude → config/claude}/commands/autopilot.md +0 -0
- /package/{.claude → config/claude}/commands/bizar.md +0 -0
- /package/{.claude → config/claude}/commands/cancel.md +0 -0
- /package/{.claude → config/claude}/commands/cron.md +0 -0
- /package/{.claude → config/claude}/commands/explain.md +0 -0
- /package/{.claude → config/claude}/commands/init.md +0 -0
- /package/{.claude → config/claude}/commands/learn.md +0 -0
- /package/{.claude → config/claude}/commands/plan.md +0 -0
- /package/{.claude → config/claude}/commands/plow-through.md +0 -0
- /package/{.claude → config/claude}/commands/pr-review.md +0 -0
- /package/{.claude → config/claude}/commands/quick.md +0 -0
- /package/{.claude → config/claude}/commands/ralph.md +0 -0
- /package/{.claude → config/claude}/commands/ralplan.md +0 -0
- /package/{.claude → config/claude}/commands/setup-provider.md +0 -0
- /package/{.claude → config/claude}/commands/spec.md +0 -0
- /package/{.claude → config/claude}/commands/sprint.md +0 -0
- /package/{.claude → config/claude}/commands/tailscale-serve.md +0 -0
- /package/{.claude → config/claude}/commands/test.md +0 -0
- /package/{.claude → config/claude}/commands/ultraqa.md +0 -0
- /package/{.claude → config/claude}/commands/ultrawork.md +0 -0
- /package/{.claude → config/claude}/commands/validate.md +0 -0
- /package/{.claude → config/claude}/commands/verify.md +0 -0
- /package/{.claude → config/claude}/hooks/README.md +0 -0
- /package/{.claude → config/claude}/hooks/advisor-context.mjs +0 -0
- /package/{.claude → config/claude}/hooks/agent-grounding.mjs +0 -0
- /package/{.claude → config/claude}/hooks/auto-instinct.sh +0 -0
- /package/{.claude → config/claude}/hooks/content-style-guard.mjs +0 -0
- /package/{.claude → config/claude}/hooks/git-command-parser.mjs +0 -0
- /package/{.claude → config/claude}/hooks/git-workflow-guard.mjs +0 -0
- /package/{.claude → config/claude}/hooks/keyword-router.mjs +0 -0
- /package/{.claude → config/claude}/hooks/learning-extract.mjs +0 -0
- /package/{.claude → config/claude}/hooks/permission-request.mjs +0 -0
- /package/{.claude → config/claude}/hooks/persistent-mode.mjs +0 -0
- /package/{.claude → config/claude}/hooks/post-tool-use-failure.mjs +0 -0
- /package/{.claude → config/claude}/hooks/posttooluse-editwrite.mjs +0 -0
- /package/{.claude → config/claude}/hooks/precompact-priorities.sh +0 -0
- /package/{.claude → config/claude}/hooks/pretooluse-bash.mjs +0 -0
- /package/{.claude → config/claude}/hooks/pretooluse-editwrite.mjs +0 -0
- /package/{.claude → config/claude}/hooks/sessionend-recall.mjs +0 -0
- /package/{.claude → config/claude}/hooks/simplify-guard.mjs +0 -0
- /package/{.claude → config/claude}/hooks/telemetry.mjs +0 -0
- /package/{.claude → config/claude}/hooks/thinking-route.mjs +0 -0
- /package/{.claude → config/claude}/hooks/worktree-bootstrap.mjs +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "bizar-harness",
|
|
4
4
|
"displayName": "Bizar Harness",
|
|
5
|
-
"version": "10.
|
|
5
|
+
"version": "10.15.0",
|
|
6
6
|
"description": "Guarded multi-agent workflows for Claude Code with a single orchestrator, durable workflow state, and explicit human approval boundaries.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Polderlabs"
|
|
@@ -18,24 +18,24 @@
|
|
|
18
18
|
"guarded-autonomy"
|
|
19
19
|
],
|
|
20
20
|
"skills": "./config/skills/",
|
|
21
|
-
"commands": "
|
|
21
|
+
"commands": "./config/claude/commands/",
|
|
22
22
|
"agents": [
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
23
|
+
"./config/claude/agents/brand-designer.md",
|
|
24
|
+
"./config/claude/agents/debug-specialist.md",
|
|
25
|
+
"./config/claude/agents/exec-assistant.md",
|
|
26
|
+
"./config/claude/agents/help-desk.md",
|
|
27
|
+
"./config/claude/agents/it-lead.md",
|
|
28
|
+
"./config/claude/agents/knowledge-manager.md",
|
|
29
|
+
"./config/claude/agents/office-coordinator.md",
|
|
30
|
+
"./config/claude/agents/office-greeter.md",
|
|
31
|
+
"./config/claude/agents/office-manager.md",
|
|
32
|
+
"./config/claude/agents/planner.md",
|
|
33
|
+
"./config/claude/agents/principal-engineer.md",
|
|
34
|
+
"./config/claude/agents/qa-reviewer.md",
|
|
35
|
+
"./config/claude/agents/research-analyst.md",
|
|
36
|
+
"./config/claude/agents/senior-engineer.md",
|
|
37
|
+
"./config/claude/agents/support-tech.md",
|
|
38
|
+
"./config/claude/agents/ui-designer.md"
|
|
39
39
|
],
|
|
40
40
|
"hooks": "./hooks/hooks.json"
|
|
41
41
|
}
|
package/AGENTS.md
CHANGED
|
@@ -43,14 +43,20 @@ still deny prohibited actions and escalate externally visible or irreversible
|
|
|
43
43
|
actions with `permissionDecision: "ask"`; that escalation list is the
|
|
44
44
|
authoritative floor, not a starting point.
|
|
45
45
|
|
|
46
|
-
Subagent dispatch through the Agent tool is the default for disjoint
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
Subagent dispatch through the Agent tool is the default for focused disjoint
|
|
47
|
+
work. Native dynamic workflows under `~/.claude/workflows/` are used when a
|
|
48
|
+
large or repeatable task benefits from deterministic scripted fan-out. Native
|
|
49
|
+
agent teams are used only when independent workers need direct communication or
|
|
50
|
+
shared task state. When two or more subtasks have non-overlapping file scopes
|
|
51
|
+
and no data dependency on each other's intermediate output, the orchestrator
|
|
52
|
+
MUST dispatch them concurrently. Sequential dispatch is reserved for dependent
|
|
53
|
+
phases and integration.
|
|
54
|
+
|
|
55
|
+
Agent roles are model-agnostic. Mike selects the cheapest sufficient tier for
|
|
56
|
+
each dispatch. It passes a concrete model only when live discovery proves a
|
|
57
|
+
configured tier candidate; otherwise it omits `model` and inherits the active
|
|
58
|
+
session. Bizar never retries a failed dispatch by cycling aliases, providers, or
|
|
59
|
+
tiers.
|
|
54
60
|
|
|
55
61
|
The authoritative hard approval list (cannot be auto-approved) is: commits, pushes, pull-request
|
|
56
62
|
mutations, releases, package publication, deployments, production/shared-
|
package/cli/commands/hook.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
14
14
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
15
|
|
|
16
16
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
17
|
-
const HOOK_ROOT = resolve(PACKAGE_ROOT, '
|
|
17
|
+
const HOOK_ROOT = resolve(PACKAGE_ROOT, 'config', 'claude', 'hooks');
|
|
18
18
|
const PRETOOL_SAFETY_LEAVES = new Set([
|
|
19
19
|
'pretooluse-editwrite', 'path-ownership-guard',
|
|
20
20
|
'pretooluse-bash', 'git-workflow-guard',
|
|
@@ -44,6 +44,7 @@ export const HOOK_PROGRAMS = Object.freeze({
|
|
|
44
44
|
'simplify-guard': 'simplify-guard.mjs',
|
|
45
45
|
telemetry: 'telemetry.mjs',
|
|
46
46
|
'thinking-route': 'thinking-route.mjs',
|
|
47
|
+
'team-lifecycle': 'team-lifecycle.mjs',
|
|
47
48
|
'verify-deliverables': 'verify-deliverables.mjs',
|
|
48
49
|
'worker-suggest': 'worker-suggest.mjs',
|
|
49
50
|
'worktree-bootstrap': 'worktree-bootstrap.mjs',
|
|
@@ -81,6 +82,9 @@ export const EVENT_CHAINS = Object.freeze({
|
|
|
81
82
|
'worktree-bootstrap',
|
|
82
83
|
]),
|
|
83
84
|
'subagent-stop': Object.freeze(['verify-deliverables']),
|
|
85
|
+
'task-created': Object.freeze(['team-lifecycle']),
|
|
86
|
+
'task-completed': Object.freeze(['team-lifecycle']),
|
|
87
|
+
'teammate-idle': Object.freeze(['team-lifecycle']),
|
|
84
88
|
'pre-compact': Object.freeze(['persistent-mode', 'precompact-priorities']),
|
|
85
89
|
stop: Object.freeze(['persistent-mode']),
|
|
86
90
|
'session-end': Object.freeze(['sessionend-recall', 'learning-extract']),
|
|
@@ -95,6 +99,9 @@ const EVENT_NAMES = Object.freeze({
|
|
|
95
99
|
'post-tool-use-failure': 'PostToolUseFailure',
|
|
96
100
|
'subagent-start': 'SubagentStart',
|
|
97
101
|
'subagent-stop': 'SubagentStop',
|
|
102
|
+
'task-created': 'TaskCreated',
|
|
103
|
+
'task-completed': 'TaskCompleted',
|
|
104
|
+
'teammate-idle': 'TeammateIdle',
|
|
98
105
|
'pre-compact': 'PreCompact',
|
|
99
106
|
stop: 'Stop',
|
|
100
107
|
'session-end': 'SessionEnd',
|
package/cli/commands/model.mjs
CHANGED
|
@@ -16,7 +16,7 @@ const BIZAR_MODEL_ROUTER_URL = process.env.BIZAR_MODEL_ROUTER_URL
|
|
|
16
16
|
const ANTHROPIC_AUTH_TOKEN = process.env.ANTHROPIC_AUTH_TOKEN || 'sk_9router';
|
|
17
17
|
|
|
18
18
|
const PROVIDER_GROUPS = [
|
|
19
|
-
'cx/', '
|
|
19
|
+
'cx/', 'claude-minimax/', 'claude-qwen/', 'oc/', 'claude/', 'anthropic/',
|
|
20
20
|
];
|
|
21
21
|
|
|
22
22
|
function showHelp() {
|
|
@@ -31,7 +31,7 @@ function showHelp() {
|
|
|
31
31
|
Fetches GET ${BIZAR_MODEL_ROUTER_URL}/models?limit=1000
|
|
32
32
|
and prints a table of provider | id | display_name.
|
|
33
33
|
The /model picker inside Claude Code shows only "claude"/"anthropic" prefixed
|
|
34
|
-
IDs. This command exposes the full set (cx/,
|
|
34
|
+
IDs. This command exposes the full set (cx/, claude-minimax/, claude-qwen/, oc/, etc.).
|
|
35
35
|
|
|
36
36
|
Flags:
|
|
37
37
|
--json Emit { providers: { "cx/": [...], ... }, total: N }
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli/commands/tier.mjs
|
|
4
|
+
*
|
|
5
|
+
* `bizar tier` — automatic task-based model selection.
|
|
6
|
+
*
|
|
7
|
+
* Replays the same tier resolution the @mike orchestrator uses:
|
|
8
|
+
* 1. Read the role default from ~/.claude/model-router.json.
|
|
9
|
+
* 2. Adjust the tier from current task risk and complexity.
|
|
10
|
+
* 3. Print ordered candidates; dispatch uses a candidate only when live
|
|
11
|
+
* discovery proves it, otherwise Agent omits model and inherits the session.
|
|
12
|
+
*
|
|
13
|
+
* A failed dispatch is never retried by cycling aliases or providers.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
const HOME = homedir();
|
|
22
|
+
const ROUTER_PATHS = [
|
|
23
|
+
process.env.BIZAR_MODEL_ROUTER_PATH,
|
|
24
|
+
join(HOME, '.claude', 'model-router.json'),
|
|
25
|
+
process.env.CLAUDE_CONFIG_DIR && join(process.env.CLAUDE_CONFIG_DIR, 'model-router.json'),
|
|
26
|
+
].filter(Boolean);
|
|
27
|
+
|
|
28
|
+
function loadRouter() {
|
|
29
|
+
for (const path of ROUTER_PATHS) {
|
|
30
|
+
if (existsSync(path)) {
|
|
31
|
+
try {
|
|
32
|
+
return { path, config: JSON.parse(readFileSync(path, 'utf8')) };
|
|
33
|
+
} catch (err) {
|
|
34
|
+
throw new Error(`failed to parse ${path}: ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new Error('no model-router.json found — run `bizar install` first');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ESCALATE_KEYWORDS = [
|
|
42
|
+
'design', 'review', 'audit', 'plan', 'spec', 'architecture',
|
|
43
|
+
'security', 'refactor', 'trade-off', 'tradeoff', 'reason',
|
|
44
|
+
'verify', 'compare', 'analyze', 'analyse', 'explain why',
|
|
45
|
+
];
|
|
46
|
+
const DEMOTE_KEYWORDS = [
|
|
47
|
+
'rename', 'format', 'lint', 'chmod', 'reformat', 'stub',
|
|
48
|
+
'comment', 'docstring', 'wording', 'spell', 'typo',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function adjustTier(baseTier, task) {
|
|
52
|
+
const lower = task.toLowerCase();
|
|
53
|
+
const wantEscalate = ESCALATE_KEYWORDS.some((kw) => lower.includes(kw));
|
|
54
|
+
const wantDemote = DEMOTE_KEYWORDS.some((kw) => lower.includes(kw));
|
|
55
|
+
if (wantEscalate && wantDemote) return { tier: baseTier, reason: 'mixed signals — default tier wins' };
|
|
56
|
+
if (wantEscalate) return { tier: 'high', reason: 'task requires reasoning / review' };
|
|
57
|
+
if (wantDemote) return { tier: 'budget', reason: 'task is a deterministic mechanical edit' };
|
|
58
|
+
return { tier: baseTier, reason: 'task matches default tier' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const TIER_ORDER = ['budget', 'mid', 'default', 'mid-design', 'high', 'premium'];
|
|
62
|
+
|
|
63
|
+
function liftTier(tier) {
|
|
64
|
+
const i = TIER_ORDER.indexOf(tier);
|
|
65
|
+
if (i === -1 || i === TIER_ORDER.length - 1) return tier;
|
|
66
|
+
return TIER_ORDER[i + 1];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function runTier(cmdArgs) {
|
|
70
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) {
|
|
71
|
+
console.log(`
|
|
72
|
+
bizar tier — automatic task-based model selection
|
|
73
|
+
|
|
74
|
+
Usage:
|
|
75
|
+
bizar tier [--agent <name>] [--list] [--json] <task-description>
|
|
76
|
+
bizar tier --list List every shipped agent and its default tier.
|
|
77
|
+
|
|
78
|
+
Returns the dynamic tier and ordered model candidates for the named role
|
|
79
|
+
(defaults to "mike"). Mike uses a concrete candidate only after live
|
|
80
|
+
discovery; otherwise Claude Code inherits the active session model.
|
|
81
|
+
`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { path, config } = loadRouter();
|
|
86
|
+
const agentName = (() => {
|
|
87
|
+
const idx = cmdArgs.indexOf('--agent');
|
|
88
|
+
return idx !== -1 ? cmdArgs[idx + 1] : 'mike';
|
|
89
|
+
})();
|
|
90
|
+
const taskParts = [];
|
|
91
|
+
for (let i = 0; i < cmdArgs.length; i++) {
|
|
92
|
+
if (cmdArgs[i] === '--agent') { i += 1; continue; }
|
|
93
|
+
if (cmdArgs[i].startsWith('--')) continue;
|
|
94
|
+
taskParts.push(cmdArgs[i]);
|
|
95
|
+
}
|
|
96
|
+
const task = taskParts.join(' ').trim();
|
|
97
|
+
|
|
98
|
+
if (cmdArgs.includes('--list')) {
|
|
99
|
+
const roles = Object.entries(config.roleDefaults || {}).sort(([a], [b]) => a.localeCompare(b));
|
|
100
|
+
for (const [name, tier] of roles) {
|
|
101
|
+
console.log(` ${name.padEnd(20)} ${String(tier).padEnd(12)} ${(config.tiers?.[tier]?.models || []).join(', ')}`);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const defaultTier = config.roleDefaults?.[agentName] || 'default';
|
|
107
|
+
|
|
108
|
+
if (!task) {
|
|
109
|
+
console.error(chalk.red(' ✗ task description required (or pass --list)'));
|
|
110
|
+
process.exit(2);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const adjusted = adjustTier(defaultTier, task);
|
|
114
|
+
const chosenTier = adjusted.tier === 'high' && defaultTier === 'premium'
|
|
115
|
+
? 'premium'
|
|
116
|
+
: adjusted.tier;
|
|
117
|
+
const candidates = config.tiers?.[chosenTier]?.models || [];
|
|
118
|
+
const endpoint = process.env.BIZAR_MODEL_ROUTER_URL
|
|
119
|
+
|| process.env.ANTHROPIC_BASE_URL
|
|
120
|
+
|| config.endpoint;
|
|
121
|
+
|
|
122
|
+
if (cmdArgs.includes('--json')) {
|
|
123
|
+
console.log(JSON.stringify({
|
|
124
|
+
agent: agentName,
|
|
125
|
+
defaultTier,
|
|
126
|
+
chosenTier,
|
|
127
|
+
candidates,
|
|
128
|
+
selection: 'first-live-candidate-or-inherit-session',
|
|
129
|
+
endpoint,
|
|
130
|
+
source: path,
|
|
131
|
+
reason: adjusted.reason,
|
|
132
|
+
task,
|
|
133
|
+
}, null, 2));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(chalk.bold(` task ${task}`));
|
|
138
|
+
console.log(` role ${agentName} (default tier: ${defaultTier})`);
|
|
139
|
+
console.log(` tier ${chosenTier} — ${adjusted.reason}`);
|
|
140
|
+
console.log(` candidates ${candidates.join(', ') || '(none; inherit session)'}`);
|
|
141
|
+
console.log(' selection first live candidate, otherwise inherit active session');
|
|
142
|
+
console.log(` endpoint ${endpoint}`);
|
|
143
|
+
console.log(` source ${path}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
|
|
147
|
+
runTier(process.argv.slice(2)).catch((error) => {
|
|
148
|
+
console.error(chalk.red(` ✗ ${error?.message || error}`));
|
|
149
|
+
process.exitCode = 1;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli/commands/tools.mjs
|
|
4
|
+
*
|
|
5
|
+
* `bizar tools` — inventory every Bizar surface installed at user level.
|
|
6
|
+
*
|
|
7
|
+
* Walks ~/.claude/{agents,skills,commands,hooks}/ plus the Bizar runtime
|
|
8
|
+
* state under ~/.config/bizar/, then queries the SDK for the live MCP
|
|
9
|
+
* tool set and prints everything grouped by surface. Also lists every
|
|
10
|
+
* wired `bizar <cmd>` subcommand from this bin.mjs dispatcher.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { join, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const HOME = homedir();
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = resolve(__filename, '..', '..');
|
|
22
|
+
const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR?.trim() || join(HOME, '.claude');
|
|
23
|
+
|
|
24
|
+
function listDir(dir, ext) {
|
|
25
|
+
if (!existsSync(dir)) return [];
|
|
26
|
+
return readdirSync(dir).filter((name) => !ext || name.endsWith(ext)).sort();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function listEvents(hooksDir) {
|
|
30
|
+
if (!existsSync(hooksDir)) return [];
|
|
31
|
+
return listDir(hooksDir, '.mjs').concat(listDir(hooksDir, '.sh')).sort();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function listWiredCommands() {
|
|
35
|
+
// Mirror bin.mjs dispatch — single source of truth lives in bin.mjs.
|
|
36
|
+
// We parse the switch cases to avoid duplicating the list here.
|
|
37
|
+
const binPath = resolve(__dirname, 'bin.mjs');
|
|
38
|
+
const text = readFileSync(binPath, 'utf8');
|
|
39
|
+
const match = text.match(/case '([a-z][a-z0-9-]*)':/g) || [];
|
|
40
|
+
const names = match
|
|
41
|
+
.map((line) => line.match(/case '([^']+)'/)?.[1])
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
return [...new Set(names)].sort();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function listMcpTools() {
|
|
47
|
+
try {
|
|
48
|
+
const sdkPath = resolve(__dirname, '..', 'packages/sdk/dist/mcp/server.js');
|
|
49
|
+
if (!existsSync(sdkPath)) return [];
|
|
50
|
+
const mod = await import(sdkPath);
|
|
51
|
+
const list = mod.BIZAR_TOOLS || mod.default?.BIZAR_TOOLS || [];
|
|
52
|
+
return list.map((tool) => tool.name).sort();
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function runTools(cmdArgs) {
|
|
59
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) {
|
|
60
|
+
console.log(`
|
|
61
|
+
bizar tools — list every installed Bizar surface.
|
|
62
|
+
|
|
63
|
+
Usage:
|
|
64
|
+
bizar tools [--json] [--user-level]
|
|
65
|
+
|
|
66
|
+
Flags:
|
|
67
|
+
--json Emit machine-readable JSON.
|
|
68
|
+
--user-level Restrict enumeration to user-level (~/.claude/).
|
|
69
|
+
`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const asJson = cmdArgs.includes('--json');
|
|
74
|
+
|
|
75
|
+
const agents = listDir(join(CLAUDE_DIR, 'agents'), '.md');
|
|
76
|
+
const commands = listDir(join(CLAUDE_DIR, 'commands'), '.md');
|
|
77
|
+
const skills = listDir(join(CLAUDE_DIR, 'skills'));
|
|
78
|
+
const hooks = listEvents(join(CLAUDE_DIR, 'hooks'));
|
|
79
|
+
const wiredCommands = listWiredCommands();
|
|
80
|
+
const mcpTools = await listMcpTools();
|
|
81
|
+
|
|
82
|
+
const settingsPath = join(CLAUDE_DIR, 'settings.json');
|
|
83
|
+
const hasSettings = existsSync(settingsPath);
|
|
84
|
+
const modelRouterPath = join(CLAUDE_DIR, 'model-router.json');
|
|
85
|
+
const hasModelRouter = existsSync(modelRouterPath);
|
|
86
|
+
|
|
87
|
+
const summary = {
|
|
88
|
+
userLevel: CLAUDE_DIR,
|
|
89
|
+
agents,
|
|
90
|
+
commands,
|
|
91
|
+
skills,
|
|
92
|
+
hooks,
|
|
93
|
+
wiredCommands,
|
|
94
|
+
mcpTools,
|
|
95
|
+
settings: { path: settingsPath, present: hasSettings },
|
|
96
|
+
modelRouter: { path: modelRouterPath, present: hasModelRouter },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (asJson) {
|
|
100
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const section = (label, items) => {
|
|
105
|
+
console.log(chalk.bold(`\n ${label}`));
|
|
106
|
+
if (!items.length) {
|
|
107
|
+
console.log(chalk.dim(' (none)'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const item of items) console.log(` ${item}`);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
console.log(chalk.bold.cyan(`\n Bizar tools — installed at ${CLAUDE_DIR}`));
|
|
114
|
+
section(`agents (${agents.length})`, agents.map((n) => n.replace(/\.md$/, '')));
|
|
115
|
+
section(`commands (${commands.length})`, commands.map((n) => '/' + n.replace(/\.md$/, '')));
|
|
116
|
+
section(`skills (${skills.length})`, skills);
|
|
117
|
+
section(`hooks (${hooks.length})`, hooks);
|
|
118
|
+
section(`mcp tools (${mcpTools.length})`, mcpTools);
|
|
119
|
+
section(`wired bizar commands (${wiredCommands.length})`, wiredCommands.map((c) => 'bizar ' + c));
|
|
120
|
+
console.log(chalk.bold('\n settings'));
|
|
121
|
+
console.log(` ${hasSettings ? '✓' : '✗'} ${settingsPath}`);
|
|
122
|
+
console.log(chalk.bold(' model-router'));
|
|
123
|
+
console.log(` ${hasModelRouter ? '✓' : '✗'} ${modelRouterPath}`);
|
|
124
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli/commands/upgrade-defaults.mjs
|
|
4
|
+
*
|
|
5
|
+
* `bizar upgrade-defaults` — re-apply the user-favored defaults to
|
|
6
|
+
* ~/.claude/settings.json (or $CLAUDE_CONFIG_DIR/settings.json).
|
|
7
|
+
*
|
|
8
|
+
* What this does:
|
|
9
|
+
* 1. Reads the existing settings.json without overwriting user-set
|
|
10
|
+
* values that are already in place.
|
|
11
|
+
* 2. Sets the permissive defaults the Bizar user typically wants:
|
|
12
|
+
* - permissions.defaultMode → "bypassPermissions"
|
|
13
|
+
* - worktree.bgIsolation → "worktree"
|
|
14
|
+
* - model → "claude/minimax/MiniMax-M3"
|
|
15
|
+
* - alwaysThinkingEnabled → true
|
|
16
|
+
* - effortLevel → "high"
|
|
17
|
+
* - skipDangerousModePermissionPrompt → true
|
|
18
|
+
* - showThinkingSummaries → true
|
|
19
|
+
* - askUserQuestionTimeout → "5m"
|
|
20
|
+
* 3. Refuses to downgrade any value the user has explicitly set to
|
|
21
|
+
* a more restrictive mode (e.g. a user who set
|
|
22
|
+
* defaultMode="default" gets to keep it).
|
|
23
|
+
* 4. Preserves every unrelated key (mcpServers, hooks, env, etc.).
|
|
24
|
+
*
|
|
25
|
+
* Why this exists: the canonical config/claude/settings.json ships with
|
|
26
|
+
* these defaults and `cli/provision.mjs` (F-163 scope-owned) is the
|
|
27
|
+
* runtime installer. Until F-163 releases the file or its scope is
|
|
28
|
+
* extended, this command lets the user apply the new defaults in one
|
|
29
|
+
* call rather than editing the merged file by hand.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import chalk from 'chalk';
|
|
33
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
34
|
+
import { homedir } from 'node:os';
|
|
35
|
+
import { join } from 'node:path';
|
|
36
|
+
|
|
37
|
+
const HOME = homedir();
|
|
38
|
+
const CLAUDE_DIR = process.env.CLAUDE_CONFIG_DIR?.trim() || join(HOME, '.claude');
|
|
39
|
+
const SETTINGS_PATH = join(CLAUDE_DIR, 'settings.json');
|
|
40
|
+
|
|
41
|
+
const FAVORED = {
|
|
42
|
+
permissions: { defaultMode: 'bypassPermissions' },
|
|
43
|
+
worktree: { bgIsolation: 'worktree' },
|
|
44
|
+
model: 'claude/minimax/MiniMax-M3',
|
|
45
|
+
alwaysThinkingEnabled: true,
|
|
46
|
+
effortLevel: 'high',
|
|
47
|
+
skipDangerousModePermissionPrompt: true,
|
|
48
|
+
showThinkingSummaries: true,
|
|
49
|
+
askUserQuestionTimeout: '5m',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function isAtLeastAsPermissive(existing, desired) {
|
|
53
|
+
// Don't downgrade: if the user picked a stricter mode, keep theirs.
|
|
54
|
+
const order = { default: 0, acceptEdits: 1, bypassPermissions: 2 };
|
|
55
|
+
const e = order[existing];
|
|
56
|
+
const d = order[desired];
|
|
57
|
+
if (e === undefined || d === undefined) return existing === desired;
|
|
58
|
+
return e >= d;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function deepAssign(target, source) {
|
|
62
|
+
for (const [k, v] of Object.entries(source)) {
|
|
63
|
+
if (
|
|
64
|
+
v !== null
|
|
65
|
+
&& typeof v === 'object'
|
|
66
|
+
&& !Array.isArray(v)
|
|
67
|
+
&& target[k] !== null
|
|
68
|
+
&& typeof target[k] === 'object'
|
|
69
|
+
&& !Array.isArray(target[k])
|
|
70
|
+
) {
|
|
71
|
+
deepAssign(target[k], v);
|
|
72
|
+
} else if (target[k] === undefined) {
|
|
73
|
+
target[k] = v;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function load() {
|
|
79
|
+
if (!existsSync(SETTINGS_PATH)) return {};
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(readFileSync(SETTINGS_PATH, 'utf8'));
|
|
82
|
+
} catch (err) {
|
|
83
|
+
throw new Error(`failed to parse ${SETTINGS_PATH}: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function diff(existing) {
|
|
88
|
+
const proposed = JSON.parse(JSON.stringify(existing));
|
|
89
|
+
deepAssign(proposed, FAVORED);
|
|
90
|
+
// Permission-mode guard.
|
|
91
|
+
const existingMode = existing?.permissions?.defaultMode;
|
|
92
|
+
if (existingMode && !isAtLeastAsPermissive(existingMode, FAVORED.permissions.defaultMode)) {
|
|
93
|
+
proposed.permissions.defaultMode = existingMode;
|
|
94
|
+
}
|
|
95
|
+
return proposed;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function runUpgradeDefaults(cmdArgs) {
|
|
99
|
+
if (cmdArgs.includes('--help') || cmdArgs.includes('-h')) {
|
|
100
|
+
console.log(`
|
|
101
|
+
bizar upgrade-defaults — re-apply the user-favored defaults to ~/.claude/settings.json.
|
|
102
|
+
|
|
103
|
+
Usage:
|
|
104
|
+
bizar upgrade-defaults [--dry-run] [--no-backup]
|
|
105
|
+
|
|
106
|
+
Flags:
|
|
107
|
+
--dry-run Print the proposed diff without writing.
|
|
108
|
+
--no-backup Skip the timestamped backup file.
|
|
109
|
+
`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const dryRun = cmdArgs.includes('--dry-run');
|
|
114
|
+
const noBackup = cmdArgs.includes('--no-backup');
|
|
115
|
+
|
|
116
|
+
if (!existsSync(SETTINGS_PATH)) {
|
|
117
|
+
console.error(chalk.red(` ✗ ${SETTINGS_PATH} not found — run \`bizar install\` first`));
|
|
118
|
+
process.exit(2);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const before = load();
|
|
122
|
+
const after = diff(before);
|
|
123
|
+
|
|
124
|
+
if (!noBackup && !dryRun) {
|
|
125
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
126
|
+
const backup = `${SETTINGS_PATH}.bak-${stamp}`;
|
|
127
|
+
copyFileSync(SETTINGS_PATH, backup);
|
|
128
|
+
console.log(chalk.dim(` backup: ${backup}`));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (dryRun) {
|
|
132
|
+
console.log(JSON.stringify({ before, after }, null, 2));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writeFileSync(SETTINGS_PATH, JSON.stringify(after, null, 2) + '\n');
|
|
137
|
+
console.log(chalk.green(' ✓ updated'));
|
|
138
|
+
console.log(chalk.dim(` ${SETTINGS_PATH}`));
|
|
139
|
+
}
|
|
@@ -217,8 +217,15 @@ export async function run(name, args, isHelpRequest) {
|
|
|
217
217
|
);
|
|
218
218
|
}
|
|
219
219
|
const registry = loadModelRouter(flags.router || process.env.BIZAR_MODEL_ROUTER_PATH);
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
let availableModelIds;
|
|
221
|
+
try {
|
|
222
|
+
requireEffectiveInferenceEndpoint(registry);
|
|
223
|
+
availableModelIds = await probeAvailableModels({ registry });
|
|
224
|
+
} catch {
|
|
225
|
+
// Dynamic routing deliberately fails open to session-model inheritance.
|
|
226
|
+
// Do not retry provider aliases or block workflow startup on discovery.
|
|
227
|
+
availableModelIds = undefined;
|
|
228
|
+
}
|
|
222
229
|
state = startWorkflow({
|
|
223
230
|
...ctx,
|
|
224
231
|
profile: flags.profile || flags.workflow || 'default',
|
|
@@ -318,31 +318,36 @@ export function validateWorkflowState(state, context) {
|
|
|
318
318
|
assert(state.assignmentSnapshot.runId === state.runId, 'ASSIGNMENT_RUN_MISMATCH', 'model assignment snapshot belongs to a different run');
|
|
319
319
|
assert(verifyRunAssignmentSnapshot(state.assignmentSnapshot), 'ASSIGNMENT_INTEGRITY_ERROR', 'model assignment snapshot fingerprint is invalid');
|
|
320
320
|
assert(
|
|
321
|
-
|
|
321
|
+
state.assignmentSnapshot.gatewayEndpoint === null || (
|
|
322
|
+
typeof state.assignmentSnapshot.gatewayEndpoint === 'string' &&
|
|
322
323
|
state.assignmentSnapshot.gatewayEndpoint.length > 0 &&
|
|
323
|
-
state.assignmentSnapshot.gatewayEndpoint.trim() === state.assignmentSnapshot.gatewayEndpoint
|
|
324
|
+
state.assignmentSnapshot.gatewayEndpoint.trim() === state.assignmentSnapshot.gatewayEndpoint
|
|
325
|
+
),
|
|
324
326
|
'INVALID_STATE',
|
|
325
|
-
'
|
|
327
|
+
'routing snapshot gateway endpoint is invalid',
|
|
326
328
|
);
|
|
327
329
|
assert(
|
|
328
|
-
|
|
330
|
+
state.assignmentSnapshot.availabilityProbe === null || (
|
|
331
|
+
typeof state.assignmentSnapshot.availabilityProbe === 'string' &&
|
|
329
332
|
state.assignmentSnapshot.availabilityProbe.length > 0 &&
|
|
330
|
-
state.assignmentSnapshot.availabilityProbe.trim() === state.assignmentSnapshot.availabilityProbe
|
|
333
|
+
state.assignmentSnapshot.availabilityProbe.trim() === state.assignmentSnapshot.availabilityProbe
|
|
334
|
+
),
|
|
331
335
|
'INVALID_STATE',
|
|
332
|
-
'
|
|
336
|
+
'routing snapshot availability probe is invalid',
|
|
333
337
|
);
|
|
334
338
|
assert(
|
|
335
|
-
state.assignmentSnapshot.
|
|
336
|
-
typeof state.assignmentSnapshot.
|
|
337
|
-
!Array.isArray(state.assignmentSnapshot.
|
|
338
|
-
Object.keys(state.assignmentSnapshot.assignments).length > 0,
|
|
339
|
+
state.assignmentSnapshot.decisions &&
|
|
340
|
+
typeof state.assignmentSnapshot.decisions === 'object' &&
|
|
341
|
+
!Array.isArray(state.assignmentSnapshot.decisions),
|
|
339
342
|
'INVALID_STATE',
|
|
340
|
-
'
|
|
343
|
+
'routing decision snapshot is invalid',
|
|
341
344
|
);
|
|
342
|
-
for (const [agent,
|
|
343
|
-
assert(typeof agent === 'string' && agent.length > 0, 'INVALID_STATE', '
|
|
344
|
-
assert(typeof
|
|
345
|
-
assert(typeof
|
|
345
|
+
for (const [agent, decision] of Object.entries(state.assignmentSnapshot.decisions)) {
|
|
346
|
+
assert(typeof agent === 'string' && agent.length > 0, 'INVALID_STATE', 'routing decision agent is invalid');
|
|
347
|
+
assert(decision && typeof decision === 'object', 'INVALID_STATE', `routing decision for ${agent} is invalid`);
|
|
348
|
+
assert(typeof decision.tier === 'string' && decision.tier.length > 0, 'INVALID_STATE', `routing tier for ${agent} is invalid`);
|
|
349
|
+
assert(decision.model === null || (typeof decision.model === 'string' && decision.model.length > 0), 'INVALID_STATE', `routing model for ${agent} is invalid`);
|
|
350
|
+
assert(typeof decision.inheritSession === 'boolean', 'INVALID_STATE', `routing inheritance for ${agent} is invalid`);
|
|
346
351
|
}
|
|
347
352
|
deepFreeze(state.assignmentSnapshot);
|
|
348
353
|
assert(state.attempts && typeof state.attempts === 'object', 'INVALID_STATE', 'workflow attempt counters are missing');
|