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,393 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dual-brain-think.mjs
|
|
4
|
+
*
|
|
5
|
+
* Runs a dual-perspective thinking process — GPT-5.5 (via Codex CLI) independently
|
|
6
|
+
* analyzes a question, then emits its output along with instructions for Claude
|
|
7
|
+
* (the main session) to provide its own independent analysis and compare both.
|
|
8
|
+
*
|
|
9
|
+
* Usage as CLI:
|
|
10
|
+
* node .claude/hooks/dual-brain-think.mjs \
|
|
11
|
+
* --question "Should we use queues or direct API calls for the notification system?"
|
|
12
|
+
*
|
|
13
|
+
* Usage as module:
|
|
14
|
+
* import { dualThink } from './dual-brain-think.mjs';
|
|
15
|
+
* const result = await dualThink({
|
|
16
|
+
* question: "Should we use queues or direct calls?",
|
|
17
|
+
* context: "Building a notification system that handles ~1000 events/min",
|
|
18
|
+
* files: ['src/notifications/'],
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawnSync } from 'child_process';
|
|
23
|
+
import { appendFileSync } from 'fs';
|
|
24
|
+
import { dirname, join } from 'path';
|
|
25
|
+
import { fileURLToPath } from 'url';
|
|
26
|
+
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const IS_REPLIT = !!(process.env.REPL_ID || process.env.REPL_SLUG);
|
|
29
|
+
const SANDBOX = IS_REPLIT ? 'danger-full-access' : 'read-only';
|
|
30
|
+
|
|
31
|
+
const CODEX_TIMEOUT_MS = 120_000;
|
|
32
|
+
const MODEL = 'gpt-5.5';
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Codex discovery — same pattern as dual-brain-review.mjs
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
function findCodex() {
|
|
39
|
+
const candidates = [
|
|
40
|
+
process.env.CODEX_BIN,
|
|
41
|
+
].filter(Boolean);
|
|
42
|
+
for (const c of candidates) {
|
|
43
|
+
try { spawnSync(c, ['--version'], { stdio: 'pipe', timeout: 3000 }); return c; } catch {}
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const which = spawnSync('which', ['codex'], { encoding: 'utf8', stdio: 'pipe', timeout: 3000 });
|
|
47
|
+
if (which.status === 0 && which.stdout.trim()) return which.stdout.trim();
|
|
48
|
+
} catch {}
|
|
49
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
50
|
+
const fallbacks = [
|
|
51
|
+
join(home, '.local', 'bin', 'codex'),
|
|
52
|
+
join(home, 'bin', 'codex'),
|
|
53
|
+
'/usr/local/bin/codex',
|
|
54
|
+
];
|
|
55
|
+
for (const p of fallbacks) {
|
|
56
|
+
try { spawnSync(p, ['--version'], { stdio: 'pipe', timeout: 3000 }); return p; } catch {}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isCodexAuthenticated(result) {
|
|
62
|
+
const out = ((result?.stdout || '') + (result?.stderr || '')).toLowerCase();
|
|
63
|
+
if (/\b(not\s+logged\s+in|unauthenticated|logged\s+out|no\s+auth)\b/.test(out)) return false;
|
|
64
|
+
return result?.status === 0 ||
|
|
65
|
+
/\b(logged\s+in|authenticated|signed\s+in)\b/.test(out);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Prompt builder
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
function buildGptPrompt({ question, context, files, round, claudePerspective }) {
|
|
73
|
+
if (round === 2 && claudePerspective) {
|
|
74
|
+
return `You are GPT-5.5 in a collaborative architectural discussion with Claude (Opus).
|
|
75
|
+
You gave your initial analysis on a question. Claude has now provided its independent perspective.
|
|
76
|
+
This is a professional dialogue — two experts refining a decision together.
|
|
77
|
+
|
|
78
|
+
Original question: ${question}
|
|
79
|
+
${context ? `\nContext: ${context}` : ''}
|
|
80
|
+
|
|
81
|
+
Claude's perspective:
|
|
82
|
+
${claudePerspective}
|
|
83
|
+
|
|
84
|
+
Now respond as a colleague, not a critic. Structure your response:
|
|
85
|
+
1. AGREEMENTS: Where Claude's analysis strengthens or confirms your thinking
|
|
86
|
+
2. PUSHBACK: Where you disagree — be specific about WHY with evidence or reasoning
|
|
87
|
+
3. NEW INSIGHTS: Anything Claude's perspective surfaced that you missed
|
|
88
|
+
4. REFINED RECOMMENDATION: Your updated recommendation incorporating both perspectives
|
|
89
|
+
5. REMAINING CONCERNS: Open questions neither of you fully resolved
|
|
90
|
+
6. CONFIDENCE DELTA: Has your confidence changed? Why?
|
|
91
|
+
|
|
92
|
+
Be direct and substantive. If Claude is right about something you got wrong, say so.
|
|
93
|
+
If you still disagree after considering their points, explain what specific evidence would change your mind.`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return `You are GPT-5.5, providing an independent architectural perspective.
|
|
97
|
+
This is Round 1 of a dual-brain analysis — Claude (Opus) will independently analyze the same question,
|
|
98
|
+
then send you their perspective for a collaborative discussion in Round 2.
|
|
99
|
+
|
|
100
|
+
Question: ${question}
|
|
101
|
+
${context ? `\nContext: ${context}` : ''}
|
|
102
|
+
${files?.length ? `\nRelevant files: ${files.join(', ')}` : ''}
|
|
103
|
+
|
|
104
|
+
Provide your analysis in this structure:
|
|
105
|
+
1. RECOMMENDATION: Your clear recommendation (1-2 sentences)
|
|
106
|
+
2. RATIONALE: Why this is the best approach (3-5 points)
|
|
107
|
+
3. ALTERNATIVES: What you considered and rejected
|
|
108
|
+
4. RISKS: What could go wrong with your recommendation
|
|
109
|
+
5. CONFIDENCE: low/medium/high and why
|
|
110
|
+
6. VERIFICATION: How to validate this decision is correct`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Codex executor
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
function runGptAnalysis(codexBin, prompt) {
|
|
118
|
+
const startTime = Date.now();
|
|
119
|
+
|
|
120
|
+
const proc = spawnSync(codexBin, [
|
|
121
|
+
'exec', '--json', '--ephemeral',
|
|
122
|
+
'-m', MODEL,
|
|
123
|
+
'-s', SANDBOX,
|
|
124
|
+
prompt,
|
|
125
|
+
], {
|
|
126
|
+
encoding: 'utf8',
|
|
127
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
128
|
+
timeout: CODEX_TIMEOUT_MS,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const durationMs = Date.now() - startTime;
|
|
132
|
+
|
|
133
|
+
// Parse JSONL output
|
|
134
|
+
const messages = (proc.stdout || '')
|
|
135
|
+
.split('\n')
|
|
136
|
+
.filter(l => l.trim())
|
|
137
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
138
|
+
.filter(Boolean);
|
|
139
|
+
|
|
140
|
+
const agentMessages = messages
|
|
141
|
+
.filter(m => m.type === 'item.completed' && m.item?.type === 'agent_message')
|
|
142
|
+
.map(m => m.item.text);
|
|
143
|
+
|
|
144
|
+
const usage = messages.find(m => m.type === 'turn.completed')?.usage ?? null;
|
|
145
|
+
const errors = messages.filter(m => m.type === 'error' || m.type === 'turn.failed');
|
|
146
|
+
|
|
147
|
+
if (agentMessages.length > 0) {
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
text: agentMessages.join('\n\n'),
|
|
151
|
+
durationMs,
|
|
152
|
+
usage,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (errors.length > 0) {
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
error: errors[0].message || errors[0].error?.message || 'unknown codex error',
|
|
160
|
+
durationMs,
|
|
161
|
+
usage: null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
error: 'No agent messages returned from Codex',
|
|
168
|
+
durationMs,
|
|
169
|
+
usage: null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Usage logger — matches schema_version: 2 used across the orchestrator
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
function logUsage({ durationMs, usage, success }) {
|
|
178
|
+
const logFile = join(__dirname, `usage-${new Date().toISOString().slice(0, 10)}.jsonl`);
|
|
179
|
+
const entry = JSON.stringify({
|
|
180
|
+
schema_version: 2,
|
|
181
|
+
timestamp: new Date().toISOString(),
|
|
182
|
+
provider: 'openai',
|
|
183
|
+
tier: 'think',
|
|
184
|
+
tool: 'dual-brain-think',
|
|
185
|
+
model: MODEL,
|
|
186
|
+
dispatcher: 'dual-brain-think',
|
|
187
|
+
status: success ? 'ok' : 'error',
|
|
188
|
+
durationMs: durationMs ?? null,
|
|
189
|
+
input_tokens: usage?.input_tokens ?? null,
|
|
190
|
+
output_tokens: usage?.output_tokens ?? null,
|
|
191
|
+
session_id: process.env.CLAUDE_SESSION_ID || null,
|
|
192
|
+
});
|
|
193
|
+
try {
|
|
194
|
+
appendFileSync(logFile, entry + '\n');
|
|
195
|
+
} catch {}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Core exported function
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
export async function dualThink({ question, context, files, round, claudePerspective } = {}) {
|
|
203
|
+
if (!question) {
|
|
204
|
+
return {
|
|
205
|
+
gpt: null,
|
|
206
|
+
error: 'No question provided',
|
|
207
|
+
fallback: 'Proceed with single-brain analysis on Claude Opus',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const effectiveRound = (round === 2 && claudePerspective) ? 2 : 1;
|
|
212
|
+
|
|
213
|
+
const codexBin = findCodex();
|
|
214
|
+
if (!codexBin) {
|
|
215
|
+
return {
|
|
216
|
+
gpt: null,
|
|
217
|
+
error: 'Codex CLI not available',
|
|
218
|
+
fallback: 'Proceed with single-brain analysis on Claude Opus',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const login = spawnSync(codexBin, ['login', 'status'], {
|
|
223
|
+
encoding: 'utf8',
|
|
224
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
225
|
+
timeout: 5000,
|
|
226
|
+
});
|
|
227
|
+
if (!isCodexAuthenticated(login)) {
|
|
228
|
+
return {
|
|
229
|
+
gpt: null,
|
|
230
|
+
error: 'Codex CLI not authenticated — run `codex login`',
|
|
231
|
+
fallback: 'Proceed with single-brain analysis on Claude Opus',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const prompt = buildGptPrompt({ question, context, files, round: effectiveRound, claudePerspective });
|
|
236
|
+
const raw = runGptAnalysis(codexBin, prompt);
|
|
237
|
+
|
|
238
|
+
logUsage({ durationMs: raw.durationMs, usage: raw.usage, success: raw.success });
|
|
239
|
+
|
|
240
|
+
if (!raw.success) {
|
|
241
|
+
return {
|
|
242
|
+
gpt: null,
|
|
243
|
+
error: raw.error || 'GPT analysis failed',
|
|
244
|
+
fallback: effectiveRound === 2
|
|
245
|
+
? 'GPT rebuttal unavailable — synthesize from Round 1 analysis alone'
|
|
246
|
+
: 'Proceed with single-brain analysis on Claude Opus',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (effectiveRound === 2) {
|
|
251
|
+
return {
|
|
252
|
+
round: 2,
|
|
253
|
+
gpt: {
|
|
254
|
+
rebuttal: raw.text,
|
|
255
|
+
model: MODEL,
|
|
256
|
+
durationMs: raw.durationMs,
|
|
257
|
+
tokens: raw.usage,
|
|
258
|
+
},
|
|
259
|
+
instructions: `GPT has responded to your analysis. Now synthesize both rounds into a FINAL DECISION:
|
|
260
|
+
1. Where you both agree → high confidence, proceed
|
|
261
|
+
2. Where GPT pushed back on your points → re-evaluate honestly
|
|
262
|
+
3. Where you still disagree → state why and what evidence would resolve it
|
|
263
|
+
4. Final recommendation with combined confidence level`,
|
|
264
|
+
question,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
round: 1,
|
|
270
|
+
gpt: {
|
|
271
|
+
recommendation: raw.text,
|
|
272
|
+
model: MODEL,
|
|
273
|
+
durationMs: raw.durationMs,
|
|
274
|
+
tokens: raw.usage,
|
|
275
|
+
},
|
|
276
|
+
instructions: `Round 1 complete. Now:
|
|
277
|
+
1. Provide YOUR independent analysis of the same question (same structure: recommendation, rationale, alternatives, risks, confidence, verification)
|
|
278
|
+
2. Then call Round 2 to send your perspective back to GPT:
|
|
279
|
+
node .claude/hooks/dual-brain-think.mjs --question "<same question>" --round 2 --claude-says "<your analysis summary>"
|
|
280
|
+
3. GPT will respond to your specific points — agreements, pushback, and refined recommendation
|
|
281
|
+
4. You then synthesize both rounds into the final decision`,
|
|
282
|
+
question,
|
|
283
|
+
context: context || null,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// CLI argument parser
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
function parseArgs(argv) {
|
|
292
|
+
const args = {};
|
|
293
|
+
let i = 0;
|
|
294
|
+
while (i < argv.length) {
|
|
295
|
+
const arg = argv[i];
|
|
296
|
+
if (arg.startsWith('--')) {
|
|
297
|
+
const eqIdx = arg.indexOf('=');
|
|
298
|
+
if (eqIdx !== -1) {
|
|
299
|
+
args[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
300
|
+
} else {
|
|
301
|
+
const key = arg.slice(2);
|
|
302
|
+
const next = argv[i + 1];
|
|
303
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
304
|
+
args[key] = next;
|
|
305
|
+
i++;
|
|
306
|
+
} else {
|
|
307
|
+
args[key] = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
i++;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Normalize files to an array
|
|
315
|
+
if (typeof args.files === 'string') {
|
|
316
|
+
args.files = args.files.split(',').map(f => f.trim()).filter(Boolean);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return args;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// CLI output formatter
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
function printResult(result, question) {
|
|
327
|
+
const BAR = '╠══════════════════════════════════════════════════╣';
|
|
328
|
+
const TOP = '╔══════════════════════════════════════════════════╗';
|
|
329
|
+
const BOT = '╚══════════════════════════════════════════════════╝';
|
|
330
|
+
|
|
331
|
+
const roundLabel = result.round === 2 ? 'Round 2 — Rebuttal' : 'Round 1 — Initial';
|
|
332
|
+
|
|
333
|
+
console.log(TOP);
|
|
334
|
+
console.log(`║ 🧠 Dual-Brain Think · ${roundLabel}`.padEnd(51) + '║');
|
|
335
|
+
console.log(BAR);
|
|
336
|
+
const q = question.length > 44 ? question.slice(0, 41) + '...' : question;
|
|
337
|
+
console.log(`║ Question: ${q.padEnd(38)} ║`);
|
|
338
|
+
console.log(BAR);
|
|
339
|
+
|
|
340
|
+
if (!result.gpt) {
|
|
341
|
+
console.log(`║ ❌ ${(result.error || 'Unknown error').padEnd(45)} ║`);
|
|
342
|
+
console.log(BAR);
|
|
343
|
+
console.log(`║ ↩️ ${(result.fallback || '').padEnd(45)} ║`);
|
|
344
|
+
console.log(BOT);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const gptData = result.gpt;
|
|
349
|
+
const durSec = (gptData.durationMs / 1000).toFixed(1);
|
|
350
|
+
console.log(`║ 🤖 GPT-5.5 (${durSec}s):`.padEnd(51) + '║');
|
|
351
|
+
console.log(BAR);
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(gptData.recommendation || gptData.rebuttal);
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(BAR);
|
|
356
|
+
|
|
357
|
+
if (result.round === 2) {
|
|
358
|
+
console.log('║ 🔄 Synthesize both rounds into final decision. ║');
|
|
359
|
+
console.log('║ Where you agree → high confidence. ║');
|
|
360
|
+
console.log('║ Where you disagree → state what would resolve it.║');
|
|
361
|
+
} else {
|
|
362
|
+
console.log('║ 📝 Your turn: analyze independently, then call ║');
|
|
363
|
+
console.log('║ Round 2 with --round 2 --claude-says "..." ║');
|
|
364
|
+
console.log('║ for GPT\'s rebuttal to your analysis. ║');
|
|
365
|
+
}
|
|
366
|
+
console.log(BOT);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
// CLI entry point
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
374
|
+
const args = parseArgs(process.argv.slice(2));
|
|
375
|
+
|
|
376
|
+
if (!args.question) {
|
|
377
|
+
console.error(
|
|
378
|
+
'Usage: node dual-brain-think.mjs --question "<question>" [--context "<ctx>"] [--files f1,f2]\n' +
|
|
379
|
+
' node dual-brain-think.mjs --question "<question>" --round 2 --claude-says "<analysis>"'
|
|
380
|
+
);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const result = await dualThink({
|
|
385
|
+
question: args.question,
|
|
386
|
+
context: args.context,
|
|
387
|
+
files: args.files,
|
|
388
|
+
round: args.round ? parseInt(args.round, 10) : 1,
|
|
389
|
+
claudePerspective: args['claude-says'] || null,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
printResult(result, args.question);
|
|
393
|
+
}
|