@orquentra/codebuddy 2.4.1 → 2.5.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.
- package/README.md +17 -2
- package/dist/chunk-3XYQGFPB.js +91 -0
- package/dist/chunk-3XYQGFPB.js.map +1 -0
- package/dist/chunk-4BYGTEJT.js +103 -0
- package/dist/chunk-4BYGTEJT.js.map +1 -0
- package/dist/{chunk-ITGRANYW.js → chunk-6MKEIDX5.js} +2 -2
- package/dist/{chunk-K63EOA4B.js → chunk-776EQXDV.js} +25 -6
- package/dist/chunk-776EQXDV.js.map +1 -0
- package/dist/{chunk-5OWZVZSO.js → chunk-CQPCHMBS.js} +8 -165
- package/dist/chunk-CQPCHMBS.js.map +1 -0
- package/dist/chunk-JDZ3VK26.js +4006 -0
- package/dist/chunk-JDZ3VK26.js.map +1 -0
- package/dist/chunk-JMWJ7V6Y.js +99 -0
- package/dist/chunk-JMWJ7V6Y.js.map +1 -0
- package/dist/chunk-KYK6RPAJ.js +99 -0
- package/dist/chunk-KYK6RPAJ.js.map +1 -0
- package/dist/{chunk-IBMJYIEP.js → chunk-L5AYZC3F.js} +7 -7
- package/dist/{chunk-3ZELDCR3.js → chunk-PCE4STAY.js} +7 -7
- package/dist/{chunk-TI5TQD6D.js → chunk-QDBDYCM2.js} +2 -2
- package/dist/{chunk-DSN5CEHO.js → chunk-ROQTT5ZO.js} +3 -3
- package/dist/{chunk-IE2OUO54.js → chunk-UFZD3JAA.js} +3 -3
- package/dist/chunk-Z5QB367W.js +5028 -0
- package/dist/chunk-Z5QB367W.js.map +1 -0
- package/dist/cli/index.js +2 -2
- package/dist/cloud-GP6G3EAK.js +17 -0
- package/dist/{codebuddy-D2xJ6hFk.d.ts → codebuddy-DM-i0Fu5.d.ts} +49 -18
- package/dist/{config-S2TULOMK.js → config-KFEHLV6W.js} +3 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +34 -25
- package/dist/langgraph/index.d.ts +1 -1
- package/dist/lean-command-DA4ZNKG3.js +15 -0
- package/dist/local-3NGNESNB.js +13 -0
- package/dist/{main-BFDVQHKO.js → main-BDVQAGAS.js} +1261 -242
- package/dist/main-BDVQAGAS.js.map +1 -0
- package/dist/{memory-NIBFAMY4.js → memory-4RGVLPKG.js} +6 -6
- package/dist/operations-EQGOFCBD.js +24 -0
- package/dist/operations-EQGOFCBD.js.map +1 -0
- package/dist/{rerank-Q7MIFNKA.js → rerank-3P3C2UHN.js} +4 -4
- package/dist/{summaries-TSE44KUQ.js → summaries-ZBV5IN7Y.js} +6 -6
- package/dist/summaries-ZBV5IN7Y.js.map +1 -0
- package/dist/{zvec-F27AR357.js → zvec-MAPSJQSR.js} +3 -3
- package/dist/zvec-MAPSJQSR.js.map +1 -0
- package/package.json +6 -2
- package/dist/chunk-5OWZVZSO.js.map +0 -1
- package/dist/chunk-K63EOA4B.js.map +0 -1
- package/dist/chunk-OLFCOR4U.js +0 -8580
- package/dist/chunk-OLFCOR4U.js.map +0 -1
- package/dist/lean-command-D6TZJEFX.js +0 -13
- package/dist/main-BFDVQHKO.js.map +0 -1
- /package/dist/{chunk-ITGRANYW.js.map → chunk-6MKEIDX5.js.map} +0 -0
- /package/dist/{chunk-IBMJYIEP.js.map → chunk-L5AYZC3F.js.map} +0 -0
- /package/dist/{chunk-3ZELDCR3.js.map → chunk-PCE4STAY.js.map} +0 -0
- /package/dist/{chunk-TI5TQD6D.js.map → chunk-QDBDYCM2.js.map} +0 -0
- /package/dist/{chunk-DSN5CEHO.js.map → chunk-ROQTT5ZO.js.map} +0 -0
- /package/dist/{chunk-IE2OUO54.js.map → chunk-UFZD3JAA.js.map} +0 -0
- /package/dist/{config-S2TULOMK.js.map → cloud-GP6G3EAK.js.map} +0 -0
- /package/dist/{lean-command-D6TZJEFX.js.map → config-KFEHLV6W.js.map} +0 -0
- /package/dist/{summaries-TSE44KUQ.js.map → lean-command-DA4ZNKG3.js.map} +0 -0
- /package/dist/{zvec-F27AR357.js.map → local-3NGNESNB.js.map} +0 -0
- /package/dist/{memory-NIBFAMY4.js.map → memory-4RGVLPKG.js.map} +0 -0
- /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-
|
|
13
|
+
} from "./chunk-ROQTT5ZO.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
|
-
|
|
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-
|
|
48
|
+
} from "./chunk-Z5QB367W.js";
|
|
49
|
+
import "./chunk-IHQM2ZDV.js";
|
|
44
50
|
import {
|
|
45
51
|
SessionCapsuleLedger,
|
|
46
|
-
|
|
47
|
-
|
|
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-CQPCHMBS.js";
|
|
59
54
|
import {
|
|
60
55
|
buildArchitectureMap,
|
|
61
56
|
generateGraphifyGraph,
|
|
@@ -65,23 +60,49 @@ import {
|
|
|
65
60
|
neighbours,
|
|
66
61
|
queryMap,
|
|
67
62
|
setupGraphify
|
|
68
|
-
} from "./chunk-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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-JDZ3VK26.js";
|
|
79
81
|
import {
|
|
80
82
|
redactPersisted
|
|
81
83
|
} from "./chunk-GDJH7XJC.js";
|
|
84
|
+
import {
|
|
85
|
+
computeFactHash,
|
|
86
|
+
generateEntityId
|
|
87
|
+
} from "./chunk-4BYGTEJT.js";
|
|
88
|
+
import "./chunk-KYK6RPAJ.js";
|
|
89
|
+
import {
|
|
90
|
+
modelCacheDir
|
|
91
|
+
} from "./chunk-3XYQGFPB.js";
|
|
92
|
+
import {
|
|
93
|
+
DEFAULT_CLOUD_EMBEDDING_MODELS,
|
|
94
|
+
DEFAULT_RERANK_MODELS,
|
|
95
|
+
LOCAL_EMBEDDING_MODEL,
|
|
96
|
+
getModelDefinition
|
|
97
|
+
} from "./chunk-JMWJ7V6Y.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-
|
|
105
|
+
} from "./chunk-6MKEIDX5.js";
|
|
85
106
|
import {
|
|
86
107
|
PLAN_POLICIES,
|
|
87
108
|
configPath,
|
|
@@ -92,17 +113,18 @@ import {
|
|
|
92
113
|
readConfigFile,
|
|
93
114
|
redactSecrets,
|
|
94
115
|
resolveNamespaceFrom,
|
|
116
|
+
setConfigBlock,
|
|
95
117
|
setLlmEnabled,
|
|
96
118
|
setPlanPolicy,
|
|
97
119
|
setZvecEnabled,
|
|
98
120
|
writeAtomic,
|
|
99
121
|
writeConfigFile
|
|
100
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-776EQXDV.js";
|
|
101
123
|
import "./chunk-PZ5AY32C.js";
|
|
102
124
|
|
|
103
125
|
// src/cli/main.ts
|
|
104
126
|
import { Command } from "commander";
|
|
105
|
-
import
|
|
127
|
+
import pc22 from "picocolors";
|
|
106
128
|
|
|
107
129
|
// src/integrations/github.ts
|
|
108
130
|
import { constants } from "fs";
|
|
@@ -2432,14 +2454,21 @@ async function runBeforeEdit(taskParts, opts) {
|
|
|
2432
2454
|
resolveTokenBudget()
|
|
2433
2455
|
]);
|
|
2434
2456
|
const task = taskParts.join(" ").trim();
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2457
|
+
const semantics = task ? await openHookSemantics(process.cwd()) : null;
|
|
2458
|
+
try {
|
|
2459
|
+
return await buildBeforeEditContext({
|
|
2460
|
+
namespace,
|
|
2461
|
+
...task ? { task } : {},
|
|
2462
|
+
...semantics?.semanticRanker ? { semanticRanker: semantics.semanticRanker } : {},
|
|
2463
|
+
...semantics?.reranker ? { reranker: semantics.reranker } : {},
|
|
2464
|
+
...opts.paths ? { paths: parsePaths2(opts.paths) } : {},
|
|
2465
|
+
...opts.plan ? { planId: opts.plan } : {},
|
|
2466
|
+
...opts.git !== void 0 ? { useGit: opts.git } : {},
|
|
2467
|
+
...tokenBudget !== void 0 ? { tokenBudget } : {}
|
|
2468
|
+
});
|
|
2469
|
+
} finally {
|
|
2470
|
+
await semantics?.close();
|
|
2471
|
+
}
|
|
2443
2472
|
}
|
|
2444
2473
|
function printBootstrap(ctx) {
|
|
2445
2474
|
console.log(`${pc3.bold("project")} ${ctx.project.namespace} ${pc3.dim(ctx.project.root)}`);
|
|
@@ -2549,9 +2578,479 @@ function scoreColor(score) {
|
|
|
2549
2578
|
return pc3.green(String(score));
|
|
2550
2579
|
}
|
|
2551
2580
|
|
|
2552
|
-
// src/cli/commands/
|
|
2581
|
+
// src/cli/commands/dashboard.ts
|
|
2582
|
+
import { spawn as spawn2 } from "child_process";
|
|
2553
2583
|
import pc4 from "picocolors";
|
|
2554
2584
|
|
|
2585
|
+
// src/dashboard/server.ts
|
|
2586
|
+
import { unwatchFile, watchFile } from "fs";
|
|
2587
|
+
import { open, stat } from "fs/promises";
|
|
2588
|
+
import { createServer } from "http";
|
|
2589
|
+
import { basename } from "path";
|
|
2590
|
+
|
|
2591
|
+
// src/dashboard/page.ts
|
|
2592
|
+
var DASHBOARD_HTML = `<!doctype html>
|
|
2593
|
+
<html lang="en">
|
|
2594
|
+
<head>
|
|
2595
|
+
<meta charset="utf-8">
|
|
2596
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2597
|
+
<title>CodeBuddy Traces</title>
|
|
2598
|
+
<style>
|
|
2599
|
+
:root {
|
|
2600
|
+
--bg: #f3f5f7; --panel: #ffffff; --fg: #141a21; --muted: #5d6874; --line: #dde3e8;
|
|
2601
|
+
--recall: #0a8f80; --tool: #c2410c; --recall-soft: #e0f2ef; --tool-soft: #fbeae2;
|
|
2602
|
+
--bad: #b42318; --bad-soft: #fdecea; --capture: #4b5563;
|
|
2603
|
+
--sans: ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
2604
|
+
--mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
2605
|
+
}
|
|
2606
|
+
@media (prefers-color-scheme: dark) { :root {
|
|
2607
|
+
--bg: #0e1116; --panel: #161b22; --fg: #e6eaee; --muted: #94a0ad; --line: #28303a;
|
|
2608
|
+
--recall: #0d9488; --tool: #ea580c; --recall-soft: #10302c; --tool-soft: #3a1e12;
|
|
2609
|
+
--bad: #f97066; --bad-soft: #3b1715; --capture: #9aa4ae; color-scheme: dark } }
|
|
2610
|
+
* { box-sizing: border-box; }
|
|
2611
|
+
body { margin: 0; background: var(--bg); color: var(--fg); font: 14px/1.5 var(--sans); }
|
|
2612
|
+
.wrap { max-width: 1280px; margin: 0 auto; padding: 20px 20px 48px; display: grid; gap: 16px; }
|
|
2613
|
+
header { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
|
|
2614
|
+
h1 { font-size: 18px; margin: 0; letter-spacing: -.01em; }
|
|
2615
|
+
h1 small { font: 500 13px var(--mono); color: var(--muted); margin-left: 8px; }
|
|
2616
|
+
h2 { font-size: 13px; margin: 0 0 10px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); font-weight: 600; }
|
|
2617
|
+
.live { display: inline-flex; align-items: center; gap: 6px; font: 12px var(--mono); color: var(--muted); }
|
|
2618
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
|
|
2619
|
+
.live.on .dot { background: var(--recall); box-shadow: 0 0 0 3px var(--recall-soft); }
|
|
2620
|
+
.spacer { flex: 1; }
|
|
2621
|
+
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--panel); }
|
|
2622
|
+
.seg button { border: 0; background: none; color: var(--muted); font: 500 12px var(--sans); padding: 6px 12px; cursor: pointer; }
|
|
2623
|
+
.seg button + button { border-left: 1px solid var(--line); }
|
|
2624
|
+
.seg button[aria-pressed="true"] { background: var(--fg); color: var(--bg); }
|
|
2625
|
+
.seg button:focus-visible { outline: 2px solid var(--recall); outline-offset: -2px; }
|
|
2626
|
+
.wrap > * { min-width: 0; }
|
|
2627
|
+
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
|
|
2628
|
+
.kpi { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; }
|
|
2629
|
+
.kpi .label { font-size: 12px; color: var(--muted); }
|
|
2630
|
+
.kpi .value { font: 600 24px/1.2 var(--mono); font-variant-numeric: tabular-nums; margin-top: 4px; }
|
|
2631
|
+
.kpi .sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
2632
|
+
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; min-width: 0; }
|
|
2633
|
+
.cols { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 16px; }
|
|
2634
|
+
@media (max-width: 900px) { .cols { grid-template-columns: 1fr; } }
|
|
2635
|
+
.legend { display: flex; gap: 16px; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
|
|
2636
|
+
.sw { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 6px; vertical-align: -1px; }
|
|
2637
|
+
svg text { fill: var(--muted); font: 11px var(--mono); }
|
|
2638
|
+
.scroll { overflow-x: auto; }
|
|
2639
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
2640
|
+
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; }
|
|
2641
|
+
td { padding: 7px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
2642
|
+
tr:last-child td { border-bottom: 0; }
|
|
2643
|
+
td.n { font-family: var(--mono); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
|
|
2644
|
+
th.n { text-align: right; }
|
|
2645
|
+
.chip { display: inline-block; font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: .06em; padding: 2px 6px; border-radius: 4px; }
|
|
2646
|
+
.chip.recall { background: var(--recall-soft); color: var(--recall); }
|
|
2647
|
+
.chip.tool { background: var(--tool-soft); color: var(--tool); }
|
|
2648
|
+
.chip.capture, .chip.sync, .chip.kw { background: var(--line); color: var(--capture); }
|
|
2649
|
+
.chip.err { background: var(--bad-soft); color: var(--bad); }
|
|
2650
|
+
.feed { display: grid; gap: 10px; max-height: 520px; overflow: auto; }
|
|
2651
|
+
.prompt { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; }
|
|
2652
|
+
.prompt .q { font-weight: 500; overflow-wrap: anywhere; }
|
|
2653
|
+
.prompt .meta { font: 12px var(--mono); color: var(--muted); margin-top: 4px; display: flex; flex-wrap: wrap; gap: 4px 12px; overflow-wrap: anywhere; }
|
|
2654
|
+
.prompt ul { margin: 6px 0 0; padding-left: 18px; font-size: 13px; }
|
|
2655
|
+
.prompt .none { font-size: 13px; color: var(--muted); margin-top: 6px; }
|
|
2656
|
+
.mono { font-family: var(--mono); }
|
|
2657
|
+
.muted { color: var(--muted); }
|
|
2658
|
+
.empty { padding: 40px 16px; text-align: center; color: var(--muted); }
|
|
2659
|
+
.empty code { font-family: var(--mono); background: var(--line); padding: 2px 6px; border-radius: 4px; color: var(--fg); }
|
|
2660
|
+
.log { max-height: 420px; overflow: auto; }
|
|
2661
|
+
@media (prefers-reduced-motion: no-preference) { tr.fresh td { animation: flash 1.2s ease-out; } }
|
|
2662
|
+
@keyframes flash { from { background: var(--recall-soft); } to { background: transparent; } }
|
|
2663
|
+
</style>
|
|
2664
|
+
</head>
|
|
2665
|
+
<body>
|
|
2666
|
+
<div class="wrap">
|
|
2667
|
+
<header>
|
|
2668
|
+
<h1>CodeBuddy traces<small id="project"></small></h1>
|
|
2669
|
+
<span class="live" id="live"><span class="dot"></span><span id="liveText">connecting</span></span>
|
|
2670
|
+
<span class="spacer"></span>
|
|
2671
|
+
<div class="seg" role="group" aria-label="Time range" id="range">
|
|
2672
|
+
<button data-range="3600000">1 hour</button>
|
|
2673
|
+
<button data-range="86400000" aria-pressed="true">24 hours</button>
|
|
2674
|
+
<button data-range="604800000">7 days</button>
|
|
2675
|
+
<button data-range="0">All</button>
|
|
2676
|
+
</div>
|
|
2677
|
+
</header>
|
|
2678
|
+
|
|
2679
|
+
<section class="kpis" id="kpis" aria-label="Summary"></section>
|
|
2680
|
+
|
|
2681
|
+
<section class="panel">
|
|
2682
|
+
<h2>Tokens delivered to the agent</h2>
|
|
2683
|
+
<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>
|
|
2684
|
+
<div class="scroll" id="chart"></div>
|
|
2685
|
+
</section>
|
|
2686
|
+
|
|
2687
|
+
<div class="cols">
|
|
2688
|
+
<section class="panel">
|
|
2689
|
+
<h2>Recent prompts and what was recalled</h2>
|
|
2690
|
+
<div class="feed" id="feed"></div>
|
|
2691
|
+
</section>
|
|
2692
|
+
<section class="panel">
|
|
2693
|
+
<h2>MCP tools</h2>
|
|
2694
|
+
<div class="scroll" id="tools"></div>
|
|
2695
|
+
</section>
|
|
2696
|
+
</div>
|
|
2697
|
+
|
|
2698
|
+
<section class="panel">
|
|
2699
|
+
<h2>Activity</h2>
|
|
2700
|
+
<div class="log scroll" id="log"></div>
|
|
2701
|
+
</section>
|
|
2702
|
+
<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>
|
|
2703
|
+
</div>
|
|
2704
|
+
<script>
|
|
2705
|
+
(function () {
|
|
2706
|
+
var events = [];
|
|
2707
|
+
var range = 86400000;
|
|
2708
|
+
var fresh = new Set();
|
|
2709
|
+
var pending = false;
|
|
2710
|
+
|
|
2711
|
+
function esc(value) {
|
|
2712
|
+
return String(value == null ? "" : value).replace(/[&<>"']/g, function (c) {
|
|
2713
|
+
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
function num(n) { return Math.round(n).toLocaleString(); }
|
|
2717
|
+
function compact(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + "M" : n >= 1e4 ? (n / 1e3).toFixed(1) + "k" : num(n); }
|
|
2718
|
+
function pct(sorted, p) { if (!sorted.length) return 0; return sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))]; }
|
|
2719
|
+
function ms(n) { return n >= 1000 ? (n / 1000).toFixed(1) + " s" : Math.round(n) + " ms"; }
|
|
2720
|
+
function ago(ts) {
|
|
2721
|
+
var s = Math.max(0, (Date.now() - Date.parse(ts)) / 1000);
|
|
2722
|
+
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";
|
|
2723
|
+
}
|
|
2724
|
+
function visible() {
|
|
2725
|
+
if (!range) return events;
|
|
2726
|
+
var since = Date.now() - range;
|
|
2727
|
+
return events.filter(function (e) { return Date.parse(e.ts) >= since; });
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
function kpis(list) {
|
|
2731
|
+
var recalls = list.filter(function (e) { return e.kind === "recall"; });
|
|
2732
|
+
var tools = list.filter(function (e) { return e.kind === "tool"; });
|
|
2733
|
+
var delivered = list.reduce(function (s, e) { return s + (e.kind === "recall" || e.kind === "tool" ? e.tokensOut || 0 : 0); }, 0);
|
|
2734
|
+
var withFacts = recalls.filter(function (e) { return e.detail && e.detail.facts && e.detail.facts.length; });
|
|
2735
|
+
var factCount = withFacts.reduce(function (s, e) { return s + e.detail.facts.length; }, 0);
|
|
2736
|
+
var dense = recalls.reduce(function (s, e) { return s + ((e.detail && e.detail.dense) || 0); }, 0);
|
|
2737
|
+
var lat = recalls.map(function (e) { return e.ms; }).sort(function (a, b) { return a - b; });
|
|
2738
|
+
var errors = list.filter(function (e) { return !e.ok; }).length;
|
|
2739
|
+
var captures = list.filter(function (e) { return e.kind === "capture"; });
|
|
2740
|
+
var saved = captures.reduce(function (s, e) { return s + ((e.detail && e.detail.saved) || 0); }, 0);
|
|
2741
|
+
var tiles = [
|
|
2742
|
+
["Prompts recalled", num(recalls.length), recalls.length ? Math.round(100 * withFacts.length / recalls.length) + "% got facts" : "no prompts yet"],
|
|
2743
|
+
["Facts injected", num(factCount), factCount ? Math.round(100 * dense / factCount) + "% matched by meaning" : "-"],
|
|
2744
|
+
["Tokens delivered", compact(delivered), recalls.length + tools.length ? "\u2248 " + num(delivered / (recalls.length + tools.length)) + " per event" : "-"],
|
|
2745
|
+
["MCP tool calls", num(tools.length), tools.length ? new Set(tools.map(function (e) { return e.name; })).size + " distinct tools" : "-"],
|
|
2746
|
+
["Recall latency", lat.length ? ms(pct(lat, 0.5)) : "-", lat.length ? "p95 " + ms(pct(lat, 0.95)) : "-"],
|
|
2747
|
+
["Memories saved", num(saved), captures.length + " capture" + (captures.length === 1 ? "" : "s")],
|
|
2748
|
+
["Errors", num(errors), errors ? "see Activity" : "all good"],
|
|
2749
|
+
];
|
|
2750
|
+
document.getElementById("kpis").innerHTML = tiles.map(function (t) {
|
|
2751
|
+
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>";
|
|
2752
|
+
}).join("");
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
function chart(list) {
|
|
2756
|
+
var el = document.getElementById("chart");
|
|
2757
|
+
var points = list.filter(function (e) { return (e.kind === "recall" || e.kind === "tool") && e.tokensOut; });
|
|
2758
|
+
if (!points.length) { el.innerHTML = '<div class="empty">No tokens delivered in this range yet.</div>'; return; }
|
|
2759
|
+
var end = Date.now();
|
|
2760
|
+
// Zoom to the data: a few minutes of traces should not be one sliver of a 24-hour axis.
|
|
2761
|
+
var first = Date.parse(points[0].ts);
|
|
2762
|
+
var start = Math.max(range ? end - range : first, first - 60000);
|
|
2763
|
+
var buckets = 48, size = Math.max(10000, (end - start) / buckets);
|
|
2764
|
+
buckets = Math.ceil((end - start) / size);
|
|
2765
|
+
var rows = [];
|
|
2766
|
+
for (var i = 0; i < buckets; i++) rows.push({ recall: 0, tool: 0, n: 0 });
|
|
2767
|
+
points.forEach(function (e) {
|
|
2768
|
+
var i = Math.min(buckets - 1, Math.max(0, Math.floor((Date.parse(e.ts) - start) / size)));
|
|
2769
|
+
rows[i][e.kind] += e.tokensOut; rows[i].n += 1;
|
|
2770
|
+
});
|
|
2771
|
+
var max = Math.max.apply(null, rows.map(function (r) { return r.recall + r.tool; })) || 1;
|
|
2772
|
+
var W = 960, H = 200, L = 52, B = 22, T = 8, plotW = W - L - 8, plotH = H - B - T;
|
|
2773
|
+
var bw = plotW / buckets;
|
|
2774
|
+
var step = Math.pow(10, Math.floor(Math.log10(max)));
|
|
2775
|
+
var tick = max / step > 5 ? step * 2 : step;
|
|
2776
|
+
var svg = '<svg viewBox="0 0 ' + W + " " + H + '" style="width:100%;min-width:560px;height:auto" role="img" aria-label="Tokens delivered over time">';
|
|
2777
|
+
for (var v = 0; v <= max; v += tick) {
|
|
2778
|
+
var y = T + plotH - (v / max) * plotH;
|
|
2779
|
+
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>";
|
|
2780
|
+
}
|
|
2781
|
+
rows.forEach(function (r, i) {
|
|
2782
|
+
var x = L + i * bw + 1, w = Math.max(1, bw - 2);
|
|
2783
|
+
var hr = (r.recall / max) * plotH, ht = (r.tool / max) * plotH;
|
|
2784
|
+
var when = new Date(start + i * size);
|
|
2785
|
+
var label = when.toLocaleString() + ": " + num(r.recall) + " recall + " + num(r.tool) + " tool tokens (" + r.n + " events)";
|
|
2786
|
+
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>";
|
|
2787
|
+
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>";
|
|
2788
|
+
});
|
|
2789
|
+
[0, Math.floor(buckets / 2), buckets - 1].forEach(function (i) {
|
|
2790
|
+
var d = new Date(start + i * size);
|
|
2791
|
+
var text = end - start <= 86400000 ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
2792
|
+
svg += '<text x="' + (L + i * bw + bw / 2) + '" y="' + (H - 6) + '" text-anchor="middle">' + esc(text) + "</text>";
|
|
2793
|
+
});
|
|
2794
|
+
el.innerHTML = svg + "</svg>";
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
function feed(list) {
|
|
2798
|
+
var recalls = list.filter(function (e) { return e.kind === "recall"; }).slice(-30).reverse();
|
|
2799
|
+
var el = document.getElementById("feed");
|
|
2800
|
+
if (!recalls.length) {
|
|
2801
|
+
el.innerHTML = '<div class="empty">No prompts yet. Run <code>codebuddy hooks install</code>, restart your agent, and send a prompt.</div>';
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
el.innerHTML = recalls.map(function (e) {
|
|
2805
|
+
var d = e.detail || {};
|
|
2806
|
+
var facts = d.facts || [];
|
|
2807
|
+
return '<div class="prompt"><div class="q">' + (esc(d.prompt) || '<span class="muted">(no prompt text)</span>') + '</div>' +
|
|
2808
|
+
'<div class="meta"><span>' + ago(e.ts) + "</span><span>" + ms(e.ms) + "</span><span>\u2248" + num(e.tokensOut || 0) + " tokens injected</span>" +
|
|
2809
|
+
(d.dense ? '<span class="chip recall">' + d.dense + " by meaning</span>" : "") +
|
|
2810
|
+
(d.lexical ? '<span class="chip kw">' + d.lexical + " by keyword</span>" : "") +
|
|
2811
|
+
(e.ok ? "" : '<span class="chip err">' + esc(d.dropped || "dropped") + "</span>") + "</div>" +
|
|
2812
|
+
(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>') +
|
|
2813
|
+
(d.files && d.files.length ? '<div class="meta">files: ' + d.files.map(esc).join(", ") + "</div>" : "") +
|
|
2814
|
+
"</div>";
|
|
2815
|
+
}).join("");
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
function tools(list) {
|
|
2819
|
+
var by = {};
|
|
2820
|
+
list.filter(function (e) { return e.kind === "tool"; }).forEach(function (e) {
|
|
2821
|
+
var t = by[e.name] || (by[e.name] = { calls: 0, errors: 0, lat: [], out: 0 });
|
|
2822
|
+
t.calls++; if (!e.ok) t.errors++; t.lat.push(e.ms); t.out += e.tokensOut || 0;
|
|
2823
|
+
});
|
|
2824
|
+
var names = Object.keys(by).sort(function (a, b) { return by[b].calls - by[a].calls; });
|
|
2825
|
+
var el = document.getElementById("tools");
|
|
2826
|
+
if (!names.length) { el.innerHTML = '<div class="empty">No MCP tool calls in this range.</div>'; return; }
|
|
2827
|
+
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>' +
|
|
2828
|
+
names.map(function (n) {
|
|
2829
|
+
var t = by[n], lat = t.lat.sort(function (a, b) { return a - b; });
|
|
2830
|
+
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>";
|
|
2831
|
+
}).join("") + "</tbody></table>";
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
function summary(e) {
|
|
2835
|
+
var d = e.detail || {};
|
|
2836
|
+
if (e.kind === "recall") return (d.facts ? d.facts.length : 0) + " fact(s), " + (d.files ? d.files.length : 0) + " file(s)";
|
|
2837
|
+
if (e.kind === "capture") return (d.saved || 0) + " saved, " + (d.queued || 0) + " queued, " + (d.changedFiles || 0) + " changed file(s)";
|
|
2838
|
+
if (e.kind === "sync") return (d.imported || 0) + " new, " + (d.updated || 0) + " updated" + (d.reembedding ? ", " + d.reembedding + " re-embedding" : "");
|
|
2839
|
+
return e.session ? "session " + e.session.slice(0, 12) : "";
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
function log(list) {
|
|
2843
|
+
var rows = list.slice(-200).reverse();
|
|
2844
|
+
var el = document.getElementById("log");
|
|
2845
|
+
if (!rows.length) { el.innerHTML = '<div class="empty">Nothing recorded in this range.</div>'; return; }
|
|
2846
|
+
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>' +
|
|
2847
|
+
rows.map(function (e) {
|
|
2848
|
+
var key = e.ts + e.name;
|
|
2849
|
+
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>";
|
|
2850
|
+
}).join("") + "</tbody></table>";
|
|
2851
|
+
fresh.clear();
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
function render() {
|
|
2855
|
+
pending = false;
|
|
2856
|
+
var list = visible();
|
|
2857
|
+
kpis(list); chart(list); feed(list); tools(list); log(list);
|
|
2858
|
+
}
|
|
2859
|
+
function schedule() { if (!pending) { pending = true; requestAnimationFrame(render); } }
|
|
2860
|
+
|
|
2861
|
+
function load() {
|
|
2862
|
+
return fetch("/api/traces?limit=10000").then(function (r) { return r.json(); }).then(function (data) {
|
|
2863
|
+
events = data.events || [];
|
|
2864
|
+
document.getElementById("project").textContent = data.project || "";
|
|
2865
|
+
document.getElementById("path").textContent = data.path || "";
|
|
2866
|
+
document.title = "CodeBuddy Traces \xB7 " + (data.project || "");
|
|
2867
|
+
schedule();
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
document.getElementById("range").addEventListener("click", function (event) {
|
|
2872
|
+
var button = event.target.closest("button");
|
|
2873
|
+
if (!button) return;
|
|
2874
|
+
range = Number(button.dataset.range);
|
|
2875
|
+
this.querySelectorAll("button").forEach(function (b) { b.setAttribute("aria-pressed", String(b === button)); });
|
|
2876
|
+
schedule();
|
|
2877
|
+
});
|
|
2878
|
+
|
|
2879
|
+
function connect() {
|
|
2880
|
+
var live = document.getElementById("live"), text = document.getElementById("liveText");
|
|
2881
|
+
var source = new EventSource("/events");
|
|
2882
|
+
source.onopen = function () { live.classList.add("on"); text.textContent = "live"; };
|
|
2883
|
+
source.onerror = function () { live.classList.remove("on"); text.textContent = "reconnecting"; };
|
|
2884
|
+
source.addEventListener("trace", function (message) {
|
|
2885
|
+
var e = JSON.parse(message.data);
|
|
2886
|
+
events.push(e); fresh.add(e.ts + e.name);
|
|
2887
|
+
if (events.length > 10000) events = events.slice(-10000);
|
|
2888
|
+
schedule();
|
|
2889
|
+
});
|
|
2890
|
+
source.addEventListener("reset", function () { load(); });
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
load().then(connect);
|
|
2894
|
+
// Relative times ("2m ago") keep moving even when nothing new arrives.
|
|
2895
|
+
setInterval(schedule, 30000);
|
|
2896
|
+
})();
|
|
2897
|
+
</script>
|
|
2898
|
+
</body>
|
|
2899
|
+
</html>`;
|
|
2900
|
+
|
|
2901
|
+
// src/dashboard/server.ts
|
|
2902
|
+
async function startDashboard(options) {
|
|
2903
|
+
const { repositoryRoot } = options;
|
|
2904
|
+
const path = tracePath(repositoryRoot);
|
|
2905
|
+
const clients = /* @__PURE__ */ new Set();
|
|
2906
|
+
let offset = (await stat(path).catch(() => null))?.size ?? 0;
|
|
2907
|
+
let reading = false;
|
|
2908
|
+
const onChange = async () => {
|
|
2909
|
+
if (reading) return;
|
|
2910
|
+
reading = true;
|
|
2911
|
+
try {
|
|
2912
|
+
const size = (await stat(path).catch(() => null))?.size ?? 0;
|
|
2913
|
+
if (size < offset) {
|
|
2914
|
+
offset = 0;
|
|
2915
|
+
broadcast("reset", "{}");
|
|
2916
|
+
}
|
|
2917
|
+
if (size > offset) {
|
|
2918
|
+
const handle = await open(path, "r");
|
|
2919
|
+
try {
|
|
2920
|
+
const buffer = Buffer.alloc(size - offset);
|
|
2921
|
+
await handle.read(buffer, 0, buffer.length, offset);
|
|
2922
|
+
const text3 = buffer.toString("utf8");
|
|
2923
|
+
const complete = text3.slice(0, text3.lastIndexOf("\n") + 1);
|
|
2924
|
+
offset += Buffer.byteLength(complete);
|
|
2925
|
+
for (const event of parseTraceLines(complete)) broadcast("trace", JSON.stringify(event));
|
|
2926
|
+
} finally {
|
|
2927
|
+
await handle.close();
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
} finally {
|
|
2931
|
+
reading = false;
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
const broadcast = (event, data) => {
|
|
2935
|
+
for (const client of clients) client.write(`event: ${event}
|
|
2936
|
+
data: ${data}
|
|
2937
|
+
|
|
2938
|
+
`);
|
|
2939
|
+
};
|
|
2940
|
+
watchFile(path, { interval: options.pollMs ?? 1e3 }, () => void onChange());
|
|
2941
|
+
const server = createServer(async (request, response) => {
|
|
2942
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
2943
|
+
if (request.method !== "GET") {
|
|
2944
|
+
response.writeHead(405).end();
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
if (url.pathname === "/") {
|
|
2948
|
+
response.writeHead(200, {
|
|
2949
|
+
"content-type": "text/html; charset=utf-8",
|
|
2950
|
+
"cache-control": "no-store"
|
|
2951
|
+
});
|
|
2952
|
+
response.end(DASHBOARD_HTML);
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
if (url.pathname === "/api/traces") {
|
|
2956
|
+
const limit = Math.min(1e4, Math.max(1, Number(url.searchParams.get("limit")) || 2e3));
|
|
2957
|
+
response.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
|
|
2958
|
+
response.end(
|
|
2959
|
+
JSON.stringify({
|
|
2960
|
+
project: basename(repositoryRoot),
|
|
2961
|
+
path,
|
|
2962
|
+
events: await readTraces(repositoryRoot, limit)
|
|
2963
|
+
})
|
|
2964
|
+
);
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
if (url.pathname === "/events") {
|
|
2968
|
+
response.writeHead(200, {
|
|
2969
|
+
"content-type": "text/event-stream",
|
|
2970
|
+
"cache-control": "no-store",
|
|
2971
|
+
connection: "keep-alive"
|
|
2972
|
+
});
|
|
2973
|
+
response.write(": connected\n\n");
|
|
2974
|
+
clients.add(response);
|
|
2975
|
+
request.on("close", () => clients.delete(response));
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
response.writeHead(404, { "content-type": "text/plain" }).end("Not found");
|
|
2979
|
+
});
|
|
2980
|
+
const port = await listen(server, options.port ?? 4777);
|
|
2981
|
+
const heartbeat = setInterval(() => {
|
|
2982
|
+
for (const client of clients) client.write(": ping\n\n");
|
|
2983
|
+
}, 25e3);
|
|
2984
|
+
heartbeat.unref();
|
|
2985
|
+
return {
|
|
2986
|
+
url: `http://127.0.0.1:${port}/`,
|
|
2987
|
+
close: () => new Promise((resolve14) => {
|
|
2988
|
+
clearInterval(heartbeat);
|
|
2989
|
+
unwatchFile(path);
|
|
2990
|
+
for (const client of clients) client.end();
|
|
2991
|
+
server.closeAllConnections();
|
|
2992
|
+
server.close(() => resolve14());
|
|
2993
|
+
})
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
async function listen(server, port) {
|
|
2997
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
2998
|
+
const candidate = port === 0 ? 0 : port + attempt;
|
|
2999
|
+
try {
|
|
3000
|
+
await new Promise((resolve14, reject) => {
|
|
3001
|
+
server.once("error", reject);
|
|
3002
|
+
server.listen(candidate, "127.0.0.1", () => {
|
|
3003
|
+
server.off("error", reject);
|
|
3004
|
+
resolve14();
|
|
3005
|
+
});
|
|
3006
|
+
});
|
|
3007
|
+
return server.address().port;
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
if (error.code !== "EADDRINUSE" || port === 0) throw error;
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
throw new Error(`Ports ${port}-${port + 9} are in use; pass --port.`);
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
// src/cli/commands/dashboard.ts
|
|
3016
|
+
function openBrowser(url) {
|
|
3017
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
3018
|
+
try {
|
|
3019
|
+
spawn2(command, args, { detached: true, stdio: "ignore" }).on("error", () => void 0).unref();
|
|
3020
|
+
} catch {
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
function registerDashboardCommand(program, runSafely2) {
|
|
3024
|
+
program.command("dashboard").description("Open a live local page of prompts recalled, MCP tool calls, tokens and latency.").option(
|
|
3025
|
+
"--port <port>",
|
|
3026
|
+
"Port on 127.0.0.1 (the next free one is used if taken).",
|
|
3027
|
+
Number,
|
|
3028
|
+
4777
|
|
3029
|
+
).option("--no-open", "Print the URL without opening a browser.").action(async (opts) => {
|
|
3030
|
+
await runSafely2(async () => {
|
|
3031
|
+
const root = process.cwd();
|
|
3032
|
+
const dashboard = await startDashboard({ repositoryRoot: root, port: opts.port });
|
|
3033
|
+
const recorded = (await readTraces(root)).length;
|
|
3034
|
+
console.log(`${pc4.green("\u25CF")} CodeBuddy dashboard ${pc4.bold(dashboard.url)}`);
|
|
3035
|
+
console.log(
|
|
3036
|
+
pc4.dim(
|
|
3037
|
+
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)."
|
|
3038
|
+
)
|
|
3039
|
+
);
|
|
3040
|
+
console.log(pc4.dim(" Ctrl+C to stop."));
|
|
3041
|
+
if (opts.open) openBrowser(dashboard.url);
|
|
3042
|
+
await new Promise((resolve14) => {
|
|
3043
|
+
const stop = () => void dashboard.close().then(resolve14);
|
|
3044
|
+
process.once("SIGINT", stop);
|
|
3045
|
+
process.once("SIGTERM", stop);
|
|
3046
|
+
});
|
|
3047
|
+
});
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
// src/cli/commands/eval.ts
|
|
3052
|
+
import pc5 from "picocolors";
|
|
3053
|
+
|
|
2555
3054
|
// src/eval/runner.ts
|
|
2556
3055
|
import { readFile as readFile12 } from "fs/promises";
|
|
2557
3056
|
import { dirname as dirname10, isAbsolute, join as join14, resolve as resolve11 } from "path";
|
|
@@ -2622,17 +3121,17 @@ function registerEvalCommand(program, runSafely2) {
|
|
|
2622
3121
|
}
|
|
2623
3122
|
function printEvaluation(result) {
|
|
2624
3123
|
console.log(
|
|
2625
|
-
`${result.passed ?
|
|
3124
|
+
`${result.passed ? pc5.green("passed") : pc5.red("failed")} ${result.summary.passed}/${result.summary.total} evaluation cases`
|
|
2626
3125
|
);
|
|
2627
3126
|
for (const item of result.cases) {
|
|
2628
|
-
console.log(` ${item.passed ?
|
|
2629
|
-
if (!item.passed) for (const failure of item.failures) console.log(
|
|
3127
|
+
console.log(` ${item.passed ? pc5.green("\u2713") : pc5.red("\xD7")} ${item.id}`);
|
|
3128
|
+
if (!item.passed) for (const failure of item.failures) console.log(pc5.dim(` ${failure}`));
|
|
2630
3129
|
}
|
|
2631
|
-
console.log(
|
|
3130
|
+
console.log(pc5.dim(`dataset: ${result.datasetPath}`));
|
|
2632
3131
|
}
|
|
2633
3132
|
|
|
2634
3133
|
// src/cli/commands/github.ts
|
|
2635
|
-
import
|
|
3134
|
+
import pc6 from "picocolors";
|
|
2636
3135
|
function registerGitHubCommands(program, runSafely2) {
|
|
2637
3136
|
const github = program.command("github").description("Install GitHub workflow integrations.");
|
|
2638
3137
|
github.command("install").option("--force", "Replace an existing CodeBuddy workflow.").description("Install the PR change-safety report workflow.").action(async (opts) => {
|
|
@@ -2640,15 +3139,16 @@ function registerGitHubCommands(program, runSafely2) {
|
|
|
2640
3139
|
const result = await installGitHubWorkflow(process.cwd(), {
|
|
2641
3140
|
...opts.force !== void 0 ? { force: opts.force } : {}
|
|
2642
3141
|
});
|
|
2643
|
-
console.log(`${result.created ? "installed" : "already present"} ${
|
|
3142
|
+
console.log(`${result.created ? "installed" : "already present"} ${pc6.dim(result.path)}`);
|
|
2644
3143
|
if (result.created) console.log("Commit this workflow to enable CodeBuddy PR reports.");
|
|
2645
3144
|
});
|
|
2646
3145
|
});
|
|
2647
3146
|
}
|
|
2648
3147
|
|
|
2649
3148
|
// src/cli/commands/hooks.ts
|
|
3149
|
+
import { spawn as spawn3 } from "child_process";
|
|
2650
3150
|
import { relative as relative2 } from "path";
|
|
2651
|
-
import
|
|
3151
|
+
import pc7 from "picocolors";
|
|
2652
3152
|
function registerHooksCommands(program, runSafely2) {
|
|
2653
3153
|
const hooks = program.command("hooks").description("Enforce automatic recall/capture via client lifecycle hooks.");
|
|
2654
3154
|
hooks.command("install").option(
|
|
@@ -2665,11 +3165,12 @@ function registerHooksCommands(program, runSafely2) {
|
|
|
2665
3165
|
const where = relative2(process.cwd(), step.target) || step.target;
|
|
2666
3166
|
const changed = step.action !== "unchanged";
|
|
2667
3167
|
console.log(
|
|
2668
|
-
`${changed ?
|
|
3168
|
+
`${changed ? pc7.green("installed") : pc7.dim("unchanged")} CodeBuddy hooks in ${where}`
|
|
2669
3169
|
);
|
|
2670
3170
|
await reconcileRules(client, "hooks");
|
|
3171
|
+
syncInBackground(process.cwd());
|
|
2671
3172
|
console.log(
|
|
2672
|
-
|
|
3173
|
+
pc7.dim(
|
|
2673
3174
|
` prompt\u2192recall \xB7 edit\u2192stage \xB7 turn end\u2192capture \xB7 compaction\u2192reset. Restart ${CLIENT_ADAPTERS[client].name} to activate.`
|
|
2674
3175
|
)
|
|
2675
3176
|
);
|
|
@@ -2684,7 +3185,7 @@ function registerHooksCommands(program, runSafely2) {
|
|
|
2684
3185
|
const client = assertHookClient(opts.client);
|
|
2685
3186
|
const changed = (await setHooks(process.cwd(), false, client))?.action === "removed";
|
|
2686
3187
|
console.log(
|
|
2687
|
-
changed ?
|
|
3188
|
+
changed ? pc7.yellow("removed CodeBuddy hooks") : pc7.dim("no CodeBuddy hooks found")
|
|
2688
3189
|
);
|
|
2689
3190
|
await reconcileRules(client, "agent");
|
|
2690
3191
|
});
|
|
@@ -2697,7 +3198,7 @@ function registerHooksCommands(program, runSafely2) {
|
|
|
2697
3198
|
await runSafely2(async () => {
|
|
2698
3199
|
const installed = await clientHooksInstalled(process.cwd(), parseClient(opts.client));
|
|
2699
3200
|
console.log(
|
|
2700
|
-
installed ?
|
|
3201
|
+
installed ? pc7.green("CodeBuddy hooks: installed") : pc7.dim("CodeBuddy hooks: not installed")
|
|
2701
3202
|
);
|
|
2702
3203
|
});
|
|
2703
3204
|
});
|
|
@@ -2725,9 +3226,21 @@ function registerHooksCommands(program, runSafely2) {
|
|
|
2725
3226
|
`codebuddy: captured from ${changedFiles.length} changed file(s) \u2014 ${result.saved.length} saved, ${result.queuedForReview.length} queued
|
|
2726
3227
|
`
|
|
2727
3228
|
);
|
|
3229
|
+
if (result.saved.length > 0) syncInBackground(input.cwd ?? process.cwd());
|
|
2728
3230
|
}
|
|
2729
3231
|
});
|
|
2730
3232
|
});
|
|
3233
|
+
hooks.command("sync").description(
|
|
3234
|
+
"[hook] Reindex Markdown memory into PostgreSQL and embed it locally (runs detached after capture)."
|
|
3235
|
+
).action(async () => {
|
|
3236
|
+
await failSoft(async () => {
|
|
3237
|
+
const result = await runSyncHook(process.cwd());
|
|
3238
|
+
process.stderr.write(
|
|
3239
|
+
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)"}
|
|
3240
|
+
` : "codebuddy: no PostgreSQL configured; nothing to sync\n"
|
|
3241
|
+
);
|
|
3242
|
+
});
|
|
3243
|
+
});
|
|
2731
3244
|
hooks.command("pretool").description(
|
|
2732
3245
|
"[hook] Outline large unranged Reads and cap content Greps (never grants permission)."
|
|
2733
3246
|
).action(async () => {
|
|
@@ -2754,16 +3267,28 @@ async function reconcileRules(client, mode) {
|
|
|
2754
3267
|
if (result.action !== "unchanged") {
|
|
2755
3268
|
const where = relative2(process.cwd(), result.path) || result.path;
|
|
2756
3269
|
console.log(
|
|
2757
|
-
`${
|
|
3270
|
+
`${pc7.cyan("updated")} ${where} workflow to ${mode === "hooks" ? "hook" : "agent"}-driven mode`
|
|
2758
3271
|
);
|
|
2759
3272
|
}
|
|
2760
3273
|
}
|
|
3274
|
+
function syncInBackground(cwd) {
|
|
3275
|
+
const entry = process.argv[1];
|
|
3276
|
+
if (!entry) return;
|
|
3277
|
+
try {
|
|
3278
|
+
spawn3(process.execPath, [...process.execArgv, entry, "hooks", "sync"], {
|
|
3279
|
+
cwd,
|
|
3280
|
+
detached: true,
|
|
3281
|
+
stdio: "ignore"
|
|
3282
|
+
}).unref();
|
|
3283
|
+
} catch {
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
2761
3286
|
|
|
2762
3287
|
// src/cli/commands/indexing.ts
|
|
2763
|
-
import
|
|
3288
|
+
import pc9 from "picocolors";
|
|
2764
3289
|
|
|
2765
3290
|
// src/cli/commands/llm.ts
|
|
2766
|
-
import
|
|
3291
|
+
import pc8 from "picocolors";
|
|
2767
3292
|
import { z } from "zod";
|
|
2768
3293
|
var KEY_ENV = { groq: "GROQ_API_KEY", openrouter: "OPENROUTER_API_KEY" };
|
|
2769
3294
|
var WATCH_BATCH = 8;
|
|
@@ -2818,7 +3343,7 @@ function registerLlmCommands(program, runSafely2) {
|
|
|
2818
3343
|
await runSafely2(async () => {
|
|
2819
3344
|
const report = await llmStatusReport(process.cwd());
|
|
2820
3345
|
console.log(opts.json ? JSON.stringify(report, null, 2) : formatLlmStatus(report));
|
|
2821
|
-
if (!report.keyPresent) console.log(
|
|
3346
|
+
if (!report.keyPresent) console.log(pc8.dim(`next: export ${report.keyEnv}=<your key>`));
|
|
2822
3347
|
});
|
|
2823
3348
|
});
|
|
2824
3349
|
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 +3390,7 @@ function registerLlmCommands(program, runSafely2) {
|
|
|
2865
3390
|
const result = await summarizeRepository(process.cwd(), {
|
|
2866
3391
|
...opts.limit !== void 0 ? { limit: opts.limit } : {},
|
|
2867
3392
|
signal: controller.signal,
|
|
2868
|
-
onProgress: (done, total) => console.log(
|
|
3393
|
+
onProgress: (done, total) => console.log(pc8.dim(`summarized ${done}/${total}`))
|
|
2869
3394
|
});
|
|
2870
3395
|
console.log(
|
|
2871
3396
|
`${result.summarized} summarized, ${result.skipped} skipped, ${result.remaining} remaining (${result.stoppedBy})`
|
|
@@ -2892,7 +3417,7 @@ function registerIndexCommands(program, runSafely2) {
|
|
|
2892
3417
|
const stop = () => controller.abort();
|
|
2893
3418
|
process.once("SIGINT", stop);
|
|
2894
3419
|
process.once("SIGTERM", stop);
|
|
2895
|
-
console.log(
|
|
3420
|
+
console.log(pc9.dim("watching for changes\u2026 press Ctrl+C to stop"));
|
|
2896
3421
|
await watchRepository({
|
|
2897
3422
|
...opts.debounce !== void 0 ? { debounceMs: opts.debounce } : {},
|
|
2898
3423
|
signal: controller.signal,
|
|
@@ -2901,21 +3426,21 @@ function registerIndexCommands(program, runSafely2) {
|
|
|
2901
3426
|
if (!opts.llm || result.added + result.changed === 0) return;
|
|
2902
3427
|
void summarizeAfterIndex(process.cwd()).then((summary) => {
|
|
2903
3428
|
if (summary && summary.summarized > 0)
|
|
2904
|
-
console.log(
|
|
2905
|
-
}).catch((error) => console.error(
|
|
3429
|
+
console.log(pc9.dim(` llm: ${summary.summarized} file(s) summarized`));
|
|
3430
|
+
}).catch((error) => console.error(pc9.red(`llm: ${error.message}`)));
|
|
2906
3431
|
},
|
|
2907
|
-
onError: (error) => console.error(
|
|
3432
|
+
onError: (error) => console.error(pc9.red(error.message))
|
|
2908
3433
|
});
|
|
2909
|
-
console.log(
|
|
3434
|
+
console.log(pc9.dim("stopped watching"));
|
|
2910
3435
|
});
|
|
2911
3436
|
});
|
|
2912
3437
|
}
|
|
2913
3438
|
function printResult(result, compact = false) {
|
|
2914
|
-
const summary = `${result.totalIndexed} indexed (${
|
|
2915
|
-
console.log(compact ?
|
|
3439
|
+
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`;
|
|
3440
|
+
console.log(compact ? pc9.dim(summary) : summary);
|
|
2916
3441
|
if (result.skipped > 0)
|
|
2917
|
-
console.log(
|
|
2918
|
-
for (const error of result.errors) console.log(
|
|
3442
|
+
console.log(pc9.dim(` ${result.skipped} skipped (too large or unreadable)`));
|
|
3443
|
+
for (const error of result.errors) console.log(pc9.red(` ${error.path}: ${error.error}`));
|
|
2919
3444
|
}
|
|
2920
3445
|
|
|
2921
3446
|
// src/cli/commands/init-wizard.ts
|
|
@@ -2923,7 +3448,7 @@ import { writeFile as writeFile2 } from "fs/promises";
|
|
|
2923
3448
|
import { confirm, isCancel, log, note, password, spinner, text } from "@clack/prompts";
|
|
2924
3449
|
|
|
2925
3450
|
// src/cli/commands/postgres-docker.ts
|
|
2926
|
-
import { spawn as
|
|
3451
|
+
import { spawn as spawn4 } from "child_process";
|
|
2927
3452
|
import { existsSync as existsSync3 } from "fs";
|
|
2928
3453
|
import { dirname as dirname11, resolve as resolve12 } from "path";
|
|
2929
3454
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -2944,7 +3469,7 @@ function resolveBundledComposeFile() {
|
|
|
2944
3469
|
${candidates.join("\n ")}`);
|
|
2945
3470
|
}
|
|
2946
3471
|
async function runDockerCompose(args, options = {}) {
|
|
2947
|
-
const spawnImpl = options.spawnImpl ??
|
|
3472
|
+
const spawnImpl = options.spawnImpl ?? spawn4;
|
|
2948
3473
|
const composeFile = resolveBundledComposeFile();
|
|
2949
3474
|
const attempts = [
|
|
2950
3475
|
{ kind: "v2", bin: "docker", args: ["compose", "-f", composeFile, ...args] },
|
|
@@ -3024,8 +3549,7 @@ async function runInitWizard() {
|
|
|
3024
3549
|
);
|
|
3025
3550
|
const hfToken = unwrap(
|
|
3026
3551
|
await password({
|
|
3027
|
-
message: "Hugging Face token (
|
|
3028
|
-
validate: (value) => value && value.length > 0 ? void 0 : "HF_TOKEN is required."
|
|
3552
|
+
message: "Hugging Face token (optional; only for hosted embeddings)"
|
|
3029
3553
|
})
|
|
3030
3554
|
);
|
|
3031
3555
|
const databaseUrl = unwrap(
|
|
@@ -3095,7 +3619,7 @@ async function runInitWizard() {
|
|
|
3095
3619
|
const fileConfig = {
|
|
3096
3620
|
postgresUrl: databaseUrl,
|
|
3097
3621
|
namespace,
|
|
3098
|
-
provider: { type: "
|
|
3622
|
+
provider: { type: "local", ...hfToken ? { apiKey: hfToken } : {} },
|
|
3099
3623
|
tokenBudget: 4e3
|
|
3100
3624
|
};
|
|
3101
3625
|
await writeFile2(initialised.path, `${JSON.stringify(fileConfig, null, 2)}
|
|
@@ -3136,7 +3660,7 @@ async function runInitWizard() {
|
|
|
3136
3660
|
}
|
|
3137
3661
|
|
|
3138
3662
|
// src/cli/commands/learn.ts
|
|
3139
|
-
import
|
|
3663
|
+
import pc10 from "picocolors";
|
|
3140
3664
|
|
|
3141
3665
|
// src/learning/outcomes.ts
|
|
3142
3666
|
async function recordChangeOutcome(input) {
|
|
@@ -3197,15 +3721,15 @@ function registerLearnCommand(program, runSafely2) {
|
|
|
3197
3721
|
...opts.commit ? { commit: opts.commit } : {},
|
|
3198
3722
|
...opts.plan ? { planId: opts.plan } : {}
|
|
3199
3723
|
});
|
|
3200
|
-
console.log(`${
|
|
3201
|
-
console.log(
|
|
3724
|
+
console.log(`${pc10.green("recorded")} ${result.id} (${result.category})`);
|
|
3725
|
+
console.log(pc10.dim(result.path));
|
|
3202
3726
|
});
|
|
3203
3727
|
}
|
|
3204
3728
|
);
|
|
3205
3729
|
}
|
|
3206
3730
|
|
|
3207
3731
|
// src/cli/commands/map.ts
|
|
3208
|
-
import
|
|
3732
|
+
import pc11 from "picocolors";
|
|
3209
3733
|
function registerMapCommands(program, runSafely2) {
|
|
3210
3734
|
const map = program.command("map").description("Query the lightweight architecture map.");
|
|
3211
3735
|
map.command("build").description("Scan the current repository and summarize the module graph.").action(async () => {
|
|
@@ -3225,7 +3749,7 @@ function registerMapCommands(program, runSafely2) {
|
|
|
3225
3749
|
return;
|
|
3226
3750
|
}
|
|
3227
3751
|
if (edges.length === 0) {
|
|
3228
|
-
console.log(
|
|
3752
|
+
console.log(pc11.dim("no dependencies found"));
|
|
3229
3753
|
return;
|
|
3230
3754
|
}
|
|
3231
3755
|
for (const edge of edges) console.log(`${edge.from} -> ${edge.to}`);
|
|
@@ -3240,7 +3764,7 @@ function registerMapCommands(program, runSafely2) {
|
|
|
3240
3764
|
return;
|
|
3241
3765
|
}
|
|
3242
3766
|
if (result.edges.length === 0) {
|
|
3243
|
-
console.log(
|
|
3767
|
+
console.log(pc11.dim("no dependents found"));
|
|
3244
3768
|
return;
|
|
3245
3769
|
}
|
|
3246
3770
|
for (const edge of result.edges) console.log(`${edge.from} -> ${edge.to}`);
|
|
@@ -3249,9 +3773,9 @@ function registerMapCommands(program, runSafely2) {
|
|
|
3249
3773
|
}
|
|
3250
3774
|
|
|
3251
3775
|
// src/cli/commands/memory-review.ts
|
|
3252
|
-
import { spawn as
|
|
3776
|
+
import { spawn as spawn5 } from "child_process";
|
|
3253
3777
|
import { relative as relative3 } from "path";
|
|
3254
|
-
import
|
|
3778
|
+
import pc12 from "picocolors";
|
|
3255
3779
|
function registerMemoryCommands(program, runSafely2) {
|
|
3256
3780
|
const memory = program.command("memory").description("Inspect and curate CodeBuddy memory.");
|
|
3257
3781
|
const review = memory.command("review").description("Review memory candidates captured automatically after turns.");
|
|
@@ -3263,11 +3787,11 @@ function registerMemoryCommands(program, runSafely2) {
|
|
|
3263
3787
|
return;
|
|
3264
3788
|
}
|
|
3265
3789
|
if (items.length === 0) {
|
|
3266
|
-
console.log(
|
|
3790
|
+
console.log(pc12.dim("no pending review items"));
|
|
3267
3791
|
return;
|
|
3268
3792
|
}
|
|
3269
3793
|
for (const item of items) console.log(renderRow(item));
|
|
3270
|
-
console.log(
|
|
3794
|
+
console.log(pc12.dim(`
|
|
3271
3795
|
${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
3272
3796
|
});
|
|
3273
3797
|
});
|
|
@@ -3281,7 +3805,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
|
3281
3805
|
const factId = await approveReviewItem(id, {
|
|
3282
3806
|
...opts.incident !== void 0 ? { incidentId: opts.incident } : {}
|
|
3283
3807
|
});
|
|
3284
|
-
console.log(`${
|
|
3808
|
+
console.log(`${pc12.green("approved")} ${id} \u2192 ${factId}`);
|
|
3285
3809
|
});
|
|
3286
3810
|
});
|
|
3287
3811
|
review.command("reject").argument("<id>").description("Discard a review candidate without saving it.").action(async (id) => {
|
|
@@ -3289,7 +3813,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
|
3289
3813
|
const store = new ReviewStore();
|
|
3290
3814
|
await store.read(id);
|
|
3291
3815
|
await store.delete(id);
|
|
3292
|
-
console.log(`${
|
|
3816
|
+
console.log(`${pc12.yellow("rejected")} ${id}`);
|
|
3293
3817
|
});
|
|
3294
3818
|
});
|
|
3295
3819
|
review.command("edit").argument("<id>").description("Open a review candidate in $EDITOR and re-validate on save.").action(async (id) => {
|
|
@@ -3298,44 +3822,44 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
|
3298
3822
|
const item = await store.read(id);
|
|
3299
3823
|
await openEditor(item.path);
|
|
3300
3824
|
await store.read(id);
|
|
3301
|
-
console.log(
|
|
3825
|
+
console.log(pc12.dim("saved and validated"));
|
|
3302
3826
|
});
|
|
3303
3827
|
});
|
|
3304
3828
|
}
|
|
3305
3829
|
function renderRow(item) {
|
|
3306
|
-
const flag = item.sensitive ?
|
|
3307
|
-
return `${classColor(item.class)} ${
|
|
3830
|
+
const flag = item.sensitive ? pc12.red(" \u26A0 sensitive") : "";
|
|
3831
|
+
return `${classColor(item.class)} ${pc12.dim(item.id)} ${truncate(item.content, 70)}${flag}`;
|
|
3308
3832
|
}
|
|
3309
3833
|
function renderItem(item) {
|
|
3310
3834
|
const lines = [
|
|
3311
|
-
`${classColor(item.class)} ${
|
|
3312
|
-
`${
|
|
3313
|
-
`${
|
|
3314
|
-
`${
|
|
3835
|
+
`${classColor(item.class)} ${pc12.dim(item.id)}`,
|
|
3836
|
+
`${pc12.bold("reason:")} ${item.reason}`,
|
|
3837
|
+
`${pc12.bold("confidence:")} ${item.confidence.toFixed(2)}`,
|
|
3838
|
+
`${pc12.bold("triple:")} ${item.subject} \xB7 ${item.predicate} \xB7 ${item.object}`
|
|
3315
3839
|
];
|
|
3316
|
-
if (item.paths.length > 0) lines.push(`${
|
|
3317
|
-
if (item.severity) lines.push(`${
|
|
3840
|
+
if (item.paths.length > 0) lines.push(`${pc12.bold("paths:")} ${item.paths.join(", ")}`);
|
|
3841
|
+
if (item.severity) lines.push(`${pc12.bold("severity:")} ${item.severity}`);
|
|
3318
3842
|
if (item.resolvesIncidents) {
|
|
3319
3843
|
lines.push(
|
|
3320
|
-
`${
|
|
3844
|
+
`${pc12.bold("resolves:")} ${item.resolvesIncidents.join(", ") || "(no candidate; approve with --incident <id>)"}`
|
|
3321
3845
|
);
|
|
3322
3846
|
}
|
|
3323
|
-
if (item.sensitive) lines.push(
|
|
3324
|
-
lines.push("", item.content, "",
|
|
3847
|
+
if (item.sensitive) lines.push(pc12.red(`\u26A0 sensitive: ${item.sensitiveReasons.join(", ")}`));
|
|
3848
|
+
lines.push("", item.content, "", pc12.dim(relative3(process.cwd(), item.path)));
|
|
3325
3849
|
return lines.join("\n");
|
|
3326
3850
|
}
|
|
3327
3851
|
function classColor(memoryClass) {
|
|
3328
3852
|
switch (memoryClass) {
|
|
3329
3853
|
case "incident":
|
|
3330
|
-
return
|
|
3854
|
+
return pc12.red(memoryClass);
|
|
3331
3855
|
case "decision":
|
|
3332
|
-
return
|
|
3856
|
+
return pc12.cyan(memoryClass);
|
|
3333
3857
|
case "fact":
|
|
3334
|
-
return
|
|
3858
|
+
return pc12.green(memoryClass);
|
|
3335
3859
|
case "note":
|
|
3336
|
-
return
|
|
3860
|
+
return pc12.yellow(memoryClass);
|
|
3337
3861
|
default:
|
|
3338
|
-
return
|
|
3862
|
+
return pc12.dim(memoryClass);
|
|
3339
3863
|
}
|
|
3340
3864
|
}
|
|
3341
3865
|
function truncate(value, max) {
|
|
@@ -3345,16 +3869,422 @@ function truncate(value, max) {
|
|
|
3345
3869
|
function openEditor(path) {
|
|
3346
3870
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
3347
3871
|
return new Promise((resolvePromise, reject) => {
|
|
3348
|
-
const child =
|
|
3872
|
+
const child = spawn5(editor, [path], { stdio: "inherit" });
|
|
3349
3873
|
child.on("error", reject);
|
|
3350
3874
|
child.on("close", () => resolvePromise());
|
|
3351
3875
|
});
|
|
3352
3876
|
}
|
|
3353
3877
|
|
|
3878
|
+
// src/cli/commands/models.ts
|
|
3879
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3880
|
+
import { join as join15 } from "path";
|
|
3881
|
+
import { spinner as spinner2 } from "@clack/prompts";
|
|
3882
|
+
import pc13 from "picocolors";
|
|
3883
|
+
|
|
3884
|
+
// evals/fact-relevance.json
|
|
3885
|
+
var fact_relevance_default = {
|
|
3886
|
+
version: 1,
|
|
3887
|
+
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.",
|
|
3888
|
+
facts: {
|
|
3889
|
+
cookie: "OAuth callback breaks unless the session cookie is SameSite=Lax; Strict drops it on the cross-site redirect.",
|
|
3890
|
+
pool: "Postgres connections go through one bounded pool of 10; never open ad-hoc clients in request handlers.",
|
|
3891
|
+
release: "npm releases are published from CI with provenance; never publish from a laptop.",
|
|
3892
|
+
biome: "Biome owns formatting and import order; run biome check --write before committing.",
|
|
3893
|
+
hook: "Prompt hooks must answer within 15 seconds or the host drops the injected block.",
|
|
3894
|
+
migration: "Migrations must create the pgvector extension before 0003 adds the vector column.",
|
|
3895
|
+
redaction: "Secrets are redacted before a memory is written; the content hash is computed after redaction.",
|
|
3896
|
+
ledger: "The session ledger keeps the last 8 acknowledged versions per section so a task switch back is not resent.",
|
|
3897
|
+
zvec: "zvec-grep is optional; any failure falls back to keyword ranking with a retrieval explain line.",
|
|
3898
|
+
tests: "Integration tests skip unless CODEBUDDY_TEST_DATABASE_URL points at a live Postgres.",
|
|
3899
|
+
esm: "Source imports use .js specifiers even for TypeScript files because the package is ESM.",
|
|
3900
|
+
stdout: "Never log to stdout in the MCP server: stdout carries JSON-RPC and any stray line corrupts it.",
|
|
3901
|
+
budget: "The BYOK LLM layer stops background calls at 70% of the daily budget and backs off after a 429.",
|
|
3902
|
+
worktree: "Use a temporary WIP commit instead of git stash; the stash stack is shared across worktrees.",
|
|
3903
|
+
rateLimit: "The public API allows 100 requests per minute per token; bursts above it get HTTP 429.",
|
|
3904
|
+
timezone: "All timestamps are stored in UTC and converted to the user's zone only for display.",
|
|
3905
|
+
avatar: "Profile images are resized to 256px and stored as WebP in the media bucket.",
|
|
3906
|
+
flags: "Feature flags are read once at boot; changing one requires a restart of the worker.",
|
|
3907
|
+
pagination: "List endpoints use cursor pagination with an opaque base64url cursor of createdAt and id.",
|
|
3908
|
+
i18n: "UI strings live in locales/en.json; never hard-code user-facing text in components."
|
|
3909
|
+
},
|
|
3910
|
+
queries: [
|
|
3911
|
+
{
|
|
3912
|
+
q: "users get bounced back to sign-in forever after the identity provider returns",
|
|
3913
|
+
relevant: ["cookie"]
|
|
3914
|
+
},
|
|
3915
|
+
{ q: "handlers are exhausting connections to the database under load", relevant: ["pool"] },
|
|
3916
|
+
{ q: "cut a new version and push the package to the registry", relevant: ["release"] },
|
|
3917
|
+
{ q: "the injected block sometimes never shows up in the chat", relevant: ["hook"] },
|
|
3918
|
+
{ q: "fresh database setup fails on the embedding column", relevant: ["migration"] },
|
|
3919
|
+
{ q: "an API key ended up in a saved note", relevant: ["redaction"] },
|
|
3920
|
+
{ q: "context is sent again after I switch tasks and come back", relevant: ["ledger"] },
|
|
3921
|
+
{ q: "semantic code search crashes and the agent gets no files", relevant: ["zvec"] },
|
|
3922
|
+
{ q: "why do the db tests not run on my machine", relevant: ["tests"] },
|
|
3923
|
+
{ q: "module not found when importing a sibling ts file at runtime", relevant: ["esm"] },
|
|
3924
|
+
{ q: "Claude Desktop says Unexpected token in the server response", relevant: ["stdout"] },
|
|
3925
|
+
{ q: "the summarizer quietly stopped calling Groq in the afternoon", relevant: ["budget"] },
|
|
3926
|
+
{ q: "my uncommitted changes vanished after another session ran", relevant: ["worktree"] },
|
|
3927
|
+
{
|
|
3928
|
+
q: "clients see too many requests errors during the import job",
|
|
3929
|
+
relevant: ["rateLimit"]
|
|
3930
|
+
},
|
|
3931
|
+
{ q: "meeting times show up one hour off for users in Berlin", relevant: ["timezone"] },
|
|
3932
|
+
{ q: "uploaded profile pictures look blurry", relevant: ["avatar"] },
|
|
3933
|
+
{ q: "I toggled the new checkout switch but nothing changed", relevant: ["flags"] },
|
|
3934
|
+
{ q: "infinite scroll repeats items on the second page", relevant: ["pagination"] },
|
|
3935
|
+
{ q: "translate the onboarding screen to German", relevant: ["i18n"] },
|
|
3936
|
+
{ q: "format the code before I commit", relevant: ["biome"] },
|
|
3937
|
+
{ q: "make the sidebar collapsible", relevant: [] },
|
|
3938
|
+
{ q: "add dark mode to the dashboard", relevant: [] },
|
|
3939
|
+
{ q: "write a haiku about the project", relevant: [] },
|
|
3940
|
+
{ q: "rename the button label on the settings page", relevant: [] }
|
|
3941
|
+
]
|
|
3942
|
+
};
|
|
3943
|
+
|
|
3944
|
+
// src/providers/calibration.ts
|
|
3945
|
+
async function calibrateEmbedder(embed) {
|
|
3946
|
+
const data = fact_relevance_default;
|
|
3947
|
+
const factIds = Object.keys(data.facts);
|
|
3948
|
+
const factVectors = await embed(Object.values(data.facts));
|
|
3949
|
+
const queryVectors = await embed(data.queries.map(({ q }) => q));
|
|
3950
|
+
const dot = (a, b) => a.reduce((sum, value, index) => sum + value * (b[index] ?? 0), 0);
|
|
3951
|
+
const scores = queryVectors.map((query) => factVectors.map((fact) => dot(query, fact)));
|
|
3952
|
+
const relevant = (qi, fi) => data.queries[qi]?.relevant.includes(factIds[fi] ?? "") ?? false;
|
|
3953
|
+
const positives = [];
|
|
3954
|
+
const negatives = [];
|
|
3955
|
+
for (const [qi, row2] of scores.entries()) {
|
|
3956
|
+
for (const [fi, score] of row2.entries()) (relevant(qi, fi) ? positives : negatives).push(score);
|
|
3957
|
+
}
|
|
3958
|
+
const auc = positives.reduce((wins, p) => wins + negatives.filter((n) => p > n).length, 0) / (positives.length * negatives.length);
|
|
3959
|
+
let hits = 0;
|
|
3960
|
+
let reciprocal = 0;
|
|
3961
|
+
let answerable = 0;
|
|
3962
|
+
for (const [qi, row2] of scores.entries()) {
|
|
3963
|
+
if (!data.queries[qi]?.relevant.length) continue;
|
|
3964
|
+
answerable += 1;
|
|
3965
|
+
const order = row2.map((score, fi) => ({ score, fi })).sort((a, b) => b.score - a.score);
|
|
3966
|
+
const rank = order.findIndex(({ fi }) => relevant(qi, fi)) + 1;
|
|
3967
|
+
if (rank === 1) hits += 1;
|
|
3968
|
+
reciprocal += 1 / rank;
|
|
3969
|
+
}
|
|
3970
|
+
const at = (floor) => {
|
|
3971
|
+
let tp = 0;
|
|
3972
|
+
let fp = 0;
|
|
3973
|
+
let abstained = 0;
|
|
3974
|
+
let unrelated = 0;
|
|
3975
|
+
for (const [qi, row2] of scores.entries()) {
|
|
3976
|
+
const admitted = row2.flatMap((score, fi) => score >= floor ? [fi] : []);
|
|
3977
|
+
for (const fi of admitted) {
|
|
3978
|
+
if (relevant(qi, fi)) tp += 1;
|
|
3979
|
+
else fp += 1;
|
|
3980
|
+
}
|
|
3981
|
+
if (!data.queries[qi]?.relevant.length) {
|
|
3982
|
+
unrelated += 1;
|
|
3983
|
+
if (admitted.length === 0) abstained += 1;
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
const precision = tp + fp === 0 ? 1 : tp / (tp + fp);
|
|
3987
|
+
const recall = tp / positives.length;
|
|
3988
|
+
const f05 = precision + recall === 0 ? 0 : 1.25 * precision * recall / (0.25 * precision + recall);
|
|
3989
|
+
return { floor, precision, recall, f05, abstain: abstained / unrelated };
|
|
3990
|
+
};
|
|
3991
|
+
const sweep = Array.from({ length: 96 }, (_, i) => at(Math.round((0.05 + i * 0.01) * 100) / 100));
|
|
3992
|
+
const best = sweep.reduce((left, right) => right.f05 > left.f05 ? right : left);
|
|
3993
|
+
return { auc, recallAt1: hits / answerable, mrr: reciprocal / answerable, best, at };
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
// src/cli/commands/models.ts
|
|
3997
|
+
var EMBEDDERS = ["local", "huggingface", "openrouter"];
|
|
3998
|
+
var RERANKERS = ["off", "local", "huggingface", "openrouter"];
|
|
3999
|
+
var KEY_ENV2 = { huggingface: "HF_TOKEN", openrouter: "OPENROUTER_API_KEY" };
|
|
4000
|
+
var ok = (text3) => `${pc13.green("\u2713")} ${text3}`;
|
|
4001
|
+
var warn = (text3) => `${pc13.yellow("!")} ${text3}`;
|
|
4002
|
+
var row = (label, value) => ` ${pc13.dim(label.padEnd(11))}${value}`;
|
|
4003
|
+
async function withSpinner(label, work, done) {
|
|
4004
|
+
const progress = spinner2();
|
|
4005
|
+
progress.start(label);
|
|
4006
|
+
try {
|
|
4007
|
+
const value = await work();
|
|
4008
|
+
progress.stop(done(value));
|
|
4009
|
+
return value;
|
|
4010
|
+
} catch (error) {
|
|
4011
|
+
progress.stop(pc13.red(`\u2717 ${label}`), 1);
|
|
4012
|
+
throw error;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
function keyPresent(provider, apiKey) {
|
|
4016
|
+
return Boolean(process.env[KEY_ENV2[provider]] || provider === "huggingface" && apiKey);
|
|
4017
|
+
}
|
|
4018
|
+
function localModelDownloaded(id, revision) {
|
|
4019
|
+
const dir = join15(modelCacheDir(), id, ...revision ? [revision] : []);
|
|
4020
|
+
return existsSync4(join15(dir, "onnx", "model_quantized.onnx"));
|
|
4021
|
+
}
|
|
4022
|
+
async function vectorCounts(postgresUrl, namespace, space) {
|
|
4023
|
+
const client = createDatabaseClient({ postgresUrl, maxConnections: 1, connectTimeoutSeconds: 3 });
|
|
4024
|
+
try {
|
|
4025
|
+
const [counts] = await client.sql`
|
|
4026
|
+
select
|
|
4027
|
+
count(*) filter (where e.status = 'ready' and e.embedding_space = ${space ?? ""})::int as ready,
|
|
4028
|
+
count(*) filter (where e.status in ('pending', 'processing'))::int as pending,
|
|
4029
|
+
count(*) filter (where e.status = 'failed')::int as failed,
|
|
4030
|
+
count(*) filter (where e.status = 'ready' and e.embedding_space is distinct from ${space ?? ""})::int as other
|
|
4031
|
+
from embeddings e join namespaces n on n.id = e.namespace_id
|
|
4032
|
+
where n.name = ${namespace}
|
|
4033
|
+
`;
|
|
4034
|
+
return counts ?? { ready: 0, pending: 0, failed: 0, other: 0 };
|
|
4035
|
+
} catch (error) {
|
|
4036
|
+
return String(redactSecrets(error.message)).split("\n")[0] ?? "unreachable";
|
|
4037
|
+
} finally {
|
|
4038
|
+
await client.close().catch(() => void 0);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
async function printStatus() {
|
|
4042
|
+
const file = await readConfigFile();
|
|
4043
|
+
const config = await loadRuntimeConfig(process.cwd(), { requireDatabase: false });
|
|
4044
|
+
const provider = config.provider;
|
|
4045
|
+
const embedder = buildProviderFromConfig(config.provider);
|
|
4046
|
+
const space = embedder.space;
|
|
4047
|
+
const model = provider.type === "local" ? LOCAL_EMBEDDING_MODEL.id : provider.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[provider.type];
|
|
4048
|
+
const floor = provider.relevanceFloor ?? getModelDefinition(model)?.relevanceFloor;
|
|
4049
|
+
const next = [];
|
|
4050
|
+
console.log(pc13.bold("Embeddings"));
|
|
4051
|
+
console.log(row("provider", `${provider.type} ${pc13.dim(model)}`));
|
|
4052
|
+
if (provider.type === "local") {
|
|
4053
|
+
const ready = localModelDownloaded(LOCAL_EMBEDDING_MODEL.id, LOCAL_EMBEDDING_MODEL.revision);
|
|
4054
|
+
console.log(
|
|
4055
|
+
row(
|
|
4056
|
+
"model",
|
|
4057
|
+
ready ? ok(`downloaded ${pc13.dim(modelCacheDir())}`) : warn("not downloaded yet (~90 MB)")
|
|
4058
|
+
)
|
|
4059
|
+
);
|
|
4060
|
+
if (!ready) next.push("codebuddy models pull");
|
|
4061
|
+
} else {
|
|
4062
|
+
const present = keyPresent(provider.type, provider.apiKey);
|
|
4063
|
+
console.log(
|
|
4064
|
+
row(
|
|
4065
|
+
"key",
|
|
4066
|
+
present ? ok(KEY_ENV2[provider.type]) : warn(`${KEY_ENV2[provider.type]} is not set`)
|
|
4067
|
+
)
|
|
4068
|
+
);
|
|
4069
|
+
if (!present) next.push(`export ${KEY_ENV2[provider.type]}=\u2026`);
|
|
4070
|
+
}
|
|
4071
|
+
console.log(
|
|
4072
|
+
row(
|
|
4073
|
+
"floor",
|
|
4074
|
+
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")
|
|
4075
|
+
)
|
|
4076
|
+
);
|
|
4077
|
+
if (floor === void 0) next.push("codebuddy models calibrate");
|
|
4078
|
+
const rerank = file.rerank?.provider ?? "off";
|
|
4079
|
+
console.log(pc13.bold("Reranker"));
|
|
4080
|
+
if (rerank === "off") {
|
|
4081
|
+
console.log(
|
|
4082
|
+
row("provider", `off ${pc13.dim("(default: no reranker beat dense order in evals)")}`)
|
|
4083
|
+
);
|
|
4084
|
+
} else {
|
|
4085
|
+
console.log(
|
|
4086
|
+
row(
|
|
4087
|
+
"provider",
|
|
4088
|
+
`${rerank} ${pc13.dim(file.rerank?.model ?? DEFAULT_RERANK_MODELS[rerank].id)}`
|
|
4089
|
+
)
|
|
4090
|
+
);
|
|
4091
|
+
if (rerank !== "local") {
|
|
4092
|
+
const present = keyPresent(rerank, file.provider?.apiKey);
|
|
4093
|
+
console.log(
|
|
4094
|
+
row(
|
|
4095
|
+
"key",
|
|
4096
|
+
present ? ok(KEY_ENV2[rerank]) : warn(`${KEY_ENV2[rerank]} is not set; reranking is skipped`)
|
|
4097
|
+
)
|
|
4098
|
+
);
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
console.log(pc13.bold("Memory index"));
|
|
4102
|
+
if (process.env.CODEBUDDY_SEMANTIC === "0") {
|
|
4103
|
+
console.log(row("recall", warn("CODEBUDDY_SEMANTIC=0: lexical only")));
|
|
4104
|
+
}
|
|
4105
|
+
if (!config.postgresUrl) {
|
|
4106
|
+
console.log(row("postgres", warn("not configured: recall is lexical only")));
|
|
4107
|
+
next.push("codebuddy use (sets up PostgreSQL)");
|
|
4108
|
+
} else {
|
|
4109
|
+
const counts = await vectorCounts(
|
|
4110
|
+
config.postgresUrl,
|
|
4111
|
+
config.namespace,
|
|
4112
|
+
space ? embeddingSpaceKey(space) : void 0
|
|
4113
|
+
);
|
|
4114
|
+
if (typeof counts === "string") {
|
|
4115
|
+
console.log(row("postgres", warn(`unreachable: ${counts}`)));
|
|
4116
|
+
next.push("codebuddy postgres up");
|
|
4117
|
+
} else {
|
|
4118
|
+
console.log(row("postgres", ok(`namespace ${config.namespace}`)));
|
|
4119
|
+
console.log(
|
|
4120
|
+
row(
|
|
4121
|
+
"vectors",
|
|
4122
|
+
`${counts.ready} ready` + (counts.pending ? `, ${counts.pending} pending` : "") + (counts.failed ? pc13.red(`, ${counts.failed} failed`) : "") + (counts.other ? pc13.yellow(`, ${counts.other} from another model`) : "")
|
|
4123
|
+
)
|
|
4124
|
+
);
|
|
4125
|
+
if (counts.pending || counts.other)
|
|
4126
|
+
next.push("codebuddy hooks sync (embeds pending and re-embeds old vectors)");
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
console.log(
|
|
4130
|
+
next.length > 0 ? `
|
|
4131
|
+
${pc13.bold("Next")}
|
|
4132
|
+
${next.map((step) => ` ${pc13.cyan(step)}`).join("\n")}` : `
|
|
4133
|
+
${ok("Semantic recall is ready.")} ${pc13.dim('Try: codebuddy context preview "<task>"')}`
|
|
4134
|
+
);
|
|
4135
|
+
}
|
|
4136
|
+
function registerModelsCommands(program, runSafely2) {
|
|
4137
|
+
const models = program.command("models").description("Embedding model and reranker behind per-prompt memory recall.").action(async () => runSafely2(printStatus));
|
|
4138
|
+
models.command("status").description("Show the embedding model, reranker, keys and vector index health.").action(async () => runSafely2(printStatus));
|
|
4139
|
+
models.command("use").argument("<provider>", `Embedding provider: ${EMBEDDERS.join(" | ")}.`).option("--model <id>", "Hosted model id (must return 384-d vectors).").option(
|
|
4140
|
+
"--floor <cosine>",
|
|
4141
|
+
"Relevance floor; otherwise built-in or `models calibrate`.",
|
|
4142
|
+
Number
|
|
4143
|
+
).description("Choose where embeddings run: local (default, no key) or a hosted API.").addHelpText(
|
|
4144
|
+
"after",
|
|
4145
|
+
`
|
|
4146
|
+
Examples:
|
|
4147
|
+
codebuddy models use local
|
|
4148
|
+
codebuddy models use openrouter # ${DEFAULT_CLOUD_EMBEDDING_MODELS.openrouter}, needs OPENROUTER_API_KEY
|
|
4149
|
+
codebuddy models use openrouter --model nvidia/nemotron-3.5-embed-1b:free
|
|
4150
|
+
codebuddy models use huggingface # ${DEFAULT_CLOUD_EMBEDDING_MODELS.huggingface}, needs HF_TOKEN`
|
|
4151
|
+
).action(async (choice, opts) => {
|
|
4152
|
+
await runSafely2(async () => {
|
|
4153
|
+
if (!EMBEDDERS.includes(choice)) {
|
|
4154
|
+
throw new Error(`Unknown provider "${choice}". Use one of: ${EMBEDDERS.join(", ")}.`);
|
|
4155
|
+
}
|
|
4156
|
+
const type = choice;
|
|
4157
|
+
if (type === "local" && opts.model) {
|
|
4158
|
+
throw new Error(
|
|
4159
|
+
"The local provider has one built-in model; --model is for hosted providers."
|
|
4160
|
+
);
|
|
4161
|
+
}
|
|
4162
|
+
const current = (await readConfigFile()).provider;
|
|
4163
|
+
await setConfigBlock("provider", {
|
|
4164
|
+
type,
|
|
4165
|
+
...current?.apiKey ? { apiKey: current.apiKey } : {},
|
|
4166
|
+
...opts.model ? { model: opts.model } : {},
|
|
4167
|
+
...opts.floor !== void 0 ? { relevanceFloor: opts.floor } : {}
|
|
4168
|
+
});
|
|
4169
|
+
const model = type === "local" ? LOCAL_EMBEDDING_MODEL.id : opts.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[type];
|
|
4170
|
+
console.log(ok(`Embeddings: ${type} ${pc13.dim(model)}`));
|
|
4171
|
+
const missingKey = type !== "local" && !keyPresent(type, current?.apiKey);
|
|
4172
|
+
if (missingKey) {
|
|
4173
|
+
console.log(
|
|
4174
|
+
warn(
|
|
4175
|
+
`Set ${KEY_ENV2[type]} in your shell (keys are never stored in config), then run ${pc13.cyan("codebuddy hooks sync")}.`
|
|
4176
|
+
)
|
|
4177
|
+
);
|
|
4178
|
+
console.log(pc13.dim(" Existing vectors keep working until the new model can embed."));
|
|
4179
|
+
}
|
|
4180
|
+
if (opts.floor === void 0 && getModelDefinition(model)?.relevanceFloor === void 0) {
|
|
4181
|
+
console.log(
|
|
4182
|
+
warn(
|
|
4183
|
+
`No floor for ${model} yet: run ${pc13.cyan("codebuddy models calibrate")} to turn on dense recall.`
|
|
4184
|
+
)
|
|
4185
|
+
);
|
|
4186
|
+
}
|
|
4187
|
+
if (!missingKey) {
|
|
4188
|
+
syncInBackground(process.cwd());
|
|
4189
|
+
console.log(
|
|
4190
|
+
pc13.dim(" Re-embedding existing memory in the background (codebuddy hooks sync).")
|
|
4191
|
+
);
|
|
4192
|
+
}
|
|
4193
|
+
});
|
|
4194
|
+
});
|
|
4195
|
+
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(
|
|
4196
|
+
"after",
|
|
4197
|
+
`
|
|
4198
|
+
Defaults: local ${DEFAULT_RERANK_MODELS.local.id} \xB7 huggingface ${DEFAULT_RERANK_MODELS.huggingface.id}
|
|
4199
|
+
openrouter ${DEFAULT_RERANK_MODELS.openrouter.id} (paid per search)
|
|
4200
|
+
In CodeBuddy's eval, no reranker beat the dense order; measure on your data
|
|
4201
|
+
before keeping one on. A reranker never admits a fact, it only orders them.`
|
|
4202
|
+
).action(async (choice, opts) => {
|
|
4203
|
+
await runSafely2(async () => {
|
|
4204
|
+
if (!RERANKERS.includes(choice)) {
|
|
4205
|
+
throw new Error(`Unknown reranker "${choice}". Use one of: ${RERANKERS.join(", ")}.`);
|
|
4206
|
+
}
|
|
4207
|
+
await setConfigBlock("rerank", {
|
|
4208
|
+
provider: choice,
|
|
4209
|
+
...opts.model ? { model: opts.model } : {}
|
|
4210
|
+
});
|
|
4211
|
+
if (choice === "off") {
|
|
4212
|
+
console.log(ok("Reranker off: facts keep their fused (keyword + vector) order."));
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
const reranker = choice;
|
|
4216
|
+
console.log(
|
|
4217
|
+
ok(`Reranker: ${reranker} ${pc13.dim(opts.model ?? DEFAULT_RERANK_MODELS[reranker].id)}`)
|
|
4218
|
+
);
|
|
4219
|
+
if (reranker !== "local" && !keyPresent(reranker, (await readConfigFile()).provider?.apiKey)) {
|
|
4220
|
+
console.log(warn(`Set ${KEY_ENV2[reranker]}; without it reranking is skipped.`));
|
|
4221
|
+
}
|
|
4222
|
+
if (reranker === "local")
|
|
4223
|
+
console.log(pc13.dim(" Run codebuddy models pull to download it now."));
|
|
4224
|
+
});
|
|
4225
|
+
});
|
|
4226
|
+
models.command("pull").description("Download the local models now instead of on first use.").action(async () => {
|
|
4227
|
+
await runSafely2(async () => {
|
|
4228
|
+
const { LocalModelProvider, rerankLocally } = await import("./local-3NGNESNB.js");
|
|
4229
|
+
const file = await readConfigFile();
|
|
4230
|
+
let started = Date.now();
|
|
4231
|
+
await withSpinner(
|
|
4232
|
+
`Downloading ${LOCAL_EMBEDDING_MODEL.id}`,
|
|
4233
|
+
() => new LocalModelProvider().embed({ input: "warm up" }),
|
|
4234
|
+
() => ok(`${LOCAL_EMBEDDING_MODEL.id} ready ${pc13.dim(`${Date.now() - started} ms`)}`)
|
|
4235
|
+
);
|
|
4236
|
+
if (file.rerank?.provider === "local") {
|
|
4237
|
+
const id = file.rerank.model ?? DEFAULT_RERANK_MODELS.local.id;
|
|
4238
|
+
const revision = id === DEFAULT_RERANK_MODELS.local.id ? DEFAULT_RERANK_MODELS.local.revision : void 0;
|
|
4239
|
+
started = Date.now();
|
|
4240
|
+
await withSpinner(
|
|
4241
|
+
`Downloading ${id}`,
|
|
4242
|
+
() => rerankLocally({ id, ...revision ? { revision } : {} }, "warm", ["up"]),
|
|
4243
|
+
() => ok(`${id} ready ${pc13.dim(`${Date.now() - started} ms`)}`)
|
|
4244
|
+
);
|
|
4245
|
+
}
|
|
4246
|
+
console.log(pc13.dim(` Cache: ${modelCacheDir()}`));
|
|
4247
|
+
});
|
|
4248
|
+
});
|
|
4249
|
+
models.command("calibrate").option("--dry-run", "Measure only; do not save the floor.").description(
|
|
4250
|
+
"Measure the configured embedder on a built-in task set and save its recall floor."
|
|
4251
|
+
).action(async (opts) => {
|
|
4252
|
+
await runSafely2(async () => {
|
|
4253
|
+
const config = await loadRuntimeConfig(process.cwd(), { requireDatabase: false });
|
|
4254
|
+
const embedder = buildProviderFromConfig(config.provider);
|
|
4255
|
+
const result = await withSpinner(
|
|
4256
|
+
`Embedding 44 calibration texts with ${embedder.space?.model ?? config.provider.type}`,
|
|
4257
|
+
() => calibrateEmbedder(async (texts) => (await embedder.embed({ input: texts })).vectors),
|
|
4258
|
+
() => ok("Measured")
|
|
4259
|
+
);
|
|
4260
|
+
const pct = (value) => `${Math.round(value * 100)}%`;
|
|
4261
|
+
console.log(
|
|
4262
|
+
row(
|
|
4263
|
+
"ranking",
|
|
4264
|
+
`AUC ${result.auc.toFixed(3)} \xB7 right fact first ${pct(result.recallAt1)}`
|
|
4265
|
+
)
|
|
4266
|
+
);
|
|
4267
|
+
console.log(
|
|
4268
|
+
row(
|
|
4269
|
+
"floor",
|
|
4270
|
+
`${result.best.floor}: precision ${pct(result.best.precision)}, recall ${pct(result.best.recall)}, silent on ${pct(result.best.abstain)} of unrelated tasks`
|
|
4271
|
+
)
|
|
4272
|
+
);
|
|
4273
|
+
if (opts.dryRun) return;
|
|
4274
|
+
const file = await readConfigFile();
|
|
4275
|
+
await setConfigBlock("provider", {
|
|
4276
|
+
...file.provider ?? { type: config.provider.type },
|
|
4277
|
+
relevanceFloor: result.best.floor
|
|
4278
|
+
});
|
|
4279
|
+
console.log(ok(`Saved relevanceFloor ${result.best.floor} to .codebuddy/config.json`));
|
|
4280
|
+
});
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
|
|
3354
4284
|
// src/cli/commands/plan.ts
|
|
3355
|
-
import { spawn as
|
|
4285
|
+
import { spawn as spawn6 } from "child_process";
|
|
3356
4286
|
import { relative as relative4 } from "path";
|
|
3357
|
-
import
|
|
4287
|
+
import pc14 from "picocolors";
|
|
3358
4288
|
function makeLifecycle(cwd = process.cwd()) {
|
|
3359
4289
|
const store = new PlanFileStore(cwd);
|
|
3360
4290
|
return { store, lifecycle: new PlanLifecycle(store) };
|
|
@@ -3362,67 +4292,67 @@ function makeLifecycle(cwd = process.cwd()) {
|
|
|
3362
4292
|
function statusColor(status) {
|
|
3363
4293
|
switch (status) {
|
|
3364
4294
|
case "draft":
|
|
3365
|
-
return
|
|
4295
|
+
return pc14.dim(status);
|
|
3366
4296
|
case "approved":
|
|
3367
|
-
return
|
|
4297
|
+
return pc14.cyan(status);
|
|
3368
4298
|
case "executing":
|
|
3369
|
-
return
|
|
4299
|
+
return pc14.yellow(status);
|
|
3370
4300
|
case "complete":
|
|
3371
|
-
return
|
|
4301
|
+
return pc14.green(status);
|
|
3372
4302
|
case "abandoned":
|
|
3373
|
-
return
|
|
4303
|
+
return pc14.red(status);
|
|
3374
4304
|
}
|
|
3375
4305
|
}
|
|
3376
4306
|
function renderPlan(plan) {
|
|
3377
4307
|
const lines = [];
|
|
3378
|
-
lines.push(`${
|
|
3379
|
-
lines.push(
|
|
4308
|
+
lines.push(`${pc14.bold(plan.title)} ${statusColor(plan.status)}`);
|
|
4309
|
+
lines.push(pc14.dim(plan.id));
|
|
3380
4310
|
lines.push("");
|
|
3381
|
-
lines.push(`${
|
|
4311
|
+
lines.push(`${pc14.bold("Brief:")} ${plan.brief}`);
|
|
3382
4312
|
if (plan.filesToTouch.length > 0) {
|
|
3383
4313
|
lines.push("");
|
|
3384
|
-
lines.push(
|
|
4314
|
+
lines.push(pc14.bold("Files to touch:"));
|
|
3385
4315
|
for (const file of plan.filesToTouch) {
|
|
3386
4316
|
lines.push(` ${file.action.padEnd(6)} ${file.path}${file.notes ? ` \u2014 ${file.notes}` : ""}`);
|
|
3387
4317
|
}
|
|
3388
4318
|
}
|
|
3389
4319
|
if (plan.testsToAdd.length > 0) {
|
|
3390
4320
|
lines.push("");
|
|
3391
|
-
lines.push(
|
|
4321
|
+
lines.push(pc14.bold("Tests to add:"));
|
|
3392
4322
|
for (const test of plan.testsToAdd) {
|
|
3393
4323
|
lines.push(` ${test.path}${test.description ? ` \u2014 ${test.description}` : ""}`);
|
|
3394
4324
|
}
|
|
3395
4325
|
}
|
|
3396
4326
|
if (plan.outOfScope.length > 0) {
|
|
3397
4327
|
lines.push("");
|
|
3398
|
-
lines.push(
|
|
4328
|
+
lines.push(pc14.bold("Out of scope:"));
|
|
3399
4329
|
for (const item of plan.outOfScope) lines.push(` - ${item}`);
|
|
3400
4330
|
}
|
|
3401
4331
|
if (plan.risks.length > 0) {
|
|
3402
4332
|
lines.push("");
|
|
3403
|
-
lines.push(
|
|
4333
|
+
lines.push(pc14.bold("Risks:"));
|
|
3404
4334
|
for (const item of plan.risks) lines.push(` - ${item}`);
|
|
3405
4335
|
}
|
|
3406
4336
|
if (plan.commitSha) {
|
|
3407
4337
|
lines.push("");
|
|
3408
|
-
lines.push(`${
|
|
4338
|
+
lines.push(`${pc14.bold("Commit:")} ${plan.commitSha}`);
|
|
3409
4339
|
}
|
|
3410
4340
|
lines.push("");
|
|
3411
|
-
lines.push(
|
|
4341
|
+
lines.push(pc14.dim("\u2500".repeat(40)));
|
|
3412
4342
|
lines.push(plan.body);
|
|
3413
4343
|
return lines.join("\n");
|
|
3414
4344
|
}
|
|
3415
4345
|
function openEditor2(path) {
|
|
3416
4346
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
3417
4347
|
return new Promise((resolve14, reject) => {
|
|
3418
|
-
const child =
|
|
4348
|
+
const child = spawn6(editor, [path], { stdio: "inherit" });
|
|
3419
4349
|
child.on("error", reject);
|
|
3420
4350
|
child.on("close", () => resolve14());
|
|
3421
4351
|
});
|
|
3422
4352
|
}
|
|
3423
4353
|
function runGit(args) {
|
|
3424
4354
|
return new Promise((resolve14, reject) => {
|
|
3425
|
-
const child =
|
|
4355
|
+
const child = spawn6("git", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
3426
4356
|
let stdout = "";
|
|
3427
4357
|
let stderr = "";
|
|
3428
4358
|
child.stdout.on("data", (c) => {
|
|
@@ -3447,14 +4377,14 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3447
4377
|
title: opts.title ?? brief,
|
|
3448
4378
|
brief
|
|
3449
4379
|
});
|
|
3450
|
-
console.log(`${
|
|
4380
|
+
console.log(`${pc14.green("created")} ${created.id} (${statusColor(created.status)})`);
|
|
3451
4381
|
if (opts.edit !== false && process.stdin.isTTY) {
|
|
3452
4382
|
await openEditor2(created.path);
|
|
3453
4383
|
const { store } = makeLifecycle();
|
|
3454
4384
|
await store.readPlan(created.id);
|
|
3455
|
-
console.log(
|
|
4385
|
+
console.log(pc14.dim("saved and validated"));
|
|
3456
4386
|
}
|
|
3457
|
-
console.log(
|
|
4387
|
+
console.log(pc14.dim(relative4(process.cwd(), created.path)));
|
|
3458
4388
|
});
|
|
3459
4389
|
});
|
|
3460
4390
|
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 +4396,11 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3466
4396
|
else if (!opts.all)
|
|
3467
4397
|
plans = plans.filter((p) => p.status !== "complete" && p.status !== "abandoned");
|
|
3468
4398
|
if (plans.length === 0) {
|
|
3469
|
-
console.log(
|
|
4399
|
+
console.log(pc14.dim("no plans"));
|
|
3470
4400
|
return;
|
|
3471
4401
|
}
|
|
3472
4402
|
for (const p of plans) {
|
|
3473
|
-
console.log(`${statusColor(p.status).padEnd(20)} ${
|
|
4403
|
+
console.log(`${statusColor(p.status).padEnd(20)} ${pc14.bold(p.title)} ${pc14.dim(p.id)}`);
|
|
3474
4404
|
}
|
|
3475
4405
|
});
|
|
3476
4406
|
});
|
|
@@ -3486,7 +4416,7 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3486
4416
|
const existing = await store.readPlan(id);
|
|
3487
4417
|
await openEditor2(existing.path);
|
|
3488
4418
|
await store.readPlan(id);
|
|
3489
|
-
console.log(
|
|
4419
|
+
console.log(pc14.dim("saved and validated"));
|
|
3490
4420
|
});
|
|
3491
4421
|
});
|
|
3492
4422
|
plan.command("approve").argument("<id>").description("Promote a draft to approved (one active plan per namespace).").action(async (id) => {
|
|
@@ -3510,7 +4440,7 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3510
4440
|
...opts.commit ? { commitSha: opts.commit } : {},
|
|
3511
4441
|
...opts.notes ? { notes: opts.notes } : {}
|
|
3512
4442
|
});
|
|
3513
|
-
if (!opts.commit) console.log(
|
|
4443
|
+
if (!opts.commit) console.log(pc14.yellow("note: no --commit supplied"));
|
|
3514
4444
|
console.log(`${next.id} \u2192 ${statusColor(next.status)}`);
|
|
3515
4445
|
});
|
|
3516
4446
|
});
|
|
@@ -3529,7 +4459,7 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3529
4459
|
const args = ["diff", opts.from, ...opts.to ? [opts.to] : [], "--", relPath];
|
|
3530
4460
|
const result = await runGit(args);
|
|
3531
4461
|
if (result.stdout.trim().length === 0) {
|
|
3532
|
-
console.log(
|
|
4462
|
+
console.log(pc14.dim("no changes"));
|
|
3533
4463
|
} else {
|
|
3534
4464
|
process.stdout.write(result.stdout);
|
|
3535
4465
|
}
|
|
@@ -3546,13 +4476,13 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3546
4476
|
throw new Error(`Invalid policy "${mode}". Use one of: ${PLAN_POLICIES.join(", ")}.`);
|
|
3547
4477
|
}
|
|
3548
4478
|
await setPlanPolicy(mode);
|
|
3549
|
-
console.log(`${
|
|
4479
|
+
console.log(`${pc14.green("plan policy")} = ${mode}`);
|
|
3550
4480
|
});
|
|
3551
4481
|
});
|
|
3552
4482
|
}
|
|
3553
4483
|
|
|
3554
4484
|
// src/cli/commands/risk.ts
|
|
3555
|
-
import
|
|
4485
|
+
import pc15 from "picocolors";
|
|
3556
4486
|
function registerRiskCommands(program, runSafely2) {
|
|
3557
4487
|
const risk = program.command("risk").description("Assess evidence-backed change risk.");
|
|
3558
4488
|
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 +4510,31 @@ function parsePaths3(value) {
|
|
|
3580
4510
|
}
|
|
3581
4511
|
function printAssessment(assessment, explain) {
|
|
3582
4512
|
if (assessment.items.length === 0) {
|
|
3583
|
-
console.log(
|
|
4513
|
+
console.log(pc15.dim("no risk evidence found"));
|
|
3584
4514
|
return;
|
|
3585
4515
|
}
|
|
3586
4516
|
for (const item of assessment.items) {
|
|
3587
4517
|
console.log(
|
|
3588
|
-
`${
|
|
4518
|
+
`${pc15.bold(item.path)} score ${scoreColor2(item.score)} category ${pc15.cyan(item.category)}`
|
|
3589
4519
|
);
|
|
3590
4520
|
console.log(` ${item.reason}`);
|
|
3591
4521
|
if (explain) {
|
|
3592
4522
|
for (const evidence of item.evidence) console.log(` \u21B3 ${formatEvidence(evidence)}`);
|
|
3593
4523
|
} else {
|
|
3594
|
-
console.log(
|
|
4524
|
+
console.log(pc15.dim(` ${item.evidence.length} evidence item(s)`));
|
|
3595
4525
|
}
|
|
3596
4526
|
}
|
|
3597
4527
|
console.log(
|
|
3598
|
-
|
|
4528
|
+
pc15.dim(
|
|
3599
4529
|
`
|
|
3600
4530
|
${assessment.stats.itemsProduced} item(s), ${assessment.stats.signalsRun} signal(s), ${assessment.stats.pathsConsidered} path(s) considered`
|
|
3601
4531
|
)
|
|
3602
4532
|
);
|
|
3603
4533
|
}
|
|
3604
4534
|
function scoreColor2(score) {
|
|
3605
|
-
if (score >= 75) return
|
|
3606
|
-
if (score >= 50) return
|
|
3607
|
-
return
|
|
4535
|
+
if (score >= 75) return pc15.red(String(score));
|
|
4536
|
+
if (score >= 50) return pc15.yellow(String(score));
|
|
4537
|
+
return pc15.green(String(score));
|
|
3608
4538
|
}
|
|
3609
4539
|
function formatEvidence(evidence) {
|
|
3610
4540
|
switch (evidence.kind) {
|
|
@@ -3626,7 +4556,7 @@ function truncate2(value, max) {
|
|
|
3626
4556
|
|
|
3627
4557
|
// src/cli/commands/rules.ts
|
|
3628
4558
|
import { relative as relative5 } from "path";
|
|
3629
|
-
import
|
|
4559
|
+
import pc16 from "picocolors";
|
|
3630
4560
|
async function resolveMode(client, value) {
|
|
3631
4561
|
if (value) {
|
|
3632
4562
|
const mode = parseMode(value);
|
|
@@ -3666,7 +4596,7 @@ function registerRulesCommands(program, runSafely2) {
|
|
|
3666
4596
|
`${actionLabel(result.action)} ${clientDisplayName(client)} workflow in ${where}`
|
|
3667
4597
|
);
|
|
3668
4598
|
if (result.action !== "unchanged") {
|
|
3669
|
-
console.log(
|
|
4599
|
+
console.log(pc16.dim(" managed block between codebuddy:workflow markers; safe to re-run"));
|
|
3670
4600
|
}
|
|
3671
4601
|
});
|
|
3672
4602
|
});
|
|
@@ -3674,20 +4604,20 @@ function registerRulesCommands(program, runSafely2) {
|
|
|
3674
4604
|
function actionLabel(action) {
|
|
3675
4605
|
switch (action) {
|
|
3676
4606
|
case "created":
|
|
3677
|
-
return
|
|
4607
|
+
return pc16.green("created");
|
|
3678
4608
|
case "added":
|
|
3679
|
-
return
|
|
4609
|
+
return pc16.green("added");
|
|
3680
4610
|
case "updated":
|
|
3681
|
-
return
|
|
4611
|
+
return pc16.cyan("updated");
|
|
3682
4612
|
case "removed":
|
|
3683
|
-
return
|
|
4613
|
+
return pc16.yellow("removed");
|
|
3684
4614
|
default:
|
|
3685
|
-
return
|
|
4615
|
+
return pc16.dim("unchanged");
|
|
3686
4616
|
}
|
|
3687
4617
|
}
|
|
3688
4618
|
|
|
3689
4619
|
// src/cli/commands/suggest.ts
|
|
3690
|
-
import
|
|
4620
|
+
import pc17 from "picocolors";
|
|
3691
4621
|
function parsePaths4(value) {
|
|
3692
4622
|
return value.split(",").map((path) => path.trim()).filter(Boolean);
|
|
3693
4623
|
}
|
|
@@ -3709,7 +4639,7 @@ function registerSuggestCommand(program, runSafely2) {
|
|
|
3709
4639
|
}
|
|
3710
4640
|
printSuggestions(result.suggestions);
|
|
3711
4641
|
console.log(
|
|
3712
|
-
|
|
4642
|
+
pc17.dim(
|
|
3713
4643
|
`
|
|
3714
4644
|
${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} path(s)`
|
|
3715
4645
|
)
|
|
@@ -3720,40 +4650,40 @@ ${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} pa
|
|
|
3720
4650
|
}
|
|
3721
4651
|
function printSuggestions(suggestions) {
|
|
3722
4652
|
if (suggestions.length === 0) {
|
|
3723
|
-
console.log(
|
|
4653
|
+
console.log(pc17.dim("no suggestions \u2014 nothing high-signal to report"));
|
|
3724
4654
|
return;
|
|
3725
4655
|
}
|
|
3726
4656
|
for (const suggestion of suggestions) {
|
|
3727
|
-
console.log(`${severityBadge2(suggestion.severity)} ${
|
|
4657
|
+
console.log(`${severityBadge2(suggestion.severity)} ${pc17.bold(suggestion.title)}`);
|
|
3728
4658
|
console.log(` ${suggestion.detail}`);
|
|
3729
4659
|
for (const evidence of suggestion.evidence) {
|
|
3730
|
-
console.log(
|
|
4660
|
+
console.log(pc17.dim(` \u21B3 ${evidence.kind}: ${evidence.detail}`));
|
|
3731
4661
|
}
|
|
3732
|
-
if (suggestion.commandHint) console.log(
|
|
4662
|
+
if (suggestion.commandHint) console.log(pc17.dim(` \u2192 ${suggestion.commandHint}`));
|
|
3733
4663
|
}
|
|
3734
4664
|
}
|
|
3735
4665
|
function severityBadge2(severity) {
|
|
3736
4666
|
switch (severity) {
|
|
3737
4667
|
case "high":
|
|
3738
|
-
return
|
|
4668
|
+
return pc17.red("[high]");
|
|
3739
4669
|
case "medium":
|
|
3740
|
-
return
|
|
4670
|
+
return pc17.yellow("[med ]");
|
|
3741
4671
|
case "low":
|
|
3742
|
-
return
|
|
4672
|
+
return pc17.cyan("[low ]");
|
|
3743
4673
|
default:
|
|
3744
|
-
return
|
|
4674
|
+
return pc17.dim("[info]");
|
|
3745
4675
|
}
|
|
3746
4676
|
}
|
|
3747
4677
|
|
|
3748
4678
|
// src/cli/commands/symbols.ts
|
|
3749
4679
|
import { readFile as readFile13 } from "fs/promises";
|
|
3750
4680
|
import { resolve as resolve13 } from "path";
|
|
3751
|
-
import
|
|
4681
|
+
import pc18 from "picocolors";
|
|
3752
4682
|
function registerSymbolsCommand(program, runSafely2) {
|
|
3753
4683
|
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
4684
|
await runSafely2(async () => {
|
|
3755
4685
|
if (!supportsSymbols(path)) {
|
|
3756
|
-
console.log(
|
|
4686
|
+
console.log(pc18.dim(`no symbol extraction for ${path} (unsupported language)`));
|
|
3757
4687
|
return;
|
|
3758
4688
|
}
|
|
3759
4689
|
const content = await readFile13(resolve13(path), "utf8");
|
|
@@ -3763,30 +4693,30 @@ function registerSymbolsCommand(program, runSafely2) {
|
|
|
3763
4693
|
return;
|
|
3764
4694
|
}
|
|
3765
4695
|
if (table.length === 0) {
|
|
3766
|
-
console.log(
|
|
4696
|
+
console.log(pc18.dim("no top-level symbols found"));
|
|
3767
4697
|
return;
|
|
3768
4698
|
}
|
|
3769
4699
|
for (const symbol of table) {
|
|
3770
|
-
const badge = symbol.exported ?
|
|
4700
|
+
const badge = symbol.exported ? pc18.green("export") : pc18.dim("local ");
|
|
3771
4701
|
console.log(
|
|
3772
|
-
`${badge} ${
|
|
4702
|
+
`${badge} ${pc18.cyan(symbol.kind.padEnd(9))} ${pc18.bold(symbol.name)} ${pc18.dim(`L${symbol.startLine}-${symbol.endLine}`)}`
|
|
3773
4703
|
);
|
|
3774
4704
|
}
|
|
3775
|
-
console.log(
|
|
4705
|
+
console.log(pc18.dim(`
|
|
3776
4706
|
${table.length} symbol(s)`));
|
|
3777
4707
|
});
|
|
3778
4708
|
});
|
|
3779
4709
|
}
|
|
3780
4710
|
|
|
3781
4711
|
// 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
|
|
3784
|
-
import
|
|
4712
|
+
import { basename as basename2 } from "path";
|
|
4713
|
+
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";
|
|
4714
|
+
import pc19 from "picocolors";
|
|
3785
4715
|
|
|
3786
4716
|
// src/core/global-config.ts
|
|
3787
4717
|
import { chmod, mkdir as mkdir8, readFile as readFile14, writeFile as writeFile3 } from "fs/promises";
|
|
3788
4718
|
import { homedir as homedir3 } from "os";
|
|
3789
|
-
import { dirname as dirname12, join as
|
|
4719
|
+
import { dirname as dirname12, join as join16 } from "path";
|
|
3790
4720
|
import { z as z2 } from "zod";
|
|
3791
4721
|
var GLOBAL_DIR = ".codebuddy";
|
|
3792
4722
|
var GLOBAL_FILE = "global.json";
|
|
@@ -3799,7 +4729,7 @@ var globalConfigSchema = z2.object({
|
|
|
3799
4729
|
installPath: z2.string().optional()
|
|
3800
4730
|
});
|
|
3801
4731
|
function globalConfigPath(home = homedir3()) {
|
|
3802
|
-
return
|
|
4732
|
+
return join16(home, GLOBAL_DIR, GLOBAL_FILE);
|
|
3803
4733
|
}
|
|
3804
4734
|
async function readGlobalConfig(home = homedir3()) {
|
|
3805
4735
|
const path = globalConfigPath(home);
|
|
@@ -3827,10 +4757,10 @@ async function mergeGlobalConfig(patch, home = homedir3()) {
|
|
|
3827
4757
|
}
|
|
3828
4758
|
|
|
3829
4759
|
// src/cli/commands/docker-autostart.ts
|
|
3830
|
-
import { spawn as
|
|
4760
|
+
import { spawn as spawn7 } from "child_process";
|
|
3831
4761
|
async function isDockerDaemonRunning() {
|
|
3832
4762
|
return new Promise((resolve14) => {
|
|
3833
|
-
const child =
|
|
4763
|
+
const child = spawn7("docker", ["info"], { stdio: "ignore" });
|
|
3834
4764
|
child.on("error", () => resolve14(false));
|
|
3835
4765
|
child.on("close", (code) => resolve14(code === 0));
|
|
3836
4766
|
});
|
|
@@ -3838,7 +4768,7 @@ async function isDockerDaemonRunning() {
|
|
|
3838
4768
|
async function tryStartDockerDesktop() {
|
|
3839
4769
|
if (process.platform !== "darwin") return { started: false };
|
|
3840
4770
|
return new Promise((resolve14) => {
|
|
3841
|
-
const child =
|
|
4771
|
+
const child = spawn7("open", ["-ga", "Docker"], { stdio: "ignore" });
|
|
3842
4772
|
child.on("error", () => resolve14({ started: false }));
|
|
3843
4773
|
child.on("close", (code) => resolve14({ started: code === 0 }));
|
|
3844
4774
|
});
|
|
@@ -3879,7 +4809,7 @@ async function runUseCommand(options = {}) {
|
|
|
3879
4809
|
const sigintHandler = () => bail2();
|
|
3880
4810
|
process.on("SIGINT", sigintHandler);
|
|
3881
4811
|
try {
|
|
3882
|
-
const folderName =
|
|
4812
|
+
const folderName = basename2(process.cwd());
|
|
3883
4813
|
const namespace = sanitizeNamespace(options.namespace ?? folderName);
|
|
3884
4814
|
await initProjectScaffold();
|
|
3885
4815
|
const graphifyEnabled = options.graphify ?? unwrap2(
|
|
@@ -3889,26 +4819,61 @@ async function runUseCommand(options = {}) {
|
|
|
3889
4819
|
})
|
|
3890
4820
|
);
|
|
3891
4821
|
note2(
|
|
3892
|
-
`Project folder: ${
|
|
3893
|
-
Namespace: ${
|
|
4822
|
+
`Project folder: ${pc19.bold(process.cwd())}
|
|
4823
|
+
Namespace: ${pc19.bold(namespace)}`,
|
|
3894
4824
|
"codebuddy use"
|
|
3895
4825
|
);
|
|
3896
4826
|
const stored = await readGlobalConfig();
|
|
4827
|
+
const projectConfig = await readConfigFile();
|
|
4828
|
+
let embeddings = projectConfig.provider?.type;
|
|
4829
|
+
if (embeddings) {
|
|
4830
|
+
log2.info(`Embeddings: ${embeddings} (change with ${pc19.cyan("codebuddy models use")}).`);
|
|
4831
|
+
} else {
|
|
4832
|
+
embeddings = unwrap2(
|
|
4833
|
+
await select({
|
|
4834
|
+
message: "Where should memory embeddings run?",
|
|
4835
|
+
initialValue: "local",
|
|
4836
|
+
options: [
|
|
4837
|
+
{
|
|
4838
|
+
value: "local",
|
|
4839
|
+
label: "On this machine",
|
|
4840
|
+
hint: "recommended \xB7 no key \xB7 ~90 MB once"
|
|
4841
|
+
},
|
|
4842
|
+
{
|
|
4843
|
+
value: "openrouter",
|
|
4844
|
+
label: "OpenRouter",
|
|
4845
|
+
hint: "OPENROUTER_API_KEY \xB7 free models listed"
|
|
4846
|
+
},
|
|
4847
|
+
{
|
|
4848
|
+
value: "huggingface",
|
|
4849
|
+
label: "Hugging Face",
|
|
4850
|
+
hint: "HF_TOKEN \xB7 small free monthly credit"
|
|
4851
|
+
}
|
|
4852
|
+
]
|
|
4853
|
+
})
|
|
4854
|
+
);
|
|
4855
|
+
if (embeddings === "openrouter" && !process.env.OPENROUTER_API_KEY) {
|
|
4856
|
+
log2.warn(
|
|
4857
|
+
"Export OPENROUTER_API_KEY before your agent starts; keys are never stored in config."
|
|
4858
|
+
);
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
3897
4861
|
let hfToken;
|
|
3898
|
-
if (
|
|
4862
|
+
if (embeddings !== "huggingface") {
|
|
4863
|
+
hfToken = process.env.HF_TOKEN ?? stored.hfToken;
|
|
4864
|
+
} else if (process.env.HF_TOKEN) {
|
|
3899
4865
|
hfToken = process.env.HF_TOKEN;
|
|
3900
4866
|
log2.info("Using HF_TOKEN from environment.");
|
|
3901
4867
|
} else if (stored.hfToken) {
|
|
3902
4868
|
hfToken = stored.hfToken;
|
|
3903
|
-
log2.info(`Reusing saved Hugging Face token (${
|
|
4869
|
+
log2.info(`Reusing saved Hugging Face token (${pc19.dim("~/.codebuddy/global.json")}).`);
|
|
3904
4870
|
} else {
|
|
3905
4871
|
hfToken = unwrap2(
|
|
3906
4872
|
await password2({
|
|
3907
|
-
message: "Hugging Face token (
|
|
4873
|
+
message: "Hugging Face token (for hosted embeddings)"
|
|
3908
4874
|
})
|
|
3909
4875
|
);
|
|
3910
|
-
if (!hfToken)
|
|
3911
|
-
log2.info("No Hugging Face token supplied; local file context remains available.");
|
|
4876
|
+
if (!hfToken) log2.warn("No token: hosted embeddings will fail until HF_TOKEN is set.");
|
|
3912
4877
|
}
|
|
3913
4878
|
const initialUrl = options.postgresUrl ?? process.env.DATABASE_URL ?? stored.databaseUrl ?? DEFAULT_DB_URL;
|
|
3914
4879
|
const databaseUrl = unwrap2(
|
|
@@ -3918,7 +4883,7 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
3918
4883
|
validate: (value) => value && value.length > 0 ? void 0 : "Postgres URL is required."
|
|
3919
4884
|
})
|
|
3920
4885
|
);
|
|
3921
|
-
const dbSpinner =
|
|
4886
|
+
const dbSpinner = spinner3();
|
|
3922
4887
|
dbSpinner.start("Checking Postgres");
|
|
3923
4888
|
let connected = await pingOnce2(databaseUrl);
|
|
3924
4889
|
if (connected) dbSpinner.stop("Local Postgres reachable.");
|
|
@@ -3933,7 +4898,7 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
3933
4898
|
));
|
|
3934
4899
|
if (shouldUseDocker) {
|
|
3935
4900
|
if (!await isDockerDaemonRunning()) {
|
|
3936
|
-
const dockerSpinner =
|
|
4901
|
+
const dockerSpinner = spinner3();
|
|
3937
4902
|
dockerSpinner.start("Starting Docker Desktop");
|
|
3938
4903
|
await tryStartDockerDesktop();
|
|
3939
4904
|
const ready = await waitForDockerDaemon(6e4);
|
|
@@ -3943,7 +4908,7 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
3943
4908
|
}
|
|
3944
4909
|
dockerSpinner.stop("Docker daemon is up.");
|
|
3945
4910
|
}
|
|
3946
|
-
const composeSpinner =
|
|
4911
|
+
const composeSpinner = spinner3();
|
|
3947
4912
|
composeSpinner.start("docker compose up -d");
|
|
3948
4913
|
try {
|
|
3949
4914
|
const result = await postgresUp();
|
|
@@ -3970,7 +4935,7 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
3970
4935
|
bail2();
|
|
3971
4936
|
}
|
|
3972
4937
|
if (!connected) bail2();
|
|
3973
|
-
const migrateSpinner =
|
|
4938
|
+
const migrateSpinner = spinner3();
|
|
3974
4939
|
migrateSpinner.start("Enabling pgvector + applying migrations");
|
|
3975
4940
|
{
|
|
3976
4941
|
const client = createDatabaseClient({ postgresUrl: databaseUrl });
|
|
@@ -3991,7 +4956,7 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
3991
4956
|
installPath: process.argv[1] ?? void 0
|
|
3992
4957
|
});
|
|
3993
4958
|
const existingConfig = await readConfigFile();
|
|
3994
|
-
const indexSpinner =
|
|
4959
|
+
const indexSpinner = spinner3();
|
|
3995
4960
|
indexSpinner.start("Indexing the existing repository");
|
|
3996
4961
|
try {
|
|
3997
4962
|
const result = await indexRepository({ repositoryRoot: process.cwd() });
|
|
@@ -4005,7 +4970,11 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
4005
4970
|
...existingConfig,
|
|
4006
4971
|
postgresUrl: databaseUrl,
|
|
4007
4972
|
namespace,
|
|
4008
|
-
provider: {
|
|
4973
|
+
provider: {
|
|
4974
|
+
...existingConfig.provider,
|
|
4975
|
+
type: embeddings,
|
|
4976
|
+
...hfToken ? { apiKey: hfToken } : {}
|
|
4977
|
+
},
|
|
4009
4978
|
tokenBudget: existingConfig.tokenBudget ?? 4e3,
|
|
4010
4979
|
graphify: { enabled: graphifyConfigured, graphPath: "graphify-out/graph.json" }
|
|
4011
4980
|
});
|
|
@@ -4023,11 +4992,11 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
4023
4992
|
const install = await installClaudeEntry({
|
|
4024
4993
|
namespace,
|
|
4025
4994
|
projectRoot: process.cwd(),
|
|
4026
|
-
hfToken,
|
|
4995
|
+
...hfToken ? { hfToken } : {},
|
|
4027
4996
|
databaseUrl
|
|
4028
4997
|
});
|
|
4029
4998
|
log2.success(
|
|
4030
|
-
`Registered ${
|
|
4999
|
+
`Registered ${pc19.bold(install.serverKey)} in Claude Desktop. Restart with \u2318Q to activate.`
|
|
4031
5000
|
);
|
|
4032
5001
|
} catch (error) {
|
|
4033
5002
|
log2.warn(`Claude Desktop registration skipped: ${error.message}`);
|
|
@@ -4038,11 +5007,11 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
4038
5007
|
const install = await installCodexEntry({
|
|
4039
5008
|
namespace,
|
|
4040
5009
|
projectRoot: process.cwd(),
|
|
4041
|
-
hfToken,
|
|
5010
|
+
...hfToken ? { hfToken } : {},
|
|
4042
5011
|
databaseUrl
|
|
4043
5012
|
});
|
|
4044
5013
|
log2.success(
|
|
4045
|
-
`Registered ${
|
|
5014
|
+
`Registered ${pc19.bold(install.serverKey)} in Codex. Restart Codex to activate.`
|
|
4046
5015
|
);
|
|
4047
5016
|
} catch (error) {
|
|
4048
5017
|
log2.warn(`Codex registration skipped: ${error.message}`);
|
|
@@ -4050,24 +5019,24 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
4050
5019
|
}
|
|
4051
5020
|
note2(
|
|
4052
5021
|
[
|
|
4053
|
-
`${
|
|
4054
|
-
`${
|
|
4055
|
-
`${
|
|
4056
|
-
`${
|
|
5022
|
+
`${pc19.green("\u2713")} CodeBuddy is ready for ${pc19.bold(folderName)}.`,
|
|
5023
|
+
`${pc19.green("\u2713")} Scaffolded ${pc19.cyan(".codebuddy/policies.yaml")} for project-specific guardrails.`,
|
|
5024
|
+
`${pc19.green("\u2713")} ${graphifyMessage}`,
|
|
5025
|
+
`${pc19.green("\u2713")} Added agent workflow instructions to ${pc19.cyan("CLAUDE.md")} and ${pc19.cyan("AGENTS.md")}.`,
|
|
4057
5026
|
``,
|
|
4058
5027
|
`Next:`,
|
|
4059
5028
|
` 1. Restart Claude Desktop and Codex.`,
|
|
4060
|
-
` 2. Ask Claude or Codex to use the ${
|
|
5029
|
+
` 2. Ask Claude or Codex to use the ${pc19.cyan("context_pack")} tool for each coding task.`,
|
|
4061
5030
|
` 3. Indexing, Graphify, Markdown memory import, and embeddings now update automatically while the MCP server is running.`,
|
|
4062
|
-
` 4. Run ${
|
|
5031
|
+
` 4. Run ${pc19.cyan("codebuddy doctor")} or ${pc19.cyan("codebuddy db doctor")} if you want a full health check.`,
|
|
4063
5032
|
``,
|
|
4064
5033
|
`Manage:`,
|
|
4065
|
-
` codebuddy claude list ${
|
|
4066
|
-
` codebuddy codex list ${
|
|
4067
|
-
` codebuddy db test ${
|
|
5034
|
+
` codebuddy claude list ${pc19.dim("# see every project wired up")}`,
|
|
5035
|
+
` codebuddy codex list ${pc19.dim("# see Codex entries")}`,
|
|
5036
|
+
` codebuddy db test ${pc19.dim("# quick Postgres connectivity check")}`,
|
|
4068
5037
|
` codebuddy claude remove codebuddy-${namespace}`,
|
|
4069
5038
|
` codebuddy codex remove codebuddy-${namespace}`,
|
|
4070
|
-
` codebuddy postgres down ${
|
|
5039
|
+
` codebuddy postgres down ${pc19.dim("# stop the DB (data preserved)")}`
|
|
4071
5040
|
].join("\n"),
|
|
4072
5041
|
"Done"
|
|
4073
5042
|
);
|
|
@@ -4078,7 +5047,7 @@ Namespace: ${pc17.bold(namespace)}`,
|
|
|
4078
5047
|
|
|
4079
5048
|
// src/cli/commands/worklog.ts
|
|
4080
5049
|
import { relative as relative6 } from "path";
|
|
4081
|
-
import
|
|
5050
|
+
import pc20 from "picocolors";
|
|
4082
5051
|
function registerWorklogCommands(program, runSafely2) {
|
|
4083
5052
|
const worklog = program.command("worklog").description("Inspect the cross-chat worklog of completed turns.");
|
|
4084
5053
|
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 +5058,7 @@ function registerWorklogCommands(program, runSafely2) {
|
|
|
4089
5058
|
return;
|
|
4090
5059
|
}
|
|
4091
5060
|
if (entries.length === 0) {
|
|
4092
|
-
console.log(
|
|
5061
|
+
console.log(pc20.dim("no worklog entries yet"));
|
|
4093
5062
|
return;
|
|
4094
5063
|
}
|
|
4095
5064
|
for (const entry of entries) console.log(renderRow2(entry));
|
|
@@ -4103,36 +5072,36 @@ function registerWorklogCommands(program, runSafely2) {
|
|
|
4103
5072
|
worklog.command("latest").description("Show the most recent worklog entry.").action(async () => {
|
|
4104
5073
|
await runSafely2(async () => {
|
|
4105
5074
|
const [entry] = await new WorklogStore().list();
|
|
4106
|
-
console.log(entry ? renderEntry(entry) :
|
|
5075
|
+
console.log(entry ? renderEntry(entry) : pc20.dim("no worklog entries yet"));
|
|
4107
5076
|
});
|
|
4108
5077
|
});
|
|
4109
5078
|
}
|
|
4110
5079
|
function renderRow2(entry) {
|
|
4111
5080
|
const summary = entry.summary.replace(/\s+/g, " ");
|
|
4112
5081
|
const short = summary.length <= 70 ? summary : `${summary.slice(0, 69)}\u2026`;
|
|
4113
|
-
return `${
|
|
5082
|
+
return `${pc20.dim(entry.createdAt.slice(0, 16))} ${pc20.dim(entry.id)} ${short} ${pc20.dim(`(${entry.files.length} file(s))`)}`;
|
|
4114
5083
|
}
|
|
4115
5084
|
function renderEntry(entry) {
|
|
4116
|
-
const lines = [`${
|
|
4117
|
-
if (entry.sourceSessionId) lines.push(`${
|
|
4118
|
-
if (entry.sourcePlanId) lines.push(`${
|
|
4119
|
-
if (entry.commitSha) lines.push(`${
|
|
4120
|
-
if (entry.files.length > 0) lines.push(`${
|
|
5085
|
+
const lines = [`${pc20.bold(entry.id)} ${pc20.dim(entry.createdAt)} ${pc20.dim(entry.namespace)}`];
|
|
5086
|
+
if (entry.sourceSessionId) lines.push(`${pc20.bold("session:")} ${entry.sourceSessionId}`);
|
|
5087
|
+
if (entry.sourcePlanId) lines.push(`${pc20.bold("plan:")} ${entry.sourcePlanId}`);
|
|
5088
|
+
if (entry.commitSha) lines.push(`${pc20.bold("commit:")} ${entry.commitSha}`);
|
|
5089
|
+
if (entry.files.length > 0) lines.push(`${pc20.bold("files:")} ${entry.files.join(", ")}`);
|
|
4121
5090
|
if (entry.verification.length > 0) {
|
|
4122
|
-
lines.push(`${
|
|
5091
|
+
lines.push(`${pc20.bold("verification:")}`, ...entry.verification.map((line) => ` - ${line}`));
|
|
4123
5092
|
}
|
|
4124
5093
|
if (entry.savedMemory.length > 0) {
|
|
4125
|
-
lines.push(`${
|
|
5094
|
+
lines.push(`${pc20.bold("saved memory:")} ${entry.savedMemory.join(", ")}`);
|
|
4126
5095
|
}
|
|
4127
5096
|
if (entry.queuedForReview.length > 0) {
|
|
4128
|
-
lines.push(`${
|
|
5097
|
+
lines.push(`${pc20.bold("queued for review:")} ${entry.queuedForReview.join(", ")}`);
|
|
4129
5098
|
}
|
|
4130
|
-
lines.push("", entry.summary, "",
|
|
5099
|
+
lines.push("", entry.summary, "", pc20.dim(relative6(process.cwd(), entry.path)));
|
|
4131
5100
|
return lines.join("\n");
|
|
4132
5101
|
}
|
|
4133
5102
|
|
|
4134
5103
|
// src/cli/commands/zvec.ts
|
|
4135
|
-
import
|
|
5104
|
+
import pc21 from "picocolors";
|
|
4136
5105
|
function registerZvecCommands(program, runSafely2) {
|
|
4137
5106
|
const zvec = program.command("zvec").description("Optional zvec-grep semantic file discovery for the context tools.");
|
|
4138
5107
|
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 +5111,22 @@ function registerZvecCommands(program, runSafely2) {
|
|
|
4142
5111
|
console.log(JSON.stringify(report, null, 2));
|
|
4143
5112
|
return;
|
|
4144
5113
|
}
|
|
4145
|
-
const mark = (
|
|
5114
|
+
const mark = (ok2) => ok2 ? pc21.green("yes") : pc21.yellow("no");
|
|
4146
5115
|
console.log(`enabled: ${mark(report.enabled)}`);
|
|
4147
5116
|
console.log(`binary: ${report.binary}`);
|
|
4148
5117
|
console.log(
|
|
4149
|
-
`version: ${report.version ??
|
|
5118
|
+
`version: ${report.version ?? pc21.yellow("not found")}${report.version && !report.supported ? pc21.yellow(" (unsupported; need 0.2.x)") : ""}`
|
|
4150
5119
|
);
|
|
4151
5120
|
console.log(`index ready: ${mark(report.indexReady)}`);
|
|
4152
5121
|
console.log(
|
|
4153
|
-
`zg server: ${report.server} ${
|
|
5122
|
+
`zg server: ${report.server} ${pc21.dim("(running = faster queries; optional)")}`
|
|
4154
5123
|
);
|
|
4155
5124
|
console.log(`status: ${report.reason}`);
|
|
4156
|
-
if (!report.version) console.log(
|
|
5125
|
+
if (!report.version) console.log(pc21.dim("install: npm install -g @zvec/zvec-grep"));
|
|
4157
5126
|
else if (report.supported && !report.indexReady)
|
|
4158
|
-
console.log(
|
|
5127
|
+
console.log(pc21.dim("next: codebuddy zvec index"));
|
|
4159
5128
|
else if (report.indexReady && !report.enabled)
|
|
4160
|
-
console.log(
|
|
5129
|
+
console.log(pc21.dim("next: codebuddy zvec enable"));
|
|
4161
5130
|
});
|
|
4162
5131
|
});
|
|
4163
5132
|
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 +5137,7 @@ function registerZvecCommands(program, runSafely2) {
|
|
|
4168
5137
|
});
|
|
4169
5138
|
if (code !== 0) throw new Error(`zg index exited with code ${code}`);
|
|
4170
5139
|
console.log(
|
|
4171
|
-
|
|
5140
|
+
pc21.dim("tip: add .zvec-grep/ to .gitignore; enable with `codebuddy zvec enable`")
|
|
4172
5141
|
);
|
|
4173
5142
|
});
|
|
4174
5143
|
});
|
|
@@ -4176,14 +5145,14 @@ function registerZvecCommands(program, runSafely2) {
|
|
|
4176
5145
|
await runSafely2(async () => {
|
|
4177
5146
|
await setZvecEnabled(true);
|
|
4178
5147
|
console.log(
|
|
4179
|
-
`${
|
|
5148
|
+
`${pc21.green("enabled")} zvec-grep retrieval (falls back to keywords if unavailable)`
|
|
4180
5149
|
);
|
|
4181
5150
|
});
|
|
4182
5151
|
});
|
|
4183
5152
|
zvec.command("disable").description("Stop using zvec-grep; keyword ranking only.").action(async () => {
|
|
4184
5153
|
await runSafely2(async () => {
|
|
4185
5154
|
await setZvecEnabled(false);
|
|
4186
|
-
console.log(`${
|
|
5155
|
+
console.log(`${pc21.yellow("disabled")} zvec-grep retrieval`);
|
|
4187
5156
|
});
|
|
4188
5157
|
});
|
|
4189
5158
|
}
|
|
@@ -4192,7 +5161,7 @@ function registerZvecCommands(program, runSafely2) {
|
|
|
4192
5161
|
function createCli() {
|
|
4193
5162
|
const program = new Command();
|
|
4194
5163
|
program.name("codebuddy").description(
|
|
4195
|
-
"
|
|
5164
|
+
"Project memory and context for AI coding agents: recalls the right facts, files and risks on every prompt."
|
|
4196
5165
|
).version(VERSION);
|
|
4197
5166
|
program.action(async () => {
|
|
4198
5167
|
await runSafely(async () => {
|
|
@@ -4200,7 +5169,7 @@ function createCli() {
|
|
|
4200
5169
|
});
|
|
4201
5170
|
});
|
|
4202
5171
|
program.command("use").description(
|
|
4203
|
-
"One-command setup for
|
|
5172
|
+
"One-command setup for this folder: PostgreSQL (starts Docker if needed), migrations, embeddings, and agent wiring."
|
|
4204
5173
|
).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
5174
|
"--docker",
|
|
4206
5175
|
"Start CodeBuddy's bundled Postgres container when the default DB is unavailable."
|
|
@@ -4444,9 +5413,9 @@ function createCli() {
|
|
|
4444
5413
|
});
|
|
4445
5414
|
program.command("export").argument("<namespace>").description("Export facts from a namespace as JSON.").action(async (namespace) => {
|
|
4446
5415
|
await withRuntime(async (runtime) => {
|
|
4447
|
-
const
|
|
4448
|
-
if (!
|
|
4449
|
-
const facts = await runtime.repository.listFacts({ namespaceId:
|
|
5416
|
+
const row2 = await runtime.repository.getNamespaceByName(namespace);
|
|
5417
|
+
if (!row2) throw new Error(`Namespace ${namespace} does not exist.`);
|
|
5418
|
+
const facts = await runtime.repository.listFacts({ namespaceId: row2.id, limit: 1e4 });
|
|
4450
5419
|
console.log(JSON.stringify({ namespace, facts }, null, 2));
|
|
4451
5420
|
});
|
|
4452
5421
|
});
|
|
@@ -4486,14 +5455,64 @@ function createCli() {
|
|
|
4486
5455
|
registerSuggestCommand(program, runSafely);
|
|
4487
5456
|
registerRulesCommands(program, runSafely);
|
|
4488
5457
|
registerHooksCommands(program, runSafely);
|
|
5458
|
+
registerModelsCommands(program, runSafely);
|
|
5459
|
+
registerDashboardCommand(program, runSafely);
|
|
4489
5460
|
registerClientsCommands(program, runSafely);
|
|
4490
5461
|
registerGitHubCommands(program, runSafely);
|
|
4491
5462
|
registerSymbolsCommand(program, runSafely);
|
|
4492
5463
|
program.action(async () => {
|
|
4493
5464
|
await runSafely(() => runUseCommand());
|
|
4494
5465
|
});
|
|
5466
|
+
groupHelp(program);
|
|
4495
5467
|
return program;
|
|
4496
5468
|
}
|
|
5469
|
+
var HELP_GROUPS = [
|
|
5470
|
+
["Get started:", ["use", "init", "doctor", "hooks", "clients", "models", "dashboard"]],
|
|
5471
|
+
[
|
|
5472
|
+
"Daily work:",
|
|
5473
|
+
["context", "plan", "change", "risk", "suggest", "memory", "worklog", "map", "symbols"]
|
|
5474
|
+
],
|
|
5475
|
+
["Indexing and search:", ["index", "watch", "reindex", "zvec", "graphify", "llm"]],
|
|
5476
|
+
[
|
|
5477
|
+
"Database:",
|
|
5478
|
+
[
|
|
5479
|
+
"postgres",
|
|
5480
|
+
"db",
|
|
5481
|
+
"migrate",
|
|
5482
|
+
"stats",
|
|
5483
|
+
"inspect",
|
|
5484
|
+
"namespaces",
|
|
5485
|
+
"list-facts",
|
|
5486
|
+
"export",
|
|
5487
|
+
"prune"
|
|
5488
|
+
]
|
|
5489
|
+
],
|
|
5490
|
+
["Integrations:", ["serve", "claude", "codex", "github", "rules", "learn", "eval"]]
|
|
5491
|
+
];
|
|
5492
|
+
function groupHelp(program) {
|
|
5493
|
+
const groupOf = new Map(
|
|
5494
|
+
HELP_GROUPS.flatMap(([group, names]) => names.map((name) => [name, group]))
|
|
5495
|
+
);
|
|
5496
|
+
for (const command of program.commands) {
|
|
5497
|
+
command.helpGroup(groupOf.get(command.name()) ?? "Other:");
|
|
5498
|
+
}
|
|
5499
|
+
const position = new Map(
|
|
5500
|
+
HELP_GROUPS.flatMap(([, names]) => names).map((name, index) => [name, index])
|
|
5501
|
+
);
|
|
5502
|
+
program.commands.sort(
|
|
5503
|
+
(a, b) => (position.get(a.name()) ?? 999) - (position.get(b.name()) ?? 999)
|
|
5504
|
+
);
|
|
5505
|
+
program.addHelpText(
|
|
5506
|
+
"after",
|
|
5507
|
+
`
|
|
5508
|
+
Quick start:
|
|
5509
|
+
codebuddy set up this project (PostgreSQL, hooks, agent)
|
|
5510
|
+
codebuddy models check the embedding model and memory index
|
|
5511
|
+
codebuddy dashboard live page of recalls, tool calls and tokens
|
|
5512
|
+
codebuddy context preview "fix the login redirect"
|
|
5513
|
+
see exactly what an agent would be given`
|
|
5514
|
+
);
|
|
5515
|
+
}
|
|
4497
5516
|
async function withRuntime(fn) {
|
|
4498
5517
|
await runSafely(async () => {
|
|
4499
5518
|
const runtime = await createRuntime(await loadRuntimeConfig());
|
|
@@ -4508,7 +5527,7 @@ async function runSafely(fn) {
|
|
|
4508
5527
|
try {
|
|
4509
5528
|
await fn();
|
|
4510
5529
|
} catch (error) {
|
|
4511
|
-
console.error(
|
|
5530
|
+
console.error(pc22.red(String(redactSecrets(error.message))));
|
|
4512
5531
|
process.exitCode = 1;
|
|
4513
5532
|
}
|
|
4514
5533
|
}
|
|
@@ -4522,19 +5541,19 @@ function parseDuration(value) {
|
|
|
4522
5541
|
return amount * 60 * 1e3;
|
|
4523
5542
|
}
|
|
4524
5543
|
function printDoctor(report) {
|
|
4525
|
-
const
|
|
4526
|
-
const bad =
|
|
4527
|
-
const
|
|
4528
|
-
console.log(`scaffold root: ${report.scaffold.codebuddyDir.ok ?
|
|
5544
|
+
const ok2 = pc22.green("ok");
|
|
5545
|
+
const bad = pc22.red("fail");
|
|
5546
|
+
const warn2 = pc22.yellow("warn");
|
|
5547
|
+
console.log(`scaffold root: ${report.scaffold.codebuddyDir.ok ? ok2 : bad}`);
|
|
4529
5548
|
if (report.scaffold.codebuddyDir.warning)
|
|
4530
5549
|
console.log(` ${report.scaffold.codebuddyDir.warning}`);
|
|
4531
|
-
console.log(`policy scaffold: ${report.scaffold.policies.ok ?
|
|
5550
|
+
console.log(`policy scaffold: ${report.scaffold.policies.ok ? ok2 : bad}`);
|
|
4532
5551
|
if (report.scaffold.policies.warning) console.log(` ${report.scaffold.policies.warning}`);
|
|
4533
|
-
console.log(`DB connectivity: ${report.db.ok ?
|
|
5552
|
+
console.log(`DB connectivity: ${report.db.ok ? ok2 : bad}`);
|
|
4534
5553
|
if (report.db.error) console.log(` ${report.db.error}`);
|
|
4535
|
-
console.log(`pgvector: ${report.pgvector.ok ?
|
|
5554
|
+
console.log(`pgvector: ${report.pgvector.ok ? ok2 : bad}`);
|
|
4536
5555
|
if (report.pgvector.error) console.log(` ${report.pgvector.error}`);
|
|
4537
|
-
console.log(`namespace: ${report.namespace.ok ?
|
|
5556
|
+
console.log(`namespace: ${report.namespace.ok ? ok2 : warn2}`);
|
|
4538
5557
|
console.log(
|
|
4539
5558
|
` runtime=${report.namespace.runtime ?? "unset"} default=${report.namespace.expectedDefault}`
|
|
4540
5559
|
);
|
|
@@ -4547,9 +5566,9 @@ function printDoctor(report) {
|
|
|
4547
5566
|
);
|
|
4548
5567
|
console.log(`estimated daily cap remaining: ${report.usage.estimatedDailyCapRemaining}`);
|
|
4549
5568
|
if (report.usage.warning80Percent)
|
|
4550
|
-
console.log(
|
|
5569
|
+
console.log(pc22.yellow("usage warning: daily cap is over 80% used"));
|
|
4551
5570
|
console.log(
|
|
4552
|
-
`config permissions: ${report.config.exists ? report.config.mode : "missing"} ${report.config.ok ?
|
|
5571
|
+
`config permissions: ${report.config.exists ? report.config.mode : "missing"} ${report.config.ok ? ok2 : warn2}`
|
|
4553
5572
|
);
|
|
4554
5573
|
if (report.config.warning) console.log(` ${report.config.warning}`);
|
|
4555
5574
|
for (const model of report.models) {
|
|
@@ -4557,19 +5576,19 @@ function printDoctor(report) {
|
|
|
4557
5576
|
}
|
|
4558
5577
|
}
|
|
4559
5578
|
function printDbDoctor(report) {
|
|
4560
|
-
const
|
|
4561
|
-
const bad =
|
|
4562
|
-
const
|
|
4563
|
-
console.log(`DB connectivity: ${report.db.ok ?
|
|
5579
|
+
const ok2 = pc22.green("ok");
|
|
5580
|
+
const bad = pc22.red("fail");
|
|
5581
|
+
const warn2 = pc22.yellow("warn");
|
|
5582
|
+
console.log(`DB connectivity: ${report.db.ok ? ok2 : bad}`);
|
|
4564
5583
|
if (report.db.postgresUrlSource) console.log(` url source: ${report.db.postgresUrlSource}`);
|
|
4565
5584
|
if (report.db.error) console.log(` ${report.db.error}`);
|
|
4566
|
-
console.log(`pgvector: ${report.pgvector.ok ?
|
|
5585
|
+
console.log(`pgvector: ${report.pgvector.ok ? ok2 : bad}`);
|
|
4567
5586
|
if (report.pgvector.error) console.log(` ${report.pgvector.error}`);
|
|
4568
|
-
console.log(`namespace: ${report.namespace.ok ?
|
|
5587
|
+
console.log(`namespace: ${report.namespace.ok ? ok2 : warn2}`);
|
|
4569
5588
|
console.log(` runtime=${report.namespace.runtime ?? "unset"}`);
|
|
4570
5589
|
if (report.namespace.warning) console.log(` ${report.namespace.warning}`);
|
|
4571
5590
|
}
|
|
4572
5591
|
export {
|
|
4573
5592
|
createCli
|
|
4574
5593
|
};
|
|
4575
|
-
//# sourceMappingURL=main-
|
|
5594
|
+
//# sourceMappingURL=main-BDVQAGAS.js.map
|