daedalus-cli 0.5.9 → 0.5.11

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/dist/index.js CHANGED
@@ -40,6 +40,13 @@ let turnStartTime = 0;
40
40
  let currentAbortController = null;
41
41
  // Compute stable projectHash once
42
42
  const projectHash = crypto.createHash('sha256').update(path.resolve(process.cwd())).digest('hex').slice(0, 12);
43
+ // Max chars of tool output stored in message history (prevents context-window overflow)
44
+ const TOOL_RESULT_MAX_CHARS = 32_000;
45
+ // Single source of truth for the codebase index DB path — used by all REPL
46
+ // handlers AND the indexing tools so they always share the same database.
47
+ function getIndexDbPath() {
48
+ return path.join(os.homedir(), '.daedalus', 'indexing', `${projectHash}.sqlite`);
49
+ }
43
50
  // Initialize session manager
44
51
  const sessionManager = new SessionManager();
45
52
  sessionManager.init();
@@ -144,11 +151,11 @@ A FTS5 symbol index is built automatically on startup. Use \`find_symbol\` to se
144
151
 
145
152
  | Result | Meaning | What YOU must do |
146
153
  |--------|---------|-----------------|
147
- | \`Patched <file>\` | Success — change written to disk | Continue to next step |
148
- | error contains \`PATCH_DECLINED\` | 🚫 User reviewed the diff and said No or Skip | STOP retrying. Tell the user what you tried to change and ask how they'd like to proceed |
149
- | error contains \`not found\` | old_string didn't match the file | Immediately call \`read_file\` on that file, find the exact text, then retry \`patch\` with the corrected old_string |
150
- | error contains \`multiple locations\` | old_string is too generic | Add more surrounding lines to old_string to make it unique, then retry |
151
- | error contains \`File not found\` | Wrong path | Use \`search_files\` or \`list_files\` to find the correct path |
154
+ | \`Patched <file>\` | [OK] Success — change written to disk | Continue to next step |
155
+ | \`PATCH_DECLINED\` | [SKIP] User reviewed the diff and said No or Skip | STOP retrying. Tell the user what you tried to change and ask how they'd like to proceed |
156
+ | error contains \`not found\` | [ERROR] old_string didn't match the file | Immediately call \`read_file\` on that file, find the exact text, then retry \`patch\` with the corrected old_string |
157
+ | error contains \`multiple locations\` | [ERROR] old_string is too generic | Add more surrounding lines to old_string to make it unique, then retry |
158
+ | error contains \`File not found\` | [ERROR] Wrong path | Use \`search_files\` or \`list_files\` to find the correct path |
152
159
 
153
160
  **Never freeze or loop silently.** If a patch fails, take one corrective action and tell the user what happened.`;
154
161
  // Build system prompt with project memory and user profile
@@ -219,7 +226,6 @@ function printBanner() {
219
226
  const white = pc.white.bind(pc);
220
227
  const bold = pc.bold.bind(pc);
221
228
  const dim = pc.dim.bind(pc);
222
- const mag = pc.magenta.bind(pc);
223
229
  // Top border
224
230
  console.log(hRule('╔', '╗', '═', W, cyan));
225
231
  console.log(box(' '.repeat(W), cyan));
@@ -304,8 +310,8 @@ async function buildIndexContext(userMessage) {
304
310
  if (!config.indexing.enabled || !toolContext.indexDb)
305
311
  return '';
306
312
  const indexDb = toolContext.indexDb;
307
- // Extract likely symbol names from the user message (camelCase, snake_case, class names)
308
- const symbolCandidates = userMessage.match(/\b[A-Z][a-zA-Z0-9_]*\b/g) || [];
313
+ // Extract likely symbol names from the user message (camelCase, PascalCase, snake_case, class names)
314
+ const symbolCandidates = userMessage.match(/\b(?:[a-z]+[A-Z]|[A-Z][a-z])[a-zA-Z0-9_]*\b/g) || [];
309
315
  const words = userMessage.split(/\s+/).filter(w => w.length > 2);
310
316
  const allTerms = [...symbolCandidates, ...words];
311
317
  if (allTerms.length === 0)
@@ -363,10 +369,10 @@ function initializeSessionState(loaded) {
363
369
  async function handleSpawn(role, task) {
364
370
  const validRoles = ['coder', 'reviewer', 'debugger', 'researcher', 'planner'];
365
371
  if (!validRoles.includes(role)) {
366
- console.log(pc.red(`⚠ Unknown role: ${role}. Valid: ${validRoles.join(', ')}`));
372
+ console.log(pc.red(`[WARN] Unknown role: ${role}. Valid: ${validRoles.join(', ')}`));
367
373
  return;
368
374
  }
369
- console.log(pc.cyan(`\n🤝 Spawning ${role} agent for: ${task.slice(0, 80)}...`));
375
+ console.log(pc.cyan(`\n[SPAWN] Spawning ${role} agent for: ${task.slice(0, 80)}...`));
370
376
  const context = `Active files: ${Array.from(activeFiles.values()).join(', ') || 'none'}`;
371
377
  const fakeToolCall = {
372
378
  id: `call_${Date.now()}`,
@@ -388,7 +394,7 @@ async function handleSpawn(role, task) {
388
394
  }
389
395
  // Handle /orchestrate command
390
396
  async function handleOrchestrate(goal) {
391
- console.log(pc.cyan(`\n🎭 Starting orchestration for: ${goal}`));
397
+ console.log(pc.cyan(`\n[ORCHESTRATE] Starting orchestration for: ${goal}`));
392
398
  const { Orchestrator } = await import('./agents/orchestrator.js');
393
399
  const orchestrator = new Orchestrator(router, messages, toolContext);
394
400
  const result = await orchestrator.run(goal);
@@ -418,8 +424,6 @@ async function handleModels() {
418
424
  }
419
425
  // Handle /config command
420
426
  function handleConfig() {
421
- // Max chars of tool output stored in message history (prevents context-window overflow)
422
- const TOOL_RESULT_MAX_CHARS = 32_000;
423
427
  console.log(pc.bold('\n--- Current Configuration ---'));
424
428
  console.log(JSON.stringify(config, null, 2));
425
429
  console.log(pc.bold('-----------------------------'));
@@ -469,11 +473,10 @@ async function handleDoctor() {
469
473
  console.log(pc.bold(' Config:') + pc.gray(` ${configDir}\\config.json`));
470
474
  console.log(pc.bold('----------------------\n'));
471
475
  } // end handleDoctor
472
- // Handle /index command
473
476
  async function handleIndex(opts) {
474
477
  console.log(pc.bold('\n--- Indexing Codebase ---'));
475
478
  console.log(pc.gray(`Project: ${process.cwd()}`));
476
- const indexDbPath = path.join(os.homedir(), '.daedalus', 'sessions', projectHash, 'index.sqlite');
479
+ const indexDbPath = getIndexDbPath();
477
480
  if (!fs.existsSync(path.dirname(indexDbPath))) {
478
481
  fs.mkdirSync(path.dirname(indexDbPath), { recursive: true });
479
482
  }
@@ -491,12 +494,13 @@ async function handleIndex(opts) {
491
494
  return;
492
495
  lastPct = pct;
493
496
  const filled = Math.round((current / total) * barWidth);
494
- const bar = ''.repeat(filled) + ''.repeat(barWidth - filled);
497
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
495
498
  process.stdout.write(`\r ${pc.cyan(bar)} ${pc.white(`${current}/${total}`)} ${pc.gray(file.slice(-40))}`);
496
499
  };
497
500
  const result = await indexCodebase(db, process.cwd(), projectHash, { ...opts, onProgress });
498
501
  process.stdout.write('\n');
499
502
  const elapsed = Date.now() - start;
503
+ toolContext.indexDb = db;
500
504
  console.log(pc.green(`\n✔ Indexing complete in ${elapsed}ms`));
501
505
  console.log(pc.white(` Total files: ${result.totalFiles}`));
502
506
  console.log(pc.white(` Indexed files: ${result.indexedFiles}`));
@@ -510,14 +514,13 @@ async function handleIndex(opts) {
510
514
  }
511
515
  }
512
516
  catch (err) {
513
- console.error(pc.red(`\n Indexing failed: ${err.message}`));
517
+ console.error(pc.red(`\n[ERROR] Indexing failed: ${err.message}`));
514
518
  }
515
519
  }
516
- // Handle /find <query> [limit]
517
520
  async function handleFindSymbol(query, limit) {
518
- const indexDbPath = path.join(os.homedir(), '.daedalus', 'sessions', projectHash, 'index.sqlite');
521
+ const indexDbPath = getIndexDbPath();
519
522
  if (!fs.existsSync(indexDbPath)) {
520
- console.log(pc.yellow(' No index found. Run /index first.'));
523
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
521
524
  return;
522
525
  }
523
526
  const { initIndexDb, searchSymbols } = await import('./indexing/fts.js');
@@ -538,11 +541,10 @@ async function handleFindSymbol(query, limit) {
538
541
  }
539
542
  }
540
543
  }
541
- // Handle /refs <symbol>
542
544
  async function handleGetReferences(symbol) {
543
- const indexDbPath = path.join(os.homedir(), '.daedalus', 'sessions', projectHash, 'index.sqlite');
545
+ const indexDbPath = getIndexDbPath();
544
546
  if (!fs.existsSync(indexDbPath)) {
545
- console.log(pc.yellow(' No index found. Run /index first.'));
547
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
546
548
  return;
547
549
  }
548
550
  const { initIndexDb, findReferences } = await import('./indexing/fts.js');
@@ -553,7 +555,6 @@ async function handleGetReferences(symbol) {
553
555
  console.log(pc.gray(' No references found.'));
554
556
  return;
555
557
  }
556
- // Group by caller
557
558
  const byCaller = new Map();
558
559
  for (const r of refs) {
559
560
  const key = `${r.caller_name} (${r.caller_file}:${r.caller_line})`;
@@ -572,11 +573,10 @@ async function handleGetReferences(symbol) {
572
573
  }
573
574
  }
574
575
  }
575
- // Handle /def <symbol>
576
576
  async function handleGetDefinition(symbol) {
577
- const indexDbPath = path.join(os.homedir(), '.daedalus', 'sessions', projectHash, 'index.sqlite');
577
+ const indexDbPath = getIndexDbPath();
578
578
  if (!fs.existsSync(indexDbPath)) {
579
- console.log(pc.yellow(' No index found. Run /index first.'));
579
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
580
580
  return;
581
581
  }
582
582
  const { initIndexDb, findDefinitions } = await import('./indexing/fts.js');
@@ -597,8 +597,6 @@ async function handleGetDefinition(symbol) {
597
597
  }
598
598
  }
599
599
  }
600
- // Max chars of tool output stored in message history (prevents context-window overflow)
601
- const TOOL_RESULT_MAX_CHARS = 32_000;
602
600
  function truncateToolResult(content) {
603
601
  if (content.length <= TOOL_RESULT_MAX_CHARS)
604
602
  return content;
@@ -657,21 +655,20 @@ async function checkForUpdates() {
657
655
  // Streaming response handler with tool call support — iterative, not recursive
658
656
  const MAX_TOOL_TURNS = 40;
659
657
  async function callModelWithTools(userContent, imageBase64) {
660
- // Auto-inject index context on first user turn (not on tool-result rounds)
658
+ // Push the user message as-is callers in chatLoop are responsible for
659
+ // pre-augmenting userContent with file context and index context.
661
660
  if (userContent) {
662
- const indexCtx = await buildIndexContext(userContent);
663
- const augmentedContent = indexCtx ? indexCtx + userContent : userContent;
664
661
  if (imageBase64) {
665
662
  messages.push({
666
663
  role: 'user',
667
664
  content: [
668
- { type: 'text', text: augmentedContent },
665
+ { type: 'text', text: userContent },
669
666
  { type: 'image_url', image_url: { url: `data:image/png;base64,${imageBase64}` } },
670
667
  ],
671
668
  });
672
669
  }
673
670
  else {
674
- messages.push({ role: 'user', content: augmentedContent });
671
+ messages.push({ role: 'user', content: userContent });
675
672
  }
676
673
  }
677
674
  // Combine built-in tools with MCP tools (stable across turns)
@@ -746,7 +743,7 @@ async function callModelWithTools(userContent, imageBase64) {
746
743
  if (signal.aborted) {
747
744
  if (blockOpened)
748
745
  closeAssistantBlock(fullContent.length, Date.now() - turnStart);
749
- console.log(pc.dim('\n Stopped'));
746
+ console.log(pc.dim('\n [STOP] Stopped'));
750
747
  currentAbortController = null;
751
748
  return { content: fullContent, toolCalls: [] };
752
749
  }
@@ -754,12 +751,12 @@ async function callModelWithTools(userContent, imageBase64) {
754
751
  catch (error) {
755
752
  if (signal.aborted) {
756
753
  spinner.stop();
757
- console.log(pc.dim('\n Stopped'));
754
+ console.log(pc.dim('\n [STOP] Stopped'));
758
755
  currentAbortController = null;
759
756
  return { content: '', toolCalls: [] };
760
757
  }
761
758
  spinner.stop();
762
- console.error(pc.red(`\n Error calling model: ${error.message}`));
759
+ console.error(pc.red(`\n[ERROR] Error calling model: ${error.message}`));
763
760
  throw error;
764
761
  }
765
762
  currentAbortController = null;
@@ -789,20 +786,20 @@ async function callModelWithTools(userContent, imageBase64) {
789
786
  if (dangerousTools.includes(tc.function.name) && !turnApproved) {
790
787
  const args = tc.function.arguments;
791
788
  const preview = args.length > 120 ? args.slice(0, 120) + '...' : args;
792
- process.stdout.write(`\n ${pc.yellow('')} ${pc.bold(tc.function.name)} ${pc.dim(preview)}\n`);
789
+ process.stdout.write(`\n ${pc.yellow('[WARN]')} ${pc.bold(tc.function.name)} ${pc.dim(preview)}\n`);
793
790
  const line = await askLine(` ${pc.dim('Allow? [y]es / [n]o / [a]ll for this turn: ')}`);
794
791
  const char = line.trim().toLowerCase().slice(0, 1);
795
792
  if (char === 'a')
796
793
  turnApproved = true;
797
794
  if (char === 'n') {
798
- console.log(` ${pc.red('')} ${tc.function.name} ${pc.red('— rejected')}`);
795
+ console.log(` ${pc.red('[FAIL]')} ${tc.function.name} ${pc.red(' — rejected')}`);
799
796
  continue;
800
797
  }
801
798
  }
802
799
  approvedCallIndices.add(i);
803
800
  }
804
801
  const approvedCalls = toolCallArray.filter((_, i) => approvedCallIndices.has(i));
805
- console.log(`\n ${pc.dim('🔧')} ${pc.dim(`Executing ${approvedCalls.length} tool call(s)...`)}`);
802
+ console.log(`\n ${pc.dim('[TOOL]')} ${pc.dim(`Executing ${approvedCalls.length} tool call(s)...`)}`);
806
803
  const results = await executeToolCalls(approvedCalls, toolContext);
807
804
  for (const result of results) {
808
805
  messages.push({
@@ -833,7 +830,7 @@ async function callModelWithTools(userContent, imageBase64) {
833
830
  turn++;
834
831
  }
835
832
  // Reached max turns without a clean stop
836
- console.log(`\n ${pc.yellow('')} ${pc.yellow(`Reached max tool turns (${MAX_TOOL_TURNS}). Stopping.`)}`);
833
+ console.log(`\n ${pc.yellow('[WARN]')} ${pc.yellow(`Reached max tool turns (${MAX_TOOL_TURNS}). Stopping.`)}`);
837
834
  messages.push({ role: 'assistant', content: lastContent });
838
835
  return { content: lastContent, toolCalls: [] };
839
836
  }
@@ -851,7 +848,7 @@ async function callModelWithFallback(userContent, imageBase64) {
851
848
  else {
852
849
  messages.push({ role: 'user', content: userContent });
853
850
  }
854
- console.log(pc.gray('🤖 Thinking (fallback mode)...'));
851
+ console.log(pc.gray('[THINK] Thinking (fallback mode)...'));
855
852
  try {
856
853
  const response = await router.chat.completions.create({
857
854
  model: 'auto',
@@ -868,14 +865,38 @@ async function callModelWithFallback(userContent, imageBase64) {
868
865
  return reply;
869
866
  }
870
867
  catch (error) {
871
- console.error(pc.red(`\n Fallback error: ${error.message}`));
868
+ console.error(pc.red(`\n[ERROR] Fallback error: ${error.message}`));
872
869
  throw error;
873
870
  }
874
871
  }
875
- // Promisify a single readline question
872
+ // Single-line prompt (approval gate, commit message)
876
873
  function askLine(prompt) {
877
874
  return new Promise((resolve) => rl.question(prompt, resolve));
878
875
  }
876
+ // Multi-line input for main chat prompt — captures pasted text beyond the first newline.
877
+ // Uses a timing heuristic: lines arriving within 80ms are treated as part of a single paste.
878
+ function readMultiLineInput(prompt) {
879
+ return new Promise((resolve) => {
880
+ const lines = [];
881
+ let timer = null;
882
+ let resolved = false;
883
+ const onLine = (line) => {
884
+ if (resolved)
885
+ return;
886
+ lines.push(line);
887
+ if (timer)
888
+ clearTimeout(timer);
889
+ timer = setTimeout(() => {
890
+ resolved = true;
891
+ rl.off('line', onLine);
892
+ resolve(lines.join('\n'));
893
+ }, 80);
894
+ };
895
+ rl.on('line', onLine);
896
+ process.stdout.write(prompt);
897
+ rl.resume();
898
+ });
899
+ }
879
900
  // ── Clipboard helpers ──────────────────────────────────────────────────────────
880
901
  function getClipboardText() {
881
902
  try {
@@ -1016,7 +1037,7 @@ async function chatLoop() {
1016
1037
  const prompt = activeFiles.size > 0
1017
1038
  ? `\n${pc.cyan(' ⬡')} ${pc.dim(`[${activeFiles.size} file${activeFiles.size > 1 ? 's' : ''}]`)} ${pc.bold(pc.white('›'))} `
1018
1039
  : `\n${pc.cyan(' ⬡')} ${pc.bold(pc.white('›'))} `;
1019
- const input = await askLine(prompt);
1040
+ const input = await readMultiLineInput(prompt);
1020
1041
  const trimmedInput = input.trim();
1021
1042
  if (!trimmedInput)
1022
1043
  continue;
@@ -1025,10 +1046,10 @@ async function chatLoop() {
1025
1046
  if (lowerInput === 'exit' || lowerInput === 'quit') {
1026
1047
  const todos = getSessionTodos(sessionId);
1027
1048
  sessionManager.saveSessionState(messages, activeFiles, todos);
1028
- console.log(pc.dim(' 🧠 Extracting facts from session...'));
1049
+ console.log(pc.dim(' [EXTRACT] Extracting facts from session...'));
1029
1050
  await extractAndSave(router, sessionManager, messages);
1030
1051
  console.log(pc.gray(`Session saved: ${sessionManager.sessionId}`));
1031
- console.log(pc.yellow('\nEnding session. Goodbye! 👋\n'));
1052
+ console.log(pc.yellow('\nEnding session. Goodbye!\n'));
1032
1053
  rl.close();
1033
1054
  process.exit(0);
1034
1055
  }
@@ -1060,13 +1081,13 @@ async function chatLoop() {
1060
1081
  if (trimmedInput.startsWith('/add ')) {
1061
1082
  const fileArg = trimmedInput.substring(5).trim();
1062
1083
  if (!fileArg) {
1063
- console.log(pc.red(' Please specify a file path. Example: /add src/App.tsx'));
1084
+ console.log(pc.red('[WARN] Please specify a file path. Example: /add src/App.tsx'));
1064
1085
  }
1065
1086
  else {
1066
1087
  const absPath = path.resolve(fileArg);
1067
1088
  activeFiles.set(absPath, fileArg);
1068
1089
  toolContext.activeFiles = new Map(activeFiles);
1069
- console.log(pc.green(`✔ Added file to context: ${pc.bold(fileArg)}`));
1090
+ console.log(pc.green(`[OK] Added file to context: ${pc.bold(fileArg)}`));
1070
1091
  }
1071
1092
  continue;
1072
1093
  }
@@ -1074,16 +1095,16 @@ async function chatLoop() {
1074
1095
  if (trimmedInput.startsWith('/remove ')) {
1075
1096
  const fileArg = trimmedInput.substring(8).trim();
1076
1097
  if (!fileArg) {
1077
- console.log(pc.red(' Please specify a file path. Example: /remove src/App.tsx'));
1098
+ console.log(pc.red('[WARN] Please specify a file path. Example: /remove src/App.tsx'));
1078
1099
  }
1079
1100
  else {
1080
1101
  const absPath = path.resolve(fileArg);
1081
1102
  if (activeFiles.delete(absPath)) {
1082
1103
  toolContext.activeFiles = new Map(activeFiles);
1083
- console.log(pc.green(`✔ Removed file from context: ${pc.bold(fileArg)}`));
1104
+ console.log(pc.green(`[OK] Removed file from context: ${pc.bold(fileArg)}`));
1084
1105
  }
1085
1106
  else {
1086
- console.log(pc.yellow(`⚠ File was not in context: ${fileArg}`));
1107
+ console.log(pc.yellow(`[WARN] File was not in context: ${fileArg}`));
1087
1108
  }
1088
1109
  }
1089
1110
  continue;
@@ -1091,6 +1112,41 @@ async function chatLoop() {
1091
1112
  // Command: /paste — paste clipboard content (text or image)
1092
1113
  if (lowerInput === '/paste' || lowerInput.startsWith('/paste ')) {
1093
1114
  const extra = trimmedInput.startsWith('/paste ') ? trimmedInput.substring(7).trim() : '';
1115
+ // If extra looks like a file path, try reading it as an image
1116
+ if (extra && !extra.startsWith('http')) {
1117
+ const filePath = path.resolve(extra);
1118
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
1119
+ const ext = path.extname(filePath).toLowerCase();
1120
+ if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'].includes(ext)) {
1121
+ const imgBuffer = fs.readFileSync(filePath);
1122
+ const base64 = imgBuffer.toString('base64');
1123
+ const message = 'What do you see in this image?';
1124
+ printUserTurn(`${path.basename(filePath)} (image)`);
1125
+ try {
1126
+ const filesContext = buildFileContext();
1127
+ const indexCtx = await buildIndexContext(message);
1128
+ const userContent = `${indexCtx}${filesContext}User Prompt: ${message}`;
1129
+ await callModelWithTools(userContent, base64);
1130
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1131
+ }
1132
+ catch (error) {
1133
+ console.error(pc.red(`\n[ERROR] Error: ${error.message}`));
1134
+ try {
1135
+ const filesContext = buildFileContext();
1136
+ const userContent = `${filesContext}User Prompt: ${message}`;
1137
+ console.log(pc.yellow('\n[RETRY] Trying fallback mode...'));
1138
+ await callModelWithFallback(userContent, base64);
1139
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1140
+ }
1141
+ catch (fallbackErr) {
1142
+ console.error(pc.red(`\n[ERROR] Fallback also failed: ${fallbackErr.message}`));
1143
+ }
1144
+ }
1145
+ turnSeparator();
1146
+ continue;
1147
+ }
1148
+ }
1149
+ }
1094
1150
  // Try image first
1095
1151
  const imgPath = getClipboardImage();
1096
1152
  if (imgPath) {
@@ -1104,17 +1160,19 @@ async function chatLoop() {
1104
1160
  const indexCtx = await buildIndexContext(message);
1105
1161
  const userContent = `${indexCtx}${filesContext}User Prompt: ${message}`;
1106
1162
  await callModelWithTools(userContent, base64);
1163
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1107
1164
  }
1108
1165
  catch (error) {
1109
- console.error(pc.red(`\n Error: ${error.message}`));
1166
+ console.error(pc.red(`\n[ERROR] Error: ${error.message}`));
1110
1167
  try {
1111
1168
  const filesContext = buildFileContext();
1112
1169
  const userContent = `${filesContext}User Prompt: ${message}`;
1113
- console.log(pc.yellow('\n🔄 Trying fallback mode...'));
1170
+ console.log(pc.yellow('\n[RETRY] Trying fallback mode...'));
1114
1171
  await callModelWithFallback(userContent, base64);
1172
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1115
1173
  }
1116
1174
  catch (fallbackErr) {
1117
- console.error(pc.red(`\n Fallback also failed: ${fallbackErr.message}`));
1175
+ console.error(pc.red(`\n[ERROR] Fallback also failed: ${fallbackErr.message}`));
1118
1176
  }
1119
1177
  }
1120
1178
  turnSeparator();
@@ -1123,7 +1181,7 @@ async function chatLoop() {
1123
1181
  // Fall back to text
1124
1182
  const clipboard = getClipboardText();
1125
1183
  if (!clipboard) {
1126
- console.log(pc.red(' Clipboard is empty or inaccessible.'));
1184
+ console.log(pc.red('[WARN] Clipboard is empty or inaccessible.'));
1127
1185
  continue;
1128
1186
  }
1129
1187
  const fullMessage = extra ? `${clipboard}\n\n${extra}` : clipboard;
@@ -1133,9 +1191,10 @@ async function chatLoop() {
1133
1191
  const indexCtx = await buildIndexContext(fullMessage);
1134
1192
  const userContent = `${indexCtx}${filesContext}User Prompt: ${fullMessage}`;
1135
1193
  await callModelWithTools(userContent);
1194
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1136
1195
  }
1137
1196
  catch (error) {
1138
- console.error(pc.red(`\n Error: ${error.message}`));
1197
+ console.error(pc.red(`\n[ERROR] Error: ${error.message}`));
1139
1198
  }
1140
1199
  turnSeparator();
1141
1200
  continue;
@@ -1176,7 +1235,7 @@ async function chatLoop() {
1176
1235
  else if (subCmd === 'load') {
1177
1236
  const targetId = parts[1]?.trim();
1178
1237
  if (!targetId) {
1179
- console.log(pc.red(' Usage: /session load <id>'));
1238
+ console.log(pc.red('[WARN] Usage: /session load <id>'));
1180
1239
  }
1181
1240
  else {
1182
1241
  try {
@@ -1184,10 +1243,10 @@ async function chatLoop() {
1184
1243
  sessionManager.saveSessionState(messages, activeFiles, todos);
1185
1244
  const loaded = sessionManager.startSession(targetId);
1186
1245
  initializeSessionState(loaded);
1187
- console.log(pc.green(`✔ Loaded session: ${sessionManager.sessionTitle} (${sessionManager.sessionId})`));
1246
+ console.log(pc.green(`[OK] Loaded session: ${sessionManager.sessionTitle} (${sessionManager.sessionId})`));
1188
1247
  }
1189
1248
  catch (err) {
1190
- console.log(pc.red(`⚠ Failed to load session: ${err.message}`));
1249
+ console.log(pc.red(`[WARN] Failed to load session: ${err.message}`));
1191
1250
  }
1192
1251
  }
1193
1252
  }
@@ -1197,20 +1256,20 @@ async function chatLoop() {
1197
1256
  sessionManager.saveSessionState(messages, activeFiles, todos);
1198
1257
  const loaded = sessionManager.startSession(undefined, title || undefined);
1199
1258
  initializeSessionState(loaded);
1200
- console.log(pc.green(`✔ Started new session: ${sessionManager.sessionTitle} (${sessionManager.sessionId})`));
1259
+ console.log(pc.green(`[OK] Started new session: ${sessionManager.sessionTitle} (${sessionManager.sessionId})`));
1201
1260
  }
1202
1261
  else if (subCmd === 'delete') {
1203
1262
  const targetId = parts[1]?.trim();
1204
1263
  if (!targetId) {
1205
- console.log(pc.red(' Usage: /session delete <id>'));
1264
+ console.log(pc.red('[WARN] Usage: /session delete <id>'));
1206
1265
  }
1207
1266
  else {
1208
1267
  sessionManager.deleteSession(targetId);
1209
- console.log(pc.green(`✔ Deleted session: ${targetId}`));
1268
+ console.log(pc.green(`[OK] Deleted session: ${targetId}`));
1210
1269
  }
1211
1270
  }
1212
1271
  else {
1213
- console.log(pc.red(' Usage: /session list | load <id> | new [title] | delete <id>'));
1272
+ console.log(pc.red('[WARN] Usage: /session list | load <id> | new [title] | delete <id>'));
1214
1273
  }
1215
1274
  continue;
1216
1275
  }
@@ -1244,13 +1303,13 @@ async function chatLoop() {
1244
1303
  const argStr = trimmedInput.substring(6).trim();
1245
1304
  const eqIdx = argStr.indexOf('=');
1246
1305
  if (eqIdx < 0) {
1247
- console.log(pc.red(' Usage: /fact <key> = <value>'));
1306
+ console.log(pc.red('[WARN] Usage: /fact <key> = <value>'));
1248
1307
  }
1249
1308
  else {
1250
1309
  const key = argStr.slice(0, eqIdx).trim();
1251
1310
  const value = argStr.slice(eqIdx + 1).trim();
1252
1311
  sessionManager.addFact(key, value, 'user');
1253
- console.log(pc.green(`✔ Saved fact: ${key} = ${value}`));
1312
+ console.log(pc.green(`[OK] Saved fact: ${key} = ${value}`));
1254
1313
  }
1255
1314
  continue;
1256
1315
  }
@@ -1259,19 +1318,19 @@ async function chatLoop() {
1259
1318
  const argStr = trimmedInput.substring(12).trim();
1260
1319
  const eqIdx = argStr.indexOf('=');
1261
1320
  if (eqIdx < 0) {
1262
- console.log(pc.red(' Usage: /convention <key> = <value>'));
1321
+ console.log(pc.red('[WARN] Usage: /convention <key> = <value>'));
1263
1322
  }
1264
1323
  else {
1265
1324
  const key = argStr.slice(0, eqIdx).trim();
1266
1325
  const value = argStr.slice(eqIdx + 1).trim();
1267
1326
  sessionManager.setConvention(key, value);
1268
- console.log(pc.green(`✔ Saved convention: ${key} = ${value}`));
1327
+ console.log(pc.green(`[OK] Saved convention: ${key} = ${value}`));
1269
1328
  }
1270
1329
  continue;
1271
1330
  }
1272
1331
  // Command: /extract — manually trigger fact extraction
1273
1332
  if (lowerInput === '/extract') {
1274
- console.log(pc.dim(' 🧠 Extracting facts from conversation...'));
1333
+ console.log(pc.dim(' [EXTRACT] Extracting facts from conversation...'));
1275
1334
  await extractAndSave(router, sessionManager, messages);
1276
1335
  continue;
1277
1336
  }
@@ -1292,16 +1351,16 @@ async function chatLoop() {
1292
1351
  if (rest.startsWith('name ')) {
1293
1352
  userProfile.name = rest.substring(5).trim();
1294
1353
  saveProfile(userProfile);
1295
- console.log(pc.green(`✔ Profile name set: ${userProfile.name}`));
1354
+ console.log(pc.green(`[OK] Profile name set: ${userProfile.name}`));
1296
1355
  continue;
1297
1356
  }
1298
1357
  if (rest.startsWith('bio ')) {
1299
1358
  userProfile.bio = rest.substring(4).trim();
1300
1359
  saveProfile(userProfile);
1301
- console.log(pc.green(`✔ Profile bio set.`));
1360
+ console.log(pc.green('[OK] Profile bio set.'));
1302
1361
  continue;
1303
1362
  }
1304
- console.log(pc.red(' Usage: /profile view | /profile name = <name> | /profile bio = <bio>'));
1363
+ console.log(pc.red('[WARN] Usage: /profile view | /profile name = <name> | /profile bio = <bio>'));
1305
1364
  continue;
1306
1365
  }
1307
1366
  // Command: /style
@@ -1316,14 +1375,14 @@ async function chatLoop() {
1316
1375
  }
1317
1376
  userProfile.style = rest;
1318
1377
  saveProfile(userProfile);
1319
- console.log(pc.green(' Coding style saved. It will be injected into every session.'));
1378
+ console.log(pc.green('[OK] Coding style saved. It will be injected into every session.'));
1320
1379
  continue;
1321
1380
  }
1322
1381
  // Command: /clear
1323
1382
  if (lowerInput === '/clear') {
1324
1383
  messages.length = 0;
1325
1384
  messages.push({ role: 'system', content: getSystemPromptWithMemory() });
1326
- console.log(pc.green(' Conversation history cleared!'));
1385
+ console.log(pc.green('[OK] Conversation history cleared!'));
1327
1386
  continue;
1328
1387
  }
1329
1388
  // Command: /tools
@@ -1346,7 +1405,7 @@ async function chatLoop() {
1346
1405
  if (lowerInput.startsWith('/spawn ')) {
1347
1406
  const parts = trimmedInput.substring(7).trim().split(' ');
1348
1407
  if (parts.length < 2) {
1349
- console.log(pc.red(' Usage: /spawn <role> <task>'));
1408
+ console.log(pc.red('[WARN] Usage: /spawn <role> <task>'));
1350
1409
  console.log(pc.gray(' Roles: coder, reviewer, debugger, researcher, planner'));
1351
1410
  }
1352
1411
  else {
@@ -1360,7 +1419,7 @@ async function chatLoop() {
1360
1419
  if (lowerInput.startsWith('/delegate ')) {
1361
1420
  const match = trimmedInput.substring(10).match(/^(.+)\s+to\s+(\w+)$/i);
1362
1421
  if (!match) {
1363
- console.log(pc.red(' Usage: /delegate <task> to <role>'));
1422
+ console.log(pc.red('[WARN] Usage: /delegate <task> to <role>'));
1364
1423
  }
1365
1424
  else {
1366
1425
  const task = match[1].trim();
@@ -1373,7 +1432,7 @@ async function chatLoop() {
1373
1432
  if (lowerInput.startsWith('/orchestrate ')) {
1374
1433
  const goal = trimmedInput.substring(13).trim();
1375
1434
  if (!goal) {
1376
- console.log(pc.red(' Usage: /orchestrate <goal>'));
1435
+ console.log(pc.red('[WARN] Usage: /orchestrate <goal>'));
1377
1436
  }
1378
1437
  else {
1379
1438
  await handleOrchestrate(goal);
@@ -1397,7 +1456,7 @@ async function chatLoop() {
1397
1456
  }
1398
1457
  // Command: /onboard
1399
1458
  if (lowerInput === '/onboard') {
1400
- console.log(pc.cyan('\n🔄 Re-running onboarding wizard...\n'));
1459
+ console.log(pc.cyan('\n[RESTART] Re-running onboarding wizard...\n'));
1401
1460
  await runOnboarding(true);
1402
1461
  continue;
1403
1462
  }
@@ -1405,7 +1464,7 @@ async function chatLoop() {
1405
1464
  if (lowerInput === '/undo') {
1406
1465
  const history = toolContext.patchHistory;
1407
1466
  if (!history || history.length === 0) {
1408
- console.log(pc.yellow(' No patches to undo.'));
1467
+ console.log(pc.yellow('[WARN] No patches to undo.'));
1409
1468
  }
1410
1469
  else {
1411
1470
  const last = history[history.length - 1];
@@ -1413,22 +1472,22 @@ async function chatLoop() {
1413
1472
  const currentContent = fs.readFileSync(last.filePath, 'utf8');
1414
1473
  if (currentContent === last.newContent) {
1415
1474
  fs.writeFileSync(last.filePath, last.oldContent, 'utf8');
1416
- console.log(pc.green(`✔ Undid patch to ${last.filePath} (${last.description})`));
1475
+ console.log(pc.green(`[OK] Undid patch to ${last.filePath} (${last.description})`));
1417
1476
  }
1418
1477
  else {
1419
- console.log(pc.yellow(`⚠ File ${last.filePath} has been modified since last patch. Cannot auto-undo.`));
1478
+ console.log(pc.yellow(`[WARN] File ${last.filePath} has been modified since last patch. Cannot auto-undo.`));
1420
1479
  }
1421
1480
  history.pop();
1422
1481
  }
1423
1482
  catch (err) {
1424
- console.log(pc.red(`⚠ Failed to undo: ${err.message}`));
1483
+ console.log(pc.red(`[WARN] Failed to undo: ${err.message}`));
1425
1484
  }
1426
1485
  }
1427
1486
  continue;
1428
1487
  }
1429
1488
  // Command: /commit — stage and commit changes
1430
1489
  if (lowerInput === '/commit' || lowerInput.startsWith('/commit ')) {
1431
- const msg = trimmedInput.startsWith('/commit ') ? trimmedInput.substring(8).trim() : '';
1490
+ const forcedMsg = trimmedInput.startsWith('/commit ') ? trimmedInput.substring(8).trim() : '';
1432
1491
  try {
1433
1492
  const { execute: termExec } = await import('./tools/builtin/terminal.js');
1434
1493
  const statusResult = await termExec({ command: 'git status --short', timeout: 10, workdir: process.cwd() }, toolContext);
@@ -1443,28 +1502,65 @@ async function chatLoop() {
1443
1502
  console.log(pc.red(`Stage failed: ${addResult.error}`));
1444
1503
  continue;
1445
1504
  }
1446
- let commitMsg = msg;
1505
+ let commitMsg = forcedMsg;
1447
1506
  if (!commitMsg) {
1448
1507
  const diffResult = await termExec({ command: 'git diff --cached --stat', timeout: 10, workdir: process.cwd() }, toolContext);
1508
+ const diffFull = await termExec({ command: 'git diff --cached', timeout: 10, workdir: process.cwd() }, toolContext);
1509
+ const diffContent = diffFull.content?.slice(0, 6000) || '';
1449
1510
  if (diffResult.content)
1450
1511
  console.log(pc.gray(diffResult.content));
1451
- commitMsg = await askLine(pc.cyan(' Commit message: '));
1512
+ if (diffContent) {
1513
+ console.log(pc.dim(' Generating commit message...'));
1514
+ try {
1515
+ const aiResponse = await router.chat.completions.create({
1516
+ model: 'auto',
1517
+ messages: [
1518
+ { role: 'system', content: 'You write concise git commit messages following the Conventional Commits spec (type(scope): description). Output only the commit message — no explanation, no quotes, no extra text.' },
1519
+ { role: 'user', content: `Write a commit message for this diff:\n\n${diffContent}` }
1520
+ ],
1521
+ temperature: 0.2,
1522
+ max_tokens: 80,
1523
+ });
1524
+ const suggested = (aiResponse.choices[0]?.message?.content || '').trim().split('\n')[0].trim();
1525
+ if (suggested) {
1526
+ console.log(`\n ${pc.dim('Suggested:')} ${pc.cyan(suggested)}`);
1527
+ const choice = await askLine(pc.dim(' [Enter] accept [e] edit [n] cancel: '));
1528
+ if (choice.trim().toLowerCase() === 'n') {
1529
+ console.log(pc.yellow('Commit cancelled.'));
1530
+ await termExec({ command: 'git restore --staged .', timeout: 10, workdir: process.cwd() }, toolContext);
1531
+ continue;
1532
+ }
1533
+ else if (choice.trim().toLowerCase() === 'e') {
1534
+ commitMsg = await askLine(pc.cyan(' Commit message: '));
1535
+ }
1536
+ else {
1537
+ commitMsg = suggested;
1538
+ }
1539
+ }
1540
+ }
1541
+ catch {
1542
+ // Model unavailable — fall back to manual
1543
+ }
1544
+ }
1545
+ if (!commitMsg) {
1546
+ commitMsg = await askLine(pc.cyan(' Commit message: '));
1547
+ }
1452
1548
  if (!commitMsg.trim()) {
1453
1549
  console.log(pc.yellow('Commit cancelled — empty message.'));
1454
- await termExec({ command: 'git reset', timeout: 10, workdir: process.cwd() }, toolContext);
1550
+ await termExec({ command: 'git restore --staged .', timeout: 10, workdir: process.cwd() }, toolContext);
1455
1551
  continue;
1456
1552
  }
1457
1553
  }
1458
1554
  const commitResult = await termExec({ command: `git commit -m ${JSON.stringify(commitMsg)}`, timeout: 10, workdir: process.cwd() }, toolContext);
1459
1555
  if (commitResult.success) {
1460
- console.log(pc.green(`\n Commit: ${commitMsg.slice(0, 60)}`));
1556
+ console.log(pc.green(`\n[OK] Commit: ${commitMsg.slice(0, 60)}`));
1461
1557
  }
1462
1558
  else {
1463
1559
  console.log(pc.red(`Commit failed: ${commitResult.error}`));
1464
1560
  }
1465
1561
  }
1466
1562
  catch (err) {
1467
- console.log(pc.red(`⚠ Commit error: ${err.message}`));
1563
+ console.log(pc.red(`[WARN] Commit error: ${err.message}`));
1468
1564
  }
1469
1565
  continue;
1470
1566
  }
@@ -1492,14 +1588,14 @@ async function chatLoop() {
1492
1588
  value = parts.slice(1).join(' ');
1493
1589
  }
1494
1590
  if (!key || !value) {
1495
- console.log(pc.red(' Usage: /project set <key> = <value>'));
1591
+ console.log(pc.red('[WARN] Usage: /project set <key> = <value>'));
1496
1592
  }
1497
1593
  else {
1498
1594
  const { loadProjectConfig, saveProjectConfig } = await import('./tools/builtin/project-config.js');
1499
1595
  const cfg = loadProjectConfig(process.cwd());
1500
1596
  cfg[key] = value;
1501
1597
  saveProjectConfig(cfg);
1502
- console.log(pc.green(`✔ Set ${key} = ${value}`));
1598
+ console.log(pc.green(`[OK] Set ${key} = ${value}`));
1503
1599
  }
1504
1600
  continue;
1505
1601
  }
@@ -1510,24 +1606,23 @@ async function chatLoop() {
1510
1606
  const { execute: termExec } = await import('./tools/builtin/terminal.js');
1511
1607
  const cfg = loadProjectConfig(process.cwd());
1512
1608
  const testCmd = cfg.testCommand || 'npm test';
1513
- console.log(pc.bold(`\n🧪 Test-Run-Fix Loop (max ${maxLoops} iterations)`));
1609
+ console.log(pc.bold(`\nTest-Run-Fix Loop (max ${maxLoops} iterations)`));
1514
1610
  console.log(pc.gray(`Test command: ${testCmd}\n`));
1515
1611
  for (let i = 0; i < maxLoops; i++) {
1516
- console.log(pc.cyan(`\n─── Run ${i + 1}/${maxLoops} ───`));
1612
+ console.log(pc.cyan(`\n--- Run ${i + 1}/${maxLoops} ---`));
1517
1613
  const result = await termExec({ command: testCmd, timeout: 120, workdir: process.cwd() }, toolContext);
1518
1614
  console.log(result.content?.slice(0, 2000) || pc.gray('(no output)'));
1519
1615
  if (result.success) {
1520
- console.log(pc.green('\n All tests passed!'));
1616
+ console.log(pc.green('\n[OK] All tests passed!'));
1521
1617
  break;
1522
1618
  }
1523
1619
  if (i === maxLoops - 1) {
1524
- console.log(pc.yellow(`\n Max loops (${maxLoops}) reached. Tests still failing.`));
1620
+ console.log(pc.yellow(`\n[WARN] Max loops (${maxLoops}) reached. Tests still failing.`));
1525
1621
  break;
1526
1622
  }
1527
- const failureCtx = `Tests failed (run ${i + 1}/${maxLoops}). Here's the output:\n\n${result.content?.slice(0, 8000) || 'Unknown failure'}\n\nAnalyze the failures and fix the code.`;
1528
- const filesContext = buildFileContext();
1529
- const userContent = `${filesContext}User Prompt: ${failureCtx}`;
1530
- await callModelWithTools(userContent);
1623
+ const failureCtx = `Tests failed (run ${i + 1}/${maxLoops}). Output:\n\n${result.content?.slice(0, 8000) || 'Unknown failure'}\n\nAnalyze the failures and fix the code. Do not re-read files you already have in context.`;
1624
+ await callModelWithTools(`User Prompt: ${failureCtx}`);
1625
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1531
1626
  }
1532
1627
  continue;
1533
1628
  }
@@ -1550,13 +1645,13 @@ async function chatLoop() {
1550
1645
  if (lowerInput.startsWith('/find ')) {
1551
1646
  const parts = trimmedInput.substring(6).trim().split(/\s+/);
1552
1647
  if (parts.length === 0) {
1553
- console.log(pc.red(' Usage: /find <query> [limit]'));
1648
+ console.log(pc.red('[WARN] Usage: /find <query> [limit]'));
1554
1649
  }
1555
1650
  else {
1556
1651
  const query = parts[0];
1557
1652
  const limit = parts[1] ? parseInt(parts[1], 10) : 30;
1558
1653
  if (isNaN(limit)) {
1559
- console.log(pc.red(' Invalid limit'));
1654
+ console.log(pc.red('[WARN] Invalid limit'));
1560
1655
  }
1561
1656
  else {
1562
1657
  await handleFindSymbol(query, limit);
@@ -1568,7 +1663,7 @@ async function chatLoop() {
1568
1663
  if (lowerInput.startsWith('/refs ')) {
1569
1664
  const symbol = trimmedInput.substring(6).trim();
1570
1665
  if (!symbol) {
1571
- console.log(pc.red(' Usage: /refs <symbol>'));
1666
+ console.log(pc.red('[WARN] Usage: /refs <symbol>'));
1572
1667
  }
1573
1668
  else {
1574
1669
  await handleGetReferences(symbol);
@@ -1579,7 +1674,7 @@ async function chatLoop() {
1579
1674
  if (lowerInput.startsWith('/def ')) {
1580
1675
  const symbol = trimmedInput.substring(5).trim();
1581
1676
  if (!symbol) {
1582
- console.log(pc.red(' Usage: /def <symbol>'));
1677
+ console.log(pc.red('[WARN] Usage: /def <symbol>'));
1583
1678
  }
1584
1679
  else {
1585
1680
  await handleGetDefinition(symbol);
@@ -1593,22 +1688,26 @@ async function chatLoop() {
1593
1688
  const userContent = `${indexCtx}${filesContext}User Prompt: ${trimmedInput}`;
1594
1689
  printUserTurn(trimmedInput);
1595
1690
  await callModelWithTools(userContent);
1691
+ // Persist session incrementally after every turn
1692
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1596
1693
  // Fact extraction — learns from tool results and reasoning
1597
1694
  await extractAndSave(router, sessionManager, messages);
1598
1695
  }
1599
1696
  catch (error) {
1600
- console.error(pc.red(`\n Error: ${error.message}`));
1697
+ console.error(pc.red(`\n[ERROR] Error: ${error.message}`));
1601
1698
  try {
1602
1699
  const filesContext = buildFileContext();
1603
1700
  const userContent = `${filesContext}User Prompt: ${trimmedInput}`;
1604
- console.log(pc.yellow('\n🔄 Trying fallback mode...'));
1701
+ console.log(pc.yellow('\n[RETRY] Trying fallback mode...'));
1605
1702
  const fallbackResult = await callModelWithFallback(userContent);
1606
1703
  if (fallbackResult) {
1704
+ // Persist session after fallback too
1705
+ sessionManager.saveSessionState(messages, activeFiles, getSessionTodos(sessionId));
1607
1706
  await extractAndSave(router, sessionManager, messages);
1608
1707
  }
1609
1708
  }
1610
1709
  catch (fallbackErr) {
1611
- console.error(pc.red(`\n Fallback also failed: ${fallbackErr.message}`));
1710
+ console.error(pc.red(`\n[ERROR] Fallback also failed: ${fallbackErr.message}`));
1612
1711
  console.error(pc.gray('Check that at least one local server is running.'));
1613
1712
  }
1614
1713
  }
@@ -1617,7 +1716,6 @@ async function chatLoop() {
1617
1716
  }
1618
1717
  // Start health checks and REPL
1619
1718
  async function main() {
1620
- // SIGINT handler — cancel generation if streaming, otherwise exit
1621
1719
  process.on('SIGINT', () => {
1622
1720
  if (currentAbortController) {
1623
1721
  currentAbortController.abort();
@@ -1638,10 +1736,10 @@ async function main() {
1638
1736
  printConfigInfo();
1639
1737
  try {
1640
1738
  await router.startHealthChecks();
1641
- console.log(pc.green('\n Router started. Health checks running every 30s.'));
1739
+ console.log(pc.green('\n[OK] Router started. Health checks running every 30s.'));
1642
1740
  }
1643
1741
  catch (err) {
1644
- console.error(pc.yellow(`\n Router health checks failed: ${err.message}`));
1742
+ console.error(pc.yellow(`\n[WARN] Router health checks failed: ${err.message}`));
1645
1743
  }
1646
1744
  // Initialize MCP registry
1647
1745
  try {
@@ -1649,52 +1747,41 @@ async function main() {
1649
1747
  const servers = mcpRegistry.getConnectedServers();
1650
1748
  if (servers.length > 0) {
1651
1749
  const mcpToolCount = mcpRegistry.getToolDefinitions().length;
1652
- console.log(pc.green(`\n MCP connected: ${servers.join(', ')}`));
1750
+ console.log(pc.green(`\n[OK] MCP connected: ${servers.join(', ')}`));
1653
1751
  console.log(pc.dim(` ${mcpToolCount} MCP tool(s) registered — I'll ask before using them on your behalf.`));
1654
1752
  }
1655
1753
  }
1656
1754
  catch (err) {
1657
- console.error(pc.yellow(`\n MCP initialization failed: ${err.message}`));
1755
+ console.error(pc.yellow(`\n[WARN] MCP initialization failed: ${err.message}`));
1658
1756
  }
1659
1757
  // Check for updates — non-blocking
1660
1758
  if (config.updateCheck !== false) {
1661
1759
  setTimeout(() => checkForUpdates(), 2000);
1662
1760
  }
1663
- // Auto-index at startup — deferred so the REPL appears immediately
1664
1761
  if (config.indexing.enabled) {
1665
1762
  setTimeout(() => {
1666
1763
  (async () => {
1667
1764
  try {
1668
- const indexDbPath = path.join(os.homedir(), '.daedalus', 'sessions', projectHash, 'index.sqlite');
1669
- if (!fs.existsSync(indexDbPath)) {
1670
- console.log(pc.cyan('\n ⚡ Auto-indexing codebase (background)...'));
1671
- const { initIndexDb } = await import('./indexing/fts.js');
1672
- const { indexCodebase } = await import('./indexing/indexer.js');
1673
- const db = initIndexDb(indexDbPath);
1674
- const result = await indexCodebase(db, process.cwd(), projectHash, {
1675
- exclude: config.indexing.exclude,
1676
- });
1677
- console.log(pc.green(` ✔ Indexed ${result.indexedFiles} files (${result.skippedFiles} unchanged)`));
1678
- if (result.errors.length > 0) {
1679
- console.log(pc.yellow(` ⚠ ${result.errors.length} file(s) had errors`));
1680
- }
1681
- toolContext.indexDb = db;
1765
+ const indexDbPath = getIndexDbPath();
1766
+ if (!fs.existsSync(path.dirname(indexDbPath))) {
1767
+ fs.mkdirSync(path.dirname(indexDbPath), { recursive: true });
1682
1768
  }
1683
- else {
1684
- const { initIndexDb } = await import('./indexing/fts.js');
1685
- const { indexCodebase } = await import('./indexing/indexer.js');
1686
- const db = initIndexDb(indexDbPath);
1687
- const result = await indexCodebase(db, process.cwd(), projectHash, {
1688
- exclude: config.indexing.exclude,
1689
- });
1690
- if (result.indexedFiles > 0) {
1691
- console.log(pc.gray(` 📚 Re-indexed ${result.indexedFiles} changed file(s)`));
1692
- }
1693
- toolContext.indexDb = db;
1769
+ const { initIndexDb } = await import('./indexing/fts.js');
1770
+ const { indexCodebase } = await import('./indexing/indexer.js');
1771
+ const db = initIndexDb(indexDbPath);
1772
+ const result = await indexCodebase(db, process.cwd(), projectHash, {
1773
+ exclude: config.indexing.exclude,
1774
+ });
1775
+ if (result.indexedFiles > 0) {
1776
+ console.log(pc.cyan(` [INDEX] Indexed ${result.indexedFiles} file(s) (${result.skippedFiles} unchanged)`));
1777
+ }
1778
+ if (result.errors.length > 0) {
1779
+ console.log(pc.yellow(` [WARN] ${result.errors.length} file(s) had index errors`));
1694
1780
  }
1781
+ toolContext.indexDb = db;
1695
1782
  }
1696
1783
  catch (err) {
1697
- console.error(pc.yellow(` Auto-index failed: ${err.message}`));
1784
+ console.error(pc.yellow(` [WARN] Auto-index failed: ${err.message}`));
1698
1785
  }
1699
1786
  })();
1700
1787
  }, 100);