dual-brain 5.1.0 → 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/bin/dual-brain.mjs +200 -0
- package/hooks/agent-fleet.mjs +659 -0
- package/hooks/budget-balancer.mjs +128 -4
- package/hooks/context-guard.mjs +468 -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 +458 -31
- package/install.mjs +1 -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
|
@@ -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
|
+
});
|