dual-brain 0.1.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/AGENTS.md +97 -0
- package/CLAUDE.md +147 -0
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/agents/implementer.md +22 -0
- package/agents/researcher.md +25 -0
- package/agents/verifier.md +30 -0
- package/bin/dual-brain.mjs +2868 -0
- package/hooks/auto-update-wrapper.mjs +102 -0
- package/hooks/auto-update.sh +67 -0
- package/hooks/budget-balancer.mjs +679 -0
- package/hooks/control-panel.mjs +1195 -0
- package/hooks/cost-logger.mjs +286 -0
- package/hooks/cost-report.mjs +351 -0
- package/hooks/decision-ledger.mjs +299 -0
- package/hooks/dual-brain-review.mjs +404 -0
- package/hooks/dual-brain-think.mjs +393 -0
- package/hooks/enforce-tier.mjs +469 -0
- package/hooks/failure-detector.mjs +138 -0
- package/hooks/gpt-work-dispatcher.mjs +512 -0
- package/hooks/head-guard.mjs +105 -0
- package/hooks/health-check.mjs +444 -0
- package/hooks/install-git-hooks.mjs +106 -0
- package/hooks/model-registry.mjs +859 -0
- package/hooks/plan-generator.mjs +544 -0
- package/hooks/profiles.mjs +254 -0
- package/hooks/quality-gate.mjs +355 -0
- package/hooks/risk-classifier.mjs +41 -0
- package/hooks/session-report.mjs +514 -0
- package/hooks/setup-wizard.mjs +130 -0
- package/hooks/summary-checkpoint.mjs +432 -0
- package/hooks/task-classifier.mjs +328 -0
- package/hooks/test-orchestrator.mjs +1077 -0
- package/hooks/vibe-memory.mjs +463 -0
- package/hooks/vibe-router.mjs +387 -0
- package/hooks/wave-orchestrator.mjs +1397 -0
- package/install.mjs +1541 -0
- package/mcp-server/README.md +81 -0
- package/mcp-server/index.mjs +388 -0
- package/orchestrator.json +215 -0
- package/package.json +108 -0
- package/playbooks/debug.json +49 -0
- package/playbooks/refactor.json +57 -0
- package/playbooks/security-audit.json +57 -0
- package/playbooks/security.json +38 -0
- package/playbooks/test-gen.json +48 -0
- package/plugin.json +22 -0
- package/review-rules.md +17 -0
- package/shell-hook.sh +26 -0
- package/skills/go.md +22 -0
- package/skills/review.md +19 -0
- package/skills/status.md +13 -0
- package/skills/think.md +22 -0
- package/src/brief.mjs +266 -0
- package/src/decide.mjs +635 -0
- package/src/decompose.mjs +331 -0
- package/src/detect.mjs +345 -0
- package/src/dispatch.mjs +942 -0
- package/src/health.mjs +253 -0
- package/src/index.mjs +44 -0
- package/src/install-hooks.mjs +100 -0
- package/src/playbook.mjs +257 -0
- package/src/profile.mjs +990 -0
- package/src/redact.mjs +192 -0
- package/src/repo.mjs +292 -0
- package/src/session.mjs +1036 -0
- package/src/tui.mjs +197 -0
- package/src/update-check.mjs +35 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# dual-brain MCP Server
|
|
2
|
+
|
|
3
|
+
Exposes dual-brain's routing engine as MCP tools so any MCP-compatible client (VS Code, Cursor, Windsurf, etc.) can use smart provider/model routing.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Add to your MCP client config:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"dual-brain": {
|
|
13
|
+
"command": "node",
|
|
14
|
+
"args": ["node_modules/dual-brain/mcp-server/index.mjs"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or if installed globally:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"mcpServers": {
|
|
25
|
+
"dual-brain": {
|
|
26
|
+
"command": "node",
|
|
27
|
+
"args": ["/path/to/dual-brain/mcp-server/index.mjs"]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Tools
|
|
34
|
+
|
|
35
|
+
### `dual_brain_detect`
|
|
36
|
+
Classify a task into intent, risk, complexity, and tier.
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{ "prompt": "refactor the auth module", "files": ["src/auth.mjs"] }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Returns: `{ intent, risk, complexity, effort, tier, requiresWrite, explanation }`
|
|
43
|
+
|
|
44
|
+
### `dual_brain_decide`
|
|
45
|
+
Full routing decision — detect + route to provider and model.
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{ "prompt": "fix the login bug", "files": ["src/auth.mjs"], "profile": "quality-first" }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Returns: `{ provider, model, effort, tier, dualBrain, explanation, detection }`
|
|
52
|
+
|
|
53
|
+
### `dual_brain_status`
|
|
54
|
+
Provider health, routing scores, and session stats.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Returns: `{ providers: { claude: {...}, openai: {...} }, session, profile }`
|
|
61
|
+
|
|
62
|
+
### `dual_brain_remember`
|
|
63
|
+
Save a routing preference that persists across sessions.
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{ "preference": "prefer claude for architecture tasks" }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Returns: `{ saved: true, preference, preferences: string[] }`
|
|
70
|
+
|
|
71
|
+
## Standalone test
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node mcp-server/index.mjs
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then send JSON-RPC over stdin:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | node mcp-server/index.mjs
|
|
81
|
+
```
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mcp-server/index.mjs — MCP server for dual-brain routing engine.
|
|
4
|
+
*
|
|
5
|
+
* Exposes dual-brain capabilities as MCP tools via JSON-RPC 2.0 over stdin/stdout.
|
|
6
|
+
* No external dependencies — uses only Node.js built-ins and the src/ modules.
|
|
7
|
+
*
|
|
8
|
+
* Tools:
|
|
9
|
+
* dual_brain_detect — Classify a task (intent, risk, complexity, tier)
|
|
10
|
+
* dual_brain_decide — Route a task (provider, model, tier, reason)
|
|
11
|
+
* dual_brain_status — Provider health and budget overview
|
|
12
|
+
* dual_brain_remember — Save a routing preference
|
|
13
|
+
*
|
|
14
|
+
* Usage (standalone):
|
|
15
|
+
* node mcp-server/index.mjs
|
|
16
|
+
*
|
|
17
|
+
* MCP client config:
|
|
18
|
+
* {
|
|
19
|
+
* "mcpServers": {
|
|
20
|
+
* "dual-brain": {
|
|
21
|
+
* "command": "node",
|
|
22
|
+
* "args": ["node_modules/dual-brain/mcp-server/index.mjs"]
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { createInterface } from 'readline';
|
|
29
|
+
import { fileURLToPath } from 'url';
|
|
30
|
+
import { dirname, join } from 'path';
|
|
31
|
+
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const SRC = join(__dirname, '..', 'src');
|
|
34
|
+
|
|
35
|
+
// ─── Tool definitions (JSON Schema) ──────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
const TOOLS = [
|
|
38
|
+
{
|
|
39
|
+
name: 'dual_brain_detect',
|
|
40
|
+
description: 'Classify a task prompt into intent, risk, complexity, and routing tier. Returns classification data used by the dual-brain routing engine.',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
prompt: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'The task description or prompt to classify.',
|
|
47
|
+
},
|
|
48
|
+
files: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
items: { type: 'string' },
|
|
51
|
+
description: 'Optional list of file paths involved in the task. Used for risk classification.',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['prompt'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'dual_brain_decide',
|
|
59
|
+
description: 'Detect a task and route it to the best provider and model. Returns provider, model, tier, explanation, and whether dual-brain review is recommended.',
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
prompt: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'The task description or prompt.',
|
|
66
|
+
},
|
|
67
|
+
files: {
|
|
68
|
+
type: 'array',
|
|
69
|
+
items: { type: 'string' },
|
|
70
|
+
description: 'Optional list of file paths involved in the task.',
|
|
71
|
+
},
|
|
72
|
+
profile: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'Optional profile mode override: "auto", "balanced", "cost-saver", or "quality-first".',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
required: ['prompt'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'dual_brain_status',
|
|
82
|
+
description: 'Get current provider health, routing scores, and session statistics. Shows which providers are healthy, degraded, or rate-limited.',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'dual_brain_remember',
|
|
90
|
+
description: 'Save a routing preference that persists across sessions. Examples: "prefer claude for architecture", "use cost-saver mode", "never dual-brain".',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
preference: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'The routing preference to remember in plain English.',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ['preference'],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'dual_brain_search',
|
|
104
|
+
description: 'Search across all previous sessions for context. Use this when the user references past work ("we did this before", "yesterday we worked on", "remember when we", "didn\'t we already"). Returns matching sessions with prompts, topics, and files.',
|
|
105
|
+
inputSchema: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
query: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
description: 'Search keywords to find in previous sessions. Can be topic, file name, or description of past work.',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
required: ['query'],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'dual_brain_session_context',
|
|
118
|
+
description: 'Get detailed context from a specific previous session. Use after dual_brain_search to retrieve details about what happened in a found session.',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
sessionId: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
description: 'The session UUID to get context for (from search results).',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
required: ['sessionId'],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// ─── Tool handlers ────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
async function handleDetect({ prompt, files = [] }) {
|
|
135
|
+
const { detectTask } = await import(`${SRC}/detect.mjs`);
|
|
136
|
+
const result = detectTask({ prompt, files });
|
|
137
|
+
return {
|
|
138
|
+
intent: result.intent,
|
|
139
|
+
risk: result.risk,
|
|
140
|
+
complexity: result.complexity,
|
|
141
|
+
effort: result.effort,
|
|
142
|
+
tier: result.tier,
|
|
143
|
+
fileCount: result.fileCount,
|
|
144
|
+
requiresWrite: result.requiresWrite,
|
|
145
|
+
riskyFiles: result.riskyFiles,
|
|
146
|
+
explanation: result.explanation,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function handleDecide({ prompt, files = [], profile: profileOverride }) {
|
|
151
|
+
const { detectTask } = await import(`${SRC}/detect.mjs`);
|
|
152
|
+
const { decideRoute } = await import(`${SRC}/decide.mjs`);
|
|
153
|
+
const { loadProfile } = await import(`${SRC}/profile.mjs`);
|
|
154
|
+
|
|
155
|
+
const cwd = process.cwd();
|
|
156
|
+
let profile = loadProfile(cwd);
|
|
157
|
+
|
|
158
|
+
// Apply profile override if requested
|
|
159
|
+
if (profileOverride) {
|
|
160
|
+
profile = { ...profile, mode: profileOverride, profile: profileOverride };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const detection = detectTask({ prompt, files });
|
|
164
|
+
const decision = decideRoute({ profile, detection, cwd });
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
provider: decision.provider,
|
|
168
|
+
model: decision.model,
|
|
169
|
+
effort: decision.effort,
|
|
170
|
+
tier: decision.tier,
|
|
171
|
+
dualBrain: decision.dualBrain,
|
|
172
|
+
modes: decision.modes,
|
|
173
|
+
sandbox: decision.sandbox,
|
|
174
|
+
explanation: decision.explanation,
|
|
175
|
+
detection: {
|
|
176
|
+
intent: detection.intent,
|
|
177
|
+
risk: detection.risk,
|
|
178
|
+
complexity: detection.complexity,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleStatus() {
|
|
184
|
+
const { loadProfile, getAvailableProviders } = await import(`${SRC}/profile.mjs`);
|
|
185
|
+
const { getHealth, getProviderScore, getSessionStats } = await import(`${SRC}/health.mjs`);
|
|
186
|
+
|
|
187
|
+
const cwd = process.cwd();
|
|
188
|
+
const profile = loadProfile(cwd);
|
|
189
|
+
const health = getHealth(cwd);
|
|
190
|
+
|
|
191
|
+
const providers = {};
|
|
192
|
+
|
|
193
|
+
for (const prov of ['claude', 'openai']) {
|
|
194
|
+
const cfg = profile?.providers?.[prov];
|
|
195
|
+
if (!cfg) {
|
|
196
|
+
providers[prov] = { enabled: false };
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Get scores for each tier model
|
|
201
|
+
const models = {
|
|
202
|
+
search: prov === 'claude' ? 'haiku' : 'gpt-4.1-mini',
|
|
203
|
+
execute: prov === 'claude' ? 'sonnet' : 'gpt-5.4',
|
|
204
|
+
think: prov === 'claude' ? 'opus' : 'gpt-5.5',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const scores = {};
|
|
208
|
+
const states = {};
|
|
209
|
+
for (const [tier, modelClass] of Object.entries(models)) {
|
|
210
|
+
scores[tier] = getProviderScore(prov, modelClass, cwd);
|
|
211
|
+
const key = `${prov}:${modelClass}`;
|
|
212
|
+
states[modelClass] = health.states[key] ?? { status: 'healthy' };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
providers[prov] = {
|
|
216
|
+
enabled: cfg.enabled ?? false,
|
|
217
|
+
plan: cfg.plan ?? null,
|
|
218
|
+
scores,
|
|
219
|
+
states,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const session = getSessionStats ? getSessionStats(cwd) : (health.session ?? null);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
providers,
|
|
227
|
+
session,
|
|
228
|
+
profile: {
|
|
229
|
+
mode: profile?.mode || profile?.profile || 'auto',
|
|
230
|
+
dualBrainEnabled: profile?.dual_brain_enabled !== false,
|
|
231
|
+
preferences: (profile?.preferences || []).filter(p => p.enabled).map(p => p.text),
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function handleRemember({ preference }) {
|
|
237
|
+
const { rememberPreference, getActivePreferences } = await import(`${SRC}/profile.mjs`);
|
|
238
|
+
const cwd = process.cwd();
|
|
239
|
+
rememberPreference(preference, { cwd });
|
|
240
|
+
const all = getActivePreferences(cwd);
|
|
241
|
+
return {
|
|
242
|
+
saved: true,
|
|
243
|
+
preference,
|
|
244
|
+
preferences: all.map(p => p.text),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function handleSearch({ query }) {
|
|
249
|
+
const { searchSessions, buildSessionIndex } = await import(`${SRC}/session.mjs`);
|
|
250
|
+
const cwd = process.cwd();
|
|
251
|
+
try { buildSessionIndex(cwd); } catch {}
|
|
252
|
+
const results = searchSessions(query, cwd);
|
|
253
|
+
return {
|
|
254
|
+
count: results.length,
|
|
255
|
+
sessions: results.slice(0, 5).map(s => ({
|
|
256
|
+
id: s.id,
|
|
257
|
+
tool: s.tool,
|
|
258
|
+
date: s.date,
|
|
259
|
+
firstPrompt: s.prompts.first,
|
|
260
|
+
lastPrompt: s.prompts.last,
|
|
261
|
+
topics: s.topics.slice(0, 5),
|
|
262
|
+
files: s.files.slice(0, 10),
|
|
263
|
+
messageCount: s.messageCount,
|
|
264
|
+
})),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function handleSessionContext({ sessionId }) {
|
|
269
|
+
const { getSessionContext } = await import(`${SRC}/session.mjs`);
|
|
270
|
+
const cwd = process.cwd();
|
|
271
|
+
const ctx = getSessionContext(sessionId, cwd);
|
|
272
|
+
if (!ctx) return { error: 'Session not found' };
|
|
273
|
+
return ctx;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ─── JSON-RPC dispatcher ──────────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
async function dispatchTool(name, args) {
|
|
279
|
+
switch (name) {
|
|
280
|
+
case 'dual_brain_detect': return handleDetect(args);
|
|
281
|
+
case 'dual_brain_decide': return handleDecide(args);
|
|
282
|
+
case 'dual_brain_status': return handleStatus();
|
|
283
|
+
case 'dual_brain_remember': return handleRemember(args);
|
|
284
|
+
case 'dual_brain_search': return handleSearch(args);
|
|
285
|
+
case 'dual_brain_session_context': return handleSessionContext(args);
|
|
286
|
+
default:
|
|
287
|
+
throw Object.assign(new Error(`Unknown tool: ${name}`), { code: -32601 });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ─── JSON-RPC helpers ─────────────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
function respond(id, result) {
|
|
294
|
+
return JSON.stringify({ jsonrpc: '2.0', id, result });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function errorResponse(id, code, message, data) {
|
|
298
|
+
const err = { code, message };
|
|
299
|
+
if (data !== undefined) err.data = data;
|
|
300
|
+
return JSON.stringify({ jsonrpc: '2.0', id, error: err });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ─── Request handlers ─────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
async function handleRequest(msg) {
|
|
306
|
+
const { id, method, params } = msg;
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
switch (method) {
|
|
310
|
+
case 'initialize':
|
|
311
|
+
return respond(id, {
|
|
312
|
+
protocolVersion: '2024-11-05',
|
|
313
|
+
capabilities: { tools: {} },
|
|
314
|
+
serverInfo: { name: 'dual-brain', version: '7.1.4' },
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
case 'initialized':
|
|
318
|
+
// Notification (no id), no response needed
|
|
319
|
+
return null;
|
|
320
|
+
|
|
321
|
+
case 'tools/list':
|
|
322
|
+
return respond(id, { tools: TOOLS });
|
|
323
|
+
|
|
324
|
+
case 'tools/call': {
|
|
325
|
+
const { name, arguments: args = {} } = params || {};
|
|
326
|
+
if (!name) {
|
|
327
|
+
return errorResponse(id, -32602, 'Missing tool name');
|
|
328
|
+
}
|
|
329
|
+
const result = await dispatchTool(name, args);
|
|
330
|
+
return respond(id, {
|
|
331
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
case 'ping':
|
|
336
|
+
return respond(id, {});
|
|
337
|
+
|
|
338
|
+
default:
|
|
339
|
+
return errorResponse(id, -32601, `Method not found: ${method}`);
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
const code = err.code ?? -32000;
|
|
343
|
+
const message = err.message ?? 'Internal error';
|
|
344
|
+
return errorResponse(id, code, message);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ─── Main loop ────────────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
351
|
+
|
|
352
|
+
// Track in-flight requests so we don't exit while work is pending
|
|
353
|
+
let pending = 0;
|
|
354
|
+
let stdinClosed = false;
|
|
355
|
+
|
|
356
|
+
function maybeExit() {
|
|
357
|
+
if (stdinClosed && pending === 0) process.exit(0);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
rl.on('line', (line) => {
|
|
361
|
+
const raw = line.trim();
|
|
362
|
+
if (!raw) return;
|
|
363
|
+
|
|
364
|
+
let msg;
|
|
365
|
+
try {
|
|
366
|
+
msg = JSON.parse(raw);
|
|
367
|
+
} catch {
|
|
368
|
+
process.stdout.write(errorResponse(null, -32700, 'Parse error') + '\n');
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
pending++;
|
|
373
|
+
handleRequest(msg).then((response) => {
|
|
374
|
+
if (response !== null) {
|
|
375
|
+
process.stdout.write(response + '\n');
|
|
376
|
+
}
|
|
377
|
+
}).catch((err) => {
|
|
378
|
+
process.stdout.write(errorResponse(msg?.id ?? null, -32000, err?.message ?? 'Internal error') + '\n');
|
|
379
|
+
}).finally(() => {
|
|
380
|
+
pending--;
|
|
381
|
+
maybeExit();
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
rl.on('close', () => {
|
|
386
|
+
stdinClosed = true;
|
|
387
|
+
maybeExit();
|
|
388
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
{
|
|
2
|
+
"subscriptions": {
|
|
3
|
+
"claude": {
|
|
4
|
+
"plan": "$100",
|
|
5
|
+
"models": {
|
|
6
|
+
"opus": { "tier": "think", "input_per_mtok": 5.0, "output_per_mtok": 25.0, "context_window": 1000000, "max_output": 128000 },
|
|
7
|
+
"sonnet": { "tier": "execute", "input_per_mtok": 3.0, "output_per_mtok": 15.0, "context_window": 1000000, "max_output": 64000 },
|
|
8
|
+
"haiku": { "tier": "search", "input_per_mtok": 1.0, "output_per_mtok": 5.0, "context_window": 200000, "max_output": 64000 }
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"openai": {
|
|
12
|
+
"plan": "$100",
|
|
13
|
+
"models": {
|
|
14
|
+
"gpt-5.5": { "tier": "think", "input_per_mtok": 5.0, "output_per_mtok": 30.0, "context_window": 1000000, "max_output": 128000 },
|
|
15
|
+
"gpt-5.4": { "tier": "execute", "input_per_mtok": 2.5, "output_per_mtok": 15.0, "context_window": 1000000, "max_output": 128000 },
|
|
16
|
+
"gpt-4.1-mini": { "tier": "search", "input_per_mtok": 0.40, "output_per_mtok": 1.60, "context_window": 1047576, "max_output": 32768 }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
"model_intelligence": {
|
|
22
|
+
"opus": {
|
|
23
|
+
"model_id": "claude-opus-4-6",
|
|
24
|
+
"strengths": ["agentic coding", "complex reasoning", "tool use", "error recovery", "architecture decisions"],
|
|
25
|
+
"weaknesses": ["higher latency", "tokenizer tax on 4.7 (12-35% more tokens)"],
|
|
26
|
+
"best_for": "architecture, security review, complex debugging, multi-step planning",
|
|
27
|
+
"avoid_for": "simple file reads, grep, formatting — wasteful at this tier"
|
|
28
|
+
},
|
|
29
|
+
"sonnet": {
|
|
30
|
+
"model_id": "claude-sonnet-4-6",
|
|
31
|
+
"strengths": ["best speed/intelligence ratio", "precise minimal diffs", "1M context", "extended thinking"],
|
|
32
|
+
"weaknesses": ["less reliable on complex multi-step reasoning than Opus"],
|
|
33
|
+
"best_for": "implementation, refactoring, test writing, code edits, git operations",
|
|
34
|
+
"avoid_for": "architecture decisions, security audits — upgrade to think tier"
|
|
35
|
+
},
|
|
36
|
+
"haiku": {
|
|
37
|
+
"model_id": "claude-haiku-4-5-20251001",
|
|
38
|
+
"strengths": ["fastest latency", "cheapest", "good enough for read-only tasks"],
|
|
39
|
+
"weaknesses": ["200k context (vs 1M for others)", "weaker reasoning", "older knowledge cutoff"],
|
|
40
|
+
"best_for": "file lookups, grep, explore, read-only research, listing files",
|
|
41
|
+
"avoid_for": "any task requiring edits, reasoning, or judgment"
|
|
42
|
+
},
|
|
43
|
+
"gpt-5.5": {
|
|
44
|
+
"model_id": "gpt-5.5",
|
|
45
|
+
"strengths": ["complex reasoning", "fast interactive responses", "strong code review", "independent perspective from Claude"],
|
|
46
|
+
"weaknesses": ["different failure modes than Claude — feature not bug for dual-brain"],
|
|
47
|
+
"best_for": "independent code review (dual-brain), second opinions on architecture",
|
|
48
|
+
"codex_compatible": true
|
|
49
|
+
},
|
|
50
|
+
"gpt-5.4": {
|
|
51
|
+
"model_id": "gpt-5.4",
|
|
52
|
+
"strengths": ["good speed/cost ratio", "1M context", "strong instruction following"],
|
|
53
|
+
"weaknesses": ["less capable reasoning than gpt-5.5"],
|
|
54
|
+
"best_for": "implementation and execution tasks via Codex",
|
|
55
|
+
"codex_compatible": true
|
|
56
|
+
},
|
|
57
|
+
"gpt-4.1-mini": {
|
|
58
|
+
"model_id": "gpt-4.1-mini",
|
|
59
|
+
"strengths": ["extremely cheap", "good instruction following", "1M context"],
|
|
60
|
+
"weaknesses": ["32k max output", "no reasoning chain"],
|
|
61
|
+
"best_for": "cheap search/lookup tasks via Codex",
|
|
62
|
+
"codex_compatible": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"tiers": {
|
|
67
|
+
"search": {
|
|
68
|
+
"description": "Read-only lookups, exploration, grep, find, file reads",
|
|
69
|
+
"prefer": "cheapest available model",
|
|
70
|
+
"tasks": ["explore", "grep", "find", "ls", "read_file", "git_log", "git_status"]
|
|
71
|
+
},
|
|
72
|
+
"execute": {
|
|
73
|
+
"description": "Implementation, edits, test runs, git operations, linting",
|
|
74
|
+
"prefer": "mid-tier model",
|
|
75
|
+
"tasks": ["edit", "write", "test_run", "lint", "format", "simple_fix", "refactor_small"]
|
|
76
|
+
},
|
|
77
|
+
"think": {
|
|
78
|
+
"description": "Architecture, review, planning, security, complex debugging",
|
|
79
|
+
"prefer": "most capable model",
|
|
80
|
+
"tasks": ["architecture", "review", "planning", "security", "complex_debug", "design"]
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
"quality_gate": {
|
|
85
|
+
"enabled": true,
|
|
86
|
+
"trigger_extensions": [".ts", ".tsx", ".js", ".jsx", ".py", ".rs", ".go", ".java", ".rb", ".swift", ".kt"],
|
|
87
|
+
"skip_patterns": ["test", "__tests__", "spec", ".md", ".json", ".yaml", ".toml", ".txt"]
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
"routing_rules": {
|
|
91
|
+
"subagent_defaults": {
|
|
92
|
+
"Explore": "search",
|
|
93
|
+
"general-purpose": "execute",
|
|
94
|
+
"Plan": "think",
|
|
95
|
+
"code-reviewer": "think"
|
|
96
|
+
},
|
|
97
|
+
"max_concurrent_think": 1,
|
|
98
|
+
"max_concurrent_execute": 3,
|
|
99
|
+
"max_concurrent_search": 4,
|
|
100
|
+
"model_routing_note": "Claude Code model: param may be silently ignored (issue #43869). Set CLAUDE_CODE_SUBAGENT_MODEL env var as fallback if subagents all run on parent model."
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
"codex_skills": {
|
|
104
|
+
"review": {
|
|
105
|
+
"description": "Independent GPT code review via ChatGPT subscription",
|
|
106
|
+
"command": "node .claude/hooks/dual-brain-review.mjs",
|
|
107
|
+
"requires": "codex_auth"
|
|
108
|
+
},
|
|
109
|
+
"quality_gate": {
|
|
110
|
+
"description": "Config-driven quality gate with review artifacts",
|
|
111
|
+
"command": "node .claude/hooks/quality-gate.mjs"
|
|
112
|
+
},
|
|
113
|
+
"cost_report": {
|
|
114
|
+
"description": "Session activity and cost estimate by model tier",
|
|
115
|
+
"command": "node .claude/hooks/cost-report.mjs"
|
|
116
|
+
},
|
|
117
|
+
"test": {
|
|
118
|
+
"description": "Self-test harness for all orchestrator hooks",
|
|
119
|
+
"command": "node .claude/hooks/test-orchestrator.mjs"
|
|
120
|
+
},
|
|
121
|
+
"health_check": {
|
|
122
|
+
"description": "Verify all hooks are wired and system is healthy",
|
|
123
|
+
"command": "node .claude/hooks/health-check.mjs"
|
|
124
|
+
},
|
|
125
|
+
"session_report": {
|
|
126
|
+
"description": "Comprehensive session-end summary: activity by tier, routing compliance, quality gate status, data quality, drift warnings",
|
|
127
|
+
"command": "node .claude/hooks/session-report.mjs"
|
|
128
|
+
},
|
|
129
|
+
"setup_wizard": {
|
|
130
|
+
"description": "Interactive setup wizard — configures Claude Code hooks, dual-provider routing (Claude + OpenAI/Codex), subscription tiers, and cost-tracking settings",
|
|
131
|
+
"command": "node .claude/hooks/setup-wizard.mjs"
|
|
132
|
+
},
|
|
133
|
+
"gpt_dispatch": {
|
|
134
|
+
"description": "Dispatch execution tasks to GPT via Codex CLI",
|
|
135
|
+
"command": "node .claude/hooks/gpt-work-dispatcher.mjs"
|
|
136
|
+
},
|
|
137
|
+
"budget_balance": {
|
|
138
|
+
"description": "Show provider balance status and routing recommendations",
|
|
139
|
+
"command": "node .claude/hooks/budget-balancer.mjs"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
"pricing_verified": "2026-05-13",
|
|
144
|
+
|
|
145
|
+
"budgets": {
|
|
146
|
+
"session_warn_usd": 5.00,
|
|
147
|
+
"session_limit_usd": 10.00,
|
|
148
|
+
"daily_warn_usd": 20.00,
|
|
149
|
+
"daily_limit_usd": 50.00,
|
|
150
|
+
"alert_cooldown_minutes": 15
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
"routing": {
|
|
154
|
+
"strategy": "hybrid-specialized-balanced",
|
|
155
|
+
"codex_startup_penalty_ms": 20000,
|
|
156
|
+
"min_codex_task_ms": 180000,
|
|
157
|
+
"dual_thinking_permission_threshold": {
|
|
158
|
+
"estimated_tokens": 50000,
|
|
159
|
+
"agent_count": 2
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
"providers": {
|
|
164
|
+
"claude": {
|
|
165
|
+
"enabled": true,
|
|
166
|
+
"subscription": "max-5x",
|
|
167
|
+
"models": {
|
|
168
|
+
"think": "opus",
|
|
169
|
+
"execute": "sonnet",
|
|
170
|
+
"search": "haiku"
|
|
171
|
+
},
|
|
172
|
+
"rolling_window_hours": 5,
|
|
173
|
+
"pressure_thresholds": {
|
|
174
|
+
"warm": 0.65,
|
|
175
|
+
"hot": 0.82,
|
|
176
|
+
"throttled": 0.95
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"openai": {
|
|
180
|
+
"enabled": true,
|
|
181
|
+
"subscription": "pro",
|
|
182
|
+
"codex_command": "codex",
|
|
183
|
+
"models": {
|
|
184
|
+
"think": "gpt-5.5",
|
|
185
|
+
"execute": "gpt-5.4",
|
|
186
|
+
"search": "gpt-4.1-mini"
|
|
187
|
+
},
|
|
188
|
+
"rolling_window_hours": 5,
|
|
189
|
+
"pressure_thresholds": {
|
|
190
|
+
"warm": 0.65,
|
|
191
|
+
"hot": 0.82,
|
|
192
|
+
"throttled": 0.95
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
"dual_thinking": {
|
|
198
|
+
"enabled": true,
|
|
199
|
+
"auto_triggers": [
|
|
200
|
+
"architecture",
|
|
201
|
+
"security",
|
|
202
|
+
"database-migration",
|
|
203
|
+
"public-api",
|
|
204
|
+
"large-refactor",
|
|
205
|
+
"dependency-upgrade",
|
|
206
|
+
"production-incident"
|
|
207
|
+
],
|
|
208
|
+
"sensitive_paths": [
|
|
209
|
+
"auth", "security", "middleware/auth", "payment", "billing",
|
|
210
|
+
"migration", "schema", "permissions", "secrets", "crypto",
|
|
211
|
+
"api/public", ".env"
|
|
212
|
+
],
|
|
213
|
+
"conflict_resolver": "least-pressured-think-model"
|
|
214
|
+
}
|
|
215
|
+
}
|