dual-brain 5.0.1 → 6.0.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.md +32 -1
- package/bin/dual-brain.mjs +200 -0
- package/hooks/agent-fleet.mjs +659 -0
- package/hooks/budget-balancer.mjs +289 -112
- package/hooks/context-guard.mjs +468 -0
- package/hooks/control-panel.mjs +61 -0
- package/hooks/dag-scheduler.mjs +1249 -0
- package/hooks/dispatch.mjs +254 -0
- package/hooks/gpt-work-dispatcher.mjs +15 -9
- package/hooks/head-guard.sh +103 -0
- package/hooks/ledger-analysis.mjs +337 -0
- package/hooks/model-registry.mjs +859 -0
- package/hooks/parallelism-scaler.mjs +572 -0
- package/hooks/quality-tiers.mjs +642 -0
- package/hooks/task-classifier.mjs +328 -0
- package/hooks/wave-orchestrator.mjs +1397 -0
- package/install.mjs +2 -0
- package/package.json +17 -5
- package/src/decide.mjs +500 -0
- package/src/detect.mjs +232 -0
- package/src/dispatch.mjs +264 -0
- package/src/index.mjs +35 -0
- package/src/profile.mjs +267 -0
- package/src/update-check.mjs +35 -0
package/CLAUDE.md
CHANGED
|
@@ -98,6 +98,19 @@ Auto mode classifies risk from file paths and adjusts routing in real-time:
|
|
|
98
98
|
|
|
99
99
|
Casual natural language → structured work. The vibe coding system translates informal requests into properly routed, risk-classified, quality-gated work.
|
|
100
100
|
|
|
101
|
+
**Wave Orchestrator** — the primary way to run multi-step work:
|
|
102
|
+
```bash
|
|
103
|
+
node .claude/hooks/wave-orchestrator.mjs "fix the login bug and update the nav"
|
|
104
|
+
node .claude/hooks/wave-orchestrator.mjs --dry-run "refactor auth module"
|
|
105
|
+
node .claude/hooks/wave-orchestrator.mjs --resume <manifestId>
|
|
106
|
+
node .claude/hooks/wave-orchestrator.mjs --show <manifestId>
|
|
107
|
+
```
|
|
108
|
+
The wave orchestrator decomposes intent, plans dependency-aware waves, assigns file ownership to prevent conflicts, dispatches agents with transparent routing tables, checkpoints between waves, and supports resume on failure. Every dispatch shows: provider, model, tier, effort, agent type, and routing reason.
|
|
109
|
+
|
|
110
|
+
Manifests persist to `.dualbrain/manifests/`, checkpoints to `.dualbrain/checkpoints/`.
|
|
111
|
+
|
|
112
|
+
Also available via the control panel: `[w]` Vibe workflow.
|
|
113
|
+
|
|
101
114
|
**Intent compiler** — decompose multi-task requests:
|
|
102
115
|
```bash
|
|
103
116
|
node .claude/hooks/vibe-router.mjs "fix the login bug and also update the nav"
|
|
@@ -119,13 +132,31 @@ node .claude/hooks/vibe-memory.mjs --infer # preference sug
|
|
|
119
132
|
```
|
|
120
133
|
Tracks preferred profile, risk tolerance, active threads, and learns from usage patterns.
|
|
121
134
|
|
|
135
|
+
## Budget Balancer (Token-Based)
|
|
136
|
+
|
|
137
|
+
The budget balancer tracks real token usage against actual subscription limits.
|
|
138
|
+
|
|
139
|
+
**Subscription tiers** (configured in `orchestrator.json` → `subscriptions.*.plan`):
|
|
140
|
+
- Claude: Pro $20, Max x5 $100, Max x20 $200
|
|
141
|
+
- ChatGPT: Plus $20, Pro $100, Pro $200
|
|
142
|
+
|
|
143
|
+
**Two rolling windows**: 5-hour and 7-day weekly. The higher pressure is the binding constraint.
|
|
144
|
+
|
|
145
|
+
**Token tracking**: Uses actual `input_tokens + output_tokens` from usage logs when available, falls back to conservative estimates only when logs lack token data.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node .claude/hooks/budget-balancer.mjs
|
|
149
|
+
```
|
|
150
|
+
Shows per-provider per-tier pressure with real token counts (e.g., `136.0K/350.0K`), weekly pressure when binding, and routing recommendation with reason.
|
|
151
|
+
|
|
122
152
|
## Available Tools
|
|
123
153
|
|
|
154
|
+
- `node .claude/hooks/wave-orchestrator.mjs "..."` — auto-wave orchestrator (plan, dispatch, test, review)
|
|
124
155
|
- `node .claude/hooks/vibe-router.mjs "..."` — decompose casual requests into structured work
|
|
125
156
|
- `node .claude/hooks/plan-generator.mjs --utterance "..."` — generate execution plans
|
|
126
157
|
- `node .claude/hooks/vibe-memory.mjs` — persistent preferences and work threads
|
|
127
158
|
- `node .claude/hooks/cost-report.mjs` — activity and cost estimates
|
|
128
159
|
- `node .claude/hooks/health-check.mjs` — verify system health
|
|
129
|
-
- `node .claude/hooks/budget-balancer.mjs` — provider balance
|
|
160
|
+
- `node .claude/hooks/budget-balancer.mjs` — provider balance (token-based, real limits)
|
|
130
161
|
- `node .claude/hooks/decision-ledger.mjs` — routing outcome insights
|
|
131
162
|
- `node .claude/hooks/test-orchestrator.mjs` — run self-tests (40 tests)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// dual-brain — CLI entry point. Commands: init, go, status, remember, forget
|
|
3
|
+
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
ensureProfile, loadProfile, runOnboarding,
|
|
11
|
+
rememberPreference, forgetPreference, getActivePreferences,
|
|
12
|
+
getAvailableProviders, isSoloBrain, getHeadModel,
|
|
13
|
+
} from '../src/profile.mjs';
|
|
14
|
+
|
|
15
|
+
import { detectTask } from '../src/detect.mjs';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
decideRoute, getAvailableModels, estimateBudgetPressure,
|
|
19
|
+
} from '../src/decide.mjs';
|
|
20
|
+
|
|
21
|
+
import { dispatch, detectRuntime, dispatchDualBrain } from '../src/dispatch.mjs';
|
|
22
|
+
|
|
23
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const PKG_PATH = join(__dirname, '..', 'package.json');
|
|
27
|
+
|
|
28
|
+
function readVersion() {
|
|
29
|
+
try { return JSON.parse(readFileSync(PKG_PATH, 'utf8')).version; } catch { return '0.0.0'; }
|
|
30
|
+
}
|
|
31
|
+
function flag(args, name) { const i = args.indexOf(name); return i !== -1 ? (args[i + 1] ?? true) : null; }
|
|
32
|
+
function err(msg) { process.stderr.write(`Error: ${msg}\n`); process.exit(1); }
|
|
33
|
+
|
|
34
|
+
function printHelp() {
|
|
35
|
+
console.log(`
|
|
36
|
+
dual-brain <command> [options]
|
|
37
|
+
|
|
38
|
+
Commands:
|
|
39
|
+
init First-time setup (providers, plans, optimization)
|
|
40
|
+
go "task description" Detect → decide → dispatch a task
|
|
41
|
+
--dry-run Show routing decision without executing
|
|
42
|
+
--files a.mjs,b.mjs Provide file context for risk classification
|
|
43
|
+
status Provider health, budget pressure, available models
|
|
44
|
+
remember "preference" Save a project-scoped preference
|
|
45
|
+
forget "preference" Remove a preference by fuzzy match
|
|
46
|
+
|
|
47
|
+
Options:
|
|
48
|
+
--version Print version
|
|
49
|
+
--help Show this help
|
|
50
|
+
`.trim());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
async function cmdInit() {
|
|
56
|
+
const profile = await runOnboarding({ interactive: true });
|
|
57
|
+
const rt = await detectRuntime();
|
|
58
|
+
const providers = getAvailableProviders(profile);
|
|
59
|
+
const providerSummary = providers.length
|
|
60
|
+
? providers.map(p => `${p.name === 'claude' ? 'Claude' : 'OpenAI'} (${p.plan})`).join(', ')
|
|
61
|
+
: 'none';
|
|
62
|
+
console.log(`Profile saved. Providers: ${providerSummary}. Mode: ${profile.mode}. Runtime: ${rt.runtime}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function cmdGo(args) {
|
|
66
|
+
const dryRun = args.includes('--dry-run');
|
|
67
|
+
const filesRaw = flag(args, '--files');
|
|
68
|
+
const files = filesRaw && typeof filesRaw === 'string'
|
|
69
|
+
? filesRaw.split(',').map(f => f.trim()).filter(Boolean)
|
|
70
|
+
: [];
|
|
71
|
+
|
|
72
|
+
// prompt is the first non-flag argument (or value after --dry-run which is boolean)
|
|
73
|
+
const prompt = args.find(a => !a.startsWith('--') && a !== (filesRaw ?? ''));
|
|
74
|
+
if (!prompt) err('Usage: dual-brain go "task description" [--dry-run] [--files a,b]');
|
|
75
|
+
|
|
76
|
+
const cwd = process.cwd();
|
|
77
|
+
const profile = await ensureProfile(cwd);
|
|
78
|
+
const detection = detectTask({ prompt, files });
|
|
79
|
+
|
|
80
|
+
// Print the one-sentence classification
|
|
81
|
+
console.log(detection.explanation);
|
|
82
|
+
|
|
83
|
+
const decision = decideRoute({ profile, detection, cwd });
|
|
84
|
+
|
|
85
|
+
// Print routing table
|
|
86
|
+
console.log(` provider : ${decision.provider}`);
|
|
87
|
+
console.log(` model : ${decision.model}${decision.effort ? ' (' + decision.effort + ')' : ''}`);
|
|
88
|
+
console.log(` tier : ${decision.tier}`);
|
|
89
|
+
console.log(` dual-brain : ${decision.dualBrain ? 'yes' : 'no'}`);
|
|
90
|
+
console.log(` reason : ${decision.explanation}`);
|
|
91
|
+
|
|
92
|
+
if (dryRun) {
|
|
93
|
+
console.log('\n(dry-run — not executing)');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log('\nDispatching...');
|
|
98
|
+
let result;
|
|
99
|
+
if (decision.dualBrain) {
|
|
100
|
+
result = await dispatchDualBrain({ decision, prompt, files, cwd });
|
|
101
|
+
console.log(`\nConsensus: ${result.consensus}`);
|
|
102
|
+
if (result.claude?.summary) console.log(`Claude : ${result.claude.summary}`);
|
|
103
|
+
if (result.openai?.summary) console.log(`OpenAI : ${result.openai.summary}`);
|
|
104
|
+
} else {
|
|
105
|
+
result = await dispatch({ decision, prompt, files, cwd });
|
|
106
|
+
const statusLine = result.status === 'completed' ? 'Done' : `Failed (exit ${result.exitCode})`;
|
|
107
|
+
console.log(`\n${statusLine} in ${(result.durationMs / 1000).toFixed(1)}s`);
|
|
108
|
+
if (result.summary) console.log(result.summary);
|
|
109
|
+
if (result.error) process.stderr.write(`${result.error}\n`);
|
|
110
|
+
if (result.status !== 'completed') process.exit(1); }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function cmdStatus() {
|
|
114
|
+
const cwd = process.cwd();
|
|
115
|
+
const profile = loadProfile(cwd);
|
|
116
|
+
const rt = await detectRuntime();
|
|
117
|
+
const providers = getAvailableProviders(profile);
|
|
118
|
+
const pressure = estimateBudgetPressure(profile, cwd);
|
|
119
|
+
const available = getAvailableModels(profile);
|
|
120
|
+
const prefs = getActivePreferences(cwd);
|
|
121
|
+
|
|
122
|
+
console.log('=== Dual-Brain Status ===\n');
|
|
123
|
+
|
|
124
|
+
// Providers
|
|
125
|
+
console.log('Providers:');
|
|
126
|
+
if (providers.length === 0) {
|
|
127
|
+
console.log(' (none configured — run: dual-brain init)');
|
|
128
|
+
} else {
|
|
129
|
+
for (const p of providers) {
|
|
130
|
+
const label = p.name === 'claude' ? 'Claude' : 'OpenAI';
|
|
131
|
+
const pct = Math.round((pressure[p.name] ?? 0) * 100);
|
|
132
|
+
console.log(` ${label} plan=${p.plan} budget=${pct}% used`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Models
|
|
137
|
+
console.log('\nAvailable models:');
|
|
138
|
+
if (available.claude.length) console.log(` Claude : ${available.claude.join(', ')}`);
|
|
139
|
+
if (available.openai.length) console.log(` OpenAI : ${available.openai.join(', ')}`);
|
|
140
|
+
|
|
141
|
+
// Head model
|
|
142
|
+
console.log(`\nHead model : ${getHeadModel(profile)}`);
|
|
143
|
+
console.log(`Mode : ${profile.mode}`);
|
|
144
|
+
console.log(`Solo brain : ${isSoloBrain(profile) ? 'yes' : 'no'}`);
|
|
145
|
+
|
|
146
|
+
// Runtime
|
|
147
|
+
console.log('\nRuntime:');
|
|
148
|
+
console.log(` claude CLI : ${rt.claudeAvailable ? 'available' : 'not found'}`);
|
|
149
|
+
console.log(` codex CLI : ${rt.codexAvailable ? 'available' : 'not found'}`);
|
|
150
|
+
console.log(` detected : ${rt.runtime}`);
|
|
151
|
+
|
|
152
|
+
// Preferences
|
|
153
|
+
console.log(`\nPreferences: ${prefs.length ? '' : '(none)'}`);
|
|
154
|
+
for (const p of prefs) console.log(` [${p.scope}] ${p.text}`);
|
|
155
|
+
|
|
156
|
+
// Update check
|
|
157
|
+
try {
|
|
158
|
+
const localVer = readVersion();
|
|
159
|
+
const remoteVer = execSync('npm view dual-brain version 2>/dev/null', { timeout: 5000 }).toString().trim();
|
|
160
|
+
if (remoteVer && remoteVer !== localVer) {
|
|
161
|
+
console.log(`\nUpdate available: npm i -g dual-brain@latest (${localVer} → ${remoteVer})`);
|
|
162
|
+
}
|
|
163
|
+
} catch { /* network unavailable — skip */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function cmdRemember(text) {
|
|
167
|
+
if (!text) err('Usage: dual-brain remember "preference text"');
|
|
168
|
+
const profile = rememberPreference(text, { scope: 'project', cwd: process.cwd() });
|
|
169
|
+
console.log(`Preference saved. Total active: ${profile.preferences.filter(p => p.enabled).length}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function cmdForget(text) {
|
|
173
|
+
if (!text) err('Usage: dual-brain forget "preference text"');
|
|
174
|
+
forgetPreference(text, process.cwd());
|
|
175
|
+
console.log('Preference removed (if matched).');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
async function main() {
|
|
181
|
+
const args = process.argv.slice(2);
|
|
182
|
+
const cmd = args[0];
|
|
183
|
+
|
|
184
|
+
if (!cmd || cmd === '--help' || cmd === '-h') { printHelp(); return; }
|
|
185
|
+
if (cmd === '--version' || cmd === '-v') { console.log(readVersion()); return; }
|
|
186
|
+
|
|
187
|
+
if (cmd === 'init') { await cmdInit(); return; }
|
|
188
|
+
if (cmd === 'go') { await cmdGo(args.slice(1)); return; }
|
|
189
|
+
if (cmd === 'status') { await cmdStatus(); return; }
|
|
190
|
+
if (cmd === 'remember') { cmdRemember(args[1]); return; }
|
|
191
|
+
if (cmd === 'forget') { cmdForget(args[1]); return; }
|
|
192
|
+
|
|
193
|
+
process.stderr.write(`Unknown command: ${cmd}\nRun "dual-brain --help" for usage.\n`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
main().catch(e => {
|
|
198
|
+
process.stderr.write(`${e.message}\n`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|