@orquentra/codebuddy 2.4.1 → 2.6.0

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.
Files changed (61) hide show
  1. package/README.md +25 -2
  2. package/dist/{chunk-ITGRANYW.js → chunk-6MKEIDX5.js} +2 -2
  3. package/dist/{chunk-K63EOA4B.js → chunk-776EQXDV.js} +25 -6
  4. package/dist/chunk-776EQXDV.js.map +1 -0
  5. package/dist/chunk-JFDRBPYF.js +99 -0
  6. package/dist/chunk-JFDRBPYF.js.map +1 -0
  7. package/dist/chunk-JPHJBCZB.js +99 -0
  8. package/dist/chunk-JPHJBCZB.js.map +1 -0
  9. package/dist/{chunk-IBMJYIEP.js → chunk-L5AYZC3F.js} +7 -7
  10. package/dist/{chunk-DSN5CEHO.js → chunk-N34XHWBA.js} +3 -3
  11. package/dist/{chunk-3ZELDCR3.js → chunk-PCE4STAY.js} +7 -7
  12. package/dist/{chunk-TI5TQD6D.js → chunk-QDBDYCM2.js} +2 -2
  13. package/dist/{chunk-5OWZVZSO.js → chunk-SYWJG5NA.js} +8 -165
  14. package/dist/chunk-SYWJG5NA.js.map +1 -0
  15. package/dist/chunk-U4HAJLIB.js +91 -0
  16. package/dist/chunk-U4HAJLIB.js.map +1 -0
  17. package/dist/{chunk-IE2OUO54.js → chunk-UFZD3JAA.js} +3 -3
  18. package/dist/chunk-UN57FKQX.js +4033 -0
  19. package/dist/chunk-UN57FKQX.js.map +1 -0
  20. package/dist/chunk-VAKBJV26.js +5112 -0
  21. package/dist/chunk-VAKBJV26.js.map +1 -0
  22. package/dist/chunk-XBJQKCLU.js +103 -0
  23. package/dist/chunk-XBJQKCLU.js.map +1 -0
  24. package/dist/cli/index.js +2 -2
  25. package/dist/cloud-7WM3YLI4.js +17 -0
  26. package/dist/{codebuddy-D2xJ6hFk.d.ts → codebuddy-DM-i0Fu5.d.ts} +49 -18
  27. package/dist/{config-S2TULOMK.js → config-KFEHLV6W.js} +3 -3
  28. package/dist/index.d.ts +23 -3
  29. package/dist/index.js +34 -25
  30. package/dist/langgraph/index.d.ts +1 -1
  31. package/dist/lean-command-C5YPFKHP.js +15 -0
  32. package/dist/local-OTOY56B4.js +13 -0
  33. package/dist/{main-BFDVQHKO.js → main-HFHLGID4.js} +1410 -245
  34. package/dist/main-HFHLGID4.js.map +1 -0
  35. package/dist/{memory-NIBFAMY4.js → memory-4RGVLPKG.js} +6 -6
  36. package/dist/operations-LOBAQRLO.js +24 -0
  37. package/dist/operations-LOBAQRLO.js.map +1 -0
  38. package/dist/{rerank-Q7MIFNKA.js → rerank-3P3C2UHN.js} +4 -4
  39. package/dist/{summaries-TSE44KUQ.js → summaries-ZBV5IN7Y.js} +6 -6
  40. package/dist/summaries-ZBV5IN7Y.js.map +1 -0
  41. package/dist/{zvec-F27AR357.js → zvec-MAPSJQSR.js} +3 -3
  42. package/dist/zvec-MAPSJQSR.js.map +1 -0
  43. package/package.json +6 -2
  44. package/dist/chunk-5OWZVZSO.js.map +0 -1
  45. package/dist/chunk-K63EOA4B.js.map +0 -1
  46. package/dist/chunk-OLFCOR4U.js +0 -8580
  47. package/dist/chunk-OLFCOR4U.js.map +0 -1
  48. package/dist/lean-command-D6TZJEFX.js +0 -13
  49. package/dist/main-BFDVQHKO.js.map +0 -1
  50. /package/dist/{chunk-ITGRANYW.js.map → chunk-6MKEIDX5.js.map} +0 -0
  51. /package/dist/{chunk-IBMJYIEP.js.map → chunk-L5AYZC3F.js.map} +0 -0
  52. /package/dist/{chunk-DSN5CEHO.js.map → chunk-N34XHWBA.js.map} +0 -0
  53. /package/dist/{chunk-3ZELDCR3.js.map → chunk-PCE4STAY.js.map} +0 -0
  54. /package/dist/{chunk-TI5TQD6D.js.map → chunk-QDBDYCM2.js.map} +0 -0
  55. /package/dist/{chunk-IE2OUO54.js.map → chunk-UFZD3JAA.js.map} +0 -0
  56. /package/dist/{config-S2TULOMK.js.map → cloud-7WM3YLI4.js.map} +0 -0
  57. /package/dist/{lean-command-D6TZJEFX.js.map → config-KFEHLV6W.js.map} +0 -0
  58. /package/dist/{summaries-TSE44KUQ.js.map → lean-command-C5YPFKHP.js.map} +0 -0
  59. /package/dist/{zvec-F27AR357.js.map → local-OTOY56B4.js.map} +0 -0
  60. /package/dist/{memory-NIBFAMY4.js.map → memory-4RGVLPKG.js.map} +0 -0
  61. /package/dist/{rerank-Q7MIFNKA.js.map → rerank-3P3C2UHN.js.map} +0 -0
@@ -1,7 +1,16 @@
1
+ import {
2
+ loadLlmSummaries,
3
+ summarizeRepository
4
+ } from "./chunk-PCE4STAY.js";
5
+ import {
6
+ chatJson,
7
+ coolingDown,
8
+ usageToday
9
+ } from "./chunk-UFZD3JAA.js";
1
10
  import {
2
11
  renderLeanBlock,
3
12
  runLeanHookCommand
4
- } from "./chunk-DSN5CEHO.js";
13
+ } from "./chunk-N34XHWBA.js";
5
14
  import {
6
15
  runPreToolHookCommand
7
16
  } from "./chunk-WHZSVY2L.js";
@@ -11,7 +20,6 @@ import {
11
20
  writeStdout
12
21
  } from "./chunk-PYLH4MH5.js";
13
22
  import {
14
- MemoryFileStore,
15
23
  PlanFileStore,
16
24
  PlanLifecycle,
17
25
  ReviewStore,
@@ -19,43 +27,30 @@ import {
19
27
  WorklogStore,
20
28
  approveReviewItem,
21
29
  assessRisk,
22
- bootstrapDatabase,
23
30
  buildBeforeEditContext,
24
31
  buildBootstrapContext,
25
- createDatabaseClient,
26
- createRuntime,
27
32
  generateSuggestions,
28
- inspectNamespace,
29
- listFactsPage,
30
33
  migrateMemoryToFiles,
31
- pingDatabase,
34
+ openHookSemantics,
35
+ parseTraceLines,
36
+ readTraces,
32
37
  registerCodeBuddyTools,
33
38
  renderContextBlock,
34
39
  resolveRiskPaths,
35
40
  runCaptureHook,
36
41
  runContextHook,
37
- runDoctor,
38
- runMigrations,
39
42
  runResetHook,
40
43
  runStageHook,
44
+ runSyncHook,
41
45
  startMcpServer,
46
+ tracePath,
42
47
  watchRepository
43
- } from "./chunk-OLFCOR4U.js";
48
+ } from "./chunk-VAKBJV26.js";
49
+ import "./chunk-IHQM2ZDV.js";
44
50
  import {
45
51
  SessionCapsuleLedger,
46
- computeFactHash,
47
- estimateTokens,
48
- generateEntityId
49
- } from "./chunk-5OWZVZSO.js";
50
- import {
51
- DEFAULT_ZVEC_MODEL,
52
- buildZvecIndex,
53
- zvecStatus
54
- } from "./chunk-TI5TQD6D.js";
55
- import {
56
- loadLlmSummaries,
57
- summarizeRepository
58
- } from "./chunk-3ZELDCR3.js";
52
+ estimateTokens
53
+ } from "./chunk-SYWJG5NA.js";
59
54
  import {
60
55
  buildArchitectureMap,
61
56
  generateGraphifyGraph,
@@ -65,26 +60,53 @@ import {
65
60
  neighbours,
66
61
  queryMap,
67
62
  setupGraphify
68
- } from "./chunk-IBMJYIEP.js";
63
+ } from "./chunk-L5AYZC3F.js";
69
64
  import {
70
65
  extractSymbolTable,
71
66
  supportsSymbols
72
67
  } from "./chunk-PQK3GKZH.js";
73
- import "./chunk-IHQM2ZDV.js";
74
68
  import {
75
- chatJson,
76
- coolingDown,
77
- usageToday
78
- } from "./chunk-IE2OUO54.js";
69
+ MemoryFileStore,
70
+ bootstrapDatabase,
71
+ buildProviderFromConfig,
72
+ createDatabaseClient,
73
+ createRuntime,
74
+ embeddingSpaceKey,
75
+ inspectNamespace,
76
+ listFactsPage,
77
+ pingDatabase,
78
+ runDoctor,
79
+ runMigrations
80
+ } from "./chunk-UN57FKQX.js";
79
81
  import {
80
82
  redactPersisted
81
83
  } from "./chunk-GDJH7XJC.js";
84
+ import {
85
+ computeFactHash,
86
+ generateEntityId
87
+ } from "./chunk-XBJQKCLU.js";
88
+ import "./chunk-JFDRBPYF.js";
89
+ import {
90
+ modelCacheDir
91
+ } from "./chunk-U4HAJLIB.js";
92
+ import {
93
+ DEFAULT_CLOUD_EMBEDDING_MODELS,
94
+ DEFAULT_RERANK_MODELS,
95
+ LOCAL_EMBEDDING_MODEL,
96
+ getModelDefinition
97
+ } from "./chunk-JPHJBCZB.js";
98
+ import {
99
+ DEFAULT_ZVEC_MODEL,
100
+ buildZvecIndex,
101
+ zvecStatus
102
+ } from "./chunk-QDBDYCM2.js";
82
103
  import {
83
104
  resolveLlmConfig
84
- } from "./chunk-ITGRANYW.js";
105
+ } from "./chunk-6MKEIDX5.js";
85
106
  import {
86
107
  PLAN_POLICIES,
87
108
  configPath,
109
+ defaultNamespaceForCwd,
88
110
  initConfigFile,
89
111
  initProjectScaffold,
90
112
  loadPlanPolicy,
@@ -92,17 +114,18 @@ import {
92
114
  readConfigFile,
93
115
  redactSecrets,
94
116
  resolveNamespaceFrom,
117
+ setConfigBlock,
95
118
  setLlmEnabled,
96
119
  setPlanPolicy,
97
120
  setZvecEnabled,
98
121
  writeAtomic,
99
122
  writeConfigFile
100
- } from "./chunk-K63EOA4B.js";
123
+ } from "./chunk-776EQXDV.js";
101
124
  import "./chunk-PZ5AY32C.js";
102
125
 
103
126
  // src/cli/main.ts
104
127
  import { Command } from "commander";
105
- import pc20 from "picocolors";
128
+ import pc22 from "picocolors";
106
129
 
107
130
  // src/integrations/github.ts
108
131
  import { constants } from "fs";
@@ -611,9 +634,9 @@ function resolveCliEntrypoint() {
611
634
  }
612
635
  function buildEntry(options) {
613
636
  const env = {
614
- CODEBUDDY_NAMESPACE: options.namespace
637
+ CODEBUDDY_NAMESPACE: options.namespace,
638
+ CODEBUDDY_PROJECT_ROOT: resolve2(options.projectRoot ?? process.cwd())
615
639
  };
616
- if (options.projectRoot) env.CODEBUDDY_PROJECT_ROOT = options.projectRoot;
617
640
  if (options.hfToken) env.HF_TOKEN = options.hfToken;
618
641
  if (options.groqApiKey) env.GROQ_API_KEY = options.groqApiKey;
619
642
  if (options.databaseUrl) env.DATABASE_URL = options.databaseUrl;
@@ -2432,14 +2455,21 @@ async function runBeforeEdit(taskParts, opts) {
2432
2455
  resolveTokenBudget()
2433
2456
  ]);
2434
2457
  const task = taskParts.join(" ").trim();
2435
- return buildBeforeEditContext({
2436
- namespace,
2437
- ...task ? { task } : {},
2438
- ...opts.paths ? { paths: parsePaths2(opts.paths) } : {},
2439
- ...opts.plan ? { planId: opts.plan } : {},
2440
- ...opts.git !== void 0 ? { useGit: opts.git } : {},
2441
- ...tokenBudget !== void 0 ? { tokenBudget } : {}
2442
- });
2458
+ const semantics = task ? await openHookSemantics(process.cwd()) : null;
2459
+ try {
2460
+ return await buildBeforeEditContext({
2461
+ namespace,
2462
+ ...task ? { task } : {},
2463
+ ...semantics?.semanticRanker ? { semanticRanker: semantics.semanticRanker } : {},
2464
+ ...semantics?.reranker ? { reranker: semantics.reranker } : {},
2465
+ ...opts.paths ? { paths: parsePaths2(opts.paths) } : {},
2466
+ ...opts.plan ? { planId: opts.plan } : {},
2467
+ ...opts.git !== void 0 ? { useGit: opts.git } : {},
2468
+ ...tokenBudget !== void 0 ? { tokenBudget } : {}
2469
+ });
2470
+ } finally {
2471
+ await semantics?.close();
2472
+ }
2443
2473
  }
2444
2474
  function printBootstrap(ctx) {
2445
2475
  console.log(`${pc3.bold("project")} ${ctx.project.namespace} ${pc3.dim(ctx.project.root)}`);
@@ -2549,9 +2579,479 @@ function scoreColor(score) {
2549
2579
  return pc3.green(String(score));
2550
2580
  }
2551
2581
 
2552
- // src/cli/commands/eval.ts
2582
+ // src/cli/commands/dashboard.ts
2583
+ import { spawn as spawn2 } from "child_process";
2553
2584
  import pc4 from "picocolors";
2554
2585
 
2586
+ // src/dashboard/server.ts
2587
+ import { unwatchFile, watchFile } from "fs";
2588
+ import { open, stat } from "fs/promises";
2589
+ import { createServer } from "http";
2590
+ import { basename } from "path";
2591
+
2592
+ // src/dashboard/page.ts
2593
+ var DASHBOARD_HTML = `<!doctype html>
2594
+ <html lang="en">
2595
+ <head>
2596
+ <meta charset="utf-8">
2597
+ <meta name="viewport" content="width=device-width, initial-scale=1">
2598
+ <title>CodeBuddy Traces</title>
2599
+ <style>
2600
+ :root {
2601
+ --bg: #f3f5f7; --panel: #ffffff; --fg: #141a21; --muted: #5d6874; --line: #dde3e8;
2602
+ --recall: #0a8f80; --tool: #c2410c; --recall-soft: #e0f2ef; --tool-soft: #fbeae2;
2603
+ --bad: #b42318; --bad-soft: #fdecea; --capture: #4b5563;
2604
+ --sans: ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;
2605
+ --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
2606
+ }
2607
+ @media (prefers-color-scheme: dark) { :root {
2608
+ --bg: #0e1116; --panel: #161b22; --fg: #e6eaee; --muted: #94a0ad; --line: #28303a;
2609
+ --recall: #0d9488; --tool: #ea580c; --recall-soft: #10302c; --tool-soft: #3a1e12;
2610
+ --bad: #f97066; --bad-soft: #3b1715; --capture: #9aa4ae; color-scheme: dark } }
2611
+ * { box-sizing: border-box; }
2612
+ body { margin: 0; background: var(--bg); color: var(--fg); font: 14px/1.5 var(--sans); }
2613
+ .wrap { max-width: 1280px; margin: 0 auto; padding: 20px 20px 48px; display: grid; gap: 16px; }
2614
+ header { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
2615
+ h1 { font-size: 18px; margin: 0; letter-spacing: -.01em; }
2616
+ h1 small { font: 500 13px var(--mono); color: var(--muted); margin-left: 8px; }
2617
+ h2 { font-size: 13px; margin: 0 0 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); font-weight: 600; }
2618
+ .live { display: inline-flex; align-items: center; gap: 6px; font: 12px var(--mono); color: var(--muted); }
2619
+ .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
2620
+ .live.on .dot { background: var(--recall); box-shadow: 0 0 0 3px var(--recall-soft); }
2621
+ .spacer { flex: 1; }
2622
+ .seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--panel); }
2623
+ .seg button { border: 0; background: none; color: var(--muted); font: 500 12px var(--sans); padding: 6px 12px; cursor: pointer; }
2624
+ .seg button + button { border-left: 1px solid var(--line); }
2625
+ .seg button[aria-pressed="true"] { background: var(--fg); color: var(--bg); }
2626
+ .seg button:focus-visible { outline: 2px solid var(--recall); outline-offset: -2px; }
2627
+ .wrap > * { min-width: 0; }
2628
+ .kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
2629
+ .kpi { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
2630
+ .kpi .label { font-size: 12px; color: var(--muted); }
2631
+ .kpi .value { font: 600 24px/1.2 var(--mono); font-variant-numeric: tabular-nums; margin-top: 4px; }
2632
+ .kpi .sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
2633
+ .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; min-width: 0; }
2634
+ .cols { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 16px; }
2635
+ @media (max-width: 900px) { .cols { grid-template-columns: 1fr; } }
2636
+ .legend { display: flex; gap: 16px; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
2637
+ .sw { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 6px; vertical-align: -1px; }
2638
+ svg text { fill: var(--muted); font: 11px var(--mono); }
2639
+ .scroll { overflow-x: auto; }
2640
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
2641
+ th { text-align: left; font: 600 11px var(--sans); text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding: 6px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
2642
+ td { padding: 7px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
2643
+ tr:last-child td { border-bottom: 0; }
2644
+ td.n { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
2645
+ th.n { text-align: right; }
2646
+ .chip { display: inline-block; font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: .06em; padding: 2px 6px; border-radius: 4px; }
2647
+ .chip.recall { background: var(--recall-soft); color: var(--recall); }
2648
+ .chip.tool { background: var(--tool-soft); color: var(--tool); }
2649
+ .chip.capture, .chip.sync, .chip.kw { background: var(--line); color: var(--capture); }
2650
+ .chip.err { background: var(--bad-soft); color: var(--bad); }
2651
+ .feed { display: grid; gap: 10px; max-height: 520px; overflow: auto; }
2652
+ .prompt { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; }
2653
+ .prompt .q { font-weight: 500; overflow-wrap: anywhere; }
2654
+ .prompt .meta { font: 12px var(--mono); color: var(--muted); margin-top: 4px; display: flex; flex-wrap: wrap; gap: 4px 12px; overflow-wrap: anywhere; }
2655
+ .prompt ul { margin: 6px 0 0; padding-left: 18px; font-size: 13px; }
2656
+ .prompt .none { font-size: 13px; color: var(--muted); margin-top: 6px; }
2657
+ .mono { font-family: var(--mono); }
2658
+ .muted { color: var(--muted); }
2659
+ .empty { padding: 40px 16px; text-align: center; color: var(--muted); }
2660
+ .empty code { font-family: var(--mono); background: var(--line); padding: 2px 6px; border-radius: 4px; color: var(--fg); }
2661
+ .log { max-height: 420px; overflow: auto; }
2662
+ @media (prefers-reduced-motion: no-preference) { tr.fresh td { animation: flash 1.2s ease-out; } }
2663
+ @keyframes flash { from { background: var(--recall-soft); } to { background: transparent; } }
2664
+ </style>
2665
+ </head>
2666
+ <body>
2667
+ <div class="wrap">
2668
+ <header>
2669
+ <h1>CodeBuddy traces<small id="project"></small></h1>
2670
+ <span class="live" id="live"><span class="dot"></span><span id="liveText">connecting</span></span>
2671
+ <span class="spacer"></span>
2672
+ <div class="seg" role="group" aria-label="Time range" id="range">
2673
+ <button data-range="3600000">1 hour</button>
2674
+ <button data-range="86400000" aria-pressed="true">24 hours</button>
2675
+ <button data-range="604800000">7 days</button>
2676
+ <button data-range="0">All</button>
2677
+ </div>
2678
+ </header>
2679
+
2680
+ <section class="kpis" id="kpis" aria-label="Summary"></section>
2681
+
2682
+ <section class="panel">
2683
+ <h2>Tokens delivered to the agent</h2>
2684
+ <div class="legend"><span><span class="sw" style="background:var(--recall)"></span>prompt recall (hook)</span><span><span class="sw" style="background:var(--tool)"></span>MCP tool results</span></div>
2685
+ <div class="scroll" id="chart"></div>
2686
+ </section>
2687
+
2688
+ <div class="cols">
2689
+ <section class="panel">
2690
+ <h2>Recent prompts and what was recalled</h2>
2691
+ <div class="feed" id="feed"></div>
2692
+ </section>
2693
+ <section class="panel">
2694
+ <h2>MCP tools</h2>
2695
+ <div class="scroll" id="tools"></div>
2696
+ </section>
2697
+ </div>
2698
+
2699
+ <section class="panel">
2700
+ <h2>Activity</h2>
2701
+ <div class="log scroll" id="log"></div>
2702
+ </section>
2703
+ <p class="muted" style="margin:0;font-size:12px;overflow-wrap:anywhere">Token counts are cl100k estimates of the text exchanged. Traces stay on this machine in <span class="mono" id="path"></span>. Turn them off with <span class="mono">CODEBUDDY_TRACE=0</span>.</p>
2704
+ </div>
2705
+ <script>
2706
+ (function () {
2707
+ var events = [];
2708
+ var range = 86400000;
2709
+ var fresh = new Set();
2710
+ var pending = false;
2711
+
2712
+ function esc(value) {
2713
+ return String(value == null ? "" : value).replace(/[&<>"']/g, function (c) {
2714
+ return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
2715
+ });
2716
+ }
2717
+ function num(n) { return Math.round(n).toLocaleString(); }
2718
+ function compact(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + "M" : n >= 1e4 ? (n / 1e3).toFixed(1) + "k" : num(n); }
2719
+ function pct(sorted, p) { if (!sorted.length) return 0; return sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))]; }
2720
+ function ms(n) { return n >= 1000 ? (n / 1000).toFixed(1) + " s" : Math.round(n) + " ms"; }
2721
+ function ago(ts) {
2722
+ var s = Math.max(0, (Date.now() - Date.parse(ts)) / 1000);
2723
+ return s < 60 ? Math.floor(s) + "s ago" : s < 3600 ? Math.floor(s / 60) + "m ago" : s < 86400 ? Math.floor(s / 3600) + "h ago" : Math.floor(s / 86400) + "d ago";
2724
+ }
2725
+ function visible() {
2726
+ if (!range) return events;
2727
+ var since = Date.now() - range;
2728
+ return events.filter(function (e) { return Date.parse(e.ts) >= since; });
2729
+ }
2730
+
2731
+ function kpis(list) {
2732
+ var recalls = list.filter(function (e) { return e.kind === "recall"; });
2733
+ var tools = list.filter(function (e) { return e.kind === "tool"; });
2734
+ var delivered = list.reduce(function (s, e) { return s + (e.kind === "recall" || e.kind === "tool" ? e.tokensOut || 0 : 0); }, 0);
2735
+ var withFacts = recalls.filter(function (e) { return e.detail && e.detail.facts && e.detail.facts.length; });
2736
+ var factCount = withFacts.reduce(function (s, e) { return s + e.detail.facts.length; }, 0);
2737
+ var dense = recalls.reduce(function (s, e) { return s + ((e.detail && e.detail.dense) || 0); }, 0);
2738
+ var lat = recalls.map(function (e) { return e.ms; }).sort(function (a, b) { return a - b; });
2739
+ var errors = list.filter(function (e) { return !e.ok; }).length;
2740
+ var captures = list.filter(function (e) { return e.kind === "capture"; });
2741
+ var saved = captures.reduce(function (s, e) { return s + ((e.detail && e.detail.saved) || 0); }, 0);
2742
+ var tiles = [
2743
+ ["Prompts recalled", num(recalls.length), recalls.length ? Math.round(100 * withFacts.length / recalls.length) + "% got facts" : "no prompts yet"],
2744
+ ["Facts injected", num(factCount), factCount ? Math.round(100 * dense / factCount) + "% matched by meaning" : "-"],
2745
+ ["Tokens delivered", compact(delivered), recalls.length + tools.length ? "\u2248 " + num(delivered / (recalls.length + tools.length)) + " per event" : "-"],
2746
+ ["MCP tool calls", num(tools.length), tools.length ? new Set(tools.map(function (e) { return e.name; })).size + " distinct tools" : "-"],
2747
+ ["Recall latency", lat.length ? ms(pct(lat, 0.5)) : "-", lat.length ? "p95 " + ms(pct(lat, 0.95)) : "-"],
2748
+ ["Memories saved", num(saved), captures.length + " capture" + (captures.length === 1 ? "" : "s")],
2749
+ ["Errors", num(errors), errors ? "see Activity" : "all good"],
2750
+ ];
2751
+ document.getElementById("kpis").innerHTML = tiles.map(function (t) {
2752
+ return '<div class="kpi"><div class="label">' + esc(t[0]) + '</div><div class="value">' + esc(t[1]) + '</div><div class="sub">' + esc(t[2]) + "</div></div>";
2753
+ }).join("");
2754
+ }
2755
+
2756
+ function chart(list) {
2757
+ var el = document.getElementById("chart");
2758
+ var points = list.filter(function (e) { return (e.kind === "recall" || e.kind === "tool") && e.tokensOut; });
2759
+ if (!points.length) { el.innerHTML = '<div class="empty">No tokens delivered in this range yet.</div>'; return; }
2760
+ var end = Date.now();
2761
+ // Zoom to the data: a few minutes of traces should not be one sliver of a 24-hour axis.
2762
+ var first = Date.parse(points[0].ts);
2763
+ var start = Math.max(range ? end - range : first, first - 60000);
2764
+ var buckets = 48, size = Math.max(10000, (end - start) / buckets);
2765
+ buckets = Math.ceil((end - start) / size);
2766
+ var rows = [];
2767
+ for (var i = 0; i < buckets; i++) rows.push({ recall: 0, tool: 0, n: 0 });
2768
+ points.forEach(function (e) {
2769
+ var i = Math.min(buckets - 1, Math.max(0, Math.floor((Date.parse(e.ts) - start) / size)));
2770
+ rows[i][e.kind] += e.tokensOut; rows[i].n += 1;
2771
+ });
2772
+ var max = Math.max.apply(null, rows.map(function (r) { return r.recall + r.tool; })) || 1;
2773
+ var W = 960, H = 200, L = 52, B = 22, T = 8, plotW = W - L - 8, plotH = H - B - T;
2774
+ var bw = plotW / buckets;
2775
+ var step = Math.pow(10, Math.floor(Math.log10(max)));
2776
+ var tick = max / step > 5 ? step * 2 : step;
2777
+ var svg = '<svg viewBox="0 0 ' + W + " " + H + '" style="width:100%;min-width:560px;height:auto" role="img" aria-label="Tokens delivered over time">';
2778
+ for (var v = 0; v <= max; v += tick) {
2779
+ var y = T + plotH - (v / max) * plotH;
2780
+ svg += '<line x1="' + L + '" x2="' + (W - 8) + '" y1="' + y + '" y2="' + y + '" stroke="var(--line)"/><text x="' + (L - 6) + '" y="' + (y + 4) + '" text-anchor="end">' + compact(v) + "</text>";
2781
+ }
2782
+ rows.forEach(function (r, i) {
2783
+ var x = L + i * bw + 1, w = Math.max(1, bw - 2);
2784
+ var hr = (r.recall / max) * plotH, ht = (r.tool / max) * plotH;
2785
+ var when = new Date(start + i * size);
2786
+ var label = when.toLocaleString() + ": " + num(r.recall) + " recall + " + num(r.tool) + " tool tokens (" + r.n + " events)";
2787
+ if (r.recall) svg += '<rect x="' + x + '" y="' + (T + plotH - hr) + '" width="' + w + '" height="' + hr + '" rx="2" fill="var(--recall)"><title>' + esc(label) + "</title></rect>";
2788
+ if (r.tool) svg += '<rect x="' + x + '" y="' + (T + plotH - hr - ht - (r.recall ? 2 : 0)) + '" width="' + w + '" height="' + ht + '" rx="2" fill="var(--tool)"><title>' + esc(label) + "</title></rect>";
2789
+ });
2790
+ [0, Math.floor(buckets / 2), buckets - 1].forEach(function (i) {
2791
+ var d = new Date(start + i * size);
2792
+ var text = end - start <= 86400000 ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString([], { month: "short", day: "numeric" });
2793
+ svg += '<text x="' + (L + i * bw + bw / 2) + '" y="' + (H - 6) + '" text-anchor="middle">' + esc(text) + "</text>";
2794
+ });
2795
+ el.innerHTML = svg + "</svg>";
2796
+ }
2797
+
2798
+ function feed(list) {
2799
+ var recalls = list.filter(function (e) { return e.kind === "recall"; }).slice(-30).reverse();
2800
+ var el = document.getElementById("feed");
2801
+ if (!recalls.length) {
2802
+ el.innerHTML = '<div class="empty">No prompts yet. Run <code>codebuddy hooks install</code>, restart your agent, and send a prompt.</div>';
2803
+ return;
2804
+ }
2805
+ el.innerHTML = recalls.map(function (e) {
2806
+ var d = e.detail || {};
2807
+ var facts = d.facts || [];
2808
+ return '<div class="prompt"><div class="q">' + (esc(d.prompt) || '<span class="muted">(no prompt text)</span>') + '</div>' +
2809
+ '<div class="meta"><span>' + ago(e.ts) + "</span><span>" + ms(e.ms) + "</span><span>\u2248" + num(e.tokensOut || 0) + " tokens injected</span>" +
2810
+ (d.dense ? '<span class="chip recall">' + d.dense + " by meaning</span>" : "") +
2811
+ (d.lexical ? '<span class="chip kw">' + d.lexical + " by keyword</span>" : "") +
2812
+ (e.ok ? "" : '<span class="chip err">' + esc(d.dropped || "dropped") + "</span>") + "</div>" +
2813
+ (facts.length ? "<ul>" + facts.map(function (f) { return "<li>" + esc(f) + "</li>"; }).join("") + "</ul>" : '<div class="none">No new facts: nothing matched, or already sent this session.</div>') +
2814
+ (d.files && d.files.length ? '<div class="meta">files: ' + d.files.map(esc).join(", ") + "</div>" : "") +
2815
+ "</div>";
2816
+ }).join("");
2817
+ }
2818
+
2819
+ function tools(list) {
2820
+ var by = {};
2821
+ list.filter(function (e) { return e.kind === "tool"; }).forEach(function (e) {
2822
+ var t = by[e.name] || (by[e.name] = { calls: 0, errors: 0, lat: [], out: 0 });
2823
+ t.calls++; if (!e.ok) t.errors++; t.lat.push(e.ms); t.out += e.tokensOut || 0;
2824
+ });
2825
+ var names = Object.keys(by).sort(function (a, b) { return by[b].calls - by[a].calls; });
2826
+ var el = document.getElementById("tools");
2827
+ if (!names.length) { el.innerHTML = '<div class="empty">No MCP tool calls in this range.</div>'; return; }
2828
+ el.innerHTML = '<table><thead><tr><th>Tool</th><th class="n">Calls</th><th class="n">p50</th><th class="n">p95</th><th class="n">Tokens out</th><th class="n">Errors</th></tr></thead><tbody>' +
2829
+ names.map(function (n) {
2830
+ var t = by[n], lat = t.lat.sort(function (a, b) { return a - b; });
2831
+ return '<tr><td class="mono">' + esc(n) + '</td><td class="n">' + t.calls + '</td><td class="n">' + ms(pct(lat, 0.5)) + '</td><td class="n">' + ms(pct(lat, 0.95)) + '</td><td class="n">' + compact(t.out) + '</td><td class="n">' + (t.errors ? '<span class="chip err">' + t.errors + "</span>" : "0") + "</td></tr>";
2832
+ }).join("") + "</tbody></table>";
2833
+ }
2834
+
2835
+ function summary(e) {
2836
+ var d = e.detail || {};
2837
+ if (e.kind === "recall") return (d.facts ? d.facts.length : 0) + " fact(s), " + (d.files ? d.files.length : 0) + " file(s)";
2838
+ if (e.kind === "capture") return (d.saved || 0) + " saved, " + (d.queued || 0) + " queued, " + (d.changedFiles || 0) + " changed file(s)";
2839
+ if (e.kind === "sync") return (d.imported || 0) + " new, " + (d.updated || 0) + " updated" + (d.reembedding ? ", " + d.reembedding + " re-embedding" : "");
2840
+ return e.session ? "session " + e.session.slice(0, 12) : "";
2841
+ }
2842
+
2843
+ function log(list) {
2844
+ var rows = list.slice(-200).reverse();
2845
+ var el = document.getElementById("log");
2846
+ if (!rows.length) { el.innerHTML = '<div class="empty">Nothing recorded in this range.</div>'; return; }
2847
+ el.innerHTML = '<table><thead><tr><th>When</th><th>Kind</th><th>Name</th><th>Details</th><th class="n">Time</th><th class="n">In</th><th class="n">Out</th></tr></thead><tbody>' +
2848
+ rows.map(function (e) {
2849
+ var key = e.ts + e.name;
2850
+ return '<tr class="' + (fresh.has(key) ? "fresh" : "") + '"><td class="mono muted" title="' + esc(e.ts) + '">' + ago(e.ts) + '</td><td><span class="chip ' + (e.ok ? esc(e.kind) : "err") + '">' + esc(e.ok ? e.kind : e.kind + " failed") + '</span></td><td class="mono">' + esc(e.name) + "</td><td>" + esc(summary(e)) + '</td><td class="n">' + ms(e.ms) + '</td><td class="n">' + num(e.tokensIn || 0) + '</td><td class="n">' + num(e.tokensOut || 0) + "</td></tr>";
2851
+ }).join("") + "</tbody></table>";
2852
+ fresh.clear();
2853
+ }
2854
+
2855
+ function render() {
2856
+ pending = false;
2857
+ var list = visible();
2858
+ kpis(list); chart(list); feed(list); tools(list); log(list);
2859
+ }
2860
+ function schedule() { if (!pending) { pending = true; requestAnimationFrame(render); } }
2861
+
2862
+ function load() {
2863
+ return fetch("/api/traces?limit=10000").then(function (r) { return r.json(); }).then(function (data) {
2864
+ events = data.events || [];
2865
+ document.getElementById("project").textContent = data.project || "";
2866
+ document.getElementById("path").textContent = data.path || "";
2867
+ document.title = "CodeBuddy Traces \xB7 " + (data.project || "");
2868
+ schedule();
2869
+ });
2870
+ }
2871
+
2872
+ document.getElementById("range").addEventListener("click", function (event) {
2873
+ var button = event.target.closest("button");
2874
+ if (!button) return;
2875
+ range = Number(button.dataset.range);
2876
+ this.querySelectorAll("button").forEach(function (b) { b.setAttribute("aria-pressed", String(b === button)); });
2877
+ schedule();
2878
+ });
2879
+
2880
+ function connect() {
2881
+ var live = document.getElementById("live"), text = document.getElementById("liveText");
2882
+ var source = new EventSource("/events");
2883
+ source.onopen = function () { live.classList.add("on"); text.textContent = "live"; };
2884
+ source.onerror = function () { live.classList.remove("on"); text.textContent = "reconnecting"; };
2885
+ source.addEventListener("trace", function (message) {
2886
+ var e = JSON.parse(message.data);
2887
+ events.push(e); fresh.add(e.ts + e.name);
2888
+ if (events.length > 10000) events = events.slice(-10000);
2889
+ schedule();
2890
+ });
2891
+ source.addEventListener("reset", function () { load(); });
2892
+ }
2893
+
2894
+ load().then(connect);
2895
+ // Relative times ("2m ago") keep moving even when nothing new arrives.
2896
+ setInterval(schedule, 30000);
2897
+ })();
2898
+ </script>
2899
+ </body>
2900
+ </html>`;
2901
+
2902
+ // src/dashboard/server.ts
2903
+ async function startDashboard(options) {
2904
+ const { repositoryRoot } = options;
2905
+ const path = tracePath(repositoryRoot);
2906
+ const clients = /* @__PURE__ */ new Set();
2907
+ let offset = (await stat(path).catch(() => null))?.size ?? 0;
2908
+ let reading = false;
2909
+ const onChange = async () => {
2910
+ if (reading) return;
2911
+ reading = true;
2912
+ try {
2913
+ const size = (await stat(path).catch(() => null))?.size ?? 0;
2914
+ if (size < offset) {
2915
+ offset = 0;
2916
+ broadcast("reset", "{}");
2917
+ }
2918
+ if (size > offset) {
2919
+ const handle = await open(path, "r");
2920
+ try {
2921
+ const buffer = Buffer.alloc(size - offset);
2922
+ await handle.read(buffer, 0, buffer.length, offset);
2923
+ const text3 = buffer.toString("utf8");
2924
+ const complete = text3.slice(0, text3.lastIndexOf("\n") + 1);
2925
+ offset += Buffer.byteLength(complete);
2926
+ for (const event of parseTraceLines(complete)) broadcast("trace", JSON.stringify(event));
2927
+ } finally {
2928
+ await handle.close();
2929
+ }
2930
+ }
2931
+ } finally {
2932
+ reading = false;
2933
+ }
2934
+ };
2935
+ const broadcast = (event, data) => {
2936
+ for (const client of clients) client.write(`event: ${event}
2937
+ data: ${data}
2938
+
2939
+ `);
2940
+ };
2941
+ watchFile(path, { interval: options.pollMs ?? 1e3 }, () => void onChange());
2942
+ const server = createServer(async (request, response) => {
2943
+ const url = new URL(request.url ?? "/", "http://localhost");
2944
+ if (request.method !== "GET") {
2945
+ response.writeHead(405).end();
2946
+ return;
2947
+ }
2948
+ if (url.pathname === "/") {
2949
+ response.writeHead(200, {
2950
+ "content-type": "text/html; charset=utf-8",
2951
+ "cache-control": "no-store"
2952
+ });
2953
+ response.end(DASHBOARD_HTML);
2954
+ return;
2955
+ }
2956
+ if (url.pathname === "/api/traces") {
2957
+ const limit = Math.min(1e4, Math.max(1, Number(url.searchParams.get("limit")) || 2e3));
2958
+ response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
2959
+ response.end(
2960
+ JSON.stringify({
2961
+ project: basename(repositoryRoot),
2962
+ path,
2963
+ events: await readTraces(repositoryRoot, limit)
2964
+ })
2965
+ );
2966
+ return;
2967
+ }
2968
+ if (url.pathname === "/events") {
2969
+ response.writeHead(200, {
2970
+ "content-type": "text/event-stream",
2971
+ "cache-control": "no-store",
2972
+ connection: "keep-alive"
2973
+ });
2974
+ response.write(": connected\n\n");
2975
+ clients.add(response);
2976
+ request.on("close", () => clients.delete(response));
2977
+ return;
2978
+ }
2979
+ response.writeHead(404, { "content-type": "text/plain" }).end("Not found");
2980
+ });
2981
+ const port = await listen(server, options.port ?? 4777);
2982
+ const heartbeat = setInterval(() => {
2983
+ for (const client of clients) client.write(": ping\n\n");
2984
+ }, 25e3);
2985
+ heartbeat.unref();
2986
+ return {
2987
+ url: `http://127.0.0.1:${port}/`,
2988
+ close: () => new Promise((resolve14) => {
2989
+ clearInterval(heartbeat);
2990
+ unwatchFile(path);
2991
+ for (const client of clients) client.end();
2992
+ server.closeAllConnections();
2993
+ server.close(() => resolve14());
2994
+ })
2995
+ };
2996
+ }
2997
+ async function listen(server, port) {
2998
+ for (let attempt = 0; attempt < 10; attempt += 1) {
2999
+ const candidate = port === 0 ? 0 : port + attempt;
3000
+ try {
3001
+ await new Promise((resolve14, reject) => {
3002
+ server.once("error", reject);
3003
+ server.listen(candidate, "127.0.0.1", () => {
3004
+ server.off("error", reject);
3005
+ resolve14();
3006
+ });
3007
+ });
3008
+ return server.address().port;
3009
+ } catch (error) {
3010
+ if (error.code !== "EADDRINUSE" || port === 0) throw error;
3011
+ }
3012
+ }
3013
+ throw new Error(`Ports ${port}-${port + 9} are in use; pass --port.`);
3014
+ }
3015
+
3016
+ // src/cli/commands/dashboard.ts
3017
+ function openBrowser(url) {
3018
+ const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
3019
+ try {
3020
+ spawn2(command, args, { detached: true, stdio: "ignore" }).on("error", () => void 0).unref();
3021
+ } catch {
3022
+ }
3023
+ }
3024
+ function registerDashboardCommand(program, runSafely2) {
3025
+ program.command("dashboard").description("Open a live local page of prompts recalled, MCP tool calls, tokens and latency.").option(
3026
+ "--port <port>",
3027
+ "Port on 127.0.0.1 (the next free one is used if taken).",
3028
+ Number,
3029
+ 4777
3030
+ ).option("--no-open", "Print the URL without opening a browser.").action(async (opts) => {
3031
+ await runSafely2(async () => {
3032
+ const root = process.cwd();
3033
+ const dashboard = await startDashboard({ repositoryRoot: root, port: opts.port });
3034
+ const recorded = (await readTraces(root)).length;
3035
+ console.log(`${pc4.green("\u25CF")} CodeBuddy dashboard ${pc4.bold(dashboard.url)}`);
3036
+ console.log(
3037
+ pc4.dim(
3038
+ recorded > 0 ? ` ${recorded} event(s) in ${tracePath(root)}; new ones appear live.` : " No traces yet: they appear as your agent uses CodeBuddy (hooks or MCP tools)."
3039
+ )
3040
+ );
3041
+ console.log(pc4.dim(" Ctrl+C to stop."));
3042
+ if (opts.open) openBrowser(dashboard.url);
3043
+ await new Promise((resolve14) => {
3044
+ const stop = () => void dashboard.close().then(resolve14);
3045
+ process.once("SIGINT", stop);
3046
+ process.once("SIGTERM", stop);
3047
+ });
3048
+ });
3049
+ });
3050
+ }
3051
+
3052
+ // src/cli/commands/eval.ts
3053
+ import pc5 from "picocolors";
3054
+
2555
3055
  // src/eval/runner.ts
2556
3056
  import { readFile as readFile12 } from "fs/promises";
2557
3057
  import { dirname as dirname10, isAbsolute, join as join14, resolve as resolve11 } from "path";
@@ -2622,17 +3122,17 @@ function registerEvalCommand(program, runSafely2) {
2622
3122
  }
2623
3123
  function printEvaluation(result) {
2624
3124
  console.log(
2625
- `${result.passed ? pc4.green("passed") : pc4.red("failed")} ${result.summary.passed}/${result.summary.total} evaluation cases`
3125
+ `${result.passed ? pc5.green("passed") : pc5.red("failed")} ${result.summary.passed}/${result.summary.total} evaluation cases`
2626
3126
  );
2627
3127
  for (const item of result.cases) {
2628
- console.log(` ${item.passed ? pc4.green("\u2713") : pc4.red("\xD7")} ${item.id}`);
2629
- if (!item.passed) for (const failure of item.failures) console.log(pc4.dim(` ${failure}`));
3128
+ console.log(` ${item.passed ? pc5.green("\u2713") : pc5.red("\xD7")} ${item.id}`);
3129
+ if (!item.passed) for (const failure of item.failures) console.log(pc5.dim(` ${failure}`));
2630
3130
  }
2631
- console.log(pc4.dim(`dataset: ${result.datasetPath}`));
3131
+ console.log(pc5.dim(`dataset: ${result.datasetPath}`));
2632
3132
  }
2633
3133
 
2634
3134
  // src/cli/commands/github.ts
2635
- import pc5 from "picocolors";
3135
+ import pc6 from "picocolors";
2636
3136
  function registerGitHubCommands(program, runSafely2) {
2637
3137
  const github = program.command("github").description("Install GitHub workflow integrations.");
2638
3138
  github.command("install").option("--force", "Replace an existing CodeBuddy workflow.").description("Install the PR change-safety report workflow.").action(async (opts) => {
@@ -2640,15 +3140,16 @@ function registerGitHubCommands(program, runSafely2) {
2640
3140
  const result = await installGitHubWorkflow(process.cwd(), {
2641
3141
  ...opts.force !== void 0 ? { force: opts.force } : {}
2642
3142
  });
2643
- console.log(`${result.created ? "installed" : "already present"} ${pc5.dim(result.path)}`);
3143
+ console.log(`${result.created ? "installed" : "already present"} ${pc6.dim(result.path)}`);
2644
3144
  if (result.created) console.log("Commit this workflow to enable CodeBuddy PR reports.");
2645
3145
  });
2646
3146
  });
2647
3147
  }
2648
3148
 
2649
3149
  // src/cli/commands/hooks.ts
3150
+ import { spawn as spawn3 } from "child_process";
2650
3151
  import { relative as relative2 } from "path";
2651
- import pc6 from "picocolors";
3152
+ import pc7 from "picocolors";
2652
3153
  function registerHooksCommands(program, runSafely2) {
2653
3154
  const hooks = program.command("hooks").description("Enforce automatic recall/capture via client lifecycle hooks.");
2654
3155
  hooks.command("install").option(
@@ -2665,11 +3166,12 @@ function registerHooksCommands(program, runSafely2) {
2665
3166
  const where = relative2(process.cwd(), step.target) || step.target;
2666
3167
  const changed = step.action !== "unchanged";
2667
3168
  console.log(
2668
- `${changed ? pc6.green("installed") : pc6.dim("unchanged")} CodeBuddy hooks in ${where}`
3169
+ `${changed ? pc7.green("installed") : pc7.dim("unchanged")} CodeBuddy hooks in ${where}`
2669
3170
  );
2670
3171
  await reconcileRules(client, "hooks");
3172
+ syncInBackground(process.cwd());
2671
3173
  console.log(
2672
- pc6.dim(
3174
+ pc7.dim(
2673
3175
  ` prompt\u2192recall \xB7 edit\u2192stage \xB7 turn end\u2192capture \xB7 compaction\u2192reset. Restart ${CLIENT_ADAPTERS[client].name} to activate.`
2674
3176
  )
2675
3177
  );
@@ -2684,7 +3186,7 @@ function registerHooksCommands(program, runSafely2) {
2684
3186
  const client = assertHookClient(opts.client);
2685
3187
  const changed = (await setHooks(process.cwd(), false, client))?.action === "removed";
2686
3188
  console.log(
2687
- changed ? pc6.yellow("removed CodeBuddy hooks") : pc6.dim("no CodeBuddy hooks found")
3189
+ changed ? pc7.yellow("removed CodeBuddy hooks") : pc7.dim("no CodeBuddy hooks found")
2688
3190
  );
2689
3191
  await reconcileRules(client, "agent");
2690
3192
  });
@@ -2697,7 +3199,7 @@ function registerHooksCommands(program, runSafely2) {
2697
3199
  await runSafely2(async () => {
2698
3200
  const installed = await clientHooksInstalled(process.cwd(), parseClient(opts.client));
2699
3201
  console.log(
2700
- installed ? pc6.green("CodeBuddy hooks: installed") : pc6.dim("CodeBuddy hooks: not installed")
3202
+ installed ? pc7.green("CodeBuddy hooks: installed") : pc7.dim("CodeBuddy hooks: not installed")
2701
3203
  );
2702
3204
  });
2703
3205
  });
@@ -2725,9 +3227,21 @@ function registerHooksCommands(program, runSafely2) {
2725
3227
  `codebuddy: captured from ${changedFiles.length} changed file(s) \u2014 ${result.saved.length} saved, ${result.queuedForReview.length} queued
2726
3228
  `
2727
3229
  );
3230
+ if (result.saved.length > 0) syncInBackground(input.cwd ?? process.cwd());
2728
3231
  }
2729
3232
  });
2730
3233
  });
3234
+ hooks.command("sync").description(
3235
+ "[hook] Reindex Markdown memory into PostgreSQL and embed it locally (runs detached after capture)."
3236
+ ).action(async () => {
3237
+ await failSoft(async () => {
3238
+ const result = await runSyncHook(process.cwd());
3239
+ process.stderr.write(
3240
+ result ? `codebuddy: synced ${result.reindex.facts.imported} new / ${result.reindex.facts.updated} updated fact(s)${result.reindex.reembedding ? `, ${result.reindex.reembedding} re-embedding` : ""}${result.drained ? "" : " (embeddings still pending)"}
3241
+ ` : "codebuddy: no PostgreSQL configured; nothing to sync\n"
3242
+ );
3243
+ });
3244
+ });
2731
3245
  hooks.command("pretool").description(
2732
3246
  "[hook] Outline large unranged Reads and cap content Greps (never grants permission)."
2733
3247
  ).action(async () => {
@@ -2754,16 +3268,28 @@ async function reconcileRules(client, mode) {
2754
3268
  if (result.action !== "unchanged") {
2755
3269
  const where = relative2(process.cwd(), result.path) || result.path;
2756
3270
  console.log(
2757
- `${pc6.cyan("updated")} ${where} workflow to ${mode === "hooks" ? "hook" : "agent"}-driven mode`
3271
+ `${pc7.cyan("updated")} ${where} workflow to ${mode === "hooks" ? "hook" : "agent"}-driven mode`
2758
3272
  );
2759
3273
  }
2760
3274
  }
3275
+ function syncInBackground(cwd) {
3276
+ const entry = process.argv[1];
3277
+ if (!entry) return;
3278
+ try {
3279
+ spawn3(process.execPath, [...process.execArgv, entry, "hooks", "sync"], {
3280
+ cwd,
3281
+ detached: true,
3282
+ stdio: "ignore"
3283
+ }).unref();
3284
+ } catch {
3285
+ }
3286
+ }
2761
3287
 
2762
3288
  // src/cli/commands/indexing.ts
2763
- import pc8 from "picocolors";
3289
+ import pc9 from "picocolors";
2764
3290
 
2765
3291
  // src/cli/commands/llm.ts
2766
- import pc7 from "picocolors";
3292
+ import pc8 from "picocolors";
2767
3293
  import { z } from "zod";
2768
3294
  var KEY_ENV = { groq: "GROQ_API_KEY", openrouter: "OPENROUTER_API_KEY" };
2769
3295
  var WATCH_BATCH = 8;
@@ -2818,7 +3344,7 @@ function registerLlmCommands(program, runSafely2) {
2818
3344
  await runSafely2(async () => {
2819
3345
  const report = await llmStatusReport(process.cwd());
2820
3346
  console.log(opts.json ? JSON.stringify(report, null, 2) : formatLlmStatus(report));
2821
- if (!report.keyPresent) console.log(pc7.dim(`next: export ${report.keyEnv}=<your key>`));
3347
+ if (!report.keyPresent) console.log(pc8.dim(`next: export ${report.keyEnv}=<your key>`));
2822
3348
  });
2823
3349
  });
2824
3350
  llm.command("enable").option("--provider <provider>", "groq or openrouter", "groq").option("--model <model>", "Model id (default: llama-3.1-8b-instant on groq).").description("Turn the layer on in .codebuddy/config.json (the key stays in your environment).").action(async (opts) => {
@@ -2865,7 +3391,7 @@ function registerLlmCommands(program, runSafely2) {
2865
3391
  const result = await summarizeRepository(process.cwd(), {
2866
3392
  ...opts.limit !== void 0 ? { limit: opts.limit } : {},
2867
3393
  signal: controller.signal,
2868
- onProgress: (done, total) => console.log(pc7.dim(`summarized ${done}/${total}`))
3394
+ onProgress: (done, total) => console.log(pc8.dim(`summarized ${done}/${total}`))
2869
3395
  });
2870
3396
  console.log(
2871
3397
  `${result.summarized} summarized, ${result.skipped} skipped, ${result.remaining} remaining (${result.stoppedBy})`
@@ -2892,7 +3418,7 @@ function registerIndexCommands(program, runSafely2) {
2892
3418
  const stop = () => controller.abort();
2893
3419
  process.once("SIGINT", stop);
2894
3420
  process.once("SIGTERM", stop);
2895
- console.log(pc8.dim("watching for changes\u2026 press Ctrl+C to stop"));
3421
+ console.log(pc9.dim("watching for changes\u2026 press Ctrl+C to stop"));
2896
3422
  await watchRepository({
2897
3423
  ...opts.debounce !== void 0 ? { debounceMs: opts.debounce } : {},
2898
3424
  signal: controller.signal,
@@ -2901,21 +3427,21 @@ function registerIndexCommands(program, runSafely2) {
2901
3427
  if (!opts.llm || result.added + result.changed === 0) return;
2902
3428
  void summarizeAfterIndex(process.cwd()).then((summary) => {
2903
3429
  if (summary && summary.summarized > 0)
2904
- console.log(pc8.dim(` llm: ${summary.summarized} file(s) summarized`));
2905
- }).catch((error) => console.error(pc8.red(`llm: ${error.message}`)));
3430
+ console.log(pc9.dim(` llm: ${summary.summarized} file(s) summarized`));
3431
+ }).catch((error) => console.error(pc9.red(`llm: ${error.message}`)));
2906
3432
  },
2907
- onError: (error) => console.error(pc8.red(error.message))
3433
+ onError: (error) => console.error(pc9.red(error.message))
2908
3434
  });
2909
- console.log(pc8.dim("stopped watching"));
3435
+ console.log(pc9.dim("stopped watching"));
2910
3436
  });
2911
3437
  });
2912
3438
  }
2913
3439
  function printResult(result, compact = false) {
2914
- const summary = `${result.totalIndexed} indexed (${pc8.green(`+${result.added}`)} ${pc8.yellow(`~${result.changed}`)} ${pc8.dim(`=${result.unchanged}`)} ${pc8.red(`-${result.removed}`)}) in ${result.durationMs}ms`;
2915
- console.log(compact ? pc8.dim(summary) : summary);
3440
+ const summary = `${result.totalIndexed} indexed (${pc9.green(`+${result.added}`)} ${pc9.yellow(`~${result.changed}`)} ${pc9.dim(`=${result.unchanged}`)} ${pc9.red(`-${result.removed}`)}) in ${result.durationMs}ms`;
3441
+ console.log(compact ? pc9.dim(summary) : summary);
2916
3442
  if (result.skipped > 0)
2917
- console.log(pc8.dim(` ${result.skipped} skipped (too large or unreadable)`));
2918
- for (const error of result.errors) console.log(pc8.red(` ${error.path}: ${error.error}`));
3443
+ console.log(pc9.dim(` ${result.skipped} skipped (too large or unreadable)`));
3444
+ for (const error of result.errors) console.log(pc9.red(` ${error.path}: ${error.error}`));
2919
3445
  }
2920
3446
 
2921
3447
  // src/cli/commands/init-wizard.ts
@@ -2923,7 +3449,7 @@ import { writeFile as writeFile2 } from "fs/promises";
2923
3449
  import { confirm, isCancel, log, note, password, spinner, text } from "@clack/prompts";
2924
3450
 
2925
3451
  // src/cli/commands/postgres-docker.ts
2926
- import { spawn as spawn2 } from "child_process";
3452
+ import { spawn as spawn4 } from "child_process";
2927
3453
  import { existsSync as existsSync3 } from "fs";
2928
3454
  import { dirname as dirname11, resolve as resolve12 } from "path";
2929
3455
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -2944,7 +3470,7 @@ function resolveBundledComposeFile() {
2944
3470
  ${candidates.join("\n ")}`);
2945
3471
  }
2946
3472
  async function runDockerCompose(args, options = {}) {
2947
- const spawnImpl = options.spawnImpl ?? spawn2;
3473
+ const spawnImpl = options.spawnImpl ?? spawn4;
2948
3474
  const composeFile = resolveBundledComposeFile();
2949
3475
  const attempts = [
2950
3476
  { kind: "v2", bin: "docker", args: ["compose", "-f", composeFile, ...args] },
@@ -3015,6 +3541,7 @@ async function pingOnce(databaseUrl) {
3015
3541
  }
3016
3542
  }
3017
3543
  async function runInitWizard() {
3544
+ const projectRoot = process.cwd();
3018
3545
  const sigintHandler = () => bail();
3019
3546
  process.on("SIGINT", sigintHandler);
3020
3547
  try {
@@ -3024,8 +3551,7 @@ async function runInitWizard() {
3024
3551
  );
3025
3552
  const hfToken = unwrap(
3026
3553
  await password({
3027
- message: "Hugging Face token (required for embeddings)",
3028
- validate: (value) => value && value.length > 0 ? void 0 : "HF_TOKEN is required."
3554
+ message: "Hugging Face token (optional; only for hosted embeddings)"
3029
3555
  })
3030
3556
  );
3031
3557
  const databaseUrl = unwrap(
@@ -3038,7 +3564,7 @@ async function runInitWizard() {
3038
3564
  const namespace = unwrap(
3039
3565
  await text({
3040
3566
  message: "Default namespace",
3041
- initialValue: process.env.CODEBUDDY_NAMESPACE ?? "default",
3567
+ initialValue: process.env.CODEBUDDY_NAMESPACE ?? defaultNamespaceForCwd(projectRoot),
3042
3568
  validate: (value) => value && /^[a-zA-Z0-9_.:-]+$/.test(value) ? void 0 : "Letters, digits, _ . : - only."
3043
3569
  })
3044
3570
  );
@@ -3095,7 +3621,7 @@ async function runInitWizard() {
3095
3621
  const fileConfig = {
3096
3622
  postgresUrl: databaseUrl,
3097
3623
  namespace,
3098
- provider: { type: "huggingface", apiKey: hfToken },
3624
+ provider: { type: "local", ...hfToken ? { apiKey: hfToken } : {} },
3099
3625
  tokenBudget: 4e3
3100
3626
  };
3101
3627
  await writeFile2(initialised.path, `${JSON.stringify(fileConfig, null, 2)}
@@ -3114,6 +3640,7 @@ async function runInitWizard() {
3114
3640
  try {
3115
3641
  const install = await installClaudeEntry({
3116
3642
  namespace,
3643
+ projectRoot,
3117
3644
  hfToken,
3118
3645
  databaseUrl
3119
3646
  });
@@ -3136,7 +3663,7 @@ async function runInitWizard() {
3136
3663
  }
3137
3664
 
3138
3665
  // src/cli/commands/learn.ts
3139
- import pc9 from "picocolors";
3666
+ import pc10 from "picocolors";
3140
3667
 
3141
3668
  // src/learning/outcomes.ts
3142
3669
  async function recordChangeOutcome(input) {
@@ -3197,15 +3724,15 @@ function registerLearnCommand(program, runSafely2) {
3197
3724
  ...opts.commit ? { commit: opts.commit } : {},
3198
3725
  ...opts.plan ? { planId: opts.plan } : {}
3199
3726
  });
3200
- console.log(`${pc9.green("recorded")} ${result.id} (${result.category})`);
3201
- console.log(pc9.dim(result.path));
3727
+ console.log(`${pc10.green("recorded")} ${result.id} (${result.category})`);
3728
+ console.log(pc10.dim(result.path));
3202
3729
  });
3203
3730
  }
3204
3731
  );
3205
3732
  }
3206
3733
 
3207
3734
  // src/cli/commands/map.ts
3208
- import pc10 from "picocolors";
3735
+ import pc11 from "picocolors";
3209
3736
  function registerMapCommands(program, runSafely2) {
3210
3737
  const map = program.command("map").description("Query the lightweight architecture map.");
3211
3738
  map.command("build").description("Scan the current repository and summarize the module graph.").action(async () => {
@@ -3225,7 +3752,7 @@ function registerMapCommands(program, runSafely2) {
3225
3752
  return;
3226
3753
  }
3227
3754
  if (edges.length === 0) {
3228
- console.log(pc10.dim("no dependencies found"));
3755
+ console.log(pc11.dim("no dependencies found"));
3229
3756
  return;
3230
3757
  }
3231
3758
  for (const edge of edges) console.log(`${edge.from} -> ${edge.to}`);
@@ -3240,7 +3767,7 @@ function registerMapCommands(program, runSafely2) {
3240
3767
  return;
3241
3768
  }
3242
3769
  if (result.edges.length === 0) {
3243
- console.log(pc10.dim("no dependents found"));
3770
+ console.log(pc11.dim("no dependents found"));
3244
3771
  return;
3245
3772
  }
3246
3773
  for (const edge of result.edges) console.log(`${edge.from} -> ${edge.to}`);
@@ -3249,9 +3776,9 @@ function registerMapCommands(program, runSafely2) {
3249
3776
  }
3250
3777
 
3251
3778
  // src/cli/commands/memory-review.ts
3252
- import { spawn as spawn3 } from "child_process";
3779
+ import { spawn as spawn5 } from "child_process";
3253
3780
  import { relative as relative3 } from "path";
3254
- import pc11 from "picocolors";
3781
+ import pc12 from "picocolors";
3255
3782
  function registerMemoryCommands(program, runSafely2) {
3256
3783
  const memory = program.command("memory").description("Inspect and curate CodeBuddy memory.");
3257
3784
  const review = memory.command("review").description("Review memory candidates captured automatically after turns.");
@@ -3263,11 +3790,11 @@ function registerMemoryCommands(program, runSafely2) {
3263
3790
  return;
3264
3791
  }
3265
3792
  if (items.length === 0) {
3266
- console.log(pc11.dim("no pending review items"));
3793
+ console.log(pc12.dim("no pending review items"));
3267
3794
  return;
3268
3795
  }
3269
3796
  for (const item of items) console.log(renderRow(item));
3270
- console.log(pc11.dim(`
3797
+ console.log(pc12.dim(`
3271
3798
  ${items.length} pending. approve <id> | reject <id> | show <id>`));
3272
3799
  });
3273
3800
  });
@@ -3281,7 +3808,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
3281
3808
  const factId = await approveReviewItem(id, {
3282
3809
  ...opts.incident !== void 0 ? { incidentId: opts.incident } : {}
3283
3810
  });
3284
- console.log(`${pc11.green("approved")} ${id} \u2192 ${factId}`);
3811
+ console.log(`${pc12.green("approved")} ${id} \u2192 ${factId}`);
3285
3812
  });
3286
3813
  });
3287
3814
  review.command("reject").argument("<id>").description("Discard a review candidate without saving it.").action(async (id) => {
@@ -3289,7 +3816,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
3289
3816
  const store = new ReviewStore();
3290
3817
  await store.read(id);
3291
3818
  await store.delete(id);
3292
- console.log(`${pc11.yellow("rejected")} ${id}`);
3819
+ console.log(`${pc12.yellow("rejected")} ${id}`);
3293
3820
  });
3294
3821
  });
3295
3822
  review.command("edit").argument("<id>").description("Open a review candidate in $EDITOR and re-validate on save.").action(async (id) => {
@@ -3298,44 +3825,44 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
3298
3825
  const item = await store.read(id);
3299
3826
  await openEditor(item.path);
3300
3827
  await store.read(id);
3301
- console.log(pc11.dim("saved and validated"));
3828
+ console.log(pc12.dim("saved and validated"));
3302
3829
  });
3303
3830
  });
3304
3831
  }
3305
3832
  function renderRow(item) {
3306
- const flag = item.sensitive ? pc11.red(" \u26A0 sensitive") : "";
3307
- return `${classColor(item.class)} ${pc11.dim(item.id)} ${truncate(item.content, 70)}${flag}`;
3833
+ const flag = item.sensitive ? pc12.red(" \u26A0 sensitive") : "";
3834
+ return `${classColor(item.class)} ${pc12.dim(item.id)} ${truncate(item.content, 70)}${flag}`;
3308
3835
  }
3309
3836
  function renderItem(item) {
3310
3837
  const lines = [
3311
- `${classColor(item.class)} ${pc11.dim(item.id)}`,
3312
- `${pc11.bold("reason:")} ${item.reason}`,
3313
- `${pc11.bold("confidence:")} ${item.confidence.toFixed(2)}`,
3314
- `${pc11.bold("triple:")} ${item.subject} \xB7 ${item.predicate} \xB7 ${item.object}`
3838
+ `${classColor(item.class)} ${pc12.dim(item.id)}`,
3839
+ `${pc12.bold("reason:")} ${item.reason}`,
3840
+ `${pc12.bold("confidence:")} ${item.confidence.toFixed(2)}`,
3841
+ `${pc12.bold("triple:")} ${item.subject} \xB7 ${item.predicate} \xB7 ${item.object}`
3315
3842
  ];
3316
- if (item.paths.length > 0) lines.push(`${pc11.bold("paths:")} ${item.paths.join(", ")}`);
3317
- if (item.severity) lines.push(`${pc11.bold("severity:")} ${item.severity}`);
3843
+ if (item.paths.length > 0) lines.push(`${pc12.bold("paths:")} ${item.paths.join(", ")}`);
3844
+ if (item.severity) lines.push(`${pc12.bold("severity:")} ${item.severity}`);
3318
3845
  if (item.resolvesIncidents) {
3319
3846
  lines.push(
3320
- `${pc11.bold("resolves:")} ${item.resolvesIncidents.join(", ") || "(no candidate; approve with --incident <id>)"}`
3847
+ `${pc12.bold("resolves:")} ${item.resolvesIncidents.join(", ") || "(no candidate; approve with --incident <id>)"}`
3321
3848
  );
3322
3849
  }
3323
- if (item.sensitive) lines.push(pc11.red(`\u26A0 sensitive: ${item.sensitiveReasons.join(", ")}`));
3324
- lines.push("", item.content, "", pc11.dim(relative3(process.cwd(), item.path)));
3850
+ if (item.sensitive) lines.push(pc12.red(`\u26A0 sensitive: ${item.sensitiveReasons.join(", ")}`));
3851
+ lines.push("", item.content, "", pc12.dim(relative3(process.cwd(), item.path)));
3325
3852
  return lines.join("\n");
3326
3853
  }
3327
3854
  function classColor(memoryClass) {
3328
3855
  switch (memoryClass) {
3329
3856
  case "incident":
3330
- return pc11.red(memoryClass);
3857
+ return pc12.red(memoryClass);
3331
3858
  case "decision":
3332
- return pc11.cyan(memoryClass);
3859
+ return pc12.cyan(memoryClass);
3333
3860
  case "fact":
3334
- return pc11.green(memoryClass);
3861
+ return pc12.green(memoryClass);
3335
3862
  case "note":
3336
- return pc11.yellow(memoryClass);
3863
+ return pc12.yellow(memoryClass);
3337
3864
  default:
3338
- return pc11.dim(memoryClass);
3865
+ return pc12.dim(memoryClass);
3339
3866
  }
3340
3867
  }
3341
3868
  function truncate(value, max) {
@@ -3345,16 +3872,565 @@ function truncate(value, max) {
3345
3872
  function openEditor(path) {
3346
3873
  const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
3347
3874
  return new Promise((resolvePromise, reject) => {
3348
- const child = spawn3(editor, [path], { stdio: "inherit" });
3875
+ const child = spawn5(editor, [path], { stdio: "inherit" });
3349
3876
  child.on("error", reject);
3350
3877
  child.on("close", () => resolvePromise());
3351
3878
  });
3352
3879
  }
3353
3880
 
3881
+ // src/cli/commands/models.ts
3882
+ import { existsSync as existsSync4 } from "fs";
3883
+ import { join as join15 } from "path";
3884
+ import { spinner as spinner2 } from "@clack/prompts";
3885
+ import pc13 from "picocolors";
3886
+
3887
+ // evals/fact-relevance.json
3888
+ var fact_relevance_default = {
3889
+ version: 2,
3890
+ note: "Task prompts vs durable project facts. Queries are paraphrased so most share few or no words with their fact; every unlisted (query, fact) pair is irrelevant. Hard negatives share a subsystem with a relevant fact but do not answer its task. Queries alternate dev/test by position (even index = dev): floors are chosen on dev and reported on test.",
3891
+ facts: {
3892
+ cookie: "OAuth callback breaks unless the session cookie is SameSite=Lax; Strict drops it on the cross-site redirect.",
3893
+ pool: "Postgres connections go through one bounded pool of 10; never open ad-hoc clients in request handlers.",
3894
+ release: "npm releases are published from CI with provenance; never publish from a laptop.",
3895
+ biome: "Biome owns formatting and import order; run biome check --write before committing.",
3896
+ hook: "Prompt hooks must answer within 15 seconds or the host drops the injected block.",
3897
+ migration: "Migrations must create the pgvector extension before 0003 adds the vector column.",
3898
+ redaction: "Secrets are redacted before a memory is written; the content hash is computed after redaction.",
3899
+ ledger: "The session ledger keeps the last 8 acknowledged versions per section so a task switch back is not resent.",
3900
+ zvec: "zvec-grep is optional; any failure falls back to keyword ranking with a retrieval explain line.",
3901
+ tests: "Integration tests skip unless CODEBUDDY_TEST_DATABASE_URL points at a live Postgres.",
3902
+ esm: "Source imports use .js specifiers even for TypeScript files because the package is ESM.",
3903
+ stdout: "Never log to stdout in the MCP server: stdout carries JSON-RPC and any stray line corrupts it.",
3904
+ budget: "The BYOK LLM layer stops background calls at 70% of the daily budget and backs off after a 429.",
3905
+ worktree: "Use a temporary WIP commit instead of git stash; the stash stack is shared across worktrees.",
3906
+ rateLimit: "The public API allows 100 requests per minute per token; bursts above it get HTTP 429.",
3907
+ timezone: "All timestamps are stored in UTC and converted to the user's zone only for display.",
3908
+ avatar: "Profile images are resized to 256px and stored as WebP in the media bucket.",
3909
+ flags: "Feature flags are read once at boot; changing one requires a restart of the worker.",
3910
+ pagination: "List endpoints use cursor pagination with an opaque base64url cursor of createdAt and id.",
3911
+ i18n: "UI strings live in locales/en.json; never hard-code user-facing text in components.",
3912
+ cookieDomain: "Session cookies are scoped to the app subdomain; never set them on the apex domain.",
3913
+ poolMetrics: "Database pool metrics are exported to Prometheus under the codebuddy_pool_ prefix.",
3914
+ releaseNotes: "Release notes are drafted from merged pull request titles by the changelog script.",
3915
+ hookInstall: "Hooks are written to .claude/settings.json and merged idempotently with the user's own hooks.",
3916
+ migrationNaming: "Migration files are numbered by drizzle-kit; never rename or edit a migration that has been applied.",
3917
+ testsParallel: "Vitest runs test files in parallel, so every test must use its own temporary directory.",
3918
+ logsStderr: "Server logs go to stderr through pino; never log full request bodies.",
3919
+ retry5xx: "The HTTP client retries 5xx responses twice with jittered backoff.",
3920
+ dateFormat: "Dates in the UI are formatted with the user's locale through Intl.DateTimeFormat.",
3921
+ flagNames: "Feature flag names use kebab-case and are declared in flags.yaml."
3922
+ },
3923
+ queries: [
3924
+ {
3925
+ q: "users get bounced back to sign-in forever after the identity provider returns",
3926
+ relevant: ["cookie"]
3927
+ },
3928
+ {
3929
+ q: "handlers are exhausting connections to the database under load",
3930
+ relevant: ["pool"]
3931
+ },
3932
+ {
3933
+ q: "cut a new version and push the package to the registry",
3934
+ relevant: ["release"]
3935
+ },
3936
+ {
3937
+ q: "the injected block sometimes never shows up in the chat",
3938
+ relevant: ["hook"]
3939
+ },
3940
+ {
3941
+ q: "fresh database setup fails on the embedding column",
3942
+ relevant: ["migration"]
3943
+ },
3944
+ {
3945
+ q: "an API key ended up in a saved note",
3946
+ relevant: ["redaction"]
3947
+ },
3948
+ {
3949
+ q: "context is sent again after I switch tasks and come back",
3950
+ relevant: ["ledger"]
3951
+ },
3952
+ {
3953
+ q: "semantic code search crashes and the agent gets no files",
3954
+ relevant: ["zvec"]
3955
+ },
3956
+ {
3957
+ q: "why do the db tests not run on my machine",
3958
+ relevant: ["tests"]
3959
+ },
3960
+ {
3961
+ q: "module not found when importing a sibling ts file at runtime",
3962
+ relevant: ["esm"]
3963
+ },
3964
+ {
3965
+ q: "Claude Desktop says Unexpected token in the server response",
3966
+ relevant: ["stdout"]
3967
+ },
3968
+ {
3969
+ q: "the summarizer quietly stopped calling Groq in the afternoon",
3970
+ relevant: ["budget"]
3971
+ },
3972
+ {
3973
+ q: "my uncommitted changes vanished after another session ran",
3974
+ relevant: ["worktree"]
3975
+ },
3976
+ {
3977
+ q: "clients see too many requests errors during the import job",
3978
+ relevant: ["rateLimit"]
3979
+ },
3980
+ {
3981
+ q: "meeting times show up one hour off for users in Berlin",
3982
+ relevant: ["timezone"]
3983
+ },
3984
+ {
3985
+ q: "uploaded profile pictures look blurry",
3986
+ relevant: ["avatar"]
3987
+ },
3988
+ {
3989
+ q: "I toggled the new checkout switch but nothing changed",
3990
+ relevant: ["flags"]
3991
+ },
3992
+ {
3993
+ q: "infinite scroll repeats items on the second page",
3994
+ relevant: ["pagination"]
3995
+ },
3996
+ {
3997
+ q: "translate the onboarding screen to German",
3998
+ relevant: ["i18n"]
3999
+ },
4000
+ {
4001
+ q: "format the code before I commit",
4002
+ relevant: ["biome"]
4003
+ },
4004
+ {
4005
+ q: "make the sidebar collapsible",
4006
+ relevant: []
4007
+ },
4008
+ {
4009
+ q: "add dark mode to the dashboard",
4010
+ relevant: []
4011
+ },
4012
+ {
4013
+ q: "write a haiku about the project",
4014
+ relevant: []
4015
+ },
4016
+ {
4017
+ q: "rename the button label on the settings page",
4018
+ relevant: []
4019
+ },
4020
+ {
4021
+ q: "where do I declare a new feature flag",
4022
+ relevant: ["flagNames"]
4023
+ },
4024
+ {
4025
+ q: "show dates in the user's own language and order",
4026
+ relevant: ["dateFormat"]
4027
+ },
4028
+ {
4029
+ q: "can I tidy up the name of the 0004 migration file",
4030
+ relevant: ["migrationNaming"]
4031
+ },
4032
+ {
4033
+ q: "tests interfere with each other when the whole suite runs",
4034
+ relevant: ["testsParallel"]
4035
+ },
4036
+ {
4037
+ q: "where can I see how busy the connection pool is",
4038
+ relevant: ["poolMetrics"]
4039
+ },
4040
+ {
4041
+ q: "how is the text for the changelog produced",
4042
+ relevant: ["releaseNotes"]
4043
+ },
4044
+ {
4045
+ q: "the login cookie shows up on example.com instead of app.example.com",
4046
+ relevant: ["cookieDomain"]
4047
+ },
4048
+ {
4049
+ q: "is it fine to print the incoming payload for debugging",
4050
+ relevant: ["logsStderr"]
4051
+ },
4052
+ {
4053
+ q: "our hook setup appears twice in the Claude settings file",
4054
+ relevant: ["hookInstall"]
4055
+ },
4056
+ {
4057
+ q: "flaky upstream 502s should not fail the request on the first try",
4058
+ relevant: ["retry5xx"]
4059
+ },
4060
+ {
4061
+ q: "rewrite the marketing copy on the landing page",
4062
+ relevant: []
4063
+ },
4064
+ {
4065
+ q: "upgrade React to the next major version",
4066
+ relevant: []
4067
+ },
4068
+ {
4069
+ q: "add a CSV export button to the reports table",
4070
+ relevant: []
4071
+ }
4072
+ ]
4073
+ };
4074
+
4075
+ // src/providers/calibration.ts
4076
+ function splitOf(index) {
4077
+ return index % 2 === 0 ? "dev" : "test";
4078
+ }
4079
+ async function calibrateEmbedder(embed) {
4080
+ const data = fact_relevance_default;
4081
+ const factIds = Object.keys(data.facts);
4082
+ const factVectors = await embed(Object.values(data.facts));
4083
+ const queryVectors = await embed(data.queries.map(({ q }) => q));
4084
+ const dot = (a, b) => a.reduce((sum, value, index) => sum + value * (b[index] ?? 0), 0);
4085
+ const scores = queryVectors.map((query) => factVectors.map((fact) => dot(query, fact)));
4086
+ const relevant = (qi, fi) => data.queries[qi]?.relevant.includes(factIds[fi] ?? "") ?? false;
4087
+ const answerable = (qi) => (data.queries[qi]?.relevant.length ?? 0) > 0;
4088
+ const rows = (split) => scores.flatMap((row2, qi) => splitOf(qi) === split ? [{ row: row2, qi }] : []);
4089
+ const positives = [];
4090
+ const negatives = [];
4091
+ let hits = 0;
4092
+ let reciprocal = 0;
4093
+ let answered = 0;
4094
+ for (const { row: row2, qi } of rows("test")) {
4095
+ for (const [fi, score] of row2.entries()) (relevant(qi, fi) ? positives : negatives).push(score);
4096
+ if (!answerable(qi)) continue;
4097
+ answered += 1;
4098
+ const order = row2.map((score, fi) => ({ score, fi })).sort((a, b) => b.score - a.score);
4099
+ const rank = order.findIndex(({ fi }) => relevant(qi, fi)) + 1;
4100
+ if (rank === 1) hits += 1;
4101
+ reciprocal += 1 / rank;
4102
+ }
4103
+ const auc = positives.reduce((wins, p) => wins + negatives.filter((n) => p > n).length, 0) / (positives.length * negatives.length);
4104
+ const at = (floor, split = "test") => {
4105
+ let tp = 0;
4106
+ let fp = 0;
4107
+ let relevantPairs = 0;
4108
+ let abstained = 0;
4109
+ let unrelated = 0;
4110
+ for (const { row: row2, qi } of rows(split)) {
4111
+ relevantPairs += data.queries[qi]?.relevant.length ?? 0;
4112
+ const admitted = row2.flatMap((score, fi) => score >= floor ? [fi] : []);
4113
+ for (const fi of admitted) {
4114
+ if (relevant(qi, fi)) tp += 1;
4115
+ else fp += 1;
4116
+ }
4117
+ if (!answerable(qi)) {
4118
+ unrelated += 1;
4119
+ if (admitted.length === 0) abstained += 1;
4120
+ }
4121
+ }
4122
+ const precision = tp + fp === 0 ? 1 : tp / (tp + fp);
4123
+ const recall = relevantPairs === 0 ? 0 : tp / relevantPairs;
4124
+ const f05 = precision + recall === 0 ? 0 : 1.25 * precision * recall / (0.25 * precision + recall);
4125
+ return { floor, precision, recall, f05, abstain: unrelated === 0 ? 1 : abstained / unrelated };
4126
+ };
4127
+ const sweep = Array.from(
4128
+ { length: 96 },
4129
+ (_, i) => at(Math.round((0.05 + i * 0.01) * 100) / 100, "dev")
4130
+ );
4131
+ const best = sweep.reduce((left, right) => right.f05 > left.f05 ? right : left);
4132
+ return {
4133
+ auc,
4134
+ recallAt1: answered === 0 ? 0 : hits / answered,
4135
+ mrr: answered === 0 ? 0 : reciprocal / answered,
4136
+ best,
4137
+ test: at(best.floor, "test"),
4138
+ at
4139
+ };
4140
+ }
4141
+
4142
+ // src/cli/commands/models.ts
4143
+ var EMBEDDERS = ["local", "huggingface", "openrouter"];
4144
+ var RERANKERS = ["off", "local", "huggingface", "openrouter"];
4145
+ var KEY_ENV2 = { huggingface: "HF_TOKEN", openrouter: "OPENROUTER_API_KEY" };
4146
+ var ok = (text3) => `${pc13.green("\u2713")} ${text3}`;
4147
+ var warn = (text3) => `${pc13.yellow("!")} ${text3}`;
4148
+ var row = (label, value) => ` ${pc13.dim(label.padEnd(11))}${value}`;
4149
+ async function withSpinner(label, work, done) {
4150
+ const progress = spinner2();
4151
+ progress.start(label);
4152
+ try {
4153
+ const value = await work();
4154
+ progress.stop(done(value));
4155
+ return value;
4156
+ } catch (error) {
4157
+ progress.stop(pc13.red(`\u2717 ${label}`), 1);
4158
+ throw error;
4159
+ }
4160
+ }
4161
+ function keyPresent(provider, apiKey) {
4162
+ return Boolean(process.env[KEY_ENV2[provider]] || provider === "huggingface" && apiKey);
4163
+ }
4164
+ function localModelDownloaded(id, revision) {
4165
+ const dir = join15(modelCacheDir(), id, ...revision ? [revision] : []);
4166
+ return existsSync4(join15(dir, "onnx", "model_quantized.onnx"));
4167
+ }
4168
+ async function vectorCounts(postgresUrl, namespace, space) {
4169
+ const client = createDatabaseClient({ postgresUrl, maxConnections: 1, connectTimeoutSeconds: 3 });
4170
+ try {
4171
+ const [counts] = await client.sql`
4172
+ select
4173
+ count(*) filter (where e.status = 'ready' and e.embedding_space = ${space ?? ""})::int as ready,
4174
+ count(*) filter (where e.status in ('pending', 'processing'))::int as pending,
4175
+ count(*) filter (where e.status = 'failed')::int as failed,
4176
+ count(*) filter (where e.status = 'ready' and e.embedding_space is distinct from ${space ?? ""})::int as other
4177
+ from embeddings e join namespaces n on n.id = e.namespace_id
4178
+ where n.name = ${namespace}
4179
+ `;
4180
+ return counts ?? { ready: 0, pending: 0, failed: 0, other: 0 };
4181
+ } catch (error) {
4182
+ return String(redactSecrets(error.message)).split("\n")[0] ?? "unreachable";
4183
+ } finally {
4184
+ await client.close().catch(() => void 0);
4185
+ }
4186
+ }
4187
+ async function printStatus() {
4188
+ const file = await readConfigFile();
4189
+ const config = await loadRuntimeConfig(process.cwd(), { requireDatabase: false });
4190
+ const provider = config.provider;
4191
+ const embedder = buildProviderFromConfig(config.provider);
4192
+ const space = embedder.space;
4193
+ const model = provider.type === "local" ? LOCAL_EMBEDDING_MODEL.id : provider.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[provider.type];
4194
+ const floor = provider.relevanceFloor ?? getModelDefinition(model)?.relevanceFloor;
4195
+ const next = [];
4196
+ console.log(pc13.bold("Embeddings"));
4197
+ console.log(row("provider", `${provider.type} ${pc13.dim(model)}`));
4198
+ if (provider.type === "local") {
4199
+ const ready = localModelDownloaded(LOCAL_EMBEDDING_MODEL.id, LOCAL_EMBEDDING_MODEL.revision);
4200
+ console.log(
4201
+ row(
4202
+ "model",
4203
+ ready ? ok(`downloaded ${pc13.dim(modelCacheDir())}`) : warn("not downloaded yet (~90 MB)")
4204
+ )
4205
+ );
4206
+ if (!ready) next.push("codebuddy models pull");
4207
+ } else {
4208
+ const present = keyPresent(provider.type, provider.apiKey);
4209
+ console.log(
4210
+ row(
4211
+ "key",
4212
+ present ? ok(KEY_ENV2[provider.type]) : warn(`${KEY_ENV2[provider.type]} is not set`)
4213
+ )
4214
+ );
4215
+ if (!present) next.push(`export ${KEY_ENV2[provider.type]}=\u2026`);
4216
+ }
4217
+ console.log(
4218
+ row(
4219
+ "floor",
4220
+ floor !== void 0 ? `${floor} ${pc13.dim(provider.relevanceFloor !== void 0 ? "(calibrated for this project)" : "(built-in)")}` : warn("not calibrated: dense recall is off for this model")
4221
+ )
4222
+ );
4223
+ if (floor === void 0) next.push("codebuddy models calibrate");
4224
+ const rerank = file.rerank?.provider ?? "off";
4225
+ console.log(pc13.bold("Reranker"));
4226
+ if (rerank === "off") {
4227
+ console.log(
4228
+ row("provider", `off ${pc13.dim("(default: no reranker beat dense order in evals)")}`)
4229
+ );
4230
+ } else {
4231
+ console.log(
4232
+ row(
4233
+ "provider",
4234
+ `${rerank} ${pc13.dim(file.rerank?.model ?? DEFAULT_RERANK_MODELS[rerank].id)}`
4235
+ )
4236
+ );
4237
+ if (rerank !== "local") {
4238
+ const present = keyPresent(rerank, file.provider?.apiKey);
4239
+ console.log(
4240
+ row(
4241
+ "key",
4242
+ present ? ok(KEY_ENV2[rerank]) : warn(`${KEY_ENV2[rerank]} is not set; reranking is skipped`)
4243
+ )
4244
+ );
4245
+ }
4246
+ }
4247
+ console.log(pc13.bold("Memory index"));
4248
+ if (process.env.CODEBUDDY_SEMANTIC === "0") {
4249
+ console.log(row("recall", warn("CODEBUDDY_SEMANTIC=0: lexical only")));
4250
+ }
4251
+ if (!config.postgresUrl) {
4252
+ console.log(row("postgres", warn("not configured: recall is lexical only")));
4253
+ next.push("codebuddy use (sets up PostgreSQL)");
4254
+ } else {
4255
+ const counts = await vectorCounts(
4256
+ config.postgresUrl,
4257
+ config.namespace,
4258
+ space ? embeddingSpaceKey(space) : void 0
4259
+ );
4260
+ if (typeof counts === "string") {
4261
+ console.log(row("postgres", warn(`unreachable: ${counts}`)));
4262
+ next.push("codebuddy postgres up");
4263
+ } else {
4264
+ console.log(row("postgres", ok(`namespace ${config.namespace}`)));
4265
+ console.log(
4266
+ row(
4267
+ "vectors",
4268
+ `${counts.ready} ready` + (counts.pending ? `, ${counts.pending} pending` : "") + (counts.failed ? pc13.red(`, ${counts.failed} failed`) : "") + (counts.other ? pc13.yellow(`, ${counts.other} from another model`) : "")
4269
+ )
4270
+ );
4271
+ if (counts.pending || counts.other)
4272
+ next.push("codebuddy hooks sync (embeds pending and re-embeds old vectors)");
4273
+ }
4274
+ }
4275
+ console.log(
4276
+ next.length > 0 ? `
4277
+ ${pc13.bold("Next")}
4278
+ ${next.map((step) => ` ${pc13.cyan(step)}`).join("\n")}` : `
4279
+ ${ok("Semantic recall is ready.")} ${pc13.dim('Try: codebuddy context preview "<task>"')}`
4280
+ );
4281
+ }
4282
+ function registerModelsCommands(program, runSafely2) {
4283
+ const models = program.command("models").description("Embedding model and reranker behind per-prompt memory recall.").action(async () => runSafely2(printStatus));
4284
+ models.command("status").description("Show the embedding model, reranker, keys and vector index health.").action(async () => runSafely2(printStatus));
4285
+ models.command("use").argument("<provider>", `Embedding provider: ${EMBEDDERS.join(" | ")}.`).option("--model <id>", "Hosted model id (must return 384-d vectors).").option(
4286
+ "--floor <cosine>",
4287
+ "Relevance floor; otherwise built-in or `models calibrate`.",
4288
+ Number
4289
+ ).description("Choose where embeddings run: local (default, no key) or a hosted API.").addHelpText(
4290
+ "after",
4291
+ `
4292
+ Examples:
4293
+ codebuddy models use local
4294
+ codebuddy models use openrouter # ${DEFAULT_CLOUD_EMBEDDING_MODELS.openrouter}, needs OPENROUTER_API_KEY
4295
+ codebuddy models use openrouter --model nvidia/nemotron-3.5-embed-1b:free
4296
+ codebuddy models use huggingface # ${DEFAULT_CLOUD_EMBEDDING_MODELS.huggingface}, needs HF_TOKEN`
4297
+ ).action(async (choice, opts) => {
4298
+ await runSafely2(async () => {
4299
+ if (!EMBEDDERS.includes(choice)) {
4300
+ throw new Error(`Unknown provider "${choice}". Use one of: ${EMBEDDERS.join(", ")}.`);
4301
+ }
4302
+ const type = choice;
4303
+ if (type === "local" && opts.model) {
4304
+ throw new Error(
4305
+ "The local provider has one built-in model; --model is for hosted providers."
4306
+ );
4307
+ }
4308
+ const current = (await readConfigFile()).provider;
4309
+ await setConfigBlock("provider", {
4310
+ type,
4311
+ ...current?.apiKey ? { apiKey: current.apiKey } : {},
4312
+ ...opts.model ? { model: opts.model } : {},
4313
+ ...opts.floor !== void 0 ? { relevanceFloor: opts.floor } : {}
4314
+ });
4315
+ const model = type === "local" ? LOCAL_EMBEDDING_MODEL.id : opts.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[type];
4316
+ console.log(ok(`Embeddings: ${type} ${pc13.dim(model)}`));
4317
+ const missingKey = type !== "local" && !keyPresent(type, current?.apiKey);
4318
+ if (missingKey) {
4319
+ console.log(
4320
+ warn(
4321
+ `Set ${KEY_ENV2[type]} in your shell (keys are never stored in config), then run ${pc13.cyan("codebuddy hooks sync")}.`
4322
+ )
4323
+ );
4324
+ console.log(pc13.dim(" Existing vectors keep working until the new model can embed."));
4325
+ }
4326
+ if (opts.floor === void 0 && getModelDefinition(model)?.relevanceFloor === void 0) {
4327
+ console.log(
4328
+ warn(
4329
+ `No floor for ${model} yet: run ${pc13.cyan("codebuddy models calibrate")} to turn on dense recall.`
4330
+ )
4331
+ );
4332
+ }
4333
+ if (!missingKey) {
4334
+ syncInBackground(process.cwd());
4335
+ console.log(
4336
+ pc13.dim(" Re-embedding existing memory in the background (codebuddy hooks sync).")
4337
+ );
4338
+ }
4339
+ });
4340
+ });
4341
+ models.command("rerank").argument("<provider>", `Reranker: ${RERANKERS.join(" | ")}.`).option("--model <id>", "Cross-encoder model id.").description("Optionally reorder recalled facts with a cross-encoder (off by default).").addHelpText(
4342
+ "after",
4343
+ `
4344
+ Defaults: local ${DEFAULT_RERANK_MODELS.local.id} \xB7 huggingface ${DEFAULT_RERANK_MODELS.huggingface.id}
4345
+ openrouter ${DEFAULT_RERANK_MODELS.openrouter.id} (paid per search)
4346
+ In CodeBuddy's eval, no reranker beat the dense order; measure on your data
4347
+ before keeping one on. A reranker never admits a fact, it only orders them.`
4348
+ ).action(async (choice, opts) => {
4349
+ await runSafely2(async () => {
4350
+ if (!RERANKERS.includes(choice)) {
4351
+ throw new Error(`Unknown reranker "${choice}". Use one of: ${RERANKERS.join(", ")}.`);
4352
+ }
4353
+ await setConfigBlock("rerank", {
4354
+ provider: choice,
4355
+ ...opts.model ? { model: opts.model } : {}
4356
+ });
4357
+ if (choice === "off") {
4358
+ console.log(ok("Reranker off: facts keep their fused (keyword + vector) order."));
4359
+ return;
4360
+ }
4361
+ const reranker = choice;
4362
+ console.log(
4363
+ ok(`Reranker: ${reranker} ${pc13.dim(opts.model ?? DEFAULT_RERANK_MODELS[reranker].id)}`)
4364
+ );
4365
+ if (reranker !== "local" && !keyPresent(reranker, (await readConfigFile()).provider?.apiKey)) {
4366
+ console.log(warn(`Set ${KEY_ENV2[reranker]}; without it reranking is skipped.`));
4367
+ }
4368
+ if (reranker === "local")
4369
+ console.log(pc13.dim(" Run codebuddy models pull to download it now."));
4370
+ });
4371
+ });
4372
+ models.command("pull").description("Download the local models now instead of on first use.").action(async () => {
4373
+ await runSafely2(async () => {
4374
+ const { LocalModelProvider, rerankLocally } = await import("./local-OTOY56B4.js");
4375
+ const file = await readConfigFile();
4376
+ let started = Date.now();
4377
+ await withSpinner(
4378
+ `Downloading ${LOCAL_EMBEDDING_MODEL.id}`,
4379
+ () => new LocalModelProvider().embed({ input: "warm up" }),
4380
+ () => ok(`${LOCAL_EMBEDDING_MODEL.id} ready ${pc13.dim(`${Date.now() - started} ms`)}`)
4381
+ );
4382
+ if (file.rerank?.provider === "local") {
4383
+ const id = file.rerank.model ?? DEFAULT_RERANK_MODELS.local.id;
4384
+ const revision = id === DEFAULT_RERANK_MODELS.local.id ? DEFAULT_RERANK_MODELS.local.revision : void 0;
4385
+ started = Date.now();
4386
+ await withSpinner(
4387
+ `Downloading ${id}`,
4388
+ () => rerankLocally({ id, ...revision ? { revision } : {} }, "warm", ["up"]),
4389
+ () => ok(`${id} ready ${pc13.dim(`${Date.now() - started} ms`)}`)
4390
+ );
4391
+ }
4392
+ console.log(pc13.dim(` Cache: ${modelCacheDir()}`));
4393
+ });
4394
+ });
4395
+ models.command("calibrate").option("--dry-run", "Measure only; do not save the floor.").description(
4396
+ "Measure the configured embedder on a built-in task set and save its recall floor."
4397
+ ).action(async (opts) => {
4398
+ await runSafely2(async () => {
4399
+ const config = await loadRuntimeConfig(process.cwd(), { requireDatabase: false });
4400
+ const embedder = buildProviderFromConfig(config.provider);
4401
+ const result = await withSpinner(
4402
+ `Embedding 44 calibration texts with ${embedder.space?.model ?? config.provider.type}`,
4403
+ () => calibrateEmbedder(async (texts) => (await embedder.embed({ input: texts })).vectors),
4404
+ () => ok("Measured")
4405
+ );
4406
+ const pct = (value) => `${Math.round(value * 100)}%`;
4407
+ console.log(
4408
+ row(
4409
+ "ranking",
4410
+ `AUC ${result.auc.toFixed(3)} \xB7 right fact first ${pct(result.recallAt1)} ${pc13.dim("(held-out tasks)")}`
4411
+ )
4412
+ );
4413
+ console.log(
4414
+ row(
4415
+ "floor",
4416
+ `${result.best.floor}: precision ${pct(result.test.precision)}, recall ${pct(result.test.recall)}, silent on ${pct(result.test.abstain)} of unrelated tasks ${pc13.dim("(chosen on dev, measured on test)")}`
4417
+ )
4418
+ );
4419
+ if (opts.dryRun) return;
4420
+ const file = await readConfigFile();
4421
+ await setConfigBlock("provider", {
4422
+ ...file.provider ?? { type: config.provider.type },
4423
+ relevanceFloor: result.best.floor
4424
+ });
4425
+ console.log(ok(`Saved relevanceFloor ${result.best.floor} to .codebuddy/config.json`));
4426
+ });
4427
+ });
4428
+ }
4429
+
3354
4430
  // src/cli/commands/plan.ts
3355
- import { spawn as spawn4 } from "child_process";
4431
+ import { spawn as spawn6 } from "child_process";
3356
4432
  import { relative as relative4 } from "path";
3357
- import pc12 from "picocolors";
4433
+ import pc14 from "picocolors";
3358
4434
  function makeLifecycle(cwd = process.cwd()) {
3359
4435
  const store = new PlanFileStore(cwd);
3360
4436
  return { store, lifecycle: new PlanLifecycle(store) };
@@ -3362,67 +4438,67 @@ function makeLifecycle(cwd = process.cwd()) {
3362
4438
  function statusColor(status) {
3363
4439
  switch (status) {
3364
4440
  case "draft":
3365
- return pc12.dim(status);
4441
+ return pc14.dim(status);
3366
4442
  case "approved":
3367
- return pc12.cyan(status);
4443
+ return pc14.cyan(status);
3368
4444
  case "executing":
3369
- return pc12.yellow(status);
4445
+ return pc14.yellow(status);
3370
4446
  case "complete":
3371
- return pc12.green(status);
4447
+ return pc14.green(status);
3372
4448
  case "abandoned":
3373
- return pc12.red(status);
4449
+ return pc14.red(status);
3374
4450
  }
3375
4451
  }
3376
4452
  function renderPlan(plan) {
3377
4453
  const lines = [];
3378
- lines.push(`${pc12.bold(plan.title)} ${statusColor(plan.status)}`);
3379
- lines.push(pc12.dim(plan.id));
4454
+ lines.push(`${pc14.bold(plan.title)} ${statusColor(plan.status)}`);
4455
+ lines.push(pc14.dim(plan.id));
3380
4456
  lines.push("");
3381
- lines.push(`${pc12.bold("Brief:")} ${plan.brief}`);
4457
+ lines.push(`${pc14.bold("Brief:")} ${plan.brief}`);
3382
4458
  if (plan.filesToTouch.length > 0) {
3383
4459
  lines.push("");
3384
- lines.push(pc12.bold("Files to touch:"));
4460
+ lines.push(pc14.bold("Files to touch:"));
3385
4461
  for (const file of plan.filesToTouch) {
3386
4462
  lines.push(` ${file.action.padEnd(6)} ${file.path}${file.notes ? ` \u2014 ${file.notes}` : ""}`);
3387
4463
  }
3388
4464
  }
3389
4465
  if (plan.testsToAdd.length > 0) {
3390
4466
  lines.push("");
3391
- lines.push(pc12.bold("Tests to add:"));
4467
+ lines.push(pc14.bold("Tests to add:"));
3392
4468
  for (const test of plan.testsToAdd) {
3393
4469
  lines.push(` ${test.path}${test.description ? ` \u2014 ${test.description}` : ""}`);
3394
4470
  }
3395
4471
  }
3396
4472
  if (plan.outOfScope.length > 0) {
3397
4473
  lines.push("");
3398
- lines.push(pc12.bold("Out of scope:"));
4474
+ lines.push(pc14.bold("Out of scope:"));
3399
4475
  for (const item of plan.outOfScope) lines.push(` - ${item}`);
3400
4476
  }
3401
4477
  if (plan.risks.length > 0) {
3402
4478
  lines.push("");
3403
- lines.push(pc12.bold("Risks:"));
4479
+ lines.push(pc14.bold("Risks:"));
3404
4480
  for (const item of plan.risks) lines.push(` - ${item}`);
3405
4481
  }
3406
4482
  if (plan.commitSha) {
3407
4483
  lines.push("");
3408
- lines.push(`${pc12.bold("Commit:")} ${plan.commitSha}`);
4484
+ lines.push(`${pc14.bold("Commit:")} ${plan.commitSha}`);
3409
4485
  }
3410
4486
  lines.push("");
3411
- lines.push(pc12.dim("\u2500".repeat(40)));
4487
+ lines.push(pc14.dim("\u2500".repeat(40)));
3412
4488
  lines.push(plan.body);
3413
4489
  return lines.join("\n");
3414
4490
  }
3415
4491
  function openEditor2(path) {
3416
4492
  const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
3417
4493
  return new Promise((resolve14, reject) => {
3418
- const child = spawn4(editor, [path], { stdio: "inherit" });
4494
+ const child = spawn6(editor, [path], { stdio: "inherit" });
3419
4495
  child.on("error", reject);
3420
4496
  child.on("close", () => resolve14());
3421
4497
  });
3422
4498
  }
3423
4499
  function runGit(args) {
3424
4500
  return new Promise((resolve14, reject) => {
3425
- const child = spawn4("git", args, { stdio: ["ignore", "pipe", "pipe"] });
4501
+ const child = spawn6("git", args, { stdio: ["ignore", "pipe", "pipe"] });
3426
4502
  let stdout = "";
3427
4503
  let stderr = "";
3428
4504
  child.stdout.on("data", (c) => {
@@ -3447,14 +4523,14 @@ function registerPlanCommands(program, runSafely2) {
3447
4523
  title: opts.title ?? brief,
3448
4524
  brief
3449
4525
  });
3450
- console.log(`${pc12.green("created")} ${created.id} (${statusColor(created.status)})`);
4526
+ console.log(`${pc14.green("created")} ${created.id} (${statusColor(created.status)})`);
3451
4527
  if (opts.edit !== false && process.stdin.isTTY) {
3452
4528
  await openEditor2(created.path);
3453
4529
  const { store } = makeLifecycle();
3454
4530
  await store.readPlan(created.id);
3455
- console.log(pc12.dim("saved and validated"));
4531
+ console.log(pc14.dim("saved and validated"));
3456
4532
  }
3457
- console.log(pc12.dim(relative4(process.cwd(), created.path)));
4533
+ console.log(pc14.dim(relative4(process.cwd(), created.path)));
3458
4534
  });
3459
4535
  });
3460
4536
  plan.command("list").option("--status <status>", "Filter by status.").option("--all", "Include completed and abandoned plans.").description("List plans in the current namespace.").action(async (opts) => {
@@ -3466,11 +4542,11 @@ function registerPlanCommands(program, runSafely2) {
3466
4542
  else if (!opts.all)
3467
4543
  plans = plans.filter((p) => p.status !== "complete" && p.status !== "abandoned");
3468
4544
  if (plans.length === 0) {
3469
- console.log(pc12.dim("no plans"));
4545
+ console.log(pc14.dim("no plans"));
3470
4546
  return;
3471
4547
  }
3472
4548
  for (const p of plans) {
3473
- console.log(`${statusColor(p.status).padEnd(20)} ${pc12.bold(p.title)} ${pc12.dim(p.id)}`);
4549
+ console.log(`${statusColor(p.status).padEnd(20)} ${pc14.bold(p.title)} ${pc14.dim(p.id)}`);
3474
4550
  }
3475
4551
  });
3476
4552
  });
@@ -3486,7 +4562,7 @@ function registerPlanCommands(program, runSafely2) {
3486
4562
  const existing = await store.readPlan(id);
3487
4563
  await openEditor2(existing.path);
3488
4564
  await store.readPlan(id);
3489
- console.log(pc12.dim("saved and validated"));
4565
+ console.log(pc14.dim("saved and validated"));
3490
4566
  });
3491
4567
  });
3492
4568
  plan.command("approve").argument("<id>").description("Promote a draft to approved (one active plan per namespace).").action(async (id) => {
@@ -3510,7 +4586,7 @@ function registerPlanCommands(program, runSafely2) {
3510
4586
  ...opts.commit ? { commitSha: opts.commit } : {},
3511
4587
  ...opts.notes ? { notes: opts.notes } : {}
3512
4588
  });
3513
- if (!opts.commit) console.log(pc12.yellow("note: no --commit supplied"));
4589
+ if (!opts.commit) console.log(pc14.yellow("note: no --commit supplied"));
3514
4590
  console.log(`${next.id} \u2192 ${statusColor(next.status)}`);
3515
4591
  });
3516
4592
  });
@@ -3529,7 +4605,7 @@ function registerPlanCommands(program, runSafely2) {
3529
4605
  const args = ["diff", opts.from, ...opts.to ? [opts.to] : [], "--", relPath];
3530
4606
  const result = await runGit(args);
3531
4607
  if (result.stdout.trim().length === 0) {
3532
- console.log(pc12.dim("no changes"));
4608
+ console.log(pc14.dim("no changes"));
3533
4609
  } else {
3534
4610
  process.stdout.write(result.stdout);
3535
4611
  }
@@ -3546,13 +4622,13 @@ function registerPlanCommands(program, runSafely2) {
3546
4622
  throw new Error(`Invalid policy "${mode}". Use one of: ${PLAN_POLICIES.join(", ")}.`);
3547
4623
  }
3548
4624
  await setPlanPolicy(mode);
3549
- console.log(`${pc12.green("plan policy")} = ${mode}`);
4625
+ console.log(`${pc14.green("plan policy")} = ${mode}`);
3550
4626
  });
3551
4627
  });
3552
4628
  }
3553
4629
 
3554
4630
  // src/cli/commands/risk.ts
3555
- import pc13 from "picocolors";
4631
+ import pc15 from "picocolors";
3556
4632
  function registerRiskCommands(program, runSafely2) {
3557
4633
  const risk = program.command("risk").description("Assess evidence-backed change risk.");
3558
4634
  risk.command("assess").option("--plan <id>", "Assess paths listed in a CodeBuddy plan.").option("--paths <paths>", "Comma-separated repo-relative paths to assess.").option("--git", "Assess staged, unstaged, and untracked git changes.").option("--limit <n>", "Maximum risk items to show.", Number).option("--json", "Print machine-readable JSON.").option("--explain", "Show evidence for each risk item.").description("Assess risk for a plan, explicit paths, or current git changes.").action(
@@ -3580,31 +4656,31 @@ function parsePaths3(value) {
3580
4656
  }
3581
4657
  function printAssessment(assessment, explain) {
3582
4658
  if (assessment.items.length === 0) {
3583
- console.log(pc13.dim("no risk evidence found"));
4659
+ console.log(pc15.dim("no risk evidence found"));
3584
4660
  return;
3585
4661
  }
3586
4662
  for (const item of assessment.items) {
3587
4663
  console.log(
3588
- `${pc13.bold(item.path)} score ${scoreColor2(item.score)} category ${pc13.cyan(item.category)}`
4664
+ `${pc15.bold(item.path)} score ${scoreColor2(item.score)} category ${pc15.cyan(item.category)}`
3589
4665
  );
3590
4666
  console.log(` ${item.reason}`);
3591
4667
  if (explain) {
3592
4668
  for (const evidence of item.evidence) console.log(` \u21B3 ${formatEvidence(evidence)}`);
3593
4669
  } else {
3594
- console.log(pc13.dim(` ${item.evidence.length} evidence item(s)`));
4670
+ console.log(pc15.dim(` ${item.evidence.length} evidence item(s)`));
3595
4671
  }
3596
4672
  }
3597
4673
  console.log(
3598
- pc13.dim(
4674
+ pc15.dim(
3599
4675
  `
3600
4676
  ${assessment.stats.itemsProduced} item(s), ${assessment.stats.signalsRun} signal(s), ${assessment.stats.pathsConsidered} path(s) considered`
3601
4677
  )
3602
4678
  );
3603
4679
  }
3604
4680
  function scoreColor2(score) {
3605
- if (score >= 75) return pc13.red(String(score));
3606
- if (score >= 50) return pc13.yellow(String(score));
3607
- return pc13.green(String(score));
4681
+ if (score >= 75) return pc15.red(String(score));
4682
+ if (score >= 50) return pc15.yellow(String(score));
4683
+ return pc15.green(String(score));
3608
4684
  }
3609
4685
  function formatEvidence(evidence) {
3610
4686
  switch (evidence.kind) {
@@ -3626,7 +4702,7 @@ function truncate2(value, max) {
3626
4702
 
3627
4703
  // src/cli/commands/rules.ts
3628
4704
  import { relative as relative5 } from "path";
3629
- import pc14 from "picocolors";
4705
+ import pc16 from "picocolors";
3630
4706
  async function resolveMode(client, value) {
3631
4707
  if (value) {
3632
4708
  const mode = parseMode(value);
@@ -3666,7 +4742,7 @@ function registerRulesCommands(program, runSafely2) {
3666
4742
  `${actionLabel(result.action)} ${clientDisplayName(client)} workflow in ${where}`
3667
4743
  );
3668
4744
  if (result.action !== "unchanged") {
3669
- console.log(pc14.dim(" managed block between codebuddy:workflow markers; safe to re-run"));
4745
+ console.log(pc16.dim(" managed block between codebuddy:workflow markers; safe to re-run"));
3670
4746
  }
3671
4747
  });
3672
4748
  });
@@ -3674,20 +4750,20 @@ function registerRulesCommands(program, runSafely2) {
3674
4750
  function actionLabel(action) {
3675
4751
  switch (action) {
3676
4752
  case "created":
3677
- return pc14.green("created");
4753
+ return pc16.green("created");
3678
4754
  case "added":
3679
- return pc14.green("added");
4755
+ return pc16.green("added");
3680
4756
  case "updated":
3681
- return pc14.cyan("updated");
4757
+ return pc16.cyan("updated");
3682
4758
  case "removed":
3683
- return pc14.yellow("removed");
4759
+ return pc16.yellow("removed");
3684
4760
  default:
3685
- return pc14.dim("unchanged");
4761
+ return pc16.dim("unchanged");
3686
4762
  }
3687
4763
  }
3688
4764
 
3689
4765
  // src/cli/commands/suggest.ts
3690
- import pc15 from "picocolors";
4766
+ import pc17 from "picocolors";
3691
4767
  function parsePaths4(value) {
3692
4768
  return value.split(",").map((path) => path.trim()).filter(Boolean);
3693
4769
  }
@@ -3709,7 +4785,7 @@ function registerSuggestCommand(program, runSafely2) {
3709
4785
  }
3710
4786
  printSuggestions(result.suggestions);
3711
4787
  console.log(
3712
- pc15.dim(
4788
+ pc17.dim(
3713
4789
  `
3714
4790
  ${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} path(s)`
3715
4791
  )
@@ -3720,40 +4796,40 @@ ${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} pa
3720
4796
  }
3721
4797
  function printSuggestions(suggestions) {
3722
4798
  if (suggestions.length === 0) {
3723
- console.log(pc15.dim("no suggestions \u2014 nothing high-signal to report"));
4799
+ console.log(pc17.dim("no suggestions \u2014 nothing high-signal to report"));
3724
4800
  return;
3725
4801
  }
3726
4802
  for (const suggestion of suggestions) {
3727
- console.log(`${severityBadge2(suggestion.severity)} ${pc15.bold(suggestion.title)}`);
4803
+ console.log(`${severityBadge2(suggestion.severity)} ${pc17.bold(suggestion.title)}`);
3728
4804
  console.log(` ${suggestion.detail}`);
3729
4805
  for (const evidence of suggestion.evidence) {
3730
- console.log(pc15.dim(` \u21B3 ${evidence.kind}: ${evidence.detail}`));
4806
+ console.log(pc17.dim(` \u21B3 ${evidence.kind}: ${evidence.detail}`));
3731
4807
  }
3732
- if (suggestion.commandHint) console.log(pc15.dim(` \u2192 ${suggestion.commandHint}`));
4808
+ if (suggestion.commandHint) console.log(pc17.dim(` \u2192 ${suggestion.commandHint}`));
3733
4809
  }
3734
4810
  }
3735
4811
  function severityBadge2(severity) {
3736
4812
  switch (severity) {
3737
4813
  case "high":
3738
- return pc15.red("[high]");
4814
+ return pc17.red("[high]");
3739
4815
  case "medium":
3740
- return pc15.yellow("[med ]");
4816
+ return pc17.yellow("[med ]");
3741
4817
  case "low":
3742
- return pc15.cyan("[low ]");
4818
+ return pc17.cyan("[low ]");
3743
4819
  default:
3744
- return pc15.dim("[info]");
4820
+ return pc17.dim("[info]");
3745
4821
  }
3746
4822
  }
3747
4823
 
3748
4824
  // src/cli/commands/symbols.ts
3749
4825
  import { readFile as readFile13 } from "fs/promises";
3750
4826
  import { resolve as resolve13 } from "path";
3751
- import pc16 from "picocolors";
4827
+ import pc18 from "picocolors";
3752
4828
  function registerSymbolsCommand(program, runSafely2) {
3753
4829
  program.command("symbols").argument("<path>", "Repo-relative or absolute path to a code file.").option("--json", "Print machine-readable JSON.").description("List a file's top-level symbols (functions, classes, types, exports).").action(async (path, opts) => {
3754
4830
  await runSafely2(async () => {
3755
4831
  if (!supportsSymbols(path)) {
3756
- console.log(pc16.dim(`no symbol extraction for ${path} (unsupported language)`));
4832
+ console.log(pc18.dim(`no symbol extraction for ${path} (unsupported language)`));
3757
4833
  return;
3758
4834
  }
3759
4835
  const content = await readFile13(resolve13(path), "utf8");
@@ -3763,30 +4839,30 @@ function registerSymbolsCommand(program, runSafely2) {
3763
4839
  return;
3764
4840
  }
3765
4841
  if (table.length === 0) {
3766
- console.log(pc16.dim("no top-level symbols found"));
4842
+ console.log(pc18.dim("no top-level symbols found"));
3767
4843
  return;
3768
4844
  }
3769
4845
  for (const symbol of table) {
3770
- const badge = symbol.exported ? pc16.green("export") : pc16.dim("local ");
4846
+ const badge = symbol.exported ? pc18.green("export") : pc18.dim("local ");
3771
4847
  console.log(
3772
- `${badge} ${pc16.cyan(symbol.kind.padEnd(9))} ${pc16.bold(symbol.name)} ${pc16.dim(`L${symbol.startLine}-${symbol.endLine}`)}`
4848
+ `${badge} ${pc18.cyan(symbol.kind.padEnd(9))} ${pc18.bold(symbol.name)} ${pc18.dim(`L${symbol.startLine}-${symbol.endLine}`)}`
3773
4849
  );
3774
4850
  }
3775
- console.log(pc16.dim(`
4851
+ console.log(pc18.dim(`
3776
4852
  ${table.length} symbol(s)`));
3777
4853
  });
3778
4854
  });
3779
4855
  }
3780
4856
 
3781
4857
  // src/cli/commands/use-command.ts
3782
- import { basename } from "path";
3783
- import { confirm as confirm2, isCancel as isCancel2, log as log2, note as note2, password as password2, spinner as spinner2, text as text2 } from "@clack/prompts";
3784
- import pc17 from "picocolors";
4858
+ import { basename as basename2 } from "path";
4859
+ import { confirm as confirm2, isCancel as isCancel2, log as log2, note as note2, password as password2, select, spinner as spinner3, text as text2 } from "@clack/prompts";
4860
+ import pc19 from "picocolors";
3785
4861
 
3786
4862
  // src/core/global-config.ts
3787
4863
  import { chmod, mkdir as mkdir8, readFile as readFile14, writeFile as writeFile3 } from "fs/promises";
3788
4864
  import { homedir as homedir3 } from "os";
3789
- import { dirname as dirname12, join as join15 } from "path";
4865
+ import { dirname as dirname12, join as join16 } from "path";
3790
4866
  import { z as z2 } from "zod";
3791
4867
  var GLOBAL_DIR = ".codebuddy";
3792
4868
  var GLOBAL_FILE = "global.json";
@@ -3799,7 +4875,7 @@ var globalConfigSchema = z2.object({
3799
4875
  installPath: z2.string().optional()
3800
4876
  });
3801
4877
  function globalConfigPath(home = homedir3()) {
3802
- return join15(home, GLOBAL_DIR, GLOBAL_FILE);
4878
+ return join16(home, GLOBAL_DIR, GLOBAL_FILE);
3803
4879
  }
3804
4880
  async function readGlobalConfig(home = homedir3()) {
3805
4881
  const path = globalConfigPath(home);
@@ -3827,10 +4903,10 @@ async function mergeGlobalConfig(patch, home = homedir3()) {
3827
4903
  }
3828
4904
 
3829
4905
  // src/cli/commands/docker-autostart.ts
3830
- import { spawn as spawn5 } from "child_process";
4906
+ import { spawn as spawn7 } from "child_process";
3831
4907
  async function isDockerDaemonRunning() {
3832
4908
  return new Promise((resolve14) => {
3833
- const child = spawn5("docker", ["info"], { stdio: "ignore" });
4909
+ const child = spawn7("docker", ["info"], { stdio: "ignore" });
3834
4910
  child.on("error", () => resolve14(false));
3835
4911
  child.on("close", (code) => resolve14(code === 0));
3836
4912
  });
@@ -3838,7 +4914,7 @@ async function isDockerDaemonRunning() {
3838
4914
  async function tryStartDockerDesktop() {
3839
4915
  if (process.platform !== "darwin") return { started: false };
3840
4916
  return new Promise((resolve14) => {
3841
- const child = spawn5("open", ["-ga", "Docker"], { stdio: "ignore" });
4917
+ const child = spawn7("open", ["-ga", "Docker"], { stdio: "ignore" });
3842
4918
  child.on("error", () => resolve14({ started: false }));
3843
4919
  child.on("close", (code) => resolve14({ started: code === 0 }));
3844
4920
  });
@@ -3879,7 +4955,7 @@ async function runUseCommand(options = {}) {
3879
4955
  const sigintHandler = () => bail2();
3880
4956
  process.on("SIGINT", sigintHandler);
3881
4957
  try {
3882
- const folderName = basename(process.cwd());
4958
+ const folderName = basename2(process.cwd());
3883
4959
  const namespace = sanitizeNamespace(options.namespace ?? folderName);
3884
4960
  await initProjectScaffold();
3885
4961
  const graphifyEnabled = options.graphify ?? unwrap2(
@@ -3889,26 +4965,61 @@ async function runUseCommand(options = {}) {
3889
4965
  })
3890
4966
  );
3891
4967
  note2(
3892
- `Project folder: ${pc17.bold(process.cwd())}
3893
- Namespace: ${pc17.bold(namespace)}`,
4968
+ `Project folder: ${pc19.bold(process.cwd())}
4969
+ Namespace: ${pc19.bold(namespace)}`,
3894
4970
  "codebuddy use"
3895
4971
  );
3896
4972
  const stored = await readGlobalConfig();
4973
+ const projectConfig = await readConfigFile();
4974
+ let embeddings = projectConfig.provider?.type;
4975
+ if (embeddings) {
4976
+ log2.info(`Embeddings: ${embeddings} (change with ${pc19.cyan("codebuddy models use")}).`);
4977
+ } else {
4978
+ embeddings = unwrap2(
4979
+ await select({
4980
+ message: "Where should memory embeddings run?",
4981
+ initialValue: "local",
4982
+ options: [
4983
+ {
4984
+ value: "local",
4985
+ label: "On this machine",
4986
+ hint: "recommended \xB7 no key \xB7 ~90 MB once"
4987
+ },
4988
+ {
4989
+ value: "openrouter",
4990
+ label: "OpenRouter",
4991
+ hint: "OPENROUTER_API_KEY \xB7 free models listed"
4992
+ },
4993
+ {
4994
+ value: "huggingface",
4995
+ label: "Hugging Face",
4996
+ hint: "HF_TOKEN \xB7 small free monthly credit"
4997
+ }
4998
+ ]
4999
+ })
5000
+ );
5001
+ if (embeddings === "openrouter" && !process.env.OPENROUTER_API_KEY) {
5002
+ log2.warn(
5003
+ "Export OPENROUTER_API_KEY before your agent starts; keys are never stored in config."
5004
+ );
5005
+ }
5006
+ }
3897
5007
  let hfToken;
3898
- if (process.env.HF_TOKEN) {
5008
+ if (embeddings !== "huggingface") {
5009
+ hfToken = process.env.HF_TOKEN ?? stored.hfToken;
5010
+ } else if (process.env.HF_TOKEN) {
3899
5011
  hfToken = process.env.HF_TOKEN;
3900
5012
  log2.info("Using HF_TOKEN from environment.");
3901
5013
  } else if (stored.hfToken) {
3902
5014
  hfToken = stored.hfToken;
3903
- log2.info(`Reusing saved Hugging Face token (${pc17.dim("~/.codebuddy/global.json")}).`);
5015
+ log2.info(`Reusing saved Hugging Face token (${pc19.dim("~/.codebuddy/global.json")}).`);
3904
5016
  } else {
3905
5017
  hfToken = unwrap2(
3906
5018
  await password2({
3907
- message: "Hugging Face token (optional; press Enter for local-only mode)"
5019
+ message: "Hugging Face token (for hosted embeddings)"
3908
5020
  })
3909
5021
  );
3910
- if (!hfToken)
3911
- log2.info("No Hugging Face token supplied; local file context remains available.");
5022
+ if (!hfToken) log2.warn("No token: hosted embeddings will fail until HF_TOKEN is set.");
3912
5023
  }
3913
5024
  const initialUrl = options.postgresUrl ?? process.env.DATABASE_URL ?? stored.databaseUrl ?? DEFAULT_DB_URL;
3914
5025
  const databaseUrl = unwrap2(
@@ -3918,7 +5029,7 @@ Namespace: ${pc17.bold(namespace)}`,
3918
5029
  validate: (value) => value && value.length > 0 ? void 0 : "Postgres URL is required."
3919
5030
  })
3920
5031
  );
3921
- const dbSpinner = spinner2();
5032
+ const dbSpinner = spinner3();
3922
5033
  dbSpinner.start("Checking Postgres");
3923
5034
  let connected = await pingOnce2(databaseUrl);
3924
5035
  if (connected) dbSpinner.stop("Local Postgres reachable.");
@@ -3933,7 +5044,7 @@ Namespace: ${pc17.bold(namespace)}`,
3933
5044
  ));
3934
5045
  if (shouldUseDocker) {
3935
5046
  if (!await isDockerDaemonRunning()) {
3936
- const dockerSpinner = spinner2();
5047
+ const dockerSpinner = spinner3();
3937
5048
  dockerSpinner.start("Starting Docker Desktop");
3938
5049
  await tryStartDockerDesktop();
3939
5050
  const ready = await waitForDockerDaemon(6e4);
@@ -3943,7 +5054,7 @@ Namespace: ${pc17.bold(namespace)}`,
3943
5054
  }
3944
5055
  dockerSpinner.stop("Docker daemon is up.");
3945
5056
  }
3946
- const composeSpinner = spinner2();
5057
+ const composeSpinner = spinner3();
3947
5058
  composeSpinner.start("docker compose up -d");
3948
5059
  try {
3949
5060
  const result = await postgresUp();
@@ -3970,7 +5081,7 @@ Namespace: ${pc17.bold(namespace)}`,
3970
5081
  bail2();
3971
5082
  }
3972
5083
  if (!connected) bail2();
3973
- const migrateSpinner = spinner2();
5084
+ const migrateSpinner = spinner3();
3974
5085
  migrateSpinner.start("Enabling pgvector + applying migrations");
3975
5086
  {
3976
5087
  const client = createDatabaseClient({ postgresUrl: databaseUrl });
@@ -3991,7 +5102,7 @@ Namespace: ${pc17.bold(namespace)}`,
3991
5102
  installPath: process.argv[1] ?? void 0
3992
5103
  });
3993
5104
  const existingConfig = await readConfigFile();
3994
- const indexSpinner = spinner2();
5105
+ const indexSpinner = spinner3();
3995
5106
  indexSpinner.start("Indexing the existing repository");
3996
5107
  try {
3997
5108
  const result = await indexRepository({ repositoryRoot: process.cwd() });
@@ -4005,7 +5116,11 @@ Namespace: ${pc17.bold(namespace)}`,
4005
5116
  ...existingConfig,
4006
5117
  postgresUrl: databaseUrl,
4007
5118
  namespace,
4008
- provider: { type: "huggingface", ...hfToken ? { apiKey: hfToken } : {} },
5119
+ provider: {
5120
+ ...existingConfig.provider,
5121
+ type: embeddings,
5122
+ ...hfToken ? { apiKey: hfToken } : {}
5123
+ },
4009
5124
  tokenBudget: existingConfig.tokenBudget ?? 4e3,
4010
5125
  graphify: { enabled: graphifyConfigured, graphPath: "graphify-out/graph.json" }
4011
5126
  });
@@ -4023,11 +5138,11 @@ Namespace: ${pc17.bold(namespace)}`,
4023
5138
  const install = await installClaudeEntry({
4024
5139
  namespace,
4025
5140
  projectRoot: process.cwd(),
4026
- hfToken,
5141
+ ...hfToken ? { hfToken } : {},
4027
5142
  databaseUrl
4028
5143
  });
4029
5144
  log2.success(
4030
- `Registered ${pc17.bold(install.serverKey)} in Claude Desktop. Restart with \u2318Q to activate.`
5145
+ `Registered ${pc19.bold(install.serverKey)} in Claude Desktop. Restart with \u2318Q to activate.`
4031
5146
  );
4032
5147
  } catch (error) {
4033
5148
  log2.warn(`Claude Desktop registration skipped: ${error.message}`);
@@ -4038,11 +5153,11 @@ Namespace: ${pc17.bold(namespace)}`,
4038
5153
  const install = await installCodexEntry({
4039
5154
  namespace,
4040
5155
  projectRoot: process.cwd(),
4041
- hfToken,
5156
+ ...hfToken ? { hfToken } : {},
4042
5157
  databaseUrl
4043
5158
  });
4044
5159
  log2.success(
4045
- `Registered ${pc17.bold(install.serverKey)} in Codex. Restart Codex to activate.`
5160
+ `Registered ${pc19.bold(install.serverKey)} in Codex. Restart Codex to activate.`
4046
5161
  );
4047
5162
  } catch (error) {
4048
5163
  log2.warn(`Codex registration skipped: ${error.message}`);
@@ -4050,24 +5165,24 @@ Namespace: ${pc17.bold(namespace)}`,
4050
5165
  }
4051
5166
  note2(
4052
5167
  [
4053
- `${pc17.green("\u2713")} CodeBuddy is ready for ${pc17.bold(folderName)}.`,
4054
- `${pc17.green("\u2713")} Scaffolded ${pc17.cyan(".codebuddy/policies.yaml")} for project-specific guardrails.`,
4055
- `${pc17.green("\u2713")} ${graphifyMessage}`,
4056
- `${pc17.green("\u2713")} Added agent workflow instructions to ${pc17.cyan("CLAUDE.md")} and ${pc17.cyan("AGENTS.md")}.`,
5168
+ `${pc19.green("\u2713")} CodeBuddy is ready for ${pc19.bold(folderName)}.`,
5169
+ `${pc19.green("\u2713")} Scaffolded ${pc19.cyan(".codebuddy/policies.yaml")} for project-specific guardrails.`,
5170
+ `${pc19.green("\u2713")} ${graphifyMessage}`,
5171
+ `${pc19.green("\u2713")} Added agent workflow instructions to ${pc19.cyan("CLAUDE.md")} and ${pc19.cyan("AGENTS.md")}.`,
4057
5172
  ``,
4058
5173
  `Next:`,
4059
5174
  ` 1. Restart Claude Desktop and Codex.`,
4060
- ` 2. Ask Claude or Codex to use the ${pc17.cyan("context_pack")} tool for each coding task.`,
5175
+ ` 2. Ask Claude or Codex to use the ${pc19.cyan("context_pack")} tool for each coding task.`,
4061
5176
  ` 3. Indexing, Graphify, Markdown memory import, and embeddings now update automatically while the MCP server is running.`,
4062
- ` 4. Run ${pc17.cyan("codebuddy doctor")} or ${pc17.cyan("codebuddy db doctor")} if you want a full health check.`,
5177
+ ` 4. Run ${pc19.cyan("codebuddy doctor")} or ${pc19.cyan("codebuddy db doctor")} if you want a full health check.`,
4063
5178
  ``,
4064
5179
  `Manage:`,
4065
- ` codebuddy claude list ${pc17.dim("# see every project wired up")}`,
4066
- ` codebuddy codex list ${pc17.dim("# see Codex entries")}`,
4067
- ` codebuddy db test ${pc17.dim("# quick Postgres connectivity check")}`,
5180
+ ` codebuddy claude list ${pc19.dim("# see every project wired up")}`,
5181
+ ` codebuddy codex list ${pc19.dim("# see Codex entries")}`,
5182
+ ` codebuddy db test ${pc19.dim("# quick Postgres connectivity check")}`,
4068
5183
  ` codebuddy claude remove codebuddy-${namespace}`,
4069
5184
  ` codebuddy codex remove codebuddy-${namespace}`,
4070
- ` codebuddy postgres down ${pc17.dim("# stop the DB (data preserved)")}`
5185
+ ` codebuddy postgres down ${pc19.dim("# stop the DB (data preserved)")}`
4071
5186
  ].join("\n"),
4072
5187
  "Done"
4073
5188
  );
@@ -4078,7 +5193,7 @@ Namespace: ${pc17.bold(namespace)}`,
4078
5193
 
4079
5194
  // src/cli/commands/worklog.ts
4080
5195
  import { relative as relative6 } from "path";
4081
- import pc18 from "picocolors";
5196
+ import pc20 from "picocolors";
4082
5197
  function registerWorklogCommands(program, runSafely2) {
4083
5198
  const worklog = program.command("worklog").description("Inspect the cross-chat worklog of completed turns.");
4084
5199
  worklog.command("list", { isDefault: true }).option("--limit <n>", "Maximum entries to show.", "20").option("--json", "Print machine-readable JSON.").description("List worklog entries, newest first.").action(async (opts) => {
@@ -4089,7 +5204,7 @@ function registerWorklogCommands(program, runSafely2) {
4089
5204
  return;
4090
5205
  }
4091
5206
  if (entries.length === 0) {
4092
- console.log(pc18.dim("no worklog entries yet"));
5207
+ console.log(pc20.dim("no worklog entries yet"));
4093
5208
  return;
4094
5209
  }
4095
5210
  for (const entry of entries) console.log(renderRow2(entry));
@@ -4103,36 +5218,36 @@ function registerWorklogCommands(program, runSafely2) {
4103
5218
  worklog.command("latest").description("Show the most recent worklog entry.").action(async () => {
4104
5219
  await runSafely2(async () => {
4105
5220
  const [entry] = await new WorklogStore().list();
4106
- console.log(entry ? renderEntry(entry) : pc18.dim("no worklog entries yet"));
5221
+ console.log(entry ? renderEntry(entry) : pc20.dim("no worklog entries yet"));
4107
5222
  });
4108
5223
  });
4109
5224
  }
4110
5225
  function renderRow2(entry) {
4111
5226
  const summary = entry.summary.replace(/\s+/g, " ");
4112
5227
  const short = summary.length <= 70 ? summary : `${summary.slice(0, 69)}\u2026`;
4113
- return `${pc18.dim(entry.createdAt.slice(0, 16))} ${pc18.dim(entry.id)} ${short} ${pc18.dim(`(${entry.files.length} file(s))`)}`;
5228
+ return `${pc20.dim(entry.createdAt.slice(0, 16))} ${pc20.dim(entry.id)} ${short} ${pc20.dim(`(${entry.files.length} file(s))`)}`;
4114
5229
  }
4115
5230
  function renderEntry(entry) {
4116
- const lines = [`${pc18.bold(entry.id)} ${pc18.dim(entry.createdAt)} ${pc18.dim(entry.namespace)}`];
4117
- if (entry.sourceSessionId) lines.push(`${pc18.bold("session:")} ${entry.sourceSessionId}`);
4118
- if (entry.sourcePlanId) lines.push(`${pc18.bold("plan:")} ${entry.sourcePlanId}`);
4119
- if (entry.commitSha) lines.push(`${pc18.bold("commit:")} ${entry.commitSha}`);
4120
- if (entry.files.length > 0) lines.push(`${pc18.bold("files:")} ${entry.files.join(", ")}`);
5231
+ const lines = [`${pc20.bold(entry.id)} ${pc20.dim(entry.createdAt)} ${pc20.dim(entry.namespace)}`];
5232
+ if (entry.sourceSessionId) lines.push(`${pc20.bold("session:")} ${entry.sourceSessionId}`);
5233
+ if (entry.sourcePlanId) lines.push(`${pc20.bold("plan:")} ${entry.sourcePlanId}`);
5234
+ if (entry.commitSha) lines.push(`${pc20.bold("commit:")} ${entry.commitSha}`);
5235
+ if (entry.files.length > 0) lines.push(`${pc20.bold("files:")} ${entry.files.join(", ")}`);
4121
5236
  if (entry.verification.length > 0) {
4122
- lines.push(`${pc18.bold("verification:")}`, ...entry.verification.map((line) => ` - ${line}`));
5237
+ lines.push(`${pc20.bold("verification:")}`, ...entry.verification.map((line) => ` - ${line}`));
4123
5238
  }
4124
5239
  if (entry.savedMemory.length > 0) {
4125
- lines.push(`${pc18.bold("saved memory:")} ${entry.savedMemory.join(", ")}`);
5240
+ lines.push(`${pc20.bold("saved memory:")} ${entry.savedMemory.join(", ")}`);
4126
5241
  }
4127
5242
  if (entry.queuedForReview.length > 0) {
4128
- lines.push(`${pc18.bold("queued for review:")} ${entry.queuedForReview.join(", ")}`);
5243
+ lines.push(`${pc20.bold("queued for review:")} ${entry.queuedForReview.join(", ")}`);
4129
5244
  }
4130
- lines.push("", entry.summary, "", pc18.dim(relative6(process.cwd(), entry.path)));
5245
+ lines.push("", entry.summary, "", pc20.dim(relative6(process.cwd(), entry.path)));
4131
5246
  return lines.join("\n");
4132
5247
  }
4133
5248
 
4134
5249
  // src/cli/commands/zvec.ts
4135
- import pc19 from "picocolors";
5250
+ import pc21 from "picocolors";
4136
5251
  function registerZvecCommands(program, runSafely2) {
4137
5252
  const zvec = program.command("zvec").description("Optional zvec-grep semantic file discovery for the context tools.");
4138
5253
  zvec.command("status", { isDefault: true }).option("--json", "Print machine-readable JSON.").description("Show whether zvec-grep is enabled, installed, supported and indexed.").action(async (opts) => {
@@ -4142,22 +5257,22 @@ function registerZvecCommands(program, runSafely2) {
4142
5257
  console.log(JSON.stringify(report, null, 2));
4143
5258
  return;
4144
5259
  }
4145
- const mark = (ok) => ok ? pc19.green("yes") : pc19.yellow("no");
5260
+ const mark = (ok2) => ok2 ? pc21.green("yes") : pc21.yellow("no");
4146
5261
  console.log(`enabled: ${mark(report.enabled)}`);
4147
5262
  console.log(`binary: ${report.binary}`);
4148
5263
  console.log(
4149
- `version: ${report.version ?? pc19.yellow("not found")}${report.version && !report.supported ? pc19.yellow(" (unsupported; need 0.2.x)") : ""}`
5264
+ `version: ${report.version ?? pc21.yellow("not found")}${report.version && !report.supported ? pc21.yellow(" (unsupported; need 0.2.x)") : ""}`
4150
5265
  );
4151
5266
  console.log(`index ready: ${mark(report.indexReady)}`);
4152
5267
  console.log(
4153
- `zg server: ${report.server} ${pc19.dim("(running = faster queries; optional)")}`
5268
+ `zg server: ${report.server} ${pc21.dim("(running = faster queries; optional)")}`
4154
5269
  );
4155
5270
  console.log(`status: ${report.reason}`);
4156
- if (!report.version) console.log(pc19.dim("install: npm install -g @zvec/zvec-grep"));
5271
+ if (!report.version) console.log(pc21.dim("install: npm install -g @zvec/zvec-grep"));
4157
5272
  else if (report.supported && !report.indexReady)
4158
- console.log(pc19.dim("next: codebuddy zvec index"));
5273
+ console.log(pc21.dim("next: codebuddy zvec index"));
4159
5274
  else if (report.indexReady && !report.enabled)
4160
- console.log(pc19.dim("next: codebuddy zvec enable"));
5275
+ console.log(pc21.dim("next: codebuddy zvec enable"));
4161
5276
  });
4162
5277
  });
4163
5278
  zvec.command("index").option("--model <model>", "Embedding model for a new index.", DEFAULT_ZVEC_MODEL).option("--rebuild", "Rebuild the index from scratch (e.g. after changing model).").description("Build or refresh the zg index for this repository (explicit; never automatic).").action(async (opts) => {
@@ -4168,7 +5283,7 @@ function registerZvecCommands(program, runSafely2) {
4168
5283
  });
4169
5284
  if (code !== 0) throw new Error(`zg index exited with code ${code}`);
4170
5285
  console.log(
4171
- pc19.dim("tip: add .zvec-grep/ to .gitignore; enable with `codebuddy zvec enable`")
5286
+ pc21.dim("tip: add .zvec-grep/ to .gitignore; enable with `codebuddy zvec enable`")
4172
5287
  );
4173
5288
  });
4174
5289
  });
@@ -4176,14 +5291,14 @@ function registerZvecCommands(program, runSafely2) {
4176
5291
  await runSafely2(async () => {
4177
5292
  await setZvecEnabled(true);
4178
5293
  console.log(
4179
- `${pc19.green("enabled")} zvec-grep retrieval (falls back to keywords if unavailable)`
5294
+ `${pc21.green("enabled")} zvec-grep retrieval (falls back to keywords if unavailable)`
4180
5295
  );
4181
5296
  });
4182
5297
  });
4183
5298
  zvec.command("disable").description("Stop using zvec-grep; keyword ranking only.").action(async () => {
4184
5299
  await runSafely2(async () => {
4185
5300
  await setZvecEnabled(false);
4186
- console.log(`${pc19.yellow("disabled")} zvec-grep retrieval`);
5301
+ console.log(`${pc21.yellow("disabled")} zvec-grep retrieval`);
4187
5302
  });
4188
5303
  });
4189
5304
  }
@@ -4192,7 +5307,7 @@ function registerZvecCommands(program, runSafely2) {
4192
5307
  function createCli() {
4193
5308
  const program = new Command();
4194
5309
  program.name("codebuddy").description(
4195
- "Local-first MCP memory and automatic project-context engine for multi-agent coding."
5310
+ "Project memory and context for AI coding agents: recalls the right facts, files and risks on every prompt."
4196
5311
  ).version(VERSION);
4197
5312
  program.action(async () => {
4198
5313
  await runSafely(async () => {
@@ -4200,7 +5315,7 @@ function createCli() {
4200
5315
  });
4201
5316
  });
4202
5317
  program.command("use").description(
4203
- "One-command setup for the current project folder. Reuses saved HF token + DB URL, auto-starts Docker, runs migrations, and wires Claude Desktop."
5318
+ "One-command setup for this folder: PostgreSQL (starts Docker if needed), migrations, embeddings, and agent wiring."
4204
5319
  ).argument("[namespace]", "Override the namespace (defaults to current folder name).").option("--postgres-url <url>", "Postgres URL to use for this project.").option("--skip-claude", "Do not register with Claude Desktop.").option("--skip-codex", "Do not register with Codex.").option("--graphify", "Enable Graphify setup without prompting.").option("--no-graphify", "Skip Graphify setup without prompting.").option(
4205
5320
  "--docker",
4206
5321
  "Start CodeBuddy's bundled Postgres container when the default DB is unavailable."
@@ -4444,9 +5559,9 @@ function createCli() {
4444
5559
  });
4445
5560
  program.command("export").argument("<namespace>").description("Export facts from a namespace as JSON.").action(async (namespace) => {
4446
5561
  await withRuntime(async (runtime) => {
4447
- const row = await runtime.repository.getNamespaceByName(namespace);
4448
- if (!row) throw new Error(`Namespace ${namespace} does not exist.`);
4449
- const facts = await runtime.repository.listFacts({ namespaceId: row.id, limit: 1e4 });
5562
+ const row2 = await runtime.repository.getNamespaceByName(namespace);
5563
+ if (!row2) throw new Error(`Namespace ${namespace} does not exist.`);
5564
+ const facts = await runtime.repository.listFacts({ namespaceId: row2.id, limit: 1e4 });
4450
5565
  console.log(JSON.stringify({ namespace, facts }, null, 2));
4451
5566
  });
4452
5567
  });
@@ -4486,14 +5601,64 @@ function createCli() {
4486
5601
  registerSuggestCommand(program, runSafely);
4487
5602
  registerRulesCommands(program, runSafely);
4488
5603
  registerHooksCommands(program, runSafely);
5604
+ registerModelsCommands(program, runSafely);
5605
+ registerDashboardCommand(program, runSafely);
4489
5606
  registerClientsCommands(program, runSafely);
4490
5607
  registerGitHubCommands(program, runSafely);
4491
5608
  registerSymbolsCommand(program, runSafely);
4492
5609
  program.action(async () => {
4493
5610
  await runSafely(() => runUseCommand());
4494
5611
  });
5612
+ groupHelp(program);
4495
5613
  return program;
4496
5614
  }
5615
+ var HELP_GROUPS = [
5616
+ ["Get started:", ["use", "init", "doctor", "hooks", "clients", "models", "dashboard"]],
5617
+ [
5618
+ "Daily work:",
5619
+ ["context", "plan", "change", "risk", "suggest", "memory", "worklog", "map", "symbols"]
5620
+ ],
5621
+ ["Indexing and search:", ["index", "watch", "reindex", "zvec", "graphify", "llm"]],
5622
+ [
5623
+ "Database:",
5624
+ [
5625
+ "postgres",
5626
+ "db",
5627
+ "migrate",
5628
+ "stats",
5629
+ "inspect",
5630
+ "namespaces",
5631
+ "list-facts",
5632
+ "export",
5633
+ "prune"
5634
+ ]
5635
+ ],
5636
+ ["Integrations:", ["serve", "claude", "codex", "github", "rules", "learn", "eval"]]
5637
+ ];
5638
+ function groupHelp(program) {
5639
+ const groupOf = new Map(
5640
+ HELP_GROUPS.flatMap(([group, names]) => names.map((name) => [name, group]))
5641
+ );
5642
+ for (const command of program.commands) {
5643
+ command.helpGroup(groupOf.get(command.name()) ?? "Other:");
5644
+ }
5645
+ const position = new Map(
5646
+ HELP_GROUPS.flatMap(([, names]) => names).map((name, index) => [name, index])
5647
+ );
5648
+ program.commands.sort(
5649
+ (a, b) => (position.get(a.name()) ?? 999) - (position.get(b.name()) ?? 999)
5650
+ );
5651
+ program.addHelpText(
5652
+ "after",
5653
+ `
5654
+ Quick start:
5655
+ codebuddy set up this project (PostgreSQL, hooks, agent)
5656
+ codebuddy models check the embedding model and memory index
5657
+ codebuddy dashboard live page of recalls, tool calls and tokens
5658
+ codebuddy context preview "fix the login redirect"
5659
+ see exactly what an agent would be given`
5660
+ );
5661
+ }
4497
5662
  async function withRuntime(fn) {
4498
5663
  await runSafely(async () => {
4499
5664
  const runtime = await createRuntime(await loadRuntimeConfig());
@@ -4508,7 +5673,7 @@ async function runSafely(fn) {
4508
5673
  try {
4509
5674
  await fn();
4510
5675
  } catch (error) {
4511
- console.error(pc20.red(String(redactSecrets(error.message))));
5676
+ console.error(pc22.red(String(redactSecrets(error.message))));
4512
5677
  process.exitCode = 1;
4513
5678
  }
4514
5679
  }
@@ -4522,19 +5687,19 @@ function parseDuration(value) {
4522
5687
  return amount * 60 * 1e3;
4523
5688
  }
4524
5689
  function printDoctor(report) {
4525
- const ok = pc20.green("ok");
4526
- const bad = pc20.red("fail");
4527
- const warn = pc20.yellow("warn");
4528
- console.log(`scaffold root: ${report.scaffold.codebuddyDir.ok ? ok : bad}`);
5690
+ const ok2 = pc22.green("ok");
5691
+ const bad = pc22.red("fail");
5692
+ const warn2 = pc22.yellow("warn");
5693
+ console.log(`scaffold root: ${report.scaffold.codebuddyDir.ok ? ok2 : bad}`);
4529
5694
  if (report.scaffold.codebuddyDir.warning)
4530
5695
  console.log(` ${report.scaffold.codebuddyDir.warning}`);
4531
- console.log(`policy scaffold: ${report.scaffold.policies.ok ? ok : bad}`);
5696
+ console.log(`policy scaffold: ${report.scaffold.policies.ok ? ok2 : bad}`);
4532
5697
  if (report.scaffold.policies.warning) console.log(` ${report.scaffold.policies.warning}`);
4533
- console.log(`DB connectivity: ${report.db.ok ? ok : bad}`);
5698
+ console.log(`DB connectivity: ${report.db.ok ? ok2 : bad}`);
4534
5699
  if (report.db.error) console.log(` ${report.db.error}`);
4535
- console.log(`pgvector: ${report.pgvector.ok ? ok : bad}`);
5700
+ console.log(`pgvector: ${report.pgvector.ok ? ok2 : bad}`);
4536
5701
  if (report.pgvector.error) console.log(` ${report.pgvector.error}`);
4537
- console.log(`namespace: ${report.namespace.ok ? ok : warn}`);
5702
+ console.log(`namespace: ${report.namespace.ok ? ok2 : warn2}`);
4538
5703
  console.log(
4539
5704
  ` runtime=${report.namespace.runtime ?? "unset"} default=${report.namespace.expectedDefault}`
4540
5705
  );
@@ -4547,9 +5712,9 @@ function printDoctor(report) {
4547
5712
  );
4548
5713
  console.log(`estimated daily cap remaining: ${report.usage.estimatedDailyCapRemaining}`);
4549
5714
  if (report.usage.warning80Percent)
4550
- console.log(pc20.yellow("usage warning: daily cap is over 80% used"));
5715
+ console.log(pc22.yellow("usage warning: daily cap is over 80% used"));
4551
5716
  console.log(
4552
- `config permissions: ${report.config.exists ? report.config.mode : "missing"} ${report.config.ok ? ok : warn}`
5717
+ `config permissions: ${report.config.exists ? report.config.mode : "missing"} ${report.config.ok ? ok2 : warn2}`
4553
5718
  );
4554
5719
  if (report.config.warning) console.log(` ${report.config.warning}`);
4555
5720
  for (const model of report.models) {
@@ -4557,19 +5722,19 @@ function printDoctor(report) {
4557
5722
  }
4558
5723
  }
4559
5724
  function printDbDoctor(report) {
4560
- const ok = pc20.green("ok");
4561
- const bad = pc20.red("fail");
4562
- const warn = pc20.yellow("warn");
4563
- console.log(`DB connectivity: ${report.db.ok ? ok : bad}`);
5725
+ const ok2 = pc22.green("ok");
5726
+ const bad = pc22.red("fail");
5727
+ const warn2 = pc22.yellow("warn");
5728
+ console.log(`DB connectivity: ${report.db.ok ? ok2 : bad}`);
4564
5729
  if (report.db.postgresUrlSource) console.log(` url source: ${report.db.postgresUrlSource}`);
4565
5730
  if (report.db.error) console.log(` ${report.db.error}`);
4566
- console.log(`pgvector: ${report.pgvector.ok ? ok : bad}`);
5731
+ console.log(`pgvector: ${report.pgvector.ok ? ok2 : bad}`);
4567
5732
  if (report.pgvector.error) console.log(` ${report.pgvector.error}`);
4568
- console.log(`namespace: ${report.namespace.ok ? ok : warn}`);
5733
+ console.log(`namespace: ${report.namespace.ok ? ok2 : warn2}`);
4569
5734
  console.log(` runtime=${report.namespace.runtime ?? "unset"}`);
4570
5735
  if (report.namespace.warning) console.log(` ${report.namespace.warning}`);
4571
5736
  }
4572
5737
  export {
4573
5738
  createCli
4574
5739
  };
4575
- //# sourceMappingURL=main-BFDVQHKO.js.map
5740
+ //# sourceMappingURL=main-HFHLGID4.js.map