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