dual-brain 0.2.6 → 0.2.8

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 CHANGED
@@ -2,146 +2,32 @@
2
2
 
3
3
  This project uses dual-provider orchestration. Config: `.claude/orchestrator.json`.
4
4
 
5
- ## Core Architecture (v7)
6
-
7
- Four modules in `src/` form the decision pipeline:
8
-
9
- - **`profile.mjs`** Load active profile, provider availability, preferences, and subscription plan
10
- - **`detect.mjs`** Classify task intent, risk, complexity, and tier from prompt + file paths
11
- - **`decide.mjs`** Route to provider/model/tier; handles budget pressure and dual-brain threshold
12
- - **`dispatch.mjs`** Execute the decision: Claude subagent, GPT via Codex, or dual-brain flow
13
-
14
- The hooks layer (`/home/runner/workspace/.claude/hooks/`) wraps these modules for Claude Code integration and is still valid.
15
-
16
- ## CLI Commands
17
-
18
- ```bash
19
- dual-brain init # First-time setup
20
- dual-brain go "task description" # Detect → decide → dispatch
21
- dual-brain go --dry-run "..." # Show routing without executing
22
- dual-brain go --files a.mjs,b.mjs "..." # Provide file context for risk classification
23
- dual-brain status # Provider health, budget pressure, models
24
- dual-brain remember "preference" # Save project-scoped preference
25
- dual-brain forget "preference" # Remove preference by fuzzy match
26
- ```
27
-
28
- ## Tier Routing
29
-
30
- - **Search** (`haiku`): Read-only lookups, grep, explore. Return: files found, line refs, confidence.
31
- - **Execute** (`sonnet`): Edits, tests, git ops. Return: files changed, tests run, edge cases.
32
- - **Think** (main session, Opus): Architecture, review, planning. Return: decision, alternatives, risks.
33
-
34
- ## Dual-Brain Collaboration
35
-
36
- Dual-brain is a multi-round conversation between Claude and GPT — not a single-shot dispatch.
37
-
38
- **Think flow** (architecture decisions):
39
- 1. Round 1: `node .claude/hooks/dual-brain-think.mjs --question "..."` → GPT gives independent analysis
40
- 2. You analyze the same question independently
41
- 3. Round 2: `node .claude/hooks/dual-brain-think.mjs --question "..." --round 2 --claude-says "<your analysis>"` → GPT responds with agreements, pushback, refined recommendation
42
- 4. You synthesize both rounds into a final decision
43
-
44
- **Review flow** (code review):
45
- 1. Round 1: `node .claude/hooks/dual-brain-review.mjs` → GPT reviews the diff independently
46
- 2. You review the same diff independently
47
- 3. Round 2: `node .claude/hooks/dual-brain-review.mjs --round 2 --claude-review "<your findings>"` → GPT confirms shared findings, acknowledges misses
48
- 4. You synthesize into a final review verdict
49
-
50
- ## Routing Rules
51
-
52
- 1. Tasks under 3 min → Claude (Codex startup overhead not worth it)
53
- 2. Isolated tasks over 3 min → check balance: `node .claude/hooks/budget-balancer.mjs`
54
- 3. High-risk decisions → dual-brain think
55
- 4. When a task spans tiers: think > execute > search
56
-
57
- ## Mandatory Workload Distribution
58
-
59
- **Claude MUST follow these rules before implementing multi-file changes:**
60
-
61
- 1. **Before starting any batch of 3+ file edits**: run `node .claude/hooks/budget-balancer.mjs` to check provider balance, then `dual-brain go --dry-run "description"` to classify tasks
62
- 2. **When budget-balancer recommends GPT**: dispatch via `src/dispatch.mjs` (or `node .claude/hooks/gpt-work-dispatcher.mjs --task "..." --tier execute`)
63
- 3. **Security/auth/credential changes**: always require dual-brain think flow before implementation
64
- 4. **Audit remediation batches**: plan waves with dual-brain think, dispatch execution to GPT, Claude reviews
65
- 5. **Claude's role in multi-task work**: define acceptance criteria, dispatch agents, review results — not solo-implement everything
66
-
67
- **Triggers that require this workflow:** 3+ production files edited in one session · auth/credentials/tokens/secrets · changes to dispatcher, agent routing, or tier logic · audit remediation across multiple subsystems · Claude think capacity above 60% per budget-balancer.
68
-
69
- **Failure to route is itself a bug.**
70
-
71
- ## Quality Gate
72
-
73
- Before ending a session with code changes:
74
- 1. `node .claude/hooks/session-report.mjs` (allowed by head-guard for hook scripts)
75
- 2. `node .claude/hooks/quality-gate.mjs`
76
-
77
- Gate statuses: `pass` (safe to end), `issues_found` (fix first), `needs_human_review` (GPT unavailable).
78
-
79
- ## Profiles
80
-
81
- Profile persists to `.dualbrain/profile.json` (project-scoped, gitignored).
82
-
83
- - **auto** (default): Adapts routing based on task risk, provider health, and outcomes
84
- - **balanced**: Best model per tier, normal budgets, reviews at medium+ risk
85
- - **cost-saver**: Prefer cheaper models, lower budgets, skip GPT for non-critical
86
- - **quality-first**: Dual-brain for medium+ risk, higher budgets, stricter reviews
87
-
88
- Switch via the interactive Profile screen in `dual-brain`, or set `bias` in `.dualbrain/profile.json`.
89
-
90
- ## Adaptive Routing (Auto Mode)
91
-
92
- - **Risk classification**: auth/secrets→critical, billing/migrations→high, tests/utils→medium, docs→low
93
- - **Failure detection**: 2+ failures on same prompt in 2 hours → auto-escalate tier or trigger dual-brain
94
- - **Provider balance**: Routes to underused provider when one subscription is hot
95
- - **Burst awareness**: Suppresses duplicate warnings during agent waves (3+ agents in 90s)
96
-
97
- ## Budget Balancer
98
-
99
- `src/decide.mjs` handles routing decisions using the same token data internally. For inspection:
100
-
101
- ```bash
102
- node .claude/hooks/budget-balancer.mjs
103
- ```
104
-
105
- Tracks 5-hour and 7-day rolling windows against subscription limits (Claude Pro/Max, ChatGPT Plus/Pro). The higher pressure window is the binding constraint. Uses actual `input_tokens + output_tokens` from usage logs.
106
-
107
- **Subscription tiers** (configured in `orchestrator.json` → `subscriptions.*.plan`):
108
- - Claude: Pro $20, Max x5 $100, Max x20 $200
109
- - ChatGPT: Plus $20, Pro $100, Pro $200
110
-
111
- ## Multi-Step Work
112
-
113
- The wave orchestrator is available for complex multi-step tasks:
114
-
115
- ```bash
116
- node .claude/hooks/wave-orchestrator.mjs "fix the login bug and update the nav"
117
- node .claude/hooks/wave-orchestrator.mjs --dry-run "refactor auth module"
118
- node .claude/hooks/wave-orchestrator.mjs --resume <manifestId>
119
- ```
120
-
121
- For most tasks, prefer `dual-brain go "..."` — it runs the same detect→decide→dispatch pipeline with less overhead.
122
-
123
- ## Available Tools
124
-
125
- | Tool | Purpose |
126
- |------|---------|
127
- | `dual-brain go "..."` | Primary entry point: detect, decide, dispatch |
128
- | `dual-brain status` | Provider health, budget, models |
129
- | `node .claude/hooks/budget-balancer.mjs` | Token usage and routing recommendation |
130
- | `node .claude/hooks/dual-brain-think.mjs` | Multi-round architecture decisions with GPT |
131
- | `node .claude/hooks/dual-brain-review.mjs` | Multi-round code review with GPT |
132
- | `node .claude/hooks/wave-orchestrator.mjs "..."` | Dependency-aware multi-wave dispatch |
133
- | `node .claude/hooks/session-report.mjs` | End-of-session summary |
134
- | `node .claude/hooks/quality-gate.mjs` | Gate check before ending session |
135
- | `node .claude/hooks/health-check.mjs` | System health |
136
- | `node .claude/hooks/test-orchestrator.mjs` | Self-tests (40 tests) |
137
- | `node .claude/hooks/vibe-memory.mjs` | Persistent preferences across sessions |
138
- | `dual-brain search "..."` | Search across all previous sessions |
139
-
140
- ## Cross-Session Context
141
-
142
- When the user references past work ("we did this before", "yesterday we worked on", "remember when we", "didn't we already fix"), use the session search to find relevant context:
143
-
144
- 1. Run `dual-brain search "keyword"` to search the session index
145
- 2. Or use the MCP tool `dual_brain_search` if available
146
-
147
- This surfaces previous conversations so HEAD can provide continuity across sessions without the user having to re-explain.
5
+ ## HEAD Constitution
6
+
7
+ HEAD is the orchestration brain. Workers implement. This is enforced by architecture, not just policy.
8
+
9
+ 1. **HEAD plans, workers implement.** HEAD dispatches typed task contracts via agents. HEAD never edits files, runs implementation commands, or writes code directly.
10
+ 2. **Discuss before dispatching.** Every action task starts with intent classification. Ambiguous requests get clarified. Architecture decisions get discussed.
11
+ 3. **Typed contracts are mandatory.** Every dispatch includes: objective, scope, acceptance criteria, risk level, allowed operations. Use `src/templates.mjs` to generate prompts.
12
+ 4. **Dangerous work requires approval.** Auth, credentials, secrets, billing, migrations, destructive git — explicit user confirmation before dispatch.
13
+ 5. **Runtime state is source of truth.** HEAD's state machine (`src/head.mjs`) tracks phase, intent, confidence, and drift. Not CLAUDE.md text.
14
+ 6. **Hooks enforce boundaries.** head-guard blocks HEAD from implementing. enforce-tier ensures correct routing. Telemetry hooks observe but never block.
15
+ 7. **Subscription-only auth.** Users authenticate via `claude login` / `codex login`. No API keys.
16
+
17
+ ## Quick Reference
18
+
19
+ | Command | Purpose |
20
+ |---------|---------|
21
+ | `dual-brain go "..."` | Detect decide → dispatch |
22
+ | `dual-brain status` | Provider health, budget |
23
+ | `dual-brain install --global` | Set dual-brain as default for all sessions |
24
+ | `node .claude/hooks/dual-brain-think.mjs --question "..."` | Multi-round architecture decisions |
25
+ | `node .claude/hooks/dual-brain-review.mjs` | Multi-round code review |
26
+
27
+ ## Modules
28
+
29
+ Core pipeline: `profile.mjs` → `detect.mjs` → `decide.mjs` → `dispatch.mjs` → `pipeline.mjs`
30
+ HEAD brain: `head.mjs` (state machine, intent, confidence, drift)
31
+ Templates: `templates.mjs` (typed prompt generation)
32
+ Integrity: `integrity.mjs` (atomic writes, locks)
33
+ Quality: `prompt-audit.mjs` (prompt scoring, exchange logging)
@@ -35,7 +35,7 @@ import { runPipeline, buildExecutionPlan, formatExecutionPlan } from '../src/pip
35
35
  import { loadRepoCache } from '../src/repo.mjs';
36
36
  import { loadSession, saveSession, formatSessionCard, importReplitSessions, getSessionMeta, saveSessionMeta, renameSession, pinSession, unpinSession, categorizeSession, enrichSessions, archiveSession, getArchivedSessions } from '../src/session.mjs';
37
37
 
38
- import { box, bar, badge, menu, separator } from '../src/tui.mjs';
38
+ import { box, bar, badge, menu, separator, panel, divider, statusChip, headerBar, prompt as tuiPrompt, signalLine } from '../src/tui.mjs';
39
39
 
40
40
  // ─── Dynamic imports for receipts + failure memory ───────────────────────────
41
41
 
@@ -1040,43 +1040,58 @@ async function installGlobal() {
1040
1040
  return;
1041
1041
  }
1042
1042
 
1043
- // Load existing settings (merge, never clobber)
1044
- let existing = {};
1045
- if (existsSync(globalSettingsPath)) {
1046
- try { existing = JSON.parse(readFileSync(globalSettingsPath, 'utf8')); } catch {}
1047
- }
1048
-
1049
- // Ensure hooks structure exists
1050
- if (!existing.hooks) existing.hooks = {};
1051
- if (!existing.hooks.PreToolUse) existing.hooks.PreToolUse = [];
1052
- if (!existing.hooks.PostToolUse) existing.hooks.PostToolUse = [];
1053
-
1054
- // Define dual-brain hooks with ownership marker
1055
- const DB_MARKER = '# dual-brain-managed';
1056
- const preToolHooks = [
1057
- { matcher: 'Edit', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1058
- { matcher: 'Write', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1059
- { matcher: 'NotebookEdit',hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1060
- { matcher: 'Bash', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1061
- { matcher: 'Agent', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'enforce-tier.mjs')} ${DB_MARKER}` }] },
1062
- ];
1063
- const postToolHooks = [
1064
- { matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'cost-logger.mjs')} ${DB_MARKER}` }] },
1065
- { matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'auto-update-wrapper.mjs')} ${DB_MARKER}` }] },
1066
- ];
1067
-
1068
- // Remove any existing dual-brain hooks (idempotent)
1069
- const isDBHook = (entry) => entry.hooks?.some(h => h.command?.includes(DB_MARKER));
1070
- existing.hooks.PreToolUse = existing.hooks.PreToolUse.filter(e => !isDBHook(e));
1071
- existing.hooks.PostToolUse = existing.hooks.PostToolUse.filter(e => !isDBHook(e));
1043
+ // Check if project-local hooks already exist (avoids double-firing)
1044
+ const projectLocalSettings = join(pkgRoot, '.claude', 'settings.local.json');
1045
+ const hasProjectLocalHooks = (() => {
1046
+ if (!existsSync(projectLocalSettings)) return false;
1047
+ try {
1048
+ const content = readFileSync(projectLocalSettings, 'utf8');
1049
+ return content.includes('dual-brain') || content.includes('head-guard');
1050
+ } catch { return false; }
1051
+ })();
1052
+
1053
+ if (hasProjectLocalHooks) {
1054
+ console.log(' hooks already configured project-locally, skipping global hooks');
1055
+ console.log(' (project .claude/settings.local.json already contains dual-brain hooks)');
1056
+ } else {
1057
+ // Load existing settings (merge, never clobber)
1058
+ let existing = {};
1059
+ if (existsSync(globalSettingsPath)) {
1060
+ try { existing = JSON.parse(readFileSync(globalSettingsPath, 'utf8')); } catch {}
1061
+ }
1072
1062
 
1073
- // Add dual-brain hooks
1074
- existing.hooks.PreToolUse.push(...preToolHooks);
1075
- existing.hooks.PostToolUse.push(...postToolHooks);
1063
+ // Ensure hooks structure exists
1064
+ if (!existing.hooks) existing.hooks = {};
1065
+ if (!existing.hooks.PreToolUse) existing.hooks.PreToolUse = [];
1066
+ if (!existing.hooks.PostToolUse) existing.hooks.PostToolUse = [];
1076
1067
 
1077
- // Write merged settings
1078
- mkdirSync(globalClaudeDir, { recursive: true });
1079
- writeFileSync(globalSettingsPath, JSON.stringify(existing, null, 2) + '\n');
1068
+ // Define dual-brain hooks with ownership marker
1069
+ const DB_MARKER = '# dual-brain-managed';
1070
+ const preToolHooks = [
1071
+ { matcher: 'Edit', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1072
+ { matcher: 'Write', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1073
+ { matcher: 'NotebookEdit',hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1074
+ { matcher: 'Bash', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'head-guard.mjs')} ${DB_MARKER}` }] },
1075
+ { matcher: 'Agent', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'enforce-tier.mjs')} ${DB_MARKER}` }] },
1076
+ ];
1077
+ const postToolHooks = [
1078
+ { matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'cost-logger.mjs')} ${DB_MARKER}` }] },
1079
+ { matcher: '', hooks: [{ type: 'command', command: `node ${join(hooksDir, 'auto-update-wrapper.mjs')} ${DB_MARKER}` }] },
1080
+ ];
1081
+
1082
+ // Remove any existing dual-brain hooks (idempotent)
1083
+ const isDBHook = (entry) => entry.hooks?.some(h => h.command?.includes(DB_MARKER));
1084
+ existing.hooks.PreToolUse = existing.hooks.PreToolUse.filter(e => !isDBHook(e));
1085
+ existing.hooks.PostToolUse = existing.hooks.PostToolUse.filter(e => !isDBHook(e));
1086
+
1087
+ // Add dual-brain hooks
1088
+ existing.hooks.PreToolUse.push(...preToolHooks);
1089
+ existing.hooks.PostToolUse.push(...postToolHooks);
1090
+
1091
+ // Write merged settings
1092
+ mkdirSync(globalClaudeDir, { recursive: true });
1093
+ writeFileSync(globalSettingsPath, JSON.stringify(existing, null, 2) + '\n');
1094
+ }
1080
1095
 
1081
1096
  // Write minimal global CLAUDE.md (only if none exists, or append section)
1082
1097
  const globalClaudeMd = join(globalClaudeDir, 'CLAUDE.md');
@@ -1091,12 +1106,16 @@ async function installGlobal() {
1091
1106
  }
1092
1107
  }
1093
1108
 
1094
- console.log(' + dual-brain hooks installed globally');
1095
- console.log(' hooks dir: ' + hooksDir);
1096
- console.log(' settings: ' + globalSettingsPath);
1097
- console.log('');
1098
- console.log(' All new Claude sessions will load dual-brain hooks.');
1099
- console.log(' Run "dual-brain uninstall --global" to remove.');
1109
+ if (!hasProjectLocalHooks) {
1110
+ console.log(' + dual-brain hooks installed globally');
1111
+ console.log(' hooks dir: ' + hooksDir);
1112
+ console.log(' settings: ' + globalSettingsPath);
1113
+ console.log('');
1114
+ console.log(' All new Claude sessions will load dual-brain hooks.');
1115
+ console.log(' Run "dual-brain uninstall --global" to remove.');
1116
+ }
1117
+ console.log(' + global CLAUDE.md updated');
1118
+ console.log(' path: ' + globalClaudeDir);
1100
1119
  }
1101
1120
 
1102
1121
  async function uninstallGlobal() {
@@ -2163,6 +2182,15 @@ async function mainScreen(rl, ask) {
2163
2182
  dashSpinner = fx.spinner('Loading dashboard...').start();
2164
2183
  }
2165
2184
 
2185
+ // ── One-time default shell prompt for returning users (never asked before) ─
2186
+ if (profile.setupComplete && !profile.defaultShellAsked) {
2187
+ if (dashSpinner) { dashSpinner.stop(); dashSpinner = null; }
2188
+ const wantsDefault = await askDefaultShell(cwd, rl, fx);
2189
+ profile.defaultShellAsked = true;
2190
+ profile.isDefaultShell = wantsDefault;
2191
+ saveProfile(profile, { cwd });
2192
+ }
2193
+
2166
2194
  const claudeSub = profile?.providers?.claude;
2167
2195
  const openaiSub = profile?.providers?.openai;
2168
2196
 
@@ -2566,8 +2594,7 @@ async function mainScreen(rl, ask) {
2566
2594
 
2567
2595
  // ── Recent work items (dim, max 3) ────────────────────────────────────────
2568
2596
  const recentLines = recentWorkItems.slice(0, 3).map(item => {
2569
- const prefix = item.ok ? `${GRN}✓${RST}` : `${RED}!${RST}`;
2570
- return ` ${DIM}${prefix} ${item.text}${RST}`;
2597
+ return signalLine(item.ok ? 'success' : 'warning', `${DIM}${item.text}${RST}`);
2571
2598
  });
2572
2599
 
2573
2600
  // ── Resolve dashboard spinner before rendering ────────────────────────────
@@ -2578,26 +2605,48 @@ async function mainScreen(rl, ask) {
2578
2605
  process.stdout.write(`${DIM}${staleCount} stale sessions (>7d) — type "sessions" to manage${RST}\n`);
2579
2606
  }
2580
2607
 
2581
- // ── Render Studio Console ─────────────────────────────────────────────────
2582
- const out = [];
2583
- out.push(''); // breathing room
2584
- out.push(statusBar); // project branch Claude ● GPT ● v0.2.3
2585
- out.push('');
2586
- out.push(mainQuestion); // Resume previous work? / What do you want to build?
2587
- if (lastSummary) out.push(lastSummary);
2588
- out.push(` \x1b[1m›\x1b[0m`); // bright prompt cursor
2589
- out.push('');
2590
- out.push(suggestLine); // contextual suggestions
2608
+ // ── Render Studio Console (paneled layout) ────────────────────────────────
2609
+ const CYAN = '\x1b[36m';
2610
+ const panelW = Math.min(sepW + 2, 72);
2611
+
2612
+ // Header panel — project, branch, providers, version
2613
+ const headerLeft = `${DIM}${projectName}${RST} ${BOLD}${branchStr}${RST} ${DIM}Claude${RST} ${claudeDot} ${DIM}GPT${RST} ${openaiDot}`;
2614
+ const headerRight = `${DIM}v${version}${RST}`;
2615
+ const headerContent = [headerBar(headerLeft, headerRight, panelW - 4)];
2616
+ process.stdout.write('\n' + panel('dual-brain', headerContent, { width: panelW, titleColor: CYAN }) + '\n\n');
2617
+
2618
+ // Resume / prompt panel (only when there is something to show)
2619
+ if (isReturning || !anyProviderAvail) {
2620
+ const resumeContent = [];
2621
+ if (!anyProviderAvail) {
2622
+ resumeContent.push(`${BOLD}Connect a provider to start working${RST}`);
2623
+ } else {
2624
+ const labelTrunc = (resumeState.label || 'last session').slice(0, 45);
2625
+ const agePart = resumeState.ageLabel ? ` · ${resumeState.ageLabel}` : '';
2626
+ const nextPart = resumeState.nextAction ? ` · next: ${resumeState.nextAction}` : '';
2627
+ resumeContent.push(`${DIM}Last task${RST} ${BOLD}${labelTrunc}${RST}${DIM}${agePart}${RST}`);
2628
+ if (nextPart) resumeContent.push(`${DIM}Next step${RST} ${nextPart.replace(/^ · /, '')}`);
2629
+ }
2630
+ resumeContent.push('');
2631
+ resumeContent.push(` ${CYAN}›${RST} ${BOLD}${suggestions[0]}${RST} ${DIM}${suggestions[1] || ''}${RST} ${DIM}${suggestions[2] || ''}${RST}`);
2632
+ process.stdout.write(panel(isReturning ? 'Resume work' : 'Get started', resumeContent, { width: panelW }) + '\n\n');
2633
+ } else {
2634
+ // Fresh / no-resume state — just show suggestions inline
2635
+ const suggestContent = [` ${CYAN}›${RST} ${BOLD}${suggestions[0]}${RST} ${DIM}${suggestions[1] || ''}${RST} ${DIM}${suggestions[2] || ''}${RST}`];
2636
+ process.stdout.write(panel('Get started', suggestContent, { width: panelW }) + '\n\n');
2637
+ }
2638
+
2639
+ // Signals panel — recent work items (only when there are items)
2591
2640
  if (recentLines.length > 0) {
2592
- out.push('');
2593
- out.push(...recentLines); // ✓ / ! recent work items
2641
+ process.stdout.write(panel('Signals', recentLines, { width: panelW }) + '\n\n');
2594
2642
  }
2595
- out.push('');
2596
- out.push(` ${sepLine}`); // ━━━━ separator
2597
- // Input bar rendered inline — the key handler will overwrite this line
2598
- out.push(` ${DIM}> task or command...${RST}${' '.repeat(Math.max(1, sepW - 22))}${DIM}[?] help${RST}`);
2599
2643
 
2600
- process.stdout.write(out.join('\n') + '\n');
2644
+ // Input bar — rendered below panels; the key handler will overwrite this line
2645
+ const inputHint = `${DIM}[?] help${RST}`;
2646
+ const inputLeft = tuiPrompt('task or command...');
2647
+ const inputLeftW = (inputLeft + ' task or command...').replace(/\x1b\[[0-9;]*m/g, '').length;
2648
+ const inputGap = Math.max(1, panelW - inputLeftW - 8);
2649
+ process.stdout.write(` ${inputLeft}${' '.repeat(inputGap)}${inputHint}\n`);
2601
2650
 
2602
2651
  // ── Key handling ──────────────────────────────────────────────────────────
2603
2652
  // Use raw keypress mode so we can show a live type-to-start buffer.
@@ -3535,28 +3584,38 @@ async function settingsScreen(rl, ask) {
3535
3584
  ` ${DIM}Doctor${RESET} ${doctorStr}`,
3536
3585
  ];
3537
3586
 
3538
- // ── Render ────────────────────────────────────────────────────────────────
3539
- const out = [
3540
- '',
3541
- ` ${BOLD}Settings${RESET}`,
3542
- '',
3543
- ` ${DIM}Subscriptions${RESET}`,
3544
- ...subsLines,
3545
- ` ${DIM}[a] add [r] remove [h] health check${RESET}`,
3546
- '',
3547
- ` ${DIM}Work style${RESET}`,
3548
- ...wsLines,
3549
- ` ${DIM}[1-3] change${RESET}`,
3587
+ // ── Render (paneled layout) ───────────────────────────────────────────────
3588
+ const CYAN = '\x1b[36m';
3589
+ const settingsPanelW = 70;
3590
+
3591
+ const subsContent = [
3592
+ ...subsLines.map(l => l.replace(/^ /, '')),
3550
3593
  '',
3551
- ` ${DIM}System${RESET}`,
3552
- ...sysLines,
3553
- ` ${DIM}[d] run doctor [x] diagnostics${RESET}`,
3594
+ signalLine('info', `${DIM}[a] add [r] remove [h] health check${RESET}`),
3595
+ ];
3596
+
3597
+ const wsContent = [
3598
+ ...wsLines.map(l => l.replace(/^ /, '')),
3554
3599
  '',
3555
- ` ${DIM}[e] sessions [m] subscriptions [b] back${RESET}`,
3556
- ...(settingsPRs.length > 0 ? [` ${DIM}[p] PR triage (${settingsPRs.length} open)${RESET}`] : []),
3600
+ signalLine('info', `${DIM}[1-3] change${RESET}`),
3601
+ ];
3602
+
3603
+ const sysContent = [
3604
+ ...sysLines.map(l => l.replace(/^ /, '')),
3557
3605
  '',
3606
+ signalLine('info', `${DIM}[d] run doctor [x] diagnostics${RESET}`),
3607
+ ];
3608
+
3609
+ const navContent = [
3610
+ `${DIM}[e]${RESET} sessions ${DIM}[m]${RESET} subscriptions ${DIM}[b]${RESET} back`,
3611
+ ...(settingsPRs.length > 0 ? [`${DIM}[p]${RESET} PR triage ${DIM}(${settingsPRs.length} open)${RESET}`] : []),
3558
3612
  ];
3559
- process.stdout.write(out.join('\n') + '\n');
3613
+
3614
+ process.stdout.write('\n');
3615
+ process.stdout.write(panel('Subscriptions', subsContent, { width: settingsPanelW, titleColor: CYAN }) + '\n\n');
3616
+ process.stdout.write(panel('Work style', wsContent, { width: settingsPanelW, titleColor: CYAN }) + '\n\n');
3617
+ process.stdout.write(panel('System', sysContent, { width: settingsPanelW, titleColor: CYAN }) + '\n\n');
3618
+ process.stdout.write(panel('Navigation', navContent, { width: settingsPanelW }) + '\n\n');
3560
3619
 
3561
3620
  const raw = (await ask(' Choice: ')).trim();
3562
3621
  const choice = raw.toLowerCase();
@@ -3984,6 +4043,67 @@ function saveWizardCredentials(cwd, detectedProviders) {
3984
4043
  * @param {object} rl readline interface
3985
4044
  * @returns {object|null} profile object to save, or null if cancelled/skipped
3986
4045
  */
4046
+ function setAsDefaultShell(cwd) {
4047
+ const root = cwd || process.cwd();
4048
+ const replitPath = join(root, '.replit');
4049
+ if (!existsSync(replitPath)) return;
4050
+
4051
+ let content = readFileSync(replitPath, 'utf8');
4052
+ const newOnBoot = 'onBoot = "source /home/runner/workspace/.replit-tools/scripts/setup-claude-code.sh 2>/dev/null || true; ln -sf /home/runner/workspace/.replit-tools/.npm-persistent/.npmrc ~/.npmrc 2>/dev/null || true; dual-brain install --global 2>/dev/null || true"';
4053
+
4054
+ if (content.match(/^onBoot\s*=/m)) {
4055
+ content = content.replace(/^onBoot\s*=.*$/m, newOnBoot);
4056
+ } else {
4057
+ content += '\n' + newOnBoot + '\n';
4058
+ }
4059
+ writeFileSync(replitPath, content);
4060
+ }
4061
+
4062
+ function removeAsDefaultShell(cwd) {
4063
+ const root = cwd || process.cwd();
4064
+ const replitPath = join(root, '.replit');
4065
+ if (!existsSync(replitPath)) return;
4066
+
4067
+ let content = readFileSync(replitPath, 'utf8');
4068
+ const origOnBoot = 'onBoot = "source /home/runner/workspace/.replit-tools/scripts/setup-claude-code.sh 2>/dev/null || true"';
4069
+ if (content.match(/^onBoot\s*=/m)) {
4070
+ content = content.replace(/^onBoot\s*=.*$/m, origOnBoot);
4071
+ writeFileSync(replitPath, content);
4072
+ }
4073
+ }
4074
+
4075
+ async function askDefaultShell(cwd, rl, fx) {
4076
+ const cl = fx.colors || {};
4077
+ const DIM = cl.dim || '';
4078
+ const BOLD = cl.bold || '';
4079
+ const CYAN = cl.cyan || '\x1b[36m';
4080
+ const YLW = cl.yellow || '\x1b[33m';
4081
+ const GREEN = cl.green || '';
4082
+ const RST = cl.reset || '';
4083
+
4084
+ const setupContent = [
4085
+ `${DIM}Start dual-brain automatically when this Replit opens?${RST}`,
4086
+ '',
4087
+ ` ${DIM}modifies${RST} ${YLW}.replit onBoot${RST}`,
4088
+ ` ${DIM}undo${RST} Settings → System → Startup`,
4089
+ '',
4090
+ ` ${CYAN}[Y]${RST} Start on boot ${DIM}[n] Run manually${RST}`,
4091
+ ];
4092
+ process.stdout.write('\n' + panel('dual-brain setup', setupContent) + '\n');
4093
+
4094
+ const answer = await new Promise(res => rl.question(' ', (a) => res(a.trim().toLowerCase())));
4095
+ const yes = !answer || answer.startsWith('y');
4096
+
4097
+ if (yes) {
4098
+ setAsDefaultShell(cwd);
4099
+ process.stdout.write(` ${GREEN}+${RST} ${DIM}dual-brain will start on boot. Change anytime in Settings.${RST}\n`);
4100
+ } else {
4101
+ process.stdout.write(` ${DIM}No problem. Run dual-brain anytime from the command line.${RST}\n`);
4102
+ }
4103
+
4104
+ return yes;
4105
+ }
4106
+
3987
4107
  async function runOnboardingWizard(_detection, cwd, rl) {
3988
4108
  const fx = await getFx();
3989
4109
  const cl = fx.colors || {};
@@ -4278,6 +4398,23 @@ async function runOnboardingWizard(_detection, cwd, rl) {
4278
4398
  finalProfile.bias = chosenBias;
4279
4399
  finalProfile.workStyle = chosenBias;
4280
4400
 
4401
+ // Ask about default shell (only on first wizard run)
4402
+ if (!finalProfile.defaultShellAsked) {
4403
+ const wantsDefault = await askDefaultShell(cwd, rl, fx);
4404
+ finalProfile.defaultShellAsked = true;
4405
+ finalProfile.isDefaultShell = wantsDefault;
4406
+ saveProfile(finalProfile, { cwd });
4407
+
4408
+ // Also run global install if they said yes
4409
+ if (wantsDefault) {
4410
+ try {
4411
+ execSync('node ' + join(dirname(fileURLToPath(import.meta.url)), 'dual-brain.mjs') + ' install --global', {
4412
+ cwd, stdio: 'pipe', timeout: 10000,
4413
+ });
4414
+ } catch {}
4415
+ }
4416
+ }
4417
+
4281
4418
  return finalProfile;
4282
4419
  }
4283
4420
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,7 +27,11 @@
27
27
  "./continuity": "./src/continuity.mjs",
28
28
  "./checkpoint": "./src/checkpoint.mjs",
29
29
  "./pr-agent": "./src/pr-agent.mjs",
30
- "./ci-triage": "./src/ci-triage.mjs"
30
+ "./ci-triage": "./src/ci-triage.mjs",
31
+ "./integrity": "./src/integrity.mjs",
32
+ "./prompt-audit": "./src/prompt-audit.mjs",
33
+ "./head": "./src/head.mjs",
34
+ "./templates": "./src/templates.mjs"
31
35
  },
32
36
  "keywords": [
33
37
  "claude-code",
@@ -94,6 +98,10 @@
94
98
  "src/checkpoint.mjs",
95
99
  "src/ci-triage.mjs",
96
100
  "src/pr-agent.mjs",
101
+ "src/integrity.mjs",
102
+ "src/prompt-audit.mjs",
103
+ "src/head.mjs",
104
+ "src/templates.mjs",
97
105
  "bin/*.mjs",
98
106
  "hooks/enforce-tier.mjs",
99
107
  "hooks/cost-logger.mjs",