dsh-codebase-chat 0.25.3 → 0.25.6

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/cli.js CHANGED
@@ -1623,6 +1623,10 @@ function formatHealthReport(r, lang = "fr") {
1623
1623
  };
1624
1624
  const out = [];
1625
1625
  out.push(`== ${t2.title} \u2014 ${basename(r.projectPath)} ==`);
1626
+ if (r.analyzedFiles === 0) {
1627
+ out.push(lang === "en" ? "No code files detected in this project \u2014 check the path or your .codebase-chat.json ignore rules." : "Aucun fichier de code d\xE9tect\xE9 dans ce projet \u2014 v\xE9rifie le chemin ou les r\xE8gles ignore de .codebase-chat.json.");
1628
+ return out.join("\n");
1629
+ }
1626
1630
  out.push(`${t2.score}: ${r.score}/100 (${r.grade}) \xB7 ${r.analyzedFiles} ${t2.files} \xB7 ${r.importEdges} ${t2.edges}`);
1627
1631
  out.push("");
1628
1632
  out.push(`\u25CF ${t2.cycles} (${r.cycles.length})`);
@@ -2566,8 +2570,16 @@ function loadLocalModel() {
2566
2570
  throw new Error(`Local model not found: ${spec}`);
2567
2571
  }
2568
2572
  const { getLlama } = await importLlama();
2569
- const llama = await getLlama();
2570
- return llama.loadModel({ modelPath });
2573
+ const cpuOnly = /^(0|off|false|cpu)$/i.test(process.env.CODEBASE_LOCAL_GPU || "");
2574
+ const llama = await getLlama(cpuOnly ? { gpu: false } : {});
2575
+ try {
2576
+ return await llama.loadModel({ modelPath });
2577
+ } catch (err) {
2578
+ if (cpuOnly) throw err;
2579
+ console.error(`[local-llm] GPU load failed (${err instanceof Error ? err.message : err}) \u2014 falling back to CPU. Slower, but works. Set CODEBASE_LOCAL_GPU=off to skip GPU entirely.`);
2580
+ const cpuLlama = await getLlama({ gpu: false });
2581
+ return cpuLlama.loadModel({ modelPath });
2582
+ }
2571
2583
  })();
2572
2584
  modelPromise.catch(() => {
2573
2585
  modelPromise = null;
@@ -2584,13 +2596,42 @@ async function callLocalLlm(prompt, lang = "fr") {
2584
2596
  contextSequence: context.getSequence(),
2585
2597
  systemPrompt: lang === "en" ? "You are a senior codebase analyst. Be precise and cite files with [source: path:line]." : "Tu es un analyste codebase senior. Sois precis et cite les fichiers avec [source: chemin:ligne]."
2586
2598
  });
2587
- return await session.prompt(prompt, { temperature: 0.2, maxTokens: LOCAL_MAX_TOKENS });
2599
+ return await session.prompt(prompt, {
2600
+ temperature: 0.2,
2601
+ maxTokens: LOCAL_MAX_TOKENS,
2602
+ // Small models degenerate into loops at low temperature — penalize
2603
+ // repeated tokens so the answer moves forward instead of echoing.
2604
+ repeatPenalty: { lastTokens: 128, penalty: 1.2, penalizeNewLine: false }
2605
+ });
2588
2606
  } finally {
2589
2607
  await context.dispose();
2590
2608
  }
2591
2609
  }
2592
2610
 
2593
2611
  // src/cli.ts
2612
+ var ANSI = { reset: "\x1B[0m", bold: "\x1B[1m", dim: "\x1B[2m", cyan: "\x1B[36m", yellow: "\x1B[33m", magenta: "\x1B[35m", green: "\x1B[32m" };
2613
+ function renderAnswerTerminal(md) {
2614
+ let inCode = false;
2615
+ const inline = (s) => s.replace(/\*\*([^*]+)\*\*/g, `${ANSI.bold}$1${ANSI.reset}`).replace(/`([^`]+)`/g, `${ANSI.cyan}$1${ANSI.reset}`).replace(/\[source: ([^\]]+)\]/g, `${ANSI.dim}${ANSI.cyan}[source: $1]${ANSI.reset}`);
2616
+ return md.split("\n").map((line) => {
2617
+ if (/^\s*```/.test(line)) {
2618
+ inCode = !inCode;
2619
+ return `${ANSI.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${ANSI.reset}`;
2620
+ }
2621
+ if (inCode) return `${ANSI.dim} ${line}${ANSI.reset}`;
2622
+ const h = line.match(/^(#{1,6})\s+(.*)/);
2623
+ if (h) {
2624
+ const color = h[1].length <= 2 ? `${ANSI.bold}${ANSI.magenta}` : `${ANSI.bold}${ANSI.yellow}`;
2625
+ return `
2626
+ ${color}${h[2]}${ANSI.reset}`;
2627
+ }
2628
+ const bullet = line.match(/^(\s*)([-*•]|\d+\.)\s+(.*)/);
2629
+ if (bullet) return `${bullet[1]}${ANSI.cyan}${bullet[2]}${ANSI.reset} ${inline(bullet[3])}`;
2630
+ if (/^\s*>/.test(line)) return `${ANSI.dim}${ANSI.green}\u2502${ANSI.reset}${ANSI.dim} ${inline(line.replace(/^\s*>\s?/, ""))}${ANSI.reset}`;
2631
+ if (/^\s*(---+|\*\*\*+)\s*$/.test(line)) return `${ANSI.dim}${"\u2500".repeat(60)}${ANSI.reset}`;
2632
+ return inline(line);
2633
+ }).join("\n");
2634
+ }
2594
2635
  var ExitSignal = class {
2595
2636
  constructor(code) {
2596
2637
  this.code = code;
@@ -2813,7 +2854,8 @@ ${lang === "en" ? "Answer" : "R\xE9ponse"} :` : buildToolPrompt(tool, {
2813
2854
  }
2814
2855
  if (values.local || isLocalLlmEnabled()) {
2815
2856
  console.error(lang === "en" ? "Answering with the embedded local model (first run downloads ~1 GB)..." : "R\xE9ponse via le mod\xE8le local embarqu\xE9 (premier lancement : ~1 Go de t\xE9l\xE9chargement)...");
2816
- console.log(await callLocalLlm(prompt, lang));
2857
+ const answer = await callLocalLlm(prompt, lang);
2858
+ console.log(process.stdout.isTTY ? renderAnswerTerminal(answer) : answer);
2817
2859
  exit(0);
2818
2860
  }
2819
2861
  if (values.call) {
@@ -2844,7 +2886,8 @@ ${lang === "en" ? "Answer" : "R\xE9ponse"} :` : buildToolPrompt(tool, {
2844
2886
  exit(1);
2845
2887
  }
2846
2888
  const data = await res.json();
2847
- console.log(data.choices?.[0]?.message?.content || "");
2889
+ const answer = data.choices?.[0]?.message?.content || "";
2890
+ console.log(process.stdout.isTTY ? renderAnswerTerminal(answer) : answer);
2848
2891
  exit(0);
2849
2892
  }
2850
2893
  console.log(prompt);
@@ -2869,7 +2912,7 @@ ${lang === "en" ? "Answer" : "R\xE9ponse"} :` : buildToolPrompt(tool, {
2869
2912
  exit(0);
2870
2913
  }
2871
2914
  printHelp();
2872
- exit(1);
2915
+ exit(0);
2873
2916
  }
2874
2917
  main().catch((err) => {
2875
2918
  disposeTreeSitter();