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.
- package/bin/dual-brain.mjs +215 -65
- package/package.json +1 -1
- package/src/pipeline.mjs +95 -0
package/bin/dual-brain.mjs
CHANGED
|
@@ -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
|
|
1045
|
+
// --- Detect replit-tools sessions ---
|
|
1011
1046
|
const env = detectEnvironment();
|
|
1012
1047
|
const existingSessions = importReplitSessions(cwd);
|
|
1013
1048
|
if (env.hasReplitTools) {
|
|
1014
|
-
detectedLines.push(`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 [
|
|
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
|
|
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
|
|
2164
|
+
process.stdout.write(row('Import from replit-tools') + '\n');
|
|
2129
2165
|
process.stdout.write(sep + '\n');
|
|
2130
|
-
process.stdout.write(row('No
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
2571
|
-
row(
|
|
2572
|
-
row(
|
|
2573
|
-
row(
|
|
2574
|
-
|
|
2575
|
-
row('
|
|
2576
|
-
row(
|
|
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
|
-
|
|
2588
|
-
|
|
2589
|
-
const
|
|
2590
|
-
const
|
|
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
|
-
|
|
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
|
|
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
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);
|