dual-brain 7.1.24 → 7.1.25

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.
@@ -54,6 +54,14 @@ async function getFailureMem() {
54
54
  return _failureMem;
55
55
  }
56
56
 
57
+ let _livingDocs = null;
58
+ async function getLivingDocs() {
59
+ if (!_livingDocs) {
60
+ try { _livingDocs = await import('../src/living-docs.mjs'); } catch { _livingDocs = {}; }
61
+ }
62
+ return _livingDocs;
63
+ }
64
+
57
65
  // ─── Helpers ─────────────────────────────────────────────────────────────────
58
66
 
59
67
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -355,6 +363,12 @@ async function cmdGo(args, opts = {}) {
355
363
  const cwd = process.cwd();
356
364
  await ensureProfile(cwd);
357
365
 
366
+ // ── Living docs: ensure .dual-brain/ exists on session start ─────────────
367
+ try {
368
+ const ld = await getLivingDocs();
369
+ if (ld.initLivingDocs) ld.initLivingDocs(cwd);
370
+ } catch { /* non-fatal */ }
371
+
358
372
  if (verbose) console.log('\nDispatching...');
359
373
 
360
374
  // ── Failure memory: check history before dispatching ──────────────────────
@@ -408,6 +422,16 @@ async function cmdGo(args, opts = {}) {
408
422
  nextAction: null,
409
423
  }, cwd);
410
424
 
425
+ // ── Living docs: record completed session action ───────────────────────
426
+ try {
427
+ const ld = await getLivingDocs();
428
+ if (ld.appendAction) ld.appendAction({
429
+ type: 'task', intent: prompt, status: 'completed',
430
+ owner: plan?._decision?.provider ?? 'claude',
431
+ files, result: result.consensus || 'dual-brain complete',
432
+ }, cwd);
433
+ } catch { /* non-fatal */ }
434
+
411
435
  // Clear failure memory on success
412
436
  if (failureMem.clearFailures) {
413
437
  try { await failureMem.clearFailures(prompt, cwd); } catch { /* non-fatal */ }
@@ -459,6 +483,17 @@ async function cmdGo(args, opts = {}) {
459
483
  nextAction: null,
460
484
  }, cwd);
461
485
 
486
+ // ── Living docs: record completed session action ───────────────────────
487
+ try {
488
+ const ld = await getLivingDocs();
489
+ if (ld.appendAction) ld.appendAction({
490
+ type: 'task', intent: prompt, status: succeeded ? 'completed' : 'failed',
491
+ owner: plan?._decision?.provider ?? 'claude',
492
+ files: result.filesChanged || files,
493
+ result: result.summary || (succeeded ? 'completed' : `exit ${result.exitCode}`),
494
+ }, cwd);
495
+ } catch { /* non-fatal */ }
496
+
462
497
  if (!succeeded) {
463
498
  // Record failure memory
464
499
  if (failureMem.recordFailure) {
@@ -1007,14 +1042,14 @@ async function welcomeScreen(rl, ask) {
1007
1042
  }
1008
1043
  console.log('');
1009
1044
 
1010
- // --- Detect data-tools / replit-tools sessions ---
1045
+ // --- Detect replit-tools sessions ---
1011
1046
  const env = detectEnvironment();
1012
1047
  const existingSessions = importReplitSessions(cwd);
1013
1048
  if (env.hasReplitTools) {
1014
- detectedLines.push(` data-tools detected`);
1049
+ detectedLines.push(` replit-tools detected`);
1015
1050
  }
1016
1051
  if (existingSessions.length > 0) {
1017
- detectedLines.push(` ${existingSessions.length} session${existingSessions.length !== 1 ? 's' : ''} found from data-tools`);
1052
+ detectedLines.push(` ${existingSessions.length} session${existingSessions.length !== 1 ? 's' : ''} found from replit-tools`);
1018
1053
  }
1019
1054
 
1020
1055
  // --- Detect replit-tools ---
@@ -1054,7 +1089,7 @@ async function welcomeScreen(rl, ask) {
1054
1089
  console.log(' [Enter] Save and go');
1055
1090
  console.log(' [c] Customize work style');
1056
1091
  if (existingSessions.length > 0) {
1057
- console.log(` [i] Import ${existingSessions.length} session${existingSessions.length !== 1 ? 's' : ''} from data-tools`);
1092
+ console.log(` [i] Import ${existingSessions.length} session${existingSessions.length !== 1 ? 's' : ''} from replit-tools`);
1058
1093
  }
1059
1094
  if (!rt.installed) {
1060
1095
  console.log('');
@@ -1066,7 +1101,7 @@ async function welcomeScreen(rl, ask) {
1066
1101
  const choice = (await ask(' Choice: ')).trim().toLowerCase();
1067
1102
 
1068
1103
  if (choice === 'i' && existingSessions.length > 0) {
1069
- console.log(`\n Importing ${existingSessions.length} sessions from data-tools...\n`);
1104
+ console.log(`\n Importing ${existingSessions.length} sessions from replit-tools...\n`);
1070
1105
  const recent = existingSessions.slice(0, 5);
1071
1106
  for (const sess of recent) {
1072
1107
  console.log(` ${sess.age.padEnd(6)} ${sess.name}`);
@@ -1572,7 +1607,7 @@ async function mainScreen(rl, ask) {
1572
1607
  return ageMs >= 7 * 86400000;
1573
1608
  }).length;
1574
1609
 
1575
- // Detect data-tools version
1610
+ // Detect replit-tools version
1576
1611
  const rtMain = detectReplitTools(cwd);
1577
1612
  const dtVersion = (rtMain.installed && rtMain.version) ? rtMain.version : null;
1578
1613
 
@@ -1834,7 +1869,7 @@ async function mainScreen(rl, ask) {
1834
1869
  }
1835
1870
 
1836
1871
  // ── Box 5 — Input bar ──────────────────────────────────────────────────
1837
- const actionsContent = '> type anything... [s] settings [/] search [q] quit';
1872
+ const actionsContent = '> type anything... [s] settings [t] team [q] quit';
1838
1873
  const actionsRow = row(actionsContent);
1839
1874
 
1840
1875
  // ── Print the full 5-box layout ───────────────────────────────────────────
@@ -1949,7 +1984,7 @@ async function mainScreen(rl, ask) {
1949
1984
  // Single-key commands only fire when buffer is empty
1950
1985
  if (taskBuffer.length === 0) {
1951
1986
  const lower = str.toLowerCase();
1952
- const singleKeySet = new Set(['n', 's', 'q', '/', 'i']);
1987
+ const singleKeySet = new Set(['n', 's', 't', 'q', '/', 'i']);
1953
1988
  if (singleKeySet.has(lower)) {
1954
1989
  cleanup();
1955
1990
  process.stdout.write('\n');
@@ -2055,6 +2090,7 @@ async function mainScreen(rl, ask) {
2055
2090
  }
2056
2091
 
2057
2092
  if (choice === 's') { return { next: 'settings' }; }
2093
+ if (choice === 't') { return { next: 'team' }; }
2058
2094
  if (choice === 'i') { return { next: 'import-picker' }; }
2059
2095
  if (choice === 'q' || choice === 'exit') { return { next: 'exit' }; }
2060
2096
 
@@ -2079,7 +2115,7 @@ async function newSessionScreen(rl, ask) {
2079
2115
  async function importPickerScreen() {
2080
2116
  const cwd = process.cwd();
2081
2117
 
2082
- // Load all available sessions from data-tools
2118
+ // Load all available sessions from replit-tools
2083
2119
  const allSessions = importReplitSessions(cwd);
2084
2120
 
2085
2121
  // Load existing session meta to filter already-imported ones
@@ -2125,9 +2161,9 @@ async function importPickerScreen() {
2125
2161
  if (allSessions.length === 0) {
2126
2162
  process.stdout.write('\n');
2127
2163
  process.stdout.write(top + '\n');
2128
- process.stdout.write(row('Import from data-tools') + '\n');
2164
+ process.stdout.write(row('Import from replit-tools') + '\n');
2129
2165
  process.stdout.write(sep + '\n');
2130
- process.stdout.write(row('No data-tools sessions found.') + '\n');
2166
+ process.stdout.write(row('No replit-tools sessions found.') + '\n');
2131
2167
  process.stdout.write(row('Install replit-tools: npm i -g replit-tools') + '\n');
2132
2168
  process.stdout.write(sep + '\n');
2133
2169
  process.stdout.write(row('Press any key to go back...') + '\n');
@@ -2139,7 +2175,7 @@ async function importPickerScreen() {
2139
2175
  if (candidates.length === 0) {
2140
2176
  process.stdout.write('\n');
2141
2177
  process.stdout.write(top + '\n');
2142
- process.stdout.write(row('Import from data-tools') + '\n');
2178
+ process.stdout.write(row('Import from replit-tools') + '\n');
2143
2179
  process.stdout.write(sep + '\n');
2144
2180
  process.stdout.write(row(`All ${allSessions.length} sessions already imported.`) + '\n');
2145
2181
  process.stdout.write(sep + '\n');
@@ -2162,7 +2198,7 @@ async function importPickerScreen() {
2162
2198
  const renderPicker = () => {
2163
2199
  process.stdout.write('\x1b[2J\x1b[H'); // clear screen
2164
2200
 
2165
- const headerTitle = 'Import from data-tools';
2201
+ const headerTitle = 'Import from replit-tools';
2166
2202
  const footerLine = '↑↓ Navigate Space Toggle Enter Import q Back';
2167
2203
 
2168
2204
  process.stdout.write('\n');
@@ -2299,7 +2335,7 @@ async function importPickerScreen() {
2299
2335
  }
2300
2336
  saveSessionMeta(updatedMeta, cwd);
2301
2337
 
2302
- process.stdout.write(`✓ Imported ${importCount} session${importCount !== 1 ? 's' : ''} from data-tools\n\n`);
2338
+ process.stdout.write(`✓ Imported ${importCount} session${importCount !== 1 ? 's' : ''} from replit-tools\n\n`);
2303
2339
 
2304
2340
  return { next: 'main' };
2305
2341
  }
@@ -2561,22 +2597,60 @@ async function settingsScreen(rl, ask) {
2561
2597
  'balanced': '⚖️ Balanced',
2562
2598
  'quality-first': '🔥 Full Power',
2563
2599
  };
2564
- const workStyleLabel = WORK_STYLE_DISPLAY[currentBias] || '⚖️ Balanced';
2600
+
2601
+ // Work style current markers
2602
+ const _stIsFast = ['cost-saver', 'auto', 'solo-claude', 'solo-openai'].includes(currentBias);
2603
+ const _stIsBal = currentBias === 'balanced';
2604
+ const _stIsFull = currentBias === 'quality-first';
2605
+ const _stMark = (active) => active ? ' ← current' : '';
2606
+
2607
+ // Provider status dots
2608
+ const _stAuth = await detectAuth();
2609
+ const _stGDOT = '\x1b[32m●\x1b[0m';
2610
+ const _stRDOT = '\x1b[31m●\x1b[0m';
2611
+ const _stClDot = _stAuth.claude.found ? _stGDOT : _stRDOT;
2612
+ const _stOaDot = _stAuth.openai.found ? _stGDOT : _stRDOT;
2613
+ const _stClStatus = _stAuth.claude.found ? 'connected' : 'not connected';
2614
+ const _stOaStatus = _stAuth.openai.found ? 'connected' : 'not connected';
2615
+
2616
+ // Calibration from project.json
2617
+ let _stCal = { specificity: 3, corrections: 3, autonomy: 3 };
2618
+ let _stLevel = 'intermediate';
2619
+ let _stStyle = 'normal';
2620
+ try {
2621
+ const _stLd = await import('../src/living-docs.mjs');
2622
+ const _stCm = await import('../src/calibration.mjs');
2623
+ const _stPs = _stLd.getProjectState(cwd);
2624
+ if (_stPs?.project?.userCalibration) _stCal = _stPs.project.userCalibration;
2625
+ const _stAd = _stCm.getAdaptation(_stCal);
2626
+ _stLevel = _stAd.userLevel;
2627
+ _stStyle = _stAd.responseStyle;
2628
+ } catch { /* non-fatal */ }
2629
+
2630
+ const _stS = typeof _stCal.specificity === 'number' ? _stCal.specificity.toFixed(1) : String(_stCal.specificity ?? 3);
2631
+ const _stC = typeof _stCal.corrections === 'number' ? _stCal.corrections.toFixed(1) : String(_stCal.corrections ?? 3);
2632
+ const _stA = typeof _stCal.autonomy === 'number' ? _stCal.autonomy.toFixed(1) : String(_stCal.autonomy ?? 3);
2565
2633
 
2566
2634
  const lines = [
2567
2635
  top,
2568
2636
  row('Settings'),
2569
2637
  sep,
2570
- row(`[w] Work Style: ${workStyleLabel}`),
2571
- row('[m] Manage subscriptions'),
2572
- row('[e] Manage sessions'),
2573
- row('[i] Import from replit-tools'),
2574
- row('[d] Switch to data-tools'),
2575
- row('[?] Help & shortcuts'),
2576
- row('[x] Diagnostics'),
2638
+ row('Work Style'),
2639
+ row(` [1] Fast — speed over caution${_stMark(_stIsFast)}`),
2640
+ row(` [2] Balanced — smart routing, reviews on important${_stMark(_stIsBal)}`),
2641
+ row(` [3] Full Power — dual-brain everything, max quality${_stMark(_stIsFull)}`),
2642
+ sep,
2643
+ row('Providers'),
2644
+ row(` Claude: ${_stClDot} ${_stClStatus}`),
2645
+ row(` OpenAI: ${_stOaDot} ${_stOaStatus}`),
2646
+ sep,
2647
+ row('User Calibration'),
2648
+ row(` Specificity: ${_stS} Corrections: ${_stC} Autonomy: ${_stA}`),
2649
+ row(` Level: ${_stLevel} · Style: ${_stStyle}`),
2650
+ sep,
2651
+ row('[1-3] change style [r] reset calibration [b] back'),
2652
+ row('[m] subscriptions [e] sessions [x] diagnostics'),
2577
2653
  ...(settingsPRs.length > 0 ? [row(`[p] PR triage (${settingsPRs.length} open)`)] : []),
2578
- row(''),
2579
- row('[Esc/b] Back to dashboard'),
2580
2654
  bot,
2581
2655
  ];
2582
2656
  process.stdout.write('\n' + lines.join('\n') + '\n\n');
@@ -2584,45 +2658,10 @@ async function settingsScreen(rl, ask) {
2584
2658
  const raw = (await ask(' Choice: ')).trim();
2585
2659
  const choice = raw.toLowerCase();
2586
2660
 
2587
- if (choice === 'w') {
2588
- // Work style picker
2589
- const wsTop = ` ┌${''.repeat(51)}┐`;
2590
- const wsSep = ` ├${'─'.repeat(51)}┤`;
2591
- const wsBot = ` └${'─'.repeat(51)}┘`;
2592
- const wsPad = (s) => {
2593
- const plain = s.replace(/\x1b\[[0-9;]*m/g, '');
2594
- let vlen = 0;
2595
- for (const ch of plain) {
2596
- const cp = ch.codePointAt(0);
2597
- if (
2598
- (cp >= 0x1f300 && cp <= 0x1faff) ||
2599
- (cp >= 0x2600 && cp <= 0x27bf) ||
2600
- cp === 0xfe0f || cp === 0x20e3
2601
- ) { vlen += 2; } else { vlen += 1; }
2602
- }
2603
- return s + ' '.repeat(Math.max(0, 51 - vlen));
2604
- };
2605
- const wsRow = (s) => ` │ ${wsPad(s)}│`;
2606
-
2607
- const isFast = currentBias === 'cost-saver' || currentBias === 'auto' || currentBias === 'solo-claude' || currentBias === 'solo-openai';
2608
- const isBal = currentBias === 'balanced';
2609
- const isFull = currentBias === 'quality-first';
2610
-
2611
- console.log('');
2612
- console.log(wsTop);
2613
- console.log(wsRow('Work Style'));
2614
- console.log(wsSep);
2615
- console.log(wsRow(` 1. ⚡ Fast — quick, single model${isFast ? ' ← current' : ''}`));
2616
- console.log(wsRow(` 2. ⚖️ Balanced — smart routing${isBal ? ' ← current' : ''}`));
2617
- console.log(wsRow(` 3. 🔥 Full Power — dual-brain everything${isFull ? ' ← current' : ''}`));
2618
- console.log(wsSep);
2619
- console.log(wsRow('[Enter] Keep current'));
2620
- console.log(wsBot);
2621
- console.log('');
2622
-
2623
- const wsChoice = (await ask(' Choice [1/2/3/Enter]: ')).trim();
2624
- const wsMap = { '1': 'cost-saver', '2': 'balanced', '3': 'quality-first' };
2625
- const newBias = wsMap[wsChoice];
2661
+ // Direct work style keys 1/2/3
2662
+ if (choice === '1' || choice === '2' || choice === '3') {
2663
+ const _stWsMap = { '1': 'cost-saver', '2': 'balanced', '3': 'quality-first' };
2664
+ const newBias = _stWsMap[choice];
2626
2665
  if (newBias && newBias !== currentBias) {
2627
2666
  profile.bias = newBias;
2628
2667
  const enabledCount = [
@@ -2632,12 +2671,23 @@ async function settingsScreen(rl, ask) {
2632
2671
  if (enabledCount >= 2) profile.mode = newBias;
2633
2672
  saveProfile(profile, { cwd });
2634
2673
  const newLabel = WORK_STYLE_DISPLAY[newBias] || newBias;
2635
- console.log(`\n Work style set to ${newLabel}\n`);
2674
+ process.stdout.write(`\n Work style set to ${newLabel}\n\n`);
2636
2675
  await ask(' Press Enter to continue...');
2637
2676
  }
2638
2677
  return { next: 'settings' };
2639
2678
  }
2640
2679
 
2680
+ // Reset calibration to defaults
2681
+ if (choice === 'r') {
2682
+ try {
2683
+ const _stLdReset = await import('../src/living-docs.mjs');
2684
+ _stLdReset.updateProject({ userCalibration: { specificity: 3, corrections: 3, autonomy: 3 } }, cwd);
2685
+ process.stdout.write('\n Calibration reset to defaults.\n\n');
2686
+ await ask(' Press Enter to continue...');
2687
+ } catch { /* non-fatal */ }
2688
+ return { next: 'settings' };
2689
+ }
2690
+
2641
2691
  if (choice === 'm') { return { next: 'subscriptions' }; }
2642
2692
 
2643
2693
  if (choice === 'e') { return { next: 'sessions' }; }
@@ -2656,7 +2706,7 @@ async function settingsScreen(rl, ask) {
2656
2706
  if (which.status === 0) {
2657
2707
  spawnSync('claude-menu', { stdio: 'inherit' });
2658
2708
  } else {
2659
- process.stdout.write('\n data-tools not found — install with: npm i -g replit-tools\n\n');
2709
+ process.stdout.write('\n replit-tools not found — install with: npm i -g replit-tools\n\n');
2660
2710
  await ask(' Press Enter to continue...');
2661
2711
  }
2662
2712
  return { next: 'settings' };
@@ -2690,6 +2740,105 @@ async function settingsScreen(rl, ask) {
2690
2740
  return { next: 'main' };
2691
2741
  }
2692
2742
 
2743
+ // ─── Screen: teamScreen ───────────────────────────────────────────────────────
2744
+
2745
+ async function teamScreen(rl, ask) {
2746
+ const cwd = process.cwd();
2747
+
2748
+ // Box layout matching dashboard
2749
+ const termW = process.stdout.columns || 60;
2750
+ const boxW = Math.min(termW - 2, 60);
2751
+ const W = boxW - 4;
2752
+
2753
+ const top = `┌${'─'.repeat(boxW - 2)}┐`;
2754
+ const sep = `├${'─'.repeat(boxW - 2)}┤`;
2755
+ const bot = `└${'─'.repeat(boxW - 2)}┘`;
2756
+ const row = (content) => makeBoxRow(content, W);
2757
+
2758
+ // Load team from project.json
2759
+ let team = [];
2760
+ let sharedSessions = 0;
2761
+ let teamDecisions = 0;
2762
+ try {
2763
+ const _tmLd = await import('../src/living-docs.mjs');
2764
+ const _tmPs = _tmLd.getProjectState(cwd);
2765
+ if (Array.isArray(_tmPs?.project?.team)) {
2766
+ team = _tmPs.project.team;
2767
+ }
2768
+ // Count decisions with more than one participant as team decisions
2769
+ if (Array.isArray(_tmPs?.recentDecisions)) {
2770
+ teamDecisions = _tmPs.recentDecisions.filter(
2771
+ d => Array.isArray(d?.participants) && d.participants.length > 1
2772
+ ).length;
2773
+ }
2774
+ } catch { /* non-fatal */ }
2775
+
2776
+ // Fall back to git user if no team configured
2777
+ let ownerName = '(you)';
2778
+ if (team.length === 0) {
2779
+ try {
2780
+ const { execSync: _tmExec } = await import('node:child_process');
2781
+ const gitUser = _tmExec('git config user.name 2>/dev/null', {
2782
+ encoding: 'utf8', timeout: 2000, stdio: 'pipe',
2783
+ }).trim();
2784
+ if (gitUser) ownerName = gitUser;
2785
+ } catch { /* non-fatal */ }
2786
+ }
2787
+
2788
+ const memberRows = [];
2789
+ if (team.length === 0) {
2790
+ memberRows.push(row(` ${ownerName} (owner)`));
2791
+ } else {
2792
+ for (const member of team) {
2793
+ const role = member.role || 'member';
2794
+ memberRows.push(row(` ${member.name} (${role})`));
2795
+ }
2796
+ }
2797
+
2798
+ const lines = [
2799
+ top,
2800
+ row('Team'),
2801
+ sep,
2802
+ row('Members'),
2803
+ ...memberRows,
2804
+ sep,
2805
+ row(`Shared Sessions: ${sharedSessions}`),
2806
+ row(`Team decisions: ${teamDecisions}`),
2807
+ sep,
2808
+ row('[a] add member [b] back'),
2809
+ bot,
2810
+ ];
2811
+ process.stdout.write('\n' + lines.join('\n') + '\n\n');
2812
+
2813
+ const raw = (await ask(' Choice: ')).trim();
2814
+ const choice = raw.toLowerCase();
2815
+
2816
+ if (choice === 'a') {
2817
+ const name = (await ask(' Member name: ')).trim();
2818
+ if (name) {
2819
+ try {
2820
+ const _tmLdAdd = await import('../src/living-docs.mjs');
2821
+ const _tmCur = _tmLdAdd.getProjectState(cwd);
2822
+ const _tmTeam = Array.isArray(_tmCur?.project?.team) ? [..._tmCur.project.team] : [];
2823
+ _tmTeam.push({ name, role: 'member', addedAt: new Date().toISOString() });
2824
+ _tmLdAdd.updateProject({ team: _tmTeam }, cwd);
2825
+ process.stdout.write(`\n Added ${name} to team.\n\n`);
2826
+ await ask(' Press Enter to continue...');
2827
+ } catch {
2828
+ process.stdout.write('\n Could not save team member.\n\n');
2829
+ await ask(' Press Enter to continue...');
2830
+ }
2831
+ }
2832
+ return { next: 'team' };
2833
+ }
2834
+
2835
+ if (choice === 'b' || choice === 'back' || choice === 'q' || raw === '\x1b') {
2836
+ return { next: 'main' };
2837
+ }
2838
+
2839
+ return { next: 'main' };
2840
+ }
2841
+
2693
2842
 
2694
2843
  // ─── Helper: aggregatePlans ───────────────────────────────────────────────────
2695
2844
 
@@ -4027,6 +4176,7 @@ const SCREENS = {
4027
4176
  main: mainScreen,
4028
4177
  'new-session': newSessionScreen,
4029
4178
  settings: settingsScreen,
4179
+ team: teamScreen,
4030
4180
  'import-picker': importPickerScreen,
4031
4181
  'pr-triage': prTriageScreen,
4032
4182
  subscriptions: subscriptionsScreen,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dual-brain",
3
- "version": "7.1.24",
3
+ "version": "7.1.25",
4
4
  "description": "AI orchestration across Claude + OpenAI subscriptions — smart routing, budget awareness, and dual-brain collaboration",
5
5
  "type": "module",
6
6
  "bin": {
package/src/pipeline.mjs CHANGED
@@ -60,6 +60,12 @@ export function createPipelineRun(trigger = '', prompt = '') {
60
60
  // Phase 5: Outcome
61
61
  outcome: null,
62
62
 
63
+ // Ledger + calibration
64
+ taskId: null, // ledger task ID for this run
65
+ openTasks: [], // pending tasks from ledger
66
+ calibration: null, // user calibration state
67
+ adaptation: null, // behavior adaptation from calibration
68
+
63
69
  completedAt: null,
64
70
  };
65
71
  }
@@ -650,6 +656,55 @@ export async function runPipeline(trigger, prompt, options = {}) {
650
656
  // intelligence module not available — continue without it (degraded)
651
657
  }
652
658
 
659
+ // Ledger: check open tasks + create task for this run
660
+ try {
661
+ const { getOpenTasks, createTask, reconcile } = await import('./ledger.mjs');
662
+ const cwd = options.cwd || process.cwd();
663
+
664
+ // Check for stale tasks on session start
665
+ run.openTasks = getOpenTasks(cwd);
666
+ const staleTasks = reconcile(cwd);
667
+
668
+ // Create a ledger task for this pipeline run
669
+ const task = createTask({
670
+ intent: prompt,
671
+ owner: 'head',
672
+ priority: run.projectBrief?.recentFailures?.length > 0 ? 'high' : 'medium',
673
+ files: options.files || []
674
+ }, cwd);
675
+ run.taskId = task.id;
676
+ } catch (e) {
677
+ // ledger not available — continue degraded
678
+ }
679
+
680
+ // Append open tasks to situation brief if any exist
681
+ if (run.openTasks.length > 0) {
682
+ const preview = run.openTasks.slice(0, 3).map(t => t.intent).join(', ');
683
+ const pendingLine = `PENDING TASKS: ${run.openTasks.length} open (${preview})`;
684
+ run.situationBrief = run.situationBrief
685
+ ? `${run.situationBrief}\n${pendingLine}`
686
+ : pendingLine;
687
+ }
688
+
689
+ // Calibration: analyze user input and adapt
690
+ try {
691
+ const { analyzeInput, getAdaptation, detectCorrection, updateCalibration } = await import('./calibration.mjs');
692
+ const { getProjectState, updateProject } = await import('./living-docs.mjs');
693
+ const cwd = options.cwd || process.cwd();
694
+
695
+ const projectState = getProjectState(cwd);
696
+ const currentCal = projectState?.project?.userCalibration || { specificity: 3, corrections: 3, autonomy: 3, interactions: 0 };
697
+ const isCorrection = detectCorrection(prompt);
698
+
699
+ run.calibration = updateCalibration(currentCal, prompt, isCorrection);
700
+ run.adaptation = getAdaptation(run.calibration);
701
+
702
+ // Persist updated calibration
703
+ updateProject({ userCalibration: run.calibration }, cwd);
704
+ } catch (e) {
705
+ // calibration not available — continue degraded
706
+ }
707
+
653
708
  // ── Phase 1: Context ──────────────────────────────────────────────────────
654
709
 
655
710
  // Build context pack
@@ -757,8 +812,48 @@ export async function runPipeline(trigger, prompt, options = {}) {
757
812
  verbose,
758
813
  profile: run.context.profile,
759
814
  situationBrief: run.situationBrief,
815
+ adaptation: run.adaptation,
760
816
  });
761
817
 
818
+ // Update ledger task with result
819
+ if (run.taskId) {
820
+ try {
821
+ const { updateTask, failTask } = await import('./ledger.mjs');
822
+ const cwd = options.cwd || process.cwd();
823
+
824
+ if (run.result && !run.result.error) {
825
+ updateTask(run.taskId, {
826
+ status: 'done',
827
+ result: typeof run.result === 'string' ? run.result : JSON.stringify(run.result).slice(0, 500),
828
+ proof: run.verification ? 'Pipeline verification passed' : 'Execution completed',
829
+ files: run.result.filesChanged || run.plan?.targetFiles || []
830
+ }, cwd);
831
+ } else {
832
+ failTask(run.taskId, run.result?.error || 'Pipeline execution failed', cwd);
833
+ }
834
+ } catch (e) {
835
+ // ledger update failed — non-blocking
836
+ }
837
+ }
838
+
839
+ // Record action in living docs
840
+ try {
841
+ const { appendAction } = await import('./living-docs.mjs');
842
+ const cwd = options.cwd || process.cwd();
843
+
844
+ appendAction({
845
+ type: trigger || 'task',
846
+ intent: prompt,
847
+ status: (run.result && !run.result.error) ? 'done' : 'failed',
848
+ owner: 'head',
849
+ files: run.result?.filesChanged || run.plan?.targetFiles || [],
850
+ proof: run.verification ? JSON.stringify(run.verification).slice(0, 200) : null,
851
+ result: typeof run.result === 'string' ? run.result.slice(0, 300) : null
852
+ }, cwd);
853
+ } catch (e) {
854
+ // living docs not available — non-blocking
855
+ }
856
+
762
857
  // ── Phase 4: Verification ─────────────────────────────────────────────────
763
858
 
764
859
  run.verification = await verify(run.result, run.plan, cwd);