@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.
Files changed (116) hide show
  1. package/.claude-plugin/plugin.json +18 -18
  2. package/AGENTS.md +14 -8
  3. package/cli/commands/hook.mjs +8 -1
  4. package/cli/commands/model.mjs +2 -2
  5. package/cli/commands/tier.mjs +151 -0
  6. package/cli/commands/tools.mjs +124 -0
  7. package/cli/commands/upgrade-defaults.mjs +139 -0
  8. package/cli/commands/workflow.mjs +9 -2
  9. package/cli/core/workflow-state.mjs +20 -15
  10. package/cli/provision.mjs +76 -49
  11. package/cli/worker-dispatcher.mjs +1 -1
  12. package/config/AGENTS.md +1 -1
  13. package/config/agents/model-assignment.mjs +69 -109
  14. package/config/claude/CLAUDE.md +137 -0
  15. package/{.claude → config/claude}/agents/brand-designer.md +0 -1
  16. package/{.claude → config/claude}/agents/debug-specialist.md +0 -1
  17. package/{.claude → config/claude}/agents/exec-assistant.md +0 -1
  18. package/{.claude → config/claude}/agents/help-desk.md +0 -1
  19. package/{.claude → config/claude}/agents/it-lead.md +0 -1
  20. package/{.claude → config/claude}/agents/knowledge-manager.md +0 -1
  21. package/{.claude → config/claude}/agents/office-coordinator.md +0 -1
  22. package/{.claude → config/claude}/agents/office-greeter.md +0 -1
  23. package/{.claude → config/claude}/agents/office-manager.md +27 -14
  24. package/{.claude → config/claude}/agents/planner.md +0 -1
  25. package/{.claude → config/claude}/agents/principal-engineer.md +0 -1
  26. package/{.claude → config/claude}/agents/qa-reviewer.md +0 -1
  27. package/{.claude → config/claude}/agents/research-analyst.md +0 -1
  28. package/{.claude → config/claude}/agents/senior-engineer.md +0 -1
  29. package/{.claude → config/claude}/agents/support-tech.md +0 -1
  30. package/{.claude → config/claude}/agents/ui-designer.md +0 -1
  31. package/config/claude/commands/backup.md +19 -0
  32. package/config/claude/commands/browser.md +19 -0
  33. package/config/claude/commands/doctor.md +17 -0
  34. package/config/claude/commands/migrate.md +18 -0
  35. package/config/claude/commands/picker.md +20 -0
  36. package/config/claude/commands/rca.md +19 -0
  37. package/config/claude/commands/repair.md +16 -0
  38. package/config/claude/commands/restore.md +20 -0
  39. package/{.claude → config/claude}/commands/team.md +4 -1
  40. package/config/claude/commands/tier.md +32 -0
  41. package/config/claude/commands/tools.md +31 -0
  42. package/config/claude/commands/ultracode.md +8 -0
  43. package/config/claude/commands/update.md +15 -0
  44. package/config/claude/commands/upgrade-defaults.md +36 -0
  45. package/{.claude → config/claude}/commands/use-default.md +6 -6
  46. package/{.claude → config/claude}/commands/use-premium.md +13 -13
  47. package/config/claude/hooks/agent-model-guard.mjs +74 -0
  48. package/{.claude → config/claude}/hooks/control-inbox.mjs +1 -1
  49. package/{.claude → config/claude}/hooks/path-ownership-guard.mjs +1 -1
  50. package/{.claude → config/claude}/hooks/sessionstart-prime.mjs +3 -0
  51. package/config/claude/hooks/team-lifecycle.mjs +44 -0
  52. package/{.claude → config/claude}/hooks/verify-deliverables.mjs +1 -1
  53. package/{.claude → config/claude}/hooks/worker-suggest.mjs +3 -2
  54. package/config/claude/model-router.json +121 -0
  55. package/{.claude → config/claude}/settings.json +45 -6
  56. package/config/skills/bizar/SKILL.md +1 -1
  57. package/config/skills/harness-engineering/SKILL.md +1 -1
  58. package/config/skills/ultracode/SKILL.md +15 -0
  59. package/config/workflows/ultracode-research.js +42 -0
  60. package/config/workflows/ultracode-review.js +52 -0
  61. package/config/workflows/ultracode.js +77 -0
  62. package/package.json +10 -8
  63. package/packages/sdk/dist/router/agent-model-registry.d.ts +57 -81
  64. package/packages/sdk/dist/router/agent-model-registry.js +71 -217
  65. package/packages/sdk/dist/version.d.ts +1 -1
  66. package/packages/sdk/dist/version.js +1 -1
  67. package/packages/sdk/package.json +1 -1
  68. package/.claude/hooks/agent-model-guard.mjs +0 -153
  69. package/.claude/model-router.json +0 -179
  70. /package/{.claude → config/claude}/agents/_shared/AGENT_BASELINE.md +0 -0
  71. /package/{.claude → config/claude}/agents/_shared/CLAUDE_TOOLS.md +0 -0
  72. /package/{.claude → config/claude}/agents/_shared/SKILLS.md +0 -0
  73. /package/{.claude → config/claude}/commands/audit.md +0 -0
  74. /package/{.claude → config/claude}/commands/autopilot.md +0 -0
  75. /package/{.claude → config/claude}/commands/bizar.md +0 -0
  76. /package/{.claude → config/claude}/commands/cancel.md +0 -0
  77. /package/{.claude → config/claude}/commands/cron.md +0 -0
  78. /package/{.claude → config/claude}/commands/explain.md +0 -0
  79. /package/{.claude → config/claude}/commands/init.md +0 -0
  80. /package/{.claude → config/claude}/commands/learn.md +0 -0
  81. /package/{.claude → config/claude}/commands/plan.md +0 -0
  82. /package/{.claude → config/claude}/commands/plow-through.md +0 -0
  83. /package/{.claude → config/claude}/commands/pr-review.md +0 -0
  84. /package/{.claude → config/claude}/commands/quick.md +0 -0
  85. /package/{.claude → config/claude}/commands/ralph.md +0 -0
  86. /package/{.claude → config/claude}/commands/ralplan.md +0 -0
  87. /package/{.claude → config/claude}/commands/setup-provider.md +0 -0
  88. /package/{.claude → config/claude}/commands/spec.md +0 -0
  89. /package/{.claude → config/claude}/commands/sprint.md +0 -0
  90. /package/{.claude → config/claude}/commands/tailscale-serve.md +0 -0
  91. /package/{.claude → config/claude}/commands/test.md +0 -0
  92. /package/{.claude → config/claude}/commands/ultraqa.md +0 -0
  93. /package/{.claude → config/claude}/commands/ultrawork.md +0 -0
  94. /package/{.claude → config/claude}/commands/validate.md +0 -0
  95. /package/{.claude → config/claude}/commands/verify.md +0 -0
  96. /package/{.claude → config/claude}/hooks/README.md +0 -0
  97. /package/{.claude → config/claude}/hooks/advisor-context.mjs +0 -0
  98. /package/{.claude → config/claude}/hooks/agent-grounding.mjs +0 -0
  99. /package/{.claude → config/claude}/hooks/auto-instinct.sh +0 -0
  100. /package/{.claude → config/claude}/hooks/content-style-guard.mjs +0 -0
  101. /package/{.claude → config/claude}/hooks/git-command-parser.mjs +0 -0
  102. /package/{.claude → config/claude}/hooks/git-workflow-guard.mjs +0 -0
  103. /package/{.claude → config/claude}/hooks/keyword-router.mjs +0 -0
  104. /package/{.claude → config/claude}/hooks/learning-extract.mjs +0 -0
  105. /package/{.claude → config/claude}/hooks/permission-request.mjs +0 -0
  106. /package/{.claude → config/claude}/hooks/persistent-mode.mjs +0 -0
  107. /package/{.claude → config/claude}/hooks/post-tool-use-failure.mjs +0 -0
  108. /package/{.claude → config/claude}/hooks/posttooluse-editwrite.mjs +0 -0
  109. /package/{.claude → config/claude}/hooks/precompact-priorities.sh +0 -0
  110. /package/{.claude → config/claude}/hooks/pretooluse-bash.mjs +0 -0
  111. /package/{.claude → config/claude}/hooks/pretooluse-editwrite.mjs +0 -0
  112. /package/{.claude → config/claude}/hooks/sessionend-recall.mjs +0 -0
  113. /package/{.claude → config/claude}/hooks/simplify-guard.mjs +0 -0
  114. /package/{.claude → config/claude}/hooks/telemetry.mjs +0 -0
  115. /package/{.claude → config/claude}/hooks/thinking-route.mjs +0 -0
  116. /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.13.0",
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": "./.claude/commands/",
21
+ "commands": "./config/claude/commands/",
22
22
  "agents": [
23
- "./.claude/agents/brand-designer.md",
24
- "./.claude/agents/debug-specialist.md",
25
- "./.claude/agents/exec-assistant.md",
26
- "./.claude/agents/help-desk.md",
27
- "./.claude/agents/it-lead.md",
28
- "./.claude/agents/knowledge-manager.md",
29
- "./.claude/agents/office-coordinator.md",
30
- "./.claude/agents/office-greeter.md",
31
- "./.claude/agents/office-manager.md",
32
- "./.claude/agents/planner.md",
33
- "./.claude/agents/principal-engineer.md",
34
- "./.claude/agents/qa-reviewer.md",
35
- "./.claude/agents/research-analyst.md",
36
- "./.claude/agents/senior-engineer.md",
37
- "./.claude/agents/support-tech.md",
38
- "./.claude/agents/ui-designer.md"
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 work.
47
- When two or more subtasks have non-overlapping file scopes and no data
48
- dependency on each other's intermediate output, the orchestrator MUST
49
- dispatch them in parallel in a single tool block rather than sequentially.
50
- Sequential dispatch is reserved for tasks that depend on a previous step's
51
- output (research plan implement, integration that requires a commit SHA,
52
- verification that consumes a build artifact). The `office-manager` skill
53
- enforces this; individual agents do not re-derive the rule.
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-
@@ -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, '.claude', 'hooks');
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',
@@ -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/', 'bizar/', 'oc/', 'claude/', 'anthropic/',
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/, bizar/, oc/, etc.).
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
- requireEffectiveInferenceEndpoint(registry);
221
- const availableModelIds = await probeAvailableModels({ registry });
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
- typeof state.assignmentSnapshot.gatewayEndpoint === 'string' &&
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
- 'model assignment snapshot gateway endpoint is invalid',
327
+ 'routing snapshot gateway endpoint is invalid',
326
328
  );
327
329
  assert(
328
- typeof state.assignmentSnapshot.availabilityProbe === 'string' &&
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
- 'model assignment snapshot availability probe is invalid',
336
+ 'routing snapshot availability probe is invalid',
333
337
  );
334
338
  assert(
335
- state.assignmentSnapshot.assignments &&
336
- typeof state.assignmentSnapshot.assignments === 'object' &&
337
- !Array.isArray(state.assignmentSnapshot.assignments) &&
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
- 'model assignment snapshot has no required agents',
343
+ 'routing decision snapshot is invalid',
341
344
  );
342
- for (const [agent, assignment] of Object.entries(state.assignmentSnapshot.assignments)) {
343
- assert(typeof agent === 'string' && agent.length > 0, 'INVALID_STATE', 'model assignment agent is invalid');
344
- assert(typeof assignment?.model === 'string' && assignment.model.length > 0, 'INVALID_STATE', `model assignment for ${agent} is invalid`);
345
- assert(typeof assignment?.tier === 'string' && assignment.tier.length > 0, 'INVALID_STATE', `model tier for ${agent} is invalid`);
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');