@orquentra/codebuddy 2.9.0 → 2.15.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/ARCHITECTURE.md +98 -0
- package/CLAUDE_CODEX.md +33 -0
- package/README.md +114 -9
- package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
- package/dist/{chunk-GXOML4UZ.js → chunk-4BSO3HST.js} +126 -18
- package/dist/chunk-4BSO3HST.js.map +1 -0
- package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
- package/dist/chunk-57642NGU.js.map +1 -0
- package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
- package/dist/chunk-6YI2D5F5.js.map +1 -0
- package/dist/{chunk-GKNYMNFM.js → chunk-7IPSDDO5.js} +10 -3
- package/dist/chunk-7IPSDDO5.js.map +1 -0
- package/dist/chunk-BTTQWSDS.js +372 -0
- package/dist/chunk-BTTQWSDS.js.map +1 -0
- package/dist/{chunk-2WH2Q4TA.js → chunk-D5E7SGI5.js} +304 -77
- package/dist/chunk-D5E7SGI5.js.map +1 -0
- package/dist/chunk-EMWGLKPR.js +45 -0
- package/dist/chunk-EMWGLKPR.js.map +1 -0
- package/dist/chunk-GQKWUHMA.js +20 -0
- package/dist/chunk-GQKWUHMA.js.map +1 -0
- package/dist/chunk-ITXE27SZ.js +358 -0
- package/dist/chunk-ITXE27SZ.js.map +1 -0
- package/dist/{chunk-UK6XTS2Y.js → chunk-JJQDVGF7.js} +23 -23
- package/dist/chunk-JJQDVGF7.js.map +1 -0
- package/dist/{chunk-F4BJ3DXW.js → chunk-KQCP3ZYH.js} +1273 -331
- package/dist/chunk-KQCP3ZYH.js.map +1 -0
- package/dist/{chunk-QN76UJP7.js → chunk-KYRM3HPP.js} +89 -21
- package/dist/chunk-KYRM3HPP.js.map +1 -0
- package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
- package/dist/chunk-M75SN4D4.js.map +1 -0
- package/dist/{chunk-J4T6HHI4.js → chunk-RZ266NHE.js} +538 -317
- package/dist/chunk-RZ266NHE.js.map +1 -0
- package/dist/chunk-U7KDM62P.js +43 -0
- package/dist/chunk-U7KDM62P.js.map +1 -0
- package/dist/chunk-UATJCD2T.js +368 -0
- package/dist/chunk-UATJCD2T.js.map +1 -0
- package/dist/{chunk-ENKDFLOO.js → chunk-UOTKHSQ5.js} +25 -8
- package/dist/chunk-UOTKHSQ5.js.map +1 -0
- package/dist/{chunk-TLWGZVPC.js → chunk-UR3DF74I.js} +1007 -132
- package/dist/chunk-UR3DF74I.js.map +1 -0
- package/dist/chunk-UTPC66MV.js +128 -0
- package/dist/chunk-UTPC66MV.js.map +1 -0
- package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
- package/dist/chunk-V7PT6ER5.js.map +1 -0
- package/dist/chunk-WHKXUXHE.js +156 -0
- package/dist/chunk-WHKXUXHE.js.map +1 -0
- package/dist/{chunk-SRA4MDVG.js → chunk-YMXFSHXT.js} +978 -950
- package/dist/chunk-YMXFSHXT.js.map +1 -0
- package/dist/cli/index.js +23 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
- package/dist/config-TXQJCCVI.js +15 -0
- package/dist/expand-KWYGNCKZ.js +98 -0
- package/dist/expand-KWYGNCKZ.js.map +1 -0
- package/dist/file-vectors-MKXTSGGH.js +19 -0
- package/dist/index-facts-4MTEHKMN.js +38 -0
- package/dist/index-pass-TBDUEZCB.js +14 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +25 -23
- package/dist/install-E3JWINTN.js +31 -0
- package/dist/langgraph/index.d.ts +1 -1
- package/dist/lean-MLTAFS5L.js +29 -0
- package/dist/local-75DJECVR.js +48 -0
- package/dist/local-llm-MECVJTVU.js +19 -0
- package/dist/{main-YJKSYFPT.js → main-RLMKQZW4.js} +999 -328
- package/dist/main-RLMKQZW4.js.map +1 -0
- package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
- package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
- package/dist/{operations-OG4ISWIM.js → operations-STR7S5UO.js} +8 -7
- package/dist/operations-STR7S5UO.js.map +1 -0
- package/dist/{pretool-command-PRFOIHMW.js → pretool-command-B5GY5T6E.js} +2 -2
- package/dist/pretool-command-B5GY5T6E.js.map +1 -0
- package/dist/prompt-gate-TDTFFQW2.js +8 -0
- package/dist/prompt-gate-TDTFFQW2.js.map +1 -0
- package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
- package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
- package/dist/{runtime-GCSEIAVD.js → runtime-SWFWGTQ4.js} +18 -9
- package/dist/runtime-SWFWGTQ4.js.map +1 -0
- package/dist/summaries-QXWFFDUZ.js +31 -0
- package/dist/summaries-QXWFFDUZ.js.map +1 -0
- package/dist/{zvec-XDZNZ2OL.js → zvec-F73IBWO5.js} +13 -6
- package/dist/zvec-F73IBWO5.js.map +1 -0
- package/package.json +13 -3
- package/dist/chunk-2WH2Q4TA.js.map +0 -1
- package/dist/chunk-6NZEZJYD.js +0 -206
- package/dist/chunk-6NZEZJYD.js.map +0 -1
- package/dist/chunk-CAEPHLHC.js.map +0 -1
- package/dist/chunk-ENKDFLOO.js.map +0 -1
- package/dist/chunk-ESZXCZIN.js.map +0 -1
- package/dist/chunk-F4BJ3DXW.js.map +0 -1
- package/dist/chunk-GKNYMNFM.js.map +0 -1
- package/dist/chunk-GXOML4UZ.js.map +0 -1
- package/dist/chunk-ITQO632W.js +0 -673
- package/dist/chunk-ITQO632W.js.map +0 -1
- package/dist/chunk-J4T6HHI4.js.map +0 -1
- package/dist/chunk-KNR2P2UY.js.map +0 -1
- package/dist/chunk-QN76UJP7.js.map +0 -1
- package/dist/chunk-SFMIMLXR.js +0 -63
- package/dist/chunk-SFMIMLXR.js.map +0 -1
- package/dist/chunk-SRA4MDVG.js.map +0 -1
- package/dist/chunk-TLWGZVPC.js.map +0 -1
- package/dist/chunk-UK6XTS2Y.js.map +0 -1
- package/dist/chunk-W7GAXL6C.js.map +0 -1
- package/dist/config-GEZFLXK6.js +0 -12
- package/dist/install-BXM56QCD.js +0 -19
- package/dist/lean-command-7DFW32MA.js +0 -19
- package/dist/main-YJKSYFPT.js.map +0 -1
- package/dist/summaries-ML53R7EL.js +0 -21
- /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
- /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
- /package/dist/{install-BXM56QCD.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
- /package/dist/{lean-command-7DFW32MA.js.map → index-facts-4MTEHKMN.js.map} +0 -0
- /package/dist/{operations-OG4ISWIM.js.map → index-pass-TBDUEZCB.js.map} +0 -0
- /package/dist/{pretool-command-PRFOIHMW.js.map → install-E3JWINTN.js.map} +0 -0
- /package/dist/{runtime-GCSEIAVD.js.map → lean-MLTAFS5L.js.map} +0 -0
- /package/dist/{summaries-ML53R7EL.js.map → local-75DJECVR.js.map} +0 -0
- /package/dist/{zvec-XDZNZ2OL.js.map → local-llm-MECVJTVU.js.map} +0 -0
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
renderLeanBlock,
|
|
3
|
-
runLeanHookCommand
|
|
4
|
-
} from "./chunk-UK6XTS2Y.js";
|
|
5
1
|
import {
|
|
6
2
|
runPreToolHookCommand
|
|
7
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UOTKHSQ5.js";
|
|
8
4
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
LOCAL_LLM_SIZES,
|
|
6
|
+
addLocalLlm,
|
|
7
|
+
printLocalLlmStatus,
|
|
8
|
+
removeLocalLlm
|
|
9
|
+
} from "./chunk-UTPC66MV.js";
|
|
13
10
|
import {
|
|
14
11
|
WORKFLOW_CLIENTS,
|
|
15
12
|
clientDisplayName,
|
|
13
|
+
clientHookCapabilities,
|
|
16
14
|
clientHooksInstalled,
|
|
17
15
|
defaultRulesMode,
|
|
18
16
|
detectClient,
|
|
@@ -29,20 +27,16 @@ import {
|
|
|
29
27
|
removeCodexEntry,
|
|
30
28
|
renderManagedBlock,
|
|
31
29
|
renderWorkflowTemplate,
|
|
30
|
+
runBridgeHook,
|
|
31
|
+
runCodexHook,
|
|
32
32
|
setHooks,
|
|
33
33
|
uninstallClient,
|
|
34
34
|
writeProjectMcpConfig
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-RZ266NHE.js";
|
|
36
36
|
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
LIFECYCLE_EVENTS,
|
|
41
|
-
MODE_LABELS,
|
|
42
|
-
parseClient,
|
|
43
|
-
parseMode,
|
|
44
|
-
supportedModes
|
|
45
|
-
} from "./chunk-W7GAXL6C.js";
|
|
37
|
+
renderLeanBlock,
|
|
38
|
+
runLeanHook
|
|
39
|
+
} from "./chunk-JJQDVGF7.js";
|
|
46
40
|
import {
|
|
47
41
|
isDockerDaemonRunning,
|
|
48
42
|
tryStartDockerDesktop,
|
|
@@ -53,8 +47,40 @@ import {
|
|
|
53
47
|
postgresStatus,
|
|
54
48
|
postgresUp
|
|
55
49
|
} from "./chunk-CSRD5OQA.js";
|
|
50
|
+
import {
|
|
51
|
+
failSoft,
|
|
52
|
+
readHookInput,
|
|
53
|
+
writeStdout
|
|
54
|
+
} from "./chunk-FJOTP632.js";
|
|
55
|
+
import {
|
|
56
|
+
loadLlmSummaries,
|
|
57
|
+
summarizeRepository
|
|
58
|
+
} from "./chunk-ITXE27SZ.js";
|
|
59
|
+
import {
|
|
60
|
+
listIndexFacts,
|
|
61
|
+
pruneIndexFacts
|
|
62
|
+
} from "./chunk-UATJCD2T.js";
|
|
63
|
+
import {
|
|
64
|
+
chatJson,
|
|
65
|
+
coolingDown,
|
|
66
|
+
usageToday
|
|
67
|
+
} from "./chunk-6YI2D5F5.js";
|
|
68
|
+
import {
|
|
69
|
+
runIndexModelPass
|
|
70
|
+
} from "./chunk-U7KDM62P.js";
|
|
71
|
+
import {
|
|
72
|
+
AUTOMATION_MODES,
|
|
73
|
+
CLIENT_ADAPTERS,
|
|
74
|
+
CLIENT_IDS,
|
|
75
|
+
LIFECYCLE_EVENTS,
|
|
76
|
+
MODE_LABELS,
|
|
77
|
+
parseClient,
|
|
78
|
+
parseMode,
|
|
79
|
+
supportedModes
|
|
80
|
+
} from "./chunk-V7PT6ER5.js";
|
|
56
81
|
import {
|
|
57
82
|
TELEMETRY_NOTICE,
|
|
83
|
+
findReuse,
|
|
58
84
|
flushTelemetry,
|
|
59
85
|
generateSuggestions,
|
|
60
86
|
migrateMemoryToFiles,
|
|
@@ -68,13 +94,9 @@ import {
|
|
|
68
94
|
traceCliCommand,
|
|
69
95
|
watchRepository,
|
|
70
96
|
writeTelemetryState
|
|
71
|
-
} from "./chunk-
|
|
72
|
-
import {
|
|
73
|
-
DEFAULT_ZVEC_MODEL,
|
|
74
|
-
buildZvecIndex,
|
|
75
|
-
zvecStatus
|
|
76
|
-
} from "./chunk-GXOML4UZ.js";
|
|
97
|
+
} from "./chunk-D5E7SGI5.js";
|
|
77
98
|
import {
|
|
99
|
+
HookStagingStore,
|
|
78
100
|
PlanFileStore,
|
|
79
101
|
PlanLifecycle,
|
|
80
102
|
ReviewStore,
|
|
@@ -83,29 +105,62 @@ import {
|
|
|
83
105
|
assessRisk,
|
|
84
106
|
buildBeforeEditContext,
|
|
85
107
|
buildBootstrapContext,
|
|
108
|
+
findCodexRollouts,
|
|
109
|
+
forgetJournal,
|
|
110
|
+
importCodexRollout,
|
|
111
|
+
journalLag,
|
|
112
|
+
latestUnfinished,
|
|
113
|
+
listJournals,
|
|
86
114
|
openHookSemantics,
|
|
115
|
+
readCheckpoint,
|
|
116
|
+
renderCheckpointNow,
|
|
87
117
|
renderContextBlock,
|
|
88
118
|
resolveRiskPaths,
|
|
89
119
|
runCaptureHook,
|
|
120
|
+
runCheckpointHook,
|
|
90
121
|
runContextHook,
|
|
91
122
|
runResetHook,
|
|
123
|
+
runResumeHook,
|
|
92
124
|
runStageHook,
|
|
93
|
-
runSyncHook
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
SessionCapsuleLedger,
|
|
97
|
-
detectTestCommand,
|
|
98
|
-
estimateTokens
|
|
99
|
-
} from "./chunk-GKNYMNFM.js";
|
|
125
|
+
runSyncHook,
|
|
126
|
+
syncReuseVectors
|
|
127
|
+
} from "./chunk-KQCP3ZYH.js";
|
|
100
128
|
import {
|
|
101
129
|
parseTraceLines,
|
|
102
130
|
readTraces,
|
|
103
131
|
tracePath
|
|
104
132
|
} from "./chunk-YN7TEIUH.js";
|
|
133
|
+
import {
|
|
134
|
+
SessionCapsuleLedger,
|
|
135
|
+
detectTestCommand,
|
|
136
|
+
estimateTokens
|
|
137
|
+
} from "./chunk-7IPSDDO5.js";
|
|
105
138
|
import {
|
|
106
139
|
hookRoot
|
|
107
140
|
} from "./chunk-TGFTKWOR.js";
|
|
141
|
+
import "./chunk-GQKWUHMA.js";
|
|
108
142
|
import "./chunk-DFV2SK6C.js";
|
|
143
|
+
import {
|
|
144
|
+
DEFAULT_ZVEC_MODEL,
|
|
145
|
+
buildZvecIndex,
|
|
146
|
+
zvecStatus
|
|
147
|
+
} from "./chunk-4BSO3HST.js";
|
|
148
|
+
import {
|
|
149
|
+
buildArchitectureMap,
|
|
150
|
+
generateGraphifyGraph,
|
|
151
|
+
indexRepository,
|
|
152
|
+
loadFreshIndex,
|
|
153
|
+
loadGraphifyMap,
|
|
154
|
+
loadProjectArchitectureMap,
|
|
155
|
+
neighbours,
|
|
156
|
+
queryMap,
|
|
157
|
+
setupGraphify,
|
|
158
|
+
syncFileVectors
|
|
159
|
+
} from "./chunk-UR3DF74I.js";
|
|
160
|
+
import {
|
|
161
|
+
extractSymbolTable,
|
|
162
|
+
supportsSymbols
|
|
163
|
+
} from "./chunk-Y7D7PUBE.js";
|
|
109
164
|
import {
|
|
110
165
|
bootstrapDatabase,
|
|
111
166
|
buildProviderFromConfig,
|
|
@@ -117,58 +172,25 @@ import {
|
|
|
117
172
|
pingDatabase,
|
|
118
173
|
runDoctor,
|
|
119
174
|
runMigrations
|
|
120
|
-
} from "./chunk-
|
|
175
|
+
} from "./chunk-KYRM3HPP.js";
|
|
176
|
+
import "./chunk-57642NGU.js";
|
|
121
177
|
import {
|
|
122
178
|
MemoryFileStore,
|
|
123
179
|
VERSION
|
|
124
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-YMXFSHXT.js";
|
|
125
181
|
import {
|
|
126
182
|
computeFactHash,
|
|
127
183
|
generateEntityId
|
|
128
|
-
} from "./chunk-
|
|
129
|
-
import "./chunk-6JCKMIQY.js";
|
|
130
|
-
import {
|
|
131
|
-
localRuntimeInstalled,
|
|
132
|
-
modelCacheDir
|
|
133
|
-
} from "./chunk-3QGW53GD.js";
|
|
134
|
-
import {
|
|
135
|
-
loadLlmSummaries,
|
|
136
|
-
summarizeRepository
|
|
137
|
-
} from "./chunk-6NZEZJYD.js";
|
|
138
|
-
import {
|
|
139
|
-
chatJson,
|
|
140
|
-
coolingDown,
|
|
141
|
-
usageToday
|
|
142
|
-
} from "./chunk-ESZXCZIN.js";
|
|
143
|
-
import {
|
|
144
|
-
generateGraphifyGraph,
|
|
145
|
-
loadGraphifyMap,
|
|
146
|
-
setupGraphify,
|
|
147
|
-
syncFileVectors
|
|
148
|
-
} from "./chunk-TLWGZVPC.js";
|
|
149
|
-
import {
|
|
150
|
-
buildArchitectureMap,
|
|
151
|
-
indexRepository,
|
|
152
|
-
loadFreshIndex,
|
|
153
|
-
neighbours,
|
|
154
|
-
queryMap
|
|
155
|
-
} from "./chunk-ITQO632W.js";
|
|
156
|
-
import {
|
|
157
|
-
extractSymbolTable,
|
|
158
|
-
supportsSymbols
|
|
159
|
-
} from "./chunk-Y7D7PUBE.js";
|
|
184
|
+
} from "./chunk-EMWGLKPR.js";
|
|
160
185
|
import {
|
|
161
186
|
redactPersisted
|
|
162
187
|
} from "./chunk-AL6VBTNA.js";
|
|
188
|
+
import "./chunk-6JCKMIQY.js";
|
|
163
189
|
import {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
LOCAL_EMBEDDING_MODEL,
|
|
167
|
-
getModelDefinition
|
|
168
|
-
} from "./chunk-HPOR6YXA.js";
|
|
169
|
-
import {
|
|
190
|
+
mergeGlobalConfig,
|
|
191
|
+
readGlobalConfig,
|
|
170
192
|
resolveLlmConfig
|
|
171
|
-
} from "./chunk-
|
|
193
|
+
} from "./chunk-WHKXUXHE.js";
|
|
172
194
|
import {
|
|
173
195
|
PLAN_POLICIES,
|
|
174
196
|
configPath,
|
|
@@ -186,13 +208,26 @@ import {
|
|
|
186
208
|
setPlanPolicy,
|
|
187
209
|
setZvecEnabled,
|
|
188
210
|
writeConfigFile
|
|
189
|
-
} from "./chunk-
|
|
190
|
-
import
|
|
211
|
+
} from "./chunk-M75SN4D4.js";
|
|
212
|
+
import {
|
|
213
|
+
writeAtomic
|
|
214
|
+
} from "./chunk-CG5B3LYG.js";
|
|
215
|
+
import "./chunk-BTTQWSDS.js";
|
|
216
|
+
import {
|
|
217
|
+
localRuntimeInstalled,
|
|
218
|
+
modelCacheDir
|
|
219
|
+
} from "./chunk-3QGW53GD.js";
|
|
220
|
+
import {
|
|
221
|
+
DEFAULT_CLOUD_EMBEDDING_MODELS,
|
|
222
|
+
DEFAULT_RERANK_MODELS,
|
|
223
|
+
LOCAL_EMBEDDING_MODEL,
|
|
224
|
+
getModelDefinition
|
|
225
|
+
} from "./chunk-HPOR6YXA.js";
|
|
191
226
|
import "./chunk-PZ5AY32C.js";
|
|
192
227
|
|
|
193
228
|
// src/cli/main.ts
|
|
194
229
|
import { Command } from "commander";
|
|
195
|
-
import
|
|
230
|
+
import pc25 from "picocolors";
|
|
196
231
|
|
|
197
232
|
// src/integrations/github.ts
|
|
198
233
|
import { constants } from "fs";
|
|
@@ -770,6 +805,7 @@ var CONFORMANCE = {
|
|
|
770
805
|
const servers = json(result.stdout);
|
|
771
806
|
if (!Array.isArray(servers)) return [fail("mcp", "codex mcp list --json returned no JSON")];
|
|
772
807
|
return [
|
|
808
|
+
await clientHooksInstalled(root, "codex") ? pass("hooks", ".codex/hooks.json is installed; review trust in Codex /hooks") : skip("hooks", "not installed in this repo"),
|
|
773
809
|
servers.some((server) => server.name === name2) ? pass("mcp", `codex mcp list has ${name2}`) : skip("mcp", `no ${name2} entry (clients install codex --global adds it)`)
|
|
774
810
|
];
|
|
775
811
|
},
|
|
@@ -863,7 +899,7 @@ function registerClientsCommands(program, runSafely2) {
|
|
|
863
899
|
console.log(pc2.dim("Per-turn token cost per client: codebuddy clients cost"));
|
|
864
900
|
});
|
|
865
901
|
});
|
|
866
|
-
clients.command("install").argument("<client>", `One of: ${CLIENT_IDS.join(", ")}.`).requiredOption("--mode <mode>", `One of: ${AUTOMATION_MODES.join(", ")}.`).option("--namespace <name>", "Namespace for the MCP entry (defaults to config/folder).").option("--global", "Also add the MCP entry to the global config (Codex, Hermes).").description("Apply one automation mode: rules block, hooks, and MCP config.").action(
|
|
902
|
+
clients.command("install").argument("<client>", `One of: ${CLIENT_IDS.join(", ")}.`).requiredOption("--mode <mode>", `One of: ${AUTOMATION_MODES.join(", ")}.`).option("--namespace <name>", "Namespace for the MCP entry (defaults to config/folder).").option("--global", "Also add the MCP entry to the global config (Codex, Hermes).").option("--capture", "Capture turns into memory even in lean mode (Claude Code).").option("--no-capture", "Inject context without capturing turns (Claude Code).").description("Apply one automation mode: rules block, hooks, and MCP config.").action(
|
|
867
903
|
async (client, opts) => {
|
|
868
904
|
await runSafely2(async () => {
|
|
869
905
|
const mode = parseMode(opts.mode);
|
|
@@ -871,7 +907,8 @@ function registerClientsCommands(program, runSafely2) {
|
|
|
871
907
|
client: parseClient(client),
|
|
872
908
|
mode,
|
|
873
909
|
...opts.namespace ? { namespace: opts.namespace } : {},
|
|
874
|
-
...opts.global ? { global: true } : {}
|
|
910
|
+
...opts.global ? { global: true } : {},
|
|
911
|
+
...opts.capture !== void 0 ? { capture: opts.capture } : {}
|
|
875
912
|
});
|
|
876
913
|
printReport2(report, `${MODE_LABELS[mode]} mode`);
|
|
877
914
|
});
|
|
@@ -983,6 +1020,30 @@ function registerContextCommands(program, runSafely2) {
|
|
|
983
1020
|
printBootstrap(result);
|
|
984
1021
|
});
|
|
985
1022
|
});
|
|
1023
|
+
context.command("reuse").argument("<task...>", "What you are about to implement.").option("--json", "Print machine-readable JSON.").description(
|
|
1024
|
+
"Existing helpers to reuse for a task: signature, a call site and the covering test."
|
|
1025
|
+
).action(async (taskParts, opts) => {
|
|
1026
|
+
await runSafely2(async () => {
|
|
1027
|
+
const root = process.cwd();
|
|
1028
|
+
const { manifest } = await loadFreshIndex(root);
|
|
1029
|
+
const picks = await findReuse(
|
|
1030
|
+
root,
|
|
1031
|
+
taskParts.join(" "),
|
|
1032
|
+
manifest,
|
|
1033
|
+
await loadProjectArchitectureMap(root, manifest)
|
|
1034
|
+
);
|
|
1035
|
+
if (opts.json) {
|
|
1036
|
+
console.log(JSON.stringify(picks, null, 2));
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (picks.length === 0) console.log(pc3.dim("no existing helper clearly matches"));
|
|
1040
|
+
for (const pick of picks) {
|
|
1041
|
+
console.log(`${pick.path}:${pick.startLine}-${pick.endLine} ${pick.signature}`);
|
|
1042
|
+
if (pick.usage) console.log(pc3.dim(` used at ${pick.usage}`));
|
|
1043
|
+
if (pick.test) console.log(pc3.dim(` test ${pick.test}`));
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
986
1047
|
context.command("preview").argument("[task...]", "The task you are about to work on.").option("--paths <paths>", "Comma-separated repo-relative paths to target.").option("--plan <id>", "Resolve target files from a CodeBuddy plan.").option("--git", "Resolve target files from current git changes.").option("--json", "Print machine-readable JSON.").description("Show the context CodeBuddy would assemble before editing the given files.").action(
|
|
987
1048
|
async (taskParts, opts) => {
|
|
988
1049
|
await runSafely2(async () => {
|
|
@@ -1720,15 +1781,22 @@ function registerGitHubCommands(program, runSafely2) {
|
|
|
1720
1781
|
import { execFile as execFile2, spawn as spawn3 } from "child_process";
|
|
1721
1782
|
import { relative as relative2 } from "path";
|
|
1722
1783
|
import pc7 from "picocolors";
|
|
1784
|
+
|
|
1785
|
+
// src/hooks/lean-command.ts
|
|
1786
|
+
async function runLeanHookCommand() {
|
|
1787
|
+
await failSoft(async () => {
|
|
1788
|
+
await runLeanHook(await readHookInput(), writeStdout);
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// src/cli/commands/hooks.ts
|
|
1723
1793
|
function registerHooksCommands(program, runSafely2) {
|
|
1724
1794
|
const hooks = program.command("hooks").description("Enforce automatic recall/capture via client lifecycle hooks.");
|
|
1725
1795
|
hooks.command("install").option(
|
|
1726
1796
|
"--client <client>",
|
|
1727
|
-
"Client with a CodeBuddy hook adapter (claude, opencode, hermes).",
|
|
1797
|
+
"Client with a CodeBuddy hook adapter (claude, codex, opencode, hermes).",
|
|
1728
1798
|
"claude"
|
|
1729
|
-
).description(
|
|
1730
|
-
"Add CodeBuddy's recall/capture hooks (.claude/settings.json, .opencode/plugins/codebuddy.js or .hermes/plugins/codebuddy/)."
|
|
1731
|
-
).action(async (opts) => {
|
|
1799
|
+
).description("Add CodeBuddy's recall/capture hooks for the selected client.").action(async (opts) => {
|
|
1732
1800
|
await runSafely2(async () => {
|
|
1733
1801
|
const client = assertHookClient(opts.client);
|
|
1734
1802
|
const step = await setHooks(process.cwd(), true, client);
|
|
@@ -1750,7 +1818,7 @@ function registerHooksCommands(program, runSafely2) {
|
|
|
1750
1818
|
});
|
|
1751
1819
|
hooks.command("uninstall").option(
|
|
1752
1820
|
"--client <client>",
|
|
1753
|
-
"Client with a CodeBuddy hook adapter (claude, opencode, hermes).",
|
|
1821
|
+
"Client with a CodeBuddy hook adapter (claude, codex, opencode, hermes).",
|
|
1754
1822
|
"claude"
|
|
1755
1823
|
).description("Remove CodeBuddy's hooks for a client (user hooks untouched).").action(async (opts) => {
|
|
1756
1824
|
await runSafely2(async () => {
|
|
@@ -1764,14 +1832,43 @@ function registerHooksCommands(program, runSafely2) {
|
|
|
1764
1832
|
});
|
|
1765
1833
|
hooks.command("status").option(
|
|
1766
1834
|
"--client <client>",
|
|
1767
|
-
"Client with a CodeBuddy hook adapter (claude, opencode, hermes).",
|
|
1835
|
+
"Client with a CodeBuddy hook adapter (claude, codex, opencode, hermes).",
|
|
1768
1836
|
"claude"
|
|
1769
|
-
).description("Report
|
|
1837
|
+
).description("Report which CodeBuddy hooks are installed and what they do.").action(async (opts) => {
|
|
1770
1838
|
await runSafely2(async () => {
|
|
1771
|
-
const
|
|
1839
|
+
const caps = await clientHookCapabilities(process.cwd(), parseClient(opts.client));
|
|
1840
|
+
if (!caps?.injection) {
|
|
1841
|
+
console.log(pc7.dim("CodeBuddy hooks: not installed"));
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
console.log(pc7.green("CodeBuddy hooks: installed"));
|
|
1845
|
+
console.log(` injection: ${caps.injection}`);
|
|
1846
|
+
console.log(
|
|
1847
|
+
` capture: ${caps.capture ? "on (edits staged, turn end captured)" : "off (turns are not captured into memory)"}`
|
|
1848
|
+
);
|
|
1772
1849
|
console.log(
|
|
1773
|
-
|
|
1850
|
+
` recovery: ${caps.recovery ? "on (pre-compaction checkpoint, unfinished-task resume)" : "off"}`
|
|
1774
1851
|
);
|
|
1852
|
+
console.log(` compaction reset: ${caps.reset ? "on" : "off"}`);
|
|
1853
|
+
console.log(` read guard: ${caps.guard ? "on" : "off"}`);
|
|
1854
|
+
});
|
|
1855
|
+
});
|
|
1856
|
+
hooks.command("codex").option("--injection <mode>", "full or lean", "full").option("--capture", "Capture visible lifecycle events", false).description("[hook] Native Codex event adapter (JSON output, no permission changes).").action(async (opts) => {
|
|
1857
|
+
let result = {};
|
|
1858
|
+
await failSoft(async () => {
|
|
1859
|
+
if (opts.injection !== "full" && opts.injection !== "lean")
|
|
1860
|
+
throw new Error("injection must be full or lean");
|
|
1861
|
+
result = await runCodexHook(
|
|
1862
|
+
await readHookInput(),
|
|
1863
|
+
opts.injection,
|
|
1864
|
+
opts.capture
|
|
1865
|
+
);
|
|
1866
|
+
});
|
|
1867
|
+
await writeStdout(JSON.stringify(result));
|
|
1868
|
+
});
|
|
1869
|
+
hooks.command("bridge").description("[hook] Capture normalized host events and restore checkpoint context.").action(async () => {
|
|
1870
|
+
await failSoft(async () => {
|
|
1871
|
+
await runBridgeHook(await readHookInput(), writeStdout);
|
|
1775
1872
|
});
|
|
1776
1873
|
});
|
|
1777
1874
|
hooks.command("context").description("[hook] Emit compact project context for injection at prompt submit.").action(async () => {
|
|
@@ -1824,6 +1921,16 @@ function registerHooksCommands(program, runSafely2) {
|
|
|
1824
1921
|
await runResetHook(await readHookInput(), writeStdout);
|
|
1825
1922
|
});
|
|
1826
1923
|
});
|
|
1924
|
+
hooks.command("checkpoint").description("[hook] Journal the transcript before compaction (PreCompact).").action(async () => {
|
|
1925
|
+
await failSoft(async () => {
|
|
1926
|
+
await runCheckpointHook(await readHookInput());
|
|
1927
|
+
});
|
|
1928
|
+
});
|
|
1929
|
+
hooks.command("resume").description("[hook] Print another session's unfinished task checkpoint, if any.").action(async () => {
|
|
1930
|
+
await failSoft(async () => {
|
|
1931
|
+
await runResumeHook(await readHookInput(), writeStdout);
|
|
1932
|
+
});
|
|
1933
|
+
});
|
|
1827
1934
|
}
|
|
1828
1935
|
function assertHookClient(value) {
|
|
1829
1936
|
const client = parseClient(value);
|
|
@@ -1898,8 +2005,9 @@ var KEY_ENV = { groq: "GROQ_API_KEY", openrouter: "OPENROUTER_API_KEY" };
|
|
|
1898
2005
|
var WATCH_BATCH = 8;
|
|
1899
2006
|
async function llmStatusReport(root, env = process.env) {
|
|
1900
2007
|
const block = (await readConfigFile(root)).llm;
|
|
1901
|
-
const provider = block?.provider ?? "groq";
|
|
1902
2008
|
const config = await resolveLlmConfig(root, env);
|
|
2009
|
+
const provider = config.enabled ? config.provider : block?.provider ?? "groq";
|
|
2010
|
+
const keyName = provider === "local" ? "" : KEY_ENV[provider];
|
|
1903
2011
|
const model = config.enabled ? config.model : block?.model ?? null;
|
|
1904
2012
|
const key = `${provider}:${model}`;
|
|
1905
2013
|
const { manifest } = await loadFreshIndex(root);
|
|
@@ -1908,8 +2016,9 @@ async function llmStatusReport(root, env = process.env) {
|
|
|
1908
2016
|
reason: config.enabled ? "on" : config.reason,
|
|
1909
2017
|
provider,
|
|
1910
2018
|
model,
|
|
1911
|
-
keyEnv: env.CODEBUDDY_LLM_API_KEY ? "CODEBUDDY_LLM_API_KEY" :
|
|
1912
|
-
|
|
2019
|
+
keyEnv: provider === "local" ? "none needed" : env.CODEBUDDY_LLM_API_KEY ? "CODEBUDDY_LLM_API_KEY" : keyName,
|
|
2020
|
+
// Enabled implies a key (environment or saved by `codebuddy use`).
|
|
2021
|
+
keyPresent: provider === "local" || config.enabled || Boolean(env.CODEBUDDY_LLM_API_KEY || env[keyName]),
|
|
1913
2022
|
features: config.enabled ? config.features : null,
|
|
1914
2023
|
usageToday: (await usageToday(root))[key] ?? 0,
|
|
1915
2024
|
dailyTokenBudget: config.enabled ? config.dailyTokenBudget : block?.dailyTokenBudget ?? 4e5,
|
|
@@ -1950,7 +2059,7 @@ function registerLlmCommands(program, runSafely2) {
|
|
|
1950
2059
|
if (!report.keyPresent) console.log(pc8.dim(`next: export ${report.keyEnv}=<your key>`));
|
|
1951
2060
|
});
|
|
1952
2061
|
});
|
|
1953
|
-
llm.command("enable").option("--provider <provider>", "groq or openrouter", "groq").option("--model <model>", "Model id (default:
|
|
2062
|
+
llm.command("enable").option("--provider <provider>", "groq or openrouter", "groq").option("--model <model>", "Model id (default: qwen/qwen3.8-27b).").description("Turn the layer on in .codebuddy/config.json (the key stays in your environment).").action(async (opts) => {
|
|
1954
2063
|
await runSafely2(async () => {
|
|
1955
2064
|
if (opts.provider !== "groq" && opts.provider !== "openrouter") {
|
|
1956
2065
|
throw new Error("--provider must be groq or openrouter");
|
|
@@ -2006,13 +2115,36 @@ function registerLlmCommands(program, runSafely2) {
|
|
|
2006
2115
|
// src/cli/commands/indexing.ts
|
|
2007
2116
|
var VECTOR_BUDGET_MS = 10 * 6e4;
|
|
2008
2117
|
function registerIndexCommands(program, runSafely2) {
|
|
2009
|
-
program.command("index").option("--full", "Re-summarize every file, ignoring existing content hashes.").option("--json", "Print machine-readable JSON.").option("--no-vectors", "Skip embedding source files for task \u2192 file ordering.").
|
|
2118
|
+
program.command("index").option("--full", "Re-summarize every file, ignoring existing content hashes.").option("--json", "Print machine-readable JSON.").option("--no-vectors", "Skip embedding source files for task \u2192 file ordering.").option(
|
|
2119
|
+
"--cards",
|
|
2120
|
+
"Also write intent cards and index facts with the local (or BYOK) model, then embed the cards."
|
|
2121
|
+
).description("Index the repository: content hashes and lightweight file summaries.").action(async (opts) => {
|
|
2010
2122
|
await runSafely2(async () => {
|
|
2011
2123
|
const result = await indexRepository({ ...opts.full ? { full: true } : {} });
|
|
2012
2124
|
const local = opts.vectors && (await loadRuntimeConfig(process.cwd(), { requireDatabase: false }).catch(() => null))?.provider.type === "local";
|
|
2125
|
+
const vectorStarted = Date.now();
|
|
2013
2126
|
const vectors = local ? await syncFileVectors(process.cwd(), VECTOR_BUDGET_MS) : null;
|
|
2127
|
+
const reuseVectors = local ? await syncReuseVectors(process.cwd(), VECTOR_BUDGET_MS - (Date.now() - vectorStarted)) : null;
|
|
2128
|
+
const cardsStarted = Date.now();
|
|
2129
|
+
const model = opts.cards ? await runIndexModelPass(process.cwd(), {
|
|
2130
|
+
...opts.json ? {} : {
|
|
2131
|
+
onProgress: (done, total) => process.stderr.write(`\r cards: ${done}/${total}`)
|
|
2132
|
+
}
|
|
2133
|
+
}) : null;
|
|
2134
|
+
if (model && !opts.json) process.stderr.write("\n");
|
|
2014
2135
|
if (opts.json) {
|
|
2015
|
-
console.log(
|
|
2136
|
+
console.log(
|
|
2137
|
+
JSON.stringify(
|
|
2138
|
+
{
|
|
2139
|
+
...result,
|
|
2140
|
+
...vectors ? { vectors } : {},
|
|
2141
|
+
...reuseVectors ? { reuseVectors } : {},
|
|
2142
|
+
...model ? { model: { ...model, ms: Date.now() - cardsStarted } } : {}
|
|
2143
|
+
},
|
|
2144
|
+
null,
|
|
2145
|
+
2
|
|
2146
|
+
)
|
|
2147
|
+
);
|
|
2016
2148
|
return;
|
|
2017
2149
|
}
|
|
2018
2150
|
printResult(result);
|
|
@@ -2025,6 +2157,7 @@ function registerIndexCommands(program, runSafely2) {
|
|
|
2025
2157
|
} else if (local) {
|
|
2026
2158
|
console.log(pc9.dim(" file vectors: skipped (local embedding runtime not available)"));
|
|
2027
2159
|
}
|
|
2160
|
+
if (model) printModelPass(model, Date.now() - cardsStarted);
|
|
2028
2161
|
});
|
|
2029
2162
|
});
|
|
2030
2163
|
program.command("watch").option("--debounce <ms>", "Coalesce file-change bursts within this window.", Number).option("--no-llm", "Do not write BYOK model summaries after index passes.").description("Watch the repository and incrementally re-index on change (Ctrl+C to stop).").action(async (opts) => {
|
|
@@ -2051,6 +2184,24 @@ function registerIndexCommands(program, runSafely2) {
|
|
|
2051
2184
|
});
|
|
2052
2185
|
});
|
|
2053
2186
|
}
|
|
2187
|
+
function printModelPass(model, ms) {
|
|
2188
|
+
if (!model.cards && !model.facts) {
|
|
2189
|
+
console.log(pc9.dim(" cards: skipped (no model; codebuddy models add local-llm)"));
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
if (model.cards) {
|
|
2193
|
+
console.log(
|
|
2194
|
+
pc9.dim(
|
|
2195
|
+
` cards: ${model.cards.summarized} written, ${model.cards.remaining} left (${model.cards.stoppedBy}), ${model.cardVectors} embedded, ${Math.round(ms / 1e3)} s`
|
|
2196
|
+
)
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
if (model.facts) {
|
|
2200
|
+
console.log(
|
|
2201
|
+
pc9.dim(` index facts: ${model.facts.written} written, ${model.facts.removed} replaced`)
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2054
2205
|
function printResult(result, compact = false) {
|
|
2055
2206
|
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`;
|
|
2056
2207
|
console.log(compact ? pc9.dim(summary) : summary);
|
|
@@ -2202,8 +2353,59 @@ async function runInitWizard() {
|
|
|
2202
2353
|
}
|
|
2203
2354
|
}
|
|
2204
2355
|
|
|
2205
|
-
// src/cli/commands/
|
|
2356
|
+
// src/cli/commands/journal.ts
|
|
2206
2357
|
import pc10 from "picocolors";
|
|
2358
|
+
function registerJournalCommands(program, runSafely2) {
|
|
2359
|
+
const journal = program.command("journal").description("Inspect or delete session journals and active-task checkpoints.");
|
|
2360
|
+
journal.command("list", { isDefault: true }).description("List sessions with a checkpoint, newest first.").action(async () => {
|
|
2361
|
+
await runSafely2(async () => {
|
|
2362
|
+
const sessions = await listJournals(process.cwd());
|
|
2363
|
+
if (sessions.length === 0) console.log(pc10.dim("no session journals yet"));
|
|
2364
|
+
for (const { session, updatedAt } of sessions) console.log(`${updatedAt} ${session}`);
|
|
2365
|
+
});
|
|
2366
|
+
});
|
|
2367
|
+
journal.command("show").argument("<session>").description("Print a session's active-task checkpoint as JSON.").action(async (session) => {
|
|
2368
|
+
await runSafely2(async () => {
|
|
2369
|
+
const checkpoint = await readCheckpoint(process.cwd(), session);
|
|
2370
|
+
console.log(checkpoint ? JSON.stringify(checkpoint, null, 2) : pc10.dim("no checkpoint"));
|
|
2371
|
+
});
|
|
2372
|
+
});
|
|
2373
|
+
journal.command("resume").description("Print the newest unfinished task checkpoint (any session or agent).").action(async () => {
|
|
2374
|
+
await runSafely2(async () => {
|
|
2375
|
+
const checkpoint = await latestUnfinished(process.cwd(), void 0);
|
|
2376
|
+
console.log(
|
|
2377
|
+
checkpoint ? await renderCheckpointNow(
|
|
2378
|
+
process.cwd(),
|
|
2379
|
+
checkpoint,
|
|
2380
|
+
`unfinished, from session ${checkpoint.session}`
|
|
2381
|
+
) : pc10.dim("no unfinished task in the last 3 days")
|
|
2382
|
+
);
|
|
2383
|
+
});
|
|
2384
|
+
});
|
|
2385
|
+
journal.command("import-codex").option("--all", "Import every Codex rollout for this repository, not only the newest.").description("Journal Codex rollouts (~/.codex/sessions) whose working directory is this repo.").action(async (opts) => {
|
|
2386
|
+
await runSafely2(async () => {
|
|
2387
|
+
const rollouts = await findCodexRollouts(process.cwd());
|
|
2388
|
+
if (rollouts.length === 0) {
|
|
2389
|
+
console.log(pc10.dim("no Codex rollout for this repository"));
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
for (const rollout of opts.all ? rollouts : rollouts.slice(0, 1)) {
|
|
2393
|
+
const result = await importCodexRollout(process.cwd(), rollout);
|
|
2394
|
+
console.log(`codex-${rollout.id}: ${result ? `${result.added} new events` : "busy"}`);
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
});
|
|
2398
|
+
journal.command("forget").argument("[session]").option("--all", "Delete every session's journal.").description("Delete a session's journal, cursor and checkpoint.").action(async (session, opts) => {
|
|
2399
|
+
await runSafely2(async () => {
|
|
2400
|
+
if (!session && !opts.all) throw new Error("Name a session or pass --all.");
|
|
2401
|
+
await forgetJournal(process.cwd(), opts.all ? void 0 : session);
|
|
2402
|
+
console.log(pc10.yellow(opts.all ? "deleted all session journals" : `deleted ${session}`));
|
|
2403
|
+
});
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// src/cli/commands/learn.ts
|
|
2408
|
+
import pc11 from "picocolors";
|
|
2207
2409
|
|
|
2208
2410
|
// src/learning/outcomes.ts
|
|
2209
2411
|
async function recordChangeOutcome(input) {
|
|
@@ -2264,15 +2466,15 @@ function registerLearnCommand(program, runSafely2) {
|
|
|
2264
2466
|
...opts.commit ? { commit: opts.commit } : {},
|
|
2265
2467
|
...opts.plan ? { planId: opts.plan } : {}
|
|
2266
2468
|
});
|
|
2267
|
-
console.log(`${
|
|
2268
|
-
console.log(
|
|
2469
|
+
console.log(`${pc11.green("recorded")} ${result.id} (${result.category})`);
|
|
2470
|
+
console.log(pc11.dim(result.path));
|
|
2269
2471
|
});
|
|
2270
2472
|
}
|
|
2271
2473
|
);
|
|
2272
2474
|
}
|
|
2273
2475
|
|
|
2274
2476
|
// src/cli/commands/map.ts
|
|
2275
|
-
import
|
|
2477
|
+
import pc12 from "picocolors";
|
|
2276
2478
|
function registerMapCommands(program, runSafely2) {
|
|
2277
2479
|
const map = program.command("map").description("Query the lightweight architecture map.");
|
|
2278
2480
|
map.command("build").description("Scan the current repository and summarize the module graph.").action(async () => {
|
|
@@ -2292,7 +2494,7 @@ function registerMapCommands(program, runSafely2) {
|
|
|
2292
2494
|
return;
|
|
2293
2495
|
}
|
|
2294
2496
|
if (edges.length === 0) {
|
|
2295
|
-
console.log(
|
|
2497
|
+
console.log(pc12.dim("no dependencies found"));
|
|
2296
2498
|
return;
|
|
2297
2499
|
}
|
|
2298
2500
|
for (const edge of edges) console.log(`${edge.from} -> ${edge.to}`);
|
|
@@ -2307,7 +2509,7 @@ function registerMapCommands(program, runSafely2) {
|
|
|
2307
2509
|
return;
|
|
2308
2510
|
}
|
|
2309
2511
|
if (result.edges.length === 0) {
|
|
2310
|
-
console.log(
|
|
2512
|
+
console.log(pc12.dim("no dependents found"));
|
|
2311
2513
|
return;
|
|
2312
2514
|
}
|
|
2313
2515
|
for (const edge of result.edges) console.log(`${edge.from} -> ${edge.to}`);
|
|
@@ -2318,9 +2520,39 @@ function registerMapCommands(program, runSafely2) {
|
|
|
2318
2520
|
// src/cli/commands/memory-review.ts
|
|
2319
2521
|
import { spawn as spawn4 } from "child_process";
|
|
2320
2522
|
import { relative as relative3 } from "path";
|
|
2321
|
-
import
|
|
2523
|
+
import pc13 from "picocolors";
|
|
2322
2524
|
function registerMemoryCommands(program, runSafely2) {
|
|
2323
2525
|
const memory = program.command("memory").description("Inspect and curate CodeBuddy memory.");
|
|
2526
|
+
memory.command("list").option(
|
|
2527
|
+
"--origin <origin>",
|
|
2528
|
+
"Only facts from this origin: index (extracted from the repository)."
|
|
2529
|
+
).option("--json", "Print machine-readable JSON.").description("List durable facts (Markdown under .codebuddy/memory/facts/).").action(async (opts) => {
|
|
2530
|
+
await runSafely2(async () => {
|
|
2531
|
+
if (opts.origin !== void 0 && opts.origin !== "index") {
|
|
2532
|
+
throw new Error(`Unknown origin "${opts.origin}"; supported: index.`);
|
|
2533
|
+
}
|
|
2534
|
+
const facts = opts.origin === void 0 ? await new MemoryFileStore().listFacts() : await listIndexFacts(process.cwd());
|
|
2535
|
+
if (opts.json) {
|
|
2536
|
+
console.log(JSON.stringify(facts, null, 2));
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
if (facts.length === 0) console.log(pc13.dim("no facts"));
|
|
2540
|
+
for (const fact of facts) {
|
|
2541
|
+
const source = fact.verification[0] ?? fact.paths[0];
|
|
2542
|
+
console.log(`${pc13.cyan(fact.id)} ${fact.content}${source ? pc13.dim(` [${source}]`) : ""}`);
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
});
|
|
2546
|
+
memory.command("prune").requiredOption("--origin <origin>", "Which facts to delete: index.").description("Delete facts by origin (index: re-extracted on the next index pass).").action(async (opts) => {
|
|
2547
|
+
await runSafely2(async () => {
|
|
2548
|
+
if (opts.origin !== "index")
|
|
2549
|
+
throw new Error(`Unknown origin "${opts.origin}"; supported: index.`);
|
|
2550
|
+
const removed = await pruneIndexFacts(process.cwd());
|
|
2551
|
+
console.log(
|
|
2552
|
+
`${removed} index fact(s) removed ${pc13.dim("(run `codebuddy hooks sync` to update the database)")}`
|
|
2553
|
+
);
|
|
2554
|
+
});
|
|
2555
|
+
});
|
|
2324
2556
|
const review = memory.command("review").description("Review memory candidates captured automatically after turns.");
|
|
2325
2557
|
review.command("list", { isDefault: true }).option("--json", "Print machine-readable JSON.").description("List pending review candidates.").action(async (opts) => {
|
|
2326
2558
|
await runSafely2(async () => {
|
|
@@ -2330,11 +2562,11 @@ function registerMemoryCommands(program, runSafely2) {
|
|
|
2330
2562
|
return;
|
|
2331
2563
|
}
|
|
2332
2564
|
if (items.length === 0) {
|
|
2333
|
-
console.log(
|
|
2565
|
+
console.log(pc13.dim("no pending review items"));
|
|
2334
2566
|
return;
|
|
2335
2567
|
}
|
|
2336
2568
|
for (const item of items) console.log(renderRow(item));
|
|
2337
|
-
console.log(
|
|
2569
|
+
console.log(pc13.dim(`
|
|
2338
2570
|
${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
2339
2571
|
});
|
|
2340
2572
|
});
|
|
@@ -2348,7 +2580,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
|
2348
2580
|
const factId = await approveReviewItem(id, {
|
|
2349
2581
|
...opts.incident !== void 0 ? { incidentId: opts.incident } : {}
|
|
2350
2582
|
});
|
|
2351
|
-
console.log(`${
|
|
2583
|
+
console.log(`${pc13.green("approved")} ${id} \u2192 ${factId}`);
|
|
2352
2584
|
});
|
|
2353
2585
|
});
|
|
2354
2586
|
review.command("reject").argument("<id>").description("Discard a review candidate without saving it.").action(async (id) => {
|
|
@@ -2356,7 +2588,7 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
|
2356
2588
|
const store = new ReviewStore();
|
|
2357
2589
|
await store.read(id);
|
|
2358
2590
|
await store.delete(id);
|
|
2359
|
-
console.log(`${
|
|
2591
|
+
console.log(`${pc13.yellow("rejected")} ${id}`);
|
|
2360
2592
|
});
|
|
2361
2593
|
});
|
|
2362
2594
|
review.command("edit").argument("<id>").description("Open a review candidate in $EDITOR and re-validate on save.").action(async (id) => {
|
|
@@ -2365,44 +2597,44 @@ ${items.length} pending. approve <id> | reject <id> | show <id>`));
|
|
|
2365
2597
|
const item = await store.read(id);
|
|
2366
2598
|
await openEditor(item.path);
|
|
2367
2599
|
await store.read(id);
|
|
2368
|
-
console.log(
|
|
2600
|
+
console.log(pc13.dim("saved and validated"));
|
|
2369
2601
|
});
|
|
2370
2602
|
});
|
|
2371
2603
|
}
|
|
2372
2604
|
function renderRow(item) {
|
|
2373
|
-
const flag = item.sensitive ?
|
|
2374
|
-
return `${classColor(item.class)} ${
|
|
2605
|
+
const flag = item.sensitive ? pc13.red(" \u26A0 sensitive") : "";
|
|
2606
|
+
return `${classColor(item.class)} ${pc13.dim(item.id)} ${truncate(item.content, 70)}${flag}`;
|
|
2375
2607
|
}
|
|
2376
2608
|
function renderItem(item) {
|
|
2377
2609
|
const lines = [
|
|
2378
|
-
`${classColor(item.class)} ${
|
|
2379
|
-
`${
|
|
2380
|
-
`${
|
|
2381
|
-
`${
|
|
2610
|
+
`${classColor(item.class)} ${pc13.dim(item.id)}`,
|
|
2611
|
+
`${pc13.bold("reason:")} ${item.reason}`,
|
|
2612
|
+
`${pc13.bold("confidence:")} ${item.confidence.toFixed(2)}`,
|
|
2613
|
+
`${pc13.bold("triple:")} ${item.subject} \xB7 ${item.predicate} \xB7 ${item.object}`
|
|
2382
2614
|
];
|
|
2383
|
-
if (item.paths.length > 0) lines.push(`${
|
|
2384
|
-
if (item.severity) lines.push(`${
|
|
2615
|
+
if (item.paths.length > 0) lines.push(`${pc13.bold("paths:")} ${item.paths.join(", ")}`);
|
|
2616
|
+
if (item.severity) lines.push(`${pc13.bold("severity:")} ${item.severity}`);
|
|
2385
2617
|
if (item.resolvesIncidents) {
|
|
2386
2618
|
lines.push(
|
|
2387
|
-
`${
|
|
2619
|
+
`${pc13.bold("resolves:")} ${item.resolvesIncidents.join(", ") || "(no candidate; approve with --incident <id>)"}`
|
|
2388
2620
|
);
|
|
2389
2621
|
}
|
|
2390
|
-
if (item.sensitive) lines.push(
|
|
2391
|
-
lines.push("", item.content, "",
|
|
2622
|
+
if (item.sensitive) lines.push(pc13.red(`\u26A0 sensitive: ${item.sensitiveReasons.join(", ")}`));
|
|
2623
|
+
lines.push("", item.content, "", pc13.dim(relative3(process.cwd(), item.path)));
|
|
2392
2624
|
return lines.join("\n");
|
|
2393
2625
|
}
|
|
2394
2626
|
function classColor(memoryClass) {
|
|
2395
2627
|
switch (memoryClass) {
|
|
2396
2628
|
case "incident":
|
|
2397
|
-
return
|
|
2629
|
+
return pc13.red(memoryClass);
|
|
2398
2630
|
case "decision":
|
|
2399
|
-
return
|
|
2631
|
+
return pc13.cyan(memoryClass);
|
|
2400
2632
|
case "fact":
|
|
2401
|
-
return
|
|
2633
|
+
return pc13.green(memoryClass);
|
|
2402
2634
|
case "note":
|
|
2403
|
-
return
|
|
2635
|
+
return pc13.yellow(memoryClass);
|
|
2404
2636
|
default:
|
|
2405
|
-
return
|
|
2637
|
+
return pc13.dim(memoryClass);
|
|
2406
2638
|
}
|
|
2407
2639
|
}
|
|
2408
2640
|
function truncate(value, max) {
|
|
@@ -2422,7 +2654,7 @@ function openEditor(path) {
|
|
|
2422
2654
|
import { existsSync } from "fs";
|
|
2423
2655
|
import { join as join5 } from "path";
|
|
2424
2656
|
import { spinner as spinner2 } from "@clack/prompts";
|
|
2425
|
-
import
|
|
2657
|
+
import pc14 from "picocolors";
|
|
2426
2658
|
|
|
2427
2659
|
// evals/fact-relevance.json
|
|
2428
2660
|
var fact_relevance_default = {
|
|
@@ -2686,9 +2918,9 @@ async function calibrateEmbedder(embed) {
|
|
|
2686
2918
|
var EMBEDDERS = ["local", "huggingface", "openrouter"];
|
|
2687
2919
|
var RERANKERS = ["off", "local", "huggingface", "openrouter"];
|
|
2688
2920
|
var KEY_ENV2 = { huggingface: "HF_TOKEN", openrouter: "OPENROUTER_API_KEY" };
|
|
2689
|
-
var ok = (text3) => `${
|
|
2690
|
-
var warn = (text3) => `${
|
|
2691
|
-
var row = (label, value) => ` ${
|
|
2921
|
+
var ok = (text3) => `${pc14.green("\u2713")} ${text3}`;
|
|
2922
|
+
var warn = (text3) => `${pc14.yellow("!")} ${text3}`;
|
|
2923
|
+
var row = (label, value) => ` ${pc14.dim(label.padEnd(11))}${value}`;
|
|
2692
2924
|
async function withSpinner(label, work, done) {
|
|
2693
2925
|
const progress = spinner2();
|
|
2694
2926
|
progress.start(label);
|
|
@@ -2697,7 +2929,7 @@ async function withSpinner(label, work, done) {
|
|
|
2697
2929
|
progress.stop(done(value));
|
|
2698
2930
|
return value;
|
|
2699
2931
|
} catch (error) {
|
|
2700
|
-
progress.error(
|
|
2932
|
+
progress.error(pc14.red(`\u2717 ${label}`));
|
|
2701
2933
|
throw error;
|
|
2702
2934
|
}
|
|
2703
2935
|
}
|
|
@@ -2736,8 +2968,8 @@ async function printStatus() {
|
|
|
2736
2968
|
const model = provider.type === "local" ? LOCAL_EMBEDDING_MODEL.id : provider.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[provider.type];
|
|
2737
2969
|
const floor = provider.relevanceFloor ?? getModelDefinition(model)?.relevanceFloor;
|
|
2738
2970
|
const next = [];
|
|
2739
|
-
console.log(
|
|
2740
|
-
console.log(row("provider", `${provider.type} ${
|
|
2971
|
+
console.log(pc14.bold("Embeddings"));
|
|
2972
|
+
console.log(row("provider", `${provider.type} ${pc14.dim(model)}`));
|
|
2741
2973
|
if (provider.type === "local" && !localRuntimeInstalled()) {
|
|
2742
2974
|
console.log(
|
|
2743
2975
|
row("runtime", warn("@huggingface/transformers is not installed: dense recall is off"))
|
|
@@ -2748,7 +2980,7 @@ async function printStatus() {
|
|
|
2748
2980
|
console.log(
|
|
2749
2981
|
row(
|
|
2750
2982
|
"model",
|
|
2751
|
-
ready ? ok(`downloaded ${
|
|
2983
|
+
ready ? ok(`downloaded ${pc14.dim(modelCacheDir())}`) : warn("not downloaded yet (~90 MB)")
|
|
2752
2984
|
)
|
|
2753
2985
|
);
|
|
2754
2986
|
if (!ready) next.push("codebuddy models pull");
|
|
@@ -2765,21 +2997,21 @@ async function printStatus() {
|
|
|
2765
2997
|
console.log(
|
|
2766
2998
|
row(
|
|
2767
2999
|
"floor",
|
|
2768
|
-
floor !== void 0 ? `${floor} ${
|
|
3000
|
+
floor !== void 0 ? `${floor} ${pc14.dim(provider.relevanceFloor !== void 0 ? "(calibrated for this project)" : "(built-in)")}` : warn("not calibrated: dense recall is off for this model")
|
|
2769
3001
|
)
|
|
2770
3002
|
);
|
|
2771
3003
|
if (floor === void 0) next.push("codebuddy models calibrate");
|
|
2772
3004
|
const rerank = file.rerank?.provider ?? "off";
|
|
2773
|
-
console.log(
|
|
3005
|
+
console.log(pc14.bold("Reranker"));
|
|
2774
3006
|
if (rerank === "off") {
|
|
2775
3007
|
console.log(
|
|
2776
|
-
row("provider", `off ${
|
|
3008
|
+
row("provider", `off ${pc14.dim("(default: no reranker beat dense order in evals)")}`)
|
|
2777
3009
|
);
|
|
2778
3010
|
} else {
|
|
2779
3011
|
console.log(
|
|
2780
3012
|
row(
|
|
2781
3013
|
"provider",
|
|
2782
|
-
`${rerank} ${
|
|
3014
|
+
`${rerank} ${pc14.dim(file.rerank?.model ?? DEFAULT_RERANK_MODELS[rerank].id)}`
|
|
2783
3015
|
)
|
|
2784
3016
|
);
|
|
2785
3017
|
if (rerank !== "local") {
|
|
@@ -2792,7 +3024,11 @@ async function printStatus() {
|
|
|
2792
3024
|
);
|
|
2793
3025
|
}
|
|
2794
3026
|
}
|
|
2795
|
-
|
|
3027
|
+
{
|
|
3028
|
+
const { cardProgress } = await import("./summaries-QXWFFDUZ.js");
|
|
3029
|
+
await printLocalLlmStatus(await cardProgress(process.cwd()).catch(() => null));
|
|
3030
|
+
}
|
|
3031
|
+
console.log(pc14.bold("Memory index"));
|
|
2796
3032
|
if (process.env.CODEBUDDY_SEMANTIC === "0") {
|
|
2797
3033
|
console.log(row("recall", warn("CODEBUDDY_SEMANTIC=0: lexical only")));
|
|
2798
3034
|
}
|
|
@@ -2813,7 +3049,7 @@ async function printStatus() {
|
|
|
2813
3049
|
console.log(
|
|
2814
3050
|
row(
|
|
2815
3051
|
"vectors",
|
|
2816
|
-
`${counts.ready} ready` + (counts.pending ? `, ${counts.pending} pending` : "") + (counts.failed ?
|
|
3052
|
+
`${counts.ready} ready` + (counts.pending ? `, ${counts.pending} pending` : "") + (counts.failed ? pc14.red(`, ${counts.failed} failed`) : "") + (counts.other ? pc14.yellow(`, ${counts.other} from another model`) : "")
|
|
2817
3053
|
)
|
|
2818
3054
|
);
|
|
2819
3055
|
if (counts.pending || counts.other)
|
|
@@ -2822,9 +3058,9 @@ async function printStatus() {
|
|
|
2822
3058
|
}
|
|
2823
3059
|
console.log(
|
|
2824
3060
|
next.length > 0 ? `
|
|
2825
|
-
${
|
|
2826
|
-
${next.map((step) => ` ${
|
|
2827
|
-
${ok("Semantic recall is ready.")} ${
|
|
3061
|
+
${pc14.bold("Next")}
|
|
3062
|
+
${next.map((step) => ` ${pc14.cyan(step)}`).join("\n")}` : `
|
|
3063
|
+
${ok("Semantic recall is ready.")} ${pc14.dim('Try: codebuddy context preview "<task>"')}`
|
|
2828
3064
|
);
|
|
2829
3065
|
}
|
|
2830
3066
|
function registerModelsCommands(program, runSafely2) {
|
|
@@ -2861,27 +3097,27 @@ Examples:
|
|
|
2861
3097
|
...opts.floor !== void 0 ? { relevanceFloor: opts.floor } : {}
|
|
2862
3098
|
});
|
|
2863
3099
|
const model = type === "local" ? LOCAL_EMBEDDING_MODEL.id : opts.model ?? DEFAULT_CLOUD_EMBEDDING_MODELS[type];
|
|
2864
|
-
console.log(ok(`Embeddings: ${type} ${
|
|
3100
|
+
console.log(ok(`Embeddings: ${type} ${pc14.dim(model)}`));
|
|
2865
3101
|
const missingKey = type !== "local" && !keyPresent(type, current?.apiKey);
|
|
2866
3102
|
if (missingKey) {
|
|
2867
3103
|
console.log(
|
|
2868
3104
|
warn(
|
|
2869
|
-
`Set ${KEY_ENV2[type]} in your shell (keys are never stored in config), then run ${
|
|
3105
|
+
`Set ${KEY_ENV2[type]} in your shell (keys are never stored in config), then run ${pc14.cyan("codebuddy hooks sync")}.`
|
|
2870
3106
|
)
|
|
2871
3107
|
);
|
|
2872
|
-
console.log(
|
|
3108
|
+
console.log(pc14.dim(" Existing vectors keep working until the new model can embed."));
|
|
2873
3109
|
}
|
|
2874
3110
|
if (opts.floor === void 0 && getModelDefinition(model)?.relevanceFloor === void 0) {
|
|
2875
3111
|
console.log(
|
|
2876
3112
|
warn(
|
|
2877
|
-
`No floor for ${model} yet: run ${
|
|
3113
|
+
`No floor for ${model} yet: run ${pc14.cyan("codebuddy models calibrate")} to turn on dense recall.`
|
|
2878
3114
|
)
|
|
2879
3115
|
);
|
|
2880
3116
|
}
|
|
2881
3117
|
if (!missingKey) {
|
|
2882
3118
|
syncInBackground(process.cwd(), true);
|
|
2883
3119
|
console.log(
|
|
2884
|
-
|
|
3120
|
+
pc14.dim(" Re-embedding existing memory in the background (codebuddy hooks sync).")
|
|
2885
3121
|
);
|
|
2886
3122
|
}
|
|
2887
3123
|
});
|
|
@@ -2908,13 +3144,36 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
|
|
|
2908
3144
|
}
|
|
2909
3145
|
const reranker = choice;
|
|
2910
3146
|
console.log(
|
|
2911
|
-
ok(`Reranker: ${reranker} ${
|
|
3147
|
+
ok(`Reranker: ${reranker} ${pc14.dim(opts.model ?? DEFAULT_RERANK_MODELS[reranker].id)}`)
|
|
2912
3148
|
);
|
|
2913
3149
|
if (reranker !== "local" && !keyPresent(reranker, (await readConfigFile()).provider?.apiKey)) {
|
|
2914
3150
|
console.log(warn(`Set ${KEY_ENV2[reranker]}; without it reranking is skipped.`));
|
|
2915
3151
|
}
|
|
2916
3152
|
if (reranker === "local")
|
|
2917
|
-
console.log(
|
|
3153
|
+
console.log(pc14.dim(" Run codebuddy models pull to download it now."));
|
|
3154
|
+
});
|
|
3155
|
+
});
|
|
3156
|
+
models.command("add").argument("<name>", "Optional model to install: local-llm.").option("--size <size>", `Local LLM size: ${LOCAL_LLM_SIZES.join(" | ")}.`, "default").option("-y, --yes", "Install the runtime without asking.").description(
|
|
3157
|
+
"Download the optional local LLM (Qwen3-0.6B by default) that writes file cards and index facts."
|
|
3158
|
+
).action(async (name2, opts) => {
|
|
3159
|
+
await runSafely2(async () => {
|
|
3160
|
+
if (name2 !== "local-llm") throw new Error(`Unknown model "${name2}"; try: local-llm.`);
|
|
3161
|
+
if (!LOCAL_LLM_SIZES.includes(opts.size)) {
|
|
3162
|
+
throw new Error(`--size must be one of ${LOCAL_LLM_SIZES.join(", ")}.`);
|
|
3163
|
+
}
|
|
3164
|
+
const added = await addLocalLlm({
|
|
3165
|
+
size: opts.size,
|
|
3166
|
+
...opts.yes ? { yes: true } : {}
|
|
3167
|
+
});
|
|
3168
|
+
if (!added) process.exitCode = 1;
|
|
3169
|
+
});
|
|
3170
|
+
});
|
|
3171
|
+
models.command("remove").argument("<name>", "Optional model to remove: local-llm.").description(
|
|
3172
|
+
"Delete the local LLM weights; CodeBuddy falls back to keyword/vector-only behaviour."
|
|
3173
|
+
).action(async (name2) => {
|
|
3174
|
+
await runSafely2(async () => {
|
|
3175
|
+
if (name2 !== "local-llm") throw new Error(`Unknown model "${name2}"; try: local-llm.`);
|
|
3176
|
+
await removeLocalLlm();
|
|
2918
3177
|
});
|
|
2919
3178
|
});
|
|
2920
3179
|
models.command("pull").description("Download the local models now instead of on first use.").action(async () => {
|
|
@@ -2925,7 +3184,7 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
|
|
|
2925
3184
|
await withSpinner(
|
|
2926
3185
|
`Downloading ${LOCAL_EMBEDDING_MODEL.id}`,
|
|
2927
3186
|
() => new LocalModelProvider().embed({ input: "warm up" }),
|
|
2928
|
-
() => ok(`${LOCAL_EMBEDDING_MODEL.id} ready ${
|
|
3187
|
+
() => ok(`${LOCAL_EMBEDDING_MODEL.id} ready ${pc14.dim(`${Date.now() - started} ms`)}`)
|
|
2929
3188
|
);
|
|
2930
3189
|
if (file.rerank?.provider === "local") {
|
|
2931
3190
|
const id = file.rerank.model ?? DEFAULT_RERANK_MODELS.local.id;
|
|
@@ -2934,10 +3193,10 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
|
|
|
2934
3193
|
await withSpinner(
|
|
2935
3194
|
`Downloading ${id}`,
|
|
2936
3195
|
() => rerankLocally({ id, ...revision ? { revision } : {} }, "warm", ["up"]),
|
|
2937
|
-
() => ok(`${id} ready ${
|
|
3196
|
+
() => ok(`${id} ready ${pc14.dim(`${Date.now() - started} ms`)}`)
|
|
2938
3197
|
);
|
|
2939
3198
|
}
|
|
2940
|
-
console.log(
|
|
3199
|
+
console.log(pc14.dim(` Cache: ${modelCacheDir()}`));
|
|
2941
3200
|
});
|
|
2942
3201
|
});
|
|
2943
3202
|
models.command("calibrate").option("--dry-run", "Measure only; do not save the floor.").description(
|
|
@@ -2955,19 +3214,19 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
|
|
|
2955
3214
|
console.log(
|
|
2956
3215
|
row(
|
|
2957
3216
|
"ranking",
|
|
2958
|
-
`AUC ${result.auc.toFixed(3)} \xB7 right fact first ${pct(result.recallAt1)} ${
|
|
3217
|
+
`AUC ${result.auc.toFixed(3)} \xB7 right fact first ${pct(result.recallAt1)} ${pc14.dim("(held-out tasks)")}`
|
|
2959
3218
|
)
|
|
2960
3219
|
);
|
|
2961
3220
|
console.log(
|
|
2962
3221
|
row(
|
|
2963
3222
|
"floor",
|
|
2964
|
-
`${result.best.floor}: precision ${pct(result.test.precision)}, recall ${pct(result.test.recall)}, silent on ${pct(result.test.abstain)} of unrelated tasks ${
|
|
3223
|
+
`${result.best.floor}: precision ${pct(result.test.precision)}, recall ${pct(result.test.recall)}, silent on ${pct(result.test.abstain)} of unrelated tasks ${pc14.dim("(chosen on dev, measured on test)")}`
|
|
2965
3224
|
)
|
|
2966
3225
|
);
|
|
2967
3226
|
console.log(
|
|
2968
3227
|
row(
|
|
2969
3228
|
"recall floor",
|
|
2970
|
-
`${result.recall.dev.floor}: precision ${pct(result.recall.test.precision)}, recall ${pct(result.recall.test.recall)} ` +
|
|
3229
|
+
`${result.recall.dev.floor}: precision ${pct(result.recall.test.precision)}, recall ${pct(result.recall.test.recall)} ` + pc14.dim("(explicit recall: best F2 on dev, measured on test)")
|
|
2971
3230
|
)
|
|
2972
3231
|
);
|
|
2973
3232
|
if (opts.dryRun) return;
|
|
@@ -2986,10 +3245,238 @@ before keeping one on. A reranker never admits a fact, it only orders them.`
|
|
|
2986
3245
|
});
|
|
2987
3246
|
}
|
|
2988
3247
|
|
|
2989
|
-
// src/cli/commands/
|
|
3248
|
+
// src/cli/commands/output.ts
|
|
2990
3249
|
import { spawn as spawn5 } from "child_process";
|
|
3250
|
+
import { constants as constants2 } from "os";
|
|
3251
|
+
import pc15 from "picocolors";
|
|
3252
|
+
|
|
3253
|
+
// src/output/reduce.ts
|
|
3254
|
+
import { createHash } from "crypto";
|
|
3255
|
+
import { mkdir as mkdir2, readdir, readFile as readFile3, rm as rm2 } from "fs/promises";
|
|
3256
|
+
import { join as join6 } from "path";
|
|
3257
|
+
var OUTPUT_LIMITS = {
|
|
3258
|
+
/** At or below either, output is never reduced. */
|
|
3259
|
+
minLines: 80,
|
|
3260
|
+
minBytes: 6e3,
|
|
3261
|
+
head: 20,
|
|
3262
|
+
tail: 30,
|
|
3263
|
+
contextBefore: 2,
|
|
3264
|
+
contextAfter: 4,
|
|
3265
|
+
/** A reduction must save at least this share, or the original is printed. */
|
|
3266
|
+
minSaving: 0.3,
|
|
3267
|
+
/** Stored bytes per run; beyond this the stored copy says it was cut. */
|
|
3268
|
+
storeBytes: 2e7,
|
|
3269
|
+
retentionDays: 7,
|
|
3270
|
+
/** Lines one `output show` may print. */
|
|
3271
|
+
showLines: 400
|
|
3272
|
+
};
|
|
3273
|
+
var SIGNAL = /\b(error|errors|fail|failed|failure|failing|exception|traceback|panic|fatal|assert(?:ion)?|warn(?:ing)?|denied|not found|undefined|segfault|timeout|timed out)\b|✗|×|✖|FAIL|ERR!/i;
|
|
3274
|
+
function outputDir(root) {
|
|
3275
|
+
return join6(root, ".codebuddy", "cache", "outputs");
|
|
3276
|
+
}
|
|
3277
|
+
var shape = (line) => line.replace(/\d+/g, "#").trim();
|
|
3278
|
+
function reduceOutput(text3, id) {
|
|
3279
|
+
const lines = text3.split("\n");
|
|
3280
|
+
if (lines.length <= OUTPUT_LIMITS.minLines || Buffer.byteLength(text3) <= OUTPUT_LIMITS.minBytes) {
|
|
3281
|
+
return null;
|
|
3282
|
+
}
|
|
3283
|
+
if (text3.includes("\0")) return null;
|
|
3284
|
+
const keep = /* @__PURE__ */ new Set();
|
|
3285
|
+
for (let i = 0; i < Math.min(OUTPUT_LIMITS.head, lines.length); i++) keep.add(i);
|
|
3286
|
+
for (let i = Math.max(0, lines.length - OUTPUT_LIMITS.tail); i < lines.length; i++) keep.add(i);
|
|
3287
|
+
lines.forEach((line, index2) => {
|
|
3288
|
+
if (!SIGNAL.test(line)) return;
|
|
3289
|
+
const from = Math.max(0, index2 - OUTPUT_LIMITS.contextBefore);
|
|
3290
|
+
const to = Math.min(lines.length - 1, index2 + OUTPUT_LIMITS.contextAfter);
|
|
3291
|
+
for (let i = from; i <= to; i++) keep.add(i);
|
|
3292
|
+
});
|
|
3293
|
+
const out = [];
|
|
3294
|
+
let index = 0;
|
|
3295
|
+
while (index < lines.length) {
|
|
3296
|
+
if (!keep.has(index)) {
|
|
3297
|
+
const start = index;
|
|
3298
|
+
while (index < lines.length && !keep.has(index)) index++;
|
|
3299
|
+
out.push(
|
|
3300
|
+
`\u2026 ${index - start} lines omitted (codebuddy output show ${id} --range ${start + 1}:${index})`
|
|
3301
|
+
);
|
|
3302
|
+
continue;
|
|
3303
|
+
}
|
|
3304
|
+
const line = lines[index];
|
|
3305
|
+
const same = SIGNAL.test(line) ? (other) => other === line : (other) => shape(other) === shape(line);
|
|
3306
|
+
let run = index + 1;
|
|
3307
|
+
while (run < lines.length && keep.has(run) && same(lines[run])) run++;
|
|
3308
|
+
out.push(line);
|
|
3309
|
+
if (run - index > 2) {
|
|
3310
|
+
out.push(
|
|
3311
|
+
`\u2026 ${run - index - 1} similar lines (codebuddy output show ${id} --range ${index + 2}:${run})`
|
|
3312
|
+
);
|
|
3313
|
+
index = run;
|
|
3314
|
+
} else index++;
|
|
3315
|
+
}
|
|
3316
|
+
const reduced = out.join("\n");
|
|
3317
|
+
return Buffer.byteLength(reduced) <= Buffer.byteLength(text3) * (1 - OUTPUT_LIMITS.minSaving) ? reduced : null;
|
|
3318
|
+
}
|
|
3319
|
+
async function storeOutput(root, command, output, exitCode, now = /* @__PURE__ */ new Date()) {
|
|
3320
|
+
const truncated = Buffer.byteLength(output) > OUTPUT_LIMITS.storeBytes;
|
|
3321
|
+
const body = truncated ? `${output.slice(0, OUTPUT_LIMITS.storeBytes)}
|
|
3322
|
+
[codebuddy: stored copy cut at ${OUTPUT_LIMITS.storeBytes} bytes]` : output;
|
|
3323
|
+
const id = createHash("sha256").update(`${command}\0${now.toISOString()}\0${body}`).digest("hex").slice(0, 12);
|
|
3324
|
+
const meta = {
|
|
3325
|
+
id,
|
|
3326
|
+
command,
|
|
3327
|
+
exitCode,
|
|
3328
|
+
createdAt: now.toISOString(),
|
|
3329
|
+
expiresAt: new Date(now.getTime() + OUTPUT_LIMITS.retentionDays * 864e5).toISOString(),
|
|
3330
|
+
lines: body.split("\n").length,
|
|
3331
|
+
bytes: Buffer.byteLength(body),
|
|
3332
|
+
truncated
|
|
3333
|
+
};
|
|
3334
|
+
const dir = outputDir(root);
|
|
3335
|
+
await mkdir2(dir, { recursive: true, mode: 448 });
|
|
3336
|
+
await writeAtomic(join6(dir, `${id}.log`), body, { mode: 384 });
|
|
3337
|
+
await writeAtomic(join6(dir, `${id}.json`), `${JSON.stringify(meta)}
|
|
3338
|
+
`, { mode: 384 });
|
|
3339
|
+
await pruneOutputs(root, now).catch(() => void 0);
|
|
3340
|
+
return meta;
|
|
3341
|
+
}
|
|
3342
|
+
async function pruneOutputs(root, now = /* @__PURE__ */ new Date()) {
|
|
3343
|
+
const dir = outputDir(root);
|
|
3344
|
+
let removed = 0;
|
|
3345
|
+
for (const name2 of (await readdir(dir).catch(() => [])).filter(
|
|
3346
|
+
(n) => n.endsWith(".json")
|
|
3347
|
+
)) {
|
|
3348
|
+
const meta = await readMeta(root, name2.slice(0, -5));
|
|
3349
|
+
if (meta && Date.parse(meta.expiresAt) <= now.getTime()) {
|
|
3350
|
+
await rm2(join6(dir, `${meta.id}.log`), { force: true });
|
|
3351
|
+
await rm2(join6(dir, name2), { force: true });
|
|
3352
|
+
removed++;
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
return removed;
|
|
3356
|
+
}
|
|
3357
|
+
async function readMeta(root, id) {
|
|
3358
|
+
if (!/^[a-f0-9]{12}$/.test(id)) return null;
|
|
3359
|
+
try {
|
|
3360
|
+
return JSON.parse(await readFile3(join6(outputDir(root), `${id}.json`), "utf8"));
|
|
3361
|
+
} catch {
|
|
3362
|
+
return null;
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
async function showOutput(root, id, options = {}) {
|
|
3366
|
+
const meta = await readMeta(root, id);
|
|
3367
|
+
if (!meta) return { status: "missing", id };
|
|
3368
|
+
if (Date.parse(meta.expiresAt) <= (options.now ?? /* @__PURE__ */ new Date()).getTime()) {
|
|
3369
|
+
return { status: "expired", id };
|
|
3370
|
+
}
|
|
3371
|
+
const body = await readFile3(join6(outputDir(root), `${id}.log`), "utf8").catch(() => null);
|
|
3372
|
+
if (body === null) return { status: "missing", id };
|
|
3373
|
+
const lines = body.split("\n");
|
|
3374
|
+
let picked;
|
|
3375
|
+
if (options.grep) {
|
|
3376
|
+
const pattern = new RegExp(options.grep, "i");
|
|
3377
|
+
const set = /* @__PURE__ */ new Set();
|
|
3378
|
+
lines.forEach((line, index) => {
|
|
3379
|
+
if (!pattern.test(line)) return;
|
|
3380
|
+
for (let i = Math.max(0, index - 2); i <= Math.min(lines.length - 1, index + 2); i++)
|
|
3381
|
+
set.add(i);
|
|
3382
|
+
});
|
|
3383
|
+
picked = [...set].sort((a, b) => a - b);
|
|
3384
|
+
} else {
|
|
3385
|
+
const [from, to] = options.range ?? [1, lines.length];
|
|
3386
|
+
picked = [];
|
|
3387
|
+
for (let i = Math.max(1, from); i <= Math.min(lines.length, to); i++) picked.push(i - 1);
|
|
3388
|
+
}
|
|
3389
|
+
const shown = picked.slice(0, OUTPUT_LIMITS.showLines);
|
|
3390
|
+
return {
|
|
3391
|
+
status: "ok",
|
|
3392
|
+
meta,
|
|
3393
|
+
text: shown.map((i) => `${i + 1}: ${lines[i]}`).join("\n"),
|
|
3394
|
+
more: picked.length > shown.length
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
async function listOutputs(root) {
|
|
3398
|
+
const names = await readdir(outputDir(root)).catch(() => []);
|
|
3399
|
+
const metas = await Promise.all(
|
|
3400
|
+
names.filter((n) => n.endsWith(".json")).map((n) => readMeta(root, n.slice(0, -5)))
|
|
3401
|
+
);
|
|
3402
|
+
return metas.filter((meta) => meta !== null).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
// src/cli/commands/output.ts
|
|
3406
|
+
function registerOutputCommands(program, runSafely2) {
|
|
3407
|
+
program.command("run").argument("<command...>", "The command and its arguments (after --).").description(
|
|
3408
|
+
"Run a command; print a reduced view of long output and keep the original for `codebuddy output show`."
|
|
3409
|
+
).action(async (parts) => {
|
|
3410
|
+
await runSafely2(async () => {
|
|
3411
|
+
const command = parts.join(" ");
|
|
3412
|
+
const { output: output2, exitCode } = await capture(parts);
|
|
3413
|
+
const root = process.cwd();
|
|
3414
|
+
const provisional = reduceOutput(output2, "xxxxxxxxxxxx");
|
|
3415
|
+
if (provisional === null) {
|
|
3416
|
+
process.stdout.write(output2);
|
|
3417
|
+
} else {
|
|
3418
|
+
const stored = await storeOutput(root, command, output2, exitCode);
|
|
3419
|
+
const reduced = reduceOutput(output2, stored.id) ?? output2;
|
|
3420
|
+
process.stdout.write(`${reduced}
|
|
3421
|
+
`);
|
|
3422
|
+
process.stdout.write(
|
|
3423
|
+
`[codebuddy run: exit ${exitCode ?? "signal"}; ${stored.lines} lines shown as ${reduced.split("\n").length}; full output: codebuddy output show ${stored.id} [--grep RE | --range A:B], kept until ${stored.expiresAt.slice(0, 10)}]
|
|
3424
|
+
`
|
|
3425
|
+
);
|
|
3426
|
+
}
|
|
3427
|
+
process.exitCode = exitCode ?? 1;
|
|
3428
|
+
});
|
|
3429
|
+
});
|
|
3430
|
+
const output = program.command("output").description("Stored originals of `codebuddy run` output.");
|
|
3431
|
+
output.command("list", { isDefault: true }).description("List stored outputs, newest first.").action(async () => {
|
|
3432
|
+
await runSafely2(async () => {
|
|
3433
|
+
const stored = await listOutputs(process.cwd());
|
|
3434
|
+
if (stored.length === 0) console.log(pc15.dim("no stored outputs"));
|
|
3435
|
+
for (const meta of stored) {
|
|
3436
|
+
console.log(
|
|
3437
|
+
`${meta.id} exit ${meta.exitCode ?? "-"} ${meta.lines} lines until ${meta.expiresAt.slice(0, 10)} ${meta.command.slice(0, 80)}`
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
});
|
|
3441
|
+
});
|
|
3442
|
+
output.command("show").argument("<id>").option("--range <a:b>", "1-based inclusive line range.").option("--grep <pattern>", "Lines matching a regular expression, with 2 lines of context.").description("Print exact stored lines (at most 400 per call).").action(async (id, opts) => {
|
|
3443
|
+
await runSafely2(async () => {
|
|
3444
|
+
const range = opts.range?.match(/^(\d+):(\d+)$/);
|
|
3445
|
+
if (opts.range && !range) throw new Error("--range must look like 120:180");
|
|
3446
|
+
const result = await showOutput(process.cwd(), id, {
|
|
3447
|
+
...range ? { range: [Number(range[1]), Number(range[2])] } : {},
|
|
3448
|
+
...opts.grep ? { grep: opts.grep } : {}
|
|
3449
|
+
});
|
|
3450
|
+
if (result.status !== "ok") {
|
|
3451
|
+
throw new Error(`stored output ${id} is ${result.status}`);
|
|
3452
|
+
}
|
|
3453
|
+
console.log(result.text);
|
|
3454
|
+
if (result.more) console.log(pc15.dim("\u2026 more lines; narrow --range or --grep"));
|
|
3455
|
+
});
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3458
|
+
function capture(parts) {
|
|
3459
|
+
return new Promise((resolve6, reject) => {
|
|
3460
|
+
const [file, ...args] = parts;
|
|
3461
|
+
const child = parts.length === 1 ? spawn5(file, { shell: true, stdio: ["inherit", "pipe", "pipe"] }) : spawn5(file, args, { stdio: ["inherit", "pipe", "pipe"] });
|
|
3462
|
+
const chunks = [];
|
|
3463
|
+
child.stdout.on("data", (chunk) => chunks.push(chunk));
|
|
3464
|
+
child.stderr.on("data", (chunk) => chunks.push(chunk));
|
|
3465
|
+
child.on("error", reject);
|
|
3466
|
+
child.on(
|
|
3467
|
+
"close",
|
|
3468
|
+
(code, signal) => resolve6({
|
|
3469
|
+
output: Buffer.concat(chunks).toString("utf8"),
|
|
3470
|
+
exitCode: code ?? (signal ? 128 + (constants2.signals[signal] ?? 0) : null)
|
|
3471
|
+
})
|
|
3472
|
+
);
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
// src/cli/commands/plan.ts
|
|
3477
|
+
import { spawn as spawn6 } from "child_process";
|
|
2991
3478
|
import { relative as relative4 } from "path";
|
|
2992
|
-
import
|
|
3479
|
+
import pc16 from "picocolors";
|
|
2993
3480
|
function makeLifecycle(cwd = process.cwd()) {
|
|
2994
3481
|
const store = new PlanFileStore(cwd);
|
|
2995
3482
|
return { store, lifecycle: new PlanLifecycle(store) };
|
|
@@ -2997,67 +3484,67 @@ function makeLifecycle(cwd = process.cwd()) {
|
|
|
2997
3484
|
function statusColor(status) {
|
|
2998
3485
|
switch (status) {
|
|
2999
3486
|
case "draft":
|
|
3000
|
-
return
|
|
3487
|
+
return pc16.dim(status);
|
|
3001
3488
|
case "approved":
|
|
3002
|
-
return
|
|
3489
|
+
return pc16.cyan(status);
|
|
3003
3490
|
case "executing":
|
|
3004
|
-
return
|
|
3491
|
+
return pc16.yellow(status);
|
|
3005
3492
|
case "complete":
|
|
3006
|
-
return
|
|
3493
|
+
return pc16.green(status);
|
|
3007
3494
|
case "abandoned":
|
|
3008
|
-
return
|
|
3495
|
+
return pc16.red(status);
|
|
3009
3496
|
}
|
|
3010
3497
|
}
|
|
3011
3498
|
function renderPlan(plan) {
|
|
3012
3499
|
const lines = [];
|
|
3013
|
-
lines.push(`${
|
|
3014
|
-
lines.push(
|
|
3500
|
+
lines.push(`${pc16.bold(plan.title)} ${statusColor(plan.status)}`);
|
|
3501
|
+
lines.push(pc16.dim(plan.id));
|
|
3015
3502
|
lines.push("");
|
|
3016
|
-
lines.push(`${
|
|
3503
|
+
lines.push(`${pc16.bold("Brief:")} ${plan.brief}`);
|
|
3017
3504
|
if (plan.filesToTouch.length > 0) {
|
|
3018
3505
|
lines.push("");
|
|
3019
|
-
lines.push(
|
|
3506
|
+
lines.push(pc16.bold("Files to touch:"));
|
|
3020
3507
|
for (const file of plan.filesToTouch) {
|
|
3021
3508
|
lines.push(` ${file.action.padEnd(6)} ${file.path}${file.notes ? ` \u2014 ${file.notes}` : ""}`);
|
|
3022
3509
|
}
|
|
3023
3510
|
}
|
|
3024
3511
|
if (plan.testsToAdd.length > 0) {
|
|
3025
3512
|
lines.push("");
|
|
3026
|
-
lines.push(
|
|
3513
|
+
lines.push(pc16.bold("Tests to add:"));
|
|
3027
3514
|
for (const test of plan.testsToAdd) {
|
|
3028
3515
|
lines.push(` ${test.path}${test.description ? ` \u2014 ${test.description}` : ""}`);
|
|
3029
3516
|
}
|
|
3030
3517
|
}
|
|
3031
3518
|
if (plan.outOfScope.length > 0) {
|
|
3032
3519
|
lines.push("");
|
|
3033
|
-
lines.push(
|
|
3520
|
+
lines.push(pc16.bold("Out of scope:"));
|
|
3034
3521
|
for (const item of plan.outOfScope) lines.push(` - ${item}`);
|
|
3035
3522
|
}
|
|
3036
3523
|
if (plan.risks.length > 0) {
|
|
3037
3524
|
lines.push("");
|
|
3038
|
-
lines.push(
|
|
3525
|
+
lines.push(pc16.bold("Risks:"));
|
|
3039
3526
|
for (const item of plan.risks) lines.push(` - ${item}`);
|
|
3040
3527
|
}
|
|
3041
3528
|
if (plan.commitSha) {
|
|
3042
3529
|
lines.push("");
|
|
3043
|
-
lines.push(`${
|
|
3530
|
+
lines.push(`${pc16.bold("Commit:")} ${plan.commitSha}`);
|
|
3044
3531
|
}
|
|
3045
3532
|
lines.push("");
|
|
3046
|
-
lines.push(
|
|
3533
|
+
lines.push(pc16.dim("\u2500".repeat(40)));
|
|
3047
3534
|
lines.push(plan.body);
|
|
3048
3535
|
return lines.join("\n");
|
|
3049
3536
|
}
|
|
3050
3537
|
function openEditor2(path) {
|
|
3051
3538
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
3052
3539
|
return new Promise((resolve6, reject) => {
|
|
3053
|
-
const child =
|
|
3540
|
+
const child = spawn6(editor, [path], { stdio: "inherit" });
|
|
3054
3541
|
child.on("error", reject);
|
|
3055
3542
|
child.on("close", () => resolve6());
|
|
3056
3543
|
});
|
|
3057
3544
|
}
|
|
3058
3545
|
function runGit(args) {
|
|
3059
3546
|
return new Promise((resolve6, reject) => {
|
|
3060
|
-
const child =
|
|
3547
|
+
const child = spawn6("git", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
3061
3548
|
let stdout = "";
|
|
3062
3549
|
let stderr = "";
|
|
3063
3550
|
child.stdout.on("data", (c) => {
|
|
@@ -3082,14 +3569,14 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3082
3569
|
title: opts.title ?? brief,
|
|
3083
3570
|
brief
|
|
3084
3571
|
});
|
|
3085
|
-
console.log(`${
|
|
3572
|
+
console.log(`${pc16.green("created")} ${created.id} (${statusColor(created.status)})`);
|
|
3086
3573
|
if (opts.edit !== false && process.stdin.isTTY) {
|
|
3087
3574
|
await openEditor2(created.path);
|
|
3088
3575
|
const { store } = makeLifecycle();
|
|
3089
3576
|
await store.readPlan(created.id);
|
|
3090
|
-
console.log(
|
|
3577
|
+
console.log(pc16.dim("saved and validated"));
|
|
3091
3578
|
}
|
|
3092
|
-
console.log(
|
|
3579
|
+
console.log(pc16.dim(relative4(process.cwd(), created.path)));
|
|
3093
3580
|
});
|
|
3094
3581
|
});
|
|
3095
3582
|
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) => {
|
|
@@ -3101,11 +3588,11 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3101
3588
|
else if (!opts.all)
|
|
3102
3589
|
plans = plans.filter((p) => p.status !== "complete" && p.status !== "abandoned");
|
|
3103
3590
|
if (plans.length === 0) {
|
|
3104
|
-
console.log(
|
|
3591
|
+
console.log(pc16.dim("no plans"));
|
|
3105
3592
|
return;
|
|
3106
3593
|
}
|
|
3107
3594
|
for (const p of plans) {
|
|
3108
|
-
console.log(`${statusColor(p.status).padEnd(20)} ${
|
|
3595
|
+
console.log(`${statusColor(p.status).padEnd(20)} ${pc16.bold(p.title)} ${pc16.dim(p.id)}`);
|
|
3109
3596
|
}
|
|
3110
3597
|
});
|
|
3111
3598
|
});
|
|
@@ -3121,7 +3608,7 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3121
3608
|
const existing = await store.readPlan(id);
|
|
3122
3609
|
await openEditor2(existing.path);
|
|
3123
3610
|
await store.readPlan(id);
|
|
3124
|
-
console.log(
|
|
3611
|
+
console.log(pc16.dim("saved and validated"));
|
|
3125
3612
|
});
|
|
3126
3613
|
});
|
|
3127
3614
|
plan.command("approve").argument("<id>").description("Promote a draft to approved (one active plan per namespace).").action(async (id) => {
|
|
@@ -3145,7 +3632,7 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3145
3632
|
...opts.commit ? { commitSha: opts.commit } : {},
|
|
3146
3633
|
...opts.notes ? { notes: opts.notes } : {}
|
|
3147
3634
|
});
|
|
3148
|
-
if (!opts.commit) console.log(
|
|
3635
|
+
if (!opts.commit) console.log(pc16.yellow("note: no --commit supplied"));
|
|
3149
3636
|
console.log(`${next.id} \u2192 ${statusColor(next.status)}`);
|
|
3150
3637
|
});
|
|
3151
3638
|
});
|
|
@@ -3164,7 +3651,7 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3164
3651
|
const args = ["diff", opts.from, ...opts.to ? [opts.to] : [], "--", relPath];
|
|
3165
3652
|
const result = await runGit(args);
|
|
3166
3653
|
if (result.stdout.trim().length === 0) {
|
|
3167
|
-
console.log(
|
|
3654
|
+
console.log(pc16.dim("no changes"));
|
|
3168
3655
|
} else {
|
|
3169
3656
|
process.stdout.write(result.stdout);
|
|
3170
3657
|
}
|
|
@@ -3181,13 +3668,13 @@ function registerPlanCommands(program, runSafely2) {
|
|
|
3181
3668
|
throw new Error(`Invalid policy "${mode}". Use one of: ${PLAN_POLICIES.join(", ")}.`);
|
|
3182
3669
|
}
|
|
3183
3670
|
await setPlanPolicy(mode);
|
|
3184
|
-
console.log(`${
|
|
3671
|
+
console.log(`${pc16.green("plan policy")} = ${mode}`);
|
|
3185
3672
|
});
|
|
3186
3673
|
});
|
|
3187
3674
|
}
|
|
3188
3675
|
|
|
3189
3676
|
// src/cli/commands/risk.ts
|
|
3190
|
-
import
|
|
3677
|
+
import pc17 from "picocolors";
|
|
3191
3678
|
function registerRiskCommands(program, runSafely2) {
|
|
3192
3679
|
const risk = program.command("risk").description("Assess evidence-backed change risk.");
|
|
3193
3680
|
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(
|
|
@@ -3215,31 +3702,31 @@ function parsePaths3(value) {
|
|
|
3215
3702
|
}
|
|
3216
3703
|
function printAssessment(assessment, explain) {
|
|
3217
3704
|
if (assessment.items.length === 0) {
|
|
3218
|
-
console.log(
|
|
3705
|
+
console.log(pc17.dim("no risk evidence found"));
|
|
3219
3706
|
return;
|
|
3220
3707
|
}
|
|
3221
3708
|
for (const item of assessment.items) {
|
|
3222
3709
|
console.log(
|
|
3223
|
-
`${
|
|
3710
|
+
`${pc17.bold(item.path)} score ${scoreColor2(item.score)} category ${pc17.cyan(item.category)}`
|
|
3224
3711
|
);
|
|
3225
3712
|
console.log(` ${item.reason}`);
|
|
3226
3713
|
if (explain) {
|
|
3227
3714
|
for (const evidence of item.evidence) console.log(` \u21B3 ${formatEvidence(evidence)}`);
|
|
3228
3715
|
} else {
|
|
3229
|
-
console.log(
|
|
3716
|
+
console.log(pc17.dim(` ${item.evidence.length} evidence item(s)`));
|
|
3230
3717
|
}
|
|
3231
3718
|
}
|
|
3232
3719
|
console.log(
|
|
3233
|
-
|
|
3720
|
+
pc17.dim(
|
|
3234
3721
|
`
|
|
3235
3722
|
${assessment.stats.itemsProduced} item(s), ${assessment.stats.signalsRun} signal(s), ${assessment.stats.pathsConsidered} path(s) considered`
|
|
3236
3723
|
)
|
|
3237
3724
|
);
|
|
3238
3725
|
}
|
|
3239
3726
|
function scoreColor2(score) {
|
|
3240
|
-
if (score >= 75) return
|
|
3241
|
-
if (score >= 50) return
|
|
3242
|
-
return
|
|
3727
|
+
if (score >= 75) return pc17.red(String(score));
|
|
3728
|
+
if (score >= 50) return pc17.yellow(String(score));
|
|
3729
|
+
return pc17.green(String(score));
|
|
3243
3730
|
}
|
|
3244
3731
|
function formatEvidence(evidence) {
|
|
3245
3732
|
switch (evidence.kind) {
|
|
@@ -3261,7 +3748,7 @@ function truncate2(value, max) {
|
|
|
3261
3748
|
|
|
3262
3749
|
// src/cli/commands/rules.ts
|
|
3263
3750
|
import { relative as relative5 } from "path";
|
|
3264
|
-
import
|
|
3751
|
+
import pc18 from "picocolors";
|
|
3265
3752
|
async function resolveMode(client, value) {
|
|
3266
3753
|
if (value) {
|
|
3267
3754
|
const mode = parseMode(value);
|
|
@@ -3301,7 +3788,7 @@ function registerRulesCommands(program, runSafely2) {
|
|
|
3301
3788
|
`${actionLabel(result.action)} ${clientDisplayName(client)} workflow in ${where}`
|
|
3302
3789
|
);
|
|
3303
3790
|
if (result.action !== "unchanged") {
|
|
3304
|
-
console.log(
|
|
3791
|
+
console.log(pc18.dim(" managed block between codebuddy:workflow markers; safe to re-run"));
|
|
3305
3792
|
}
|
|
3306
3793
|
});
|
|
3307
3794
|
});
|
|
@@ -3309,20 +3796,20 @@ function registerRulesCommands(program, runSafely2) {
|
|
|
3309
3796
|
function actionLabel(action) {
|
|
3310
3797
|
switch (action) {
|
|
3311
3798
|
case "created":
|
|
3312
|
-
return
|
|
3799
|
+
return pc18.green("created");
|
|
3313
3800
|
case "added":
|
|
3314
|
-
return
|
|
3801
|
+
return pc18.green("added");
|
|
3315
3802
|
case "updated":
|
|
3316
|
-
return
|
|
3803
|
+
return pc18.cyan("updated");
|
|
3317
3804
|
case "removed":
|
|
3318
|
-
return
|
|
3805
|
+
return pc18.yellow("removed");
|
|
3319
3806
|
default:
|
|
3320
|
-
return
|
|
3807
|
+
return pc18.dim("unchanged");
|
|
3321
3808
|
}
|
|
3322
3809
|
}
|
|
3323
3810
|
|
|
3324
3811
|
// src/cli/commands/suggest.ts
|
|
3325
|
-
import
|
|
3812
|
+
import pc19 from "picocolors";
|
|
3326
3813
|
function parsePaths4(value) {
|
|
3327
3814
|
return value.split(",").map((path) => path.trim()).filter(Boolean);
|
|
3328
3815
|
}
|
|
@@ -3344,7 +3831,7 @@ function registerSuggestCommand(program, runSafely2) {
|
|
|
3344
3831
|
}
|
|
3345
3832
|
printSuggestions(result.suggestions);
|
|
3346
3833
|
console.log(
|
|
3347
|
-
|
|
3834
|
+
pc19.dim(
|
|
3348
3835
|
`
|
|
3349
3836
|
${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} path(s)`
|
|
3350
3837
|
)
|
|
@@ -3355,72 +3842,72 @@ ${result.stats.produced} suggestion(s) across ${result.stats.pathsConsidered} pa
|
|
|
3355
3842
|
}
|
|
3356
3843
|
function printSuggestions(suggestions) {
|
|
3357
3844
|
if (suggestions.length === 0) {
|
|
3358
|
-
console.log(
|
|
3845
|
+
console.log(pc19.dim("no suggestions \u2014 nothing high-signal to report"));
|
|
3359
3846
|
return;
|
|
3360
3847
|
}
|
|
3361
3848
|
for (const suggestion of suggestions) {
|
|
3362
|
-
console.log(`${severityBadge2(suggestion.severity)} ${
|
|
3849
|
+
console.log(`${severityBadge2(suggestion.severity)} ${pc19.bold(suggestion.title)}`);
|
|
3363
3850
|
console.log(` ${suggestion.detail}`);
|
|
3364
3851
|
for (const evidence of suggestion.evidence) {
|
|
3365
|
-
console.log(
|
|
3852
|
+
console.log(pc19.dim(` \u21B3 ${evidence.kind}: ${evidence.detail}`));
|
|
3366
3853
|
}
|
|
3367
|
-
if (suggestion.commandHint) console.log(
|
|
3854
|
+
if (suggestion.commandHint) console.log(pc19.dim(` \u2192 ${suggestion.commandHint}`));
|
|
3368
3855
|
}
|
|
3369
3856
|
}
|
|
3370
3857
|
function severityBadge2(severity) {
|
|
3371
3858
|
switch (severity) {
|
|
3372
3859
|
case "high":
|
|
3373
|
-
return
|
|
3860
|
+
return pc19.red("[high]");
|
|
3374
3861
|
case "medium":
|
|
3375
|
-
return
|
|
3862
|
+
return pc19.yellow("[med ]");
|
|
3376
3863
|
case "low":
|
|
3377
|
-
return
|
|
3864
|
+
return pc19.cyan("[low ]");
|
|
3378
3865
|
default:
|
|
3379
|
-
return
|
|
3866
|
+
return pc19.dim("[info]");
|
|
3380
3867
|
}
|
|
3381
3868
|
}
|
|
3382
3869
|
|
|
3383
3870
|
// src/cli/commands/symbols.ts
|
|
3384
|
-
import { readFile as
|
|
3871
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
3385
3872
|
import { resolve as resolve5 } from "path";
|
|
3386
|
-
import
|
|
3873
|
+
import pc20 from "picocolors";
|
|
3387
3874
|
function registerSymbolsCommand(program, runSafely2) {
|
|
3388
3875
|
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) => {
|
|
3389
3876
|
await runSafely2(async () => {
|
|
3390
3877
|
if (!supportsSymbols(path)) {
|
|
3391
|
-
console.log(
|
|
3878
|
+
console.log(pc20.dim(`no symbol extraction for ${path} (unsupported language)`));
|
|
3392
3879
|
return;
|
|
3393
3880
|
}
|
|
3394
|
-
const content = await
|
|
3881
|
+
const content = await readFile4(resolve5(path), "utf8");
|
|
3395
3882
|
const table = extractSymbolTable(content, path);
|
|
3396
3883
|
if (opts.json) {
|
|
3397
3884
|
console.log(JSON.stringify({ path, symbols: table }, null, 2));
|
|
3398
3885
|
return;
|
|
3399
3886
|
}
|
|
3400
3887
|
if (table.length === 0) {
|
|
3401
|
-
console.log(
|
|
3888
|
+
console.log(pc20.dim("no top-level symbols found"));
|
|
3402
3889
|
return;
|
|
3403
3890
|
}
|
|
3404
3891
|
for (const symbol of table) {
|
|
3405
|
-
const badge = symbol.exported ?
|
|
3892
|
+
const badge = symbol.exported ? pc20.green("export") : pc20.dim("local ");
|
|
3406
3893
|
console.log(
|
|
3407
|
-
`${badge} ${
|
|
3894
|
+
`${badge} ${pc20.cyan(symbol.kind.padEnd(9))} ${pc20.bold(symbol.name)} ${pc20.dim(`L${symbol.startLine}-${symbol.endLine}`)}`
|
|
3408
3895
|
);
|
|
3409
3896
|
}
|
|
3410
|
-
console.log(
|
|
3897
|
+
console.log(pc20.dim(`
|
|
3411
3898
|
${table.length} symbol(s)`));
|
|
3412
3899
|
});
|
|
3413
3900
|
});
|
|
3414
3901
|
}
|
|
3415
3902
|
|
|
3416
3903
|
// src/cli/commands/telemetry.ts
|
|
3417
|
-
import
|
|
3904
|
+
import pc21 from "picocolors";
|
|
3418
3905
|
|
|
3419
3906
|
// src/telemetry/collector.ts
|
|
3420
|
-
import { createHash, timingSafeEqual } from "crypto";
|
|
3421
|
-
import { appendFile, mkdir as
|
|
3907
|
+
import { createHash as createHash2, timingSafeEqual } from "crypto";
|
|
3908
|
+
import { appendFile, mkdir as mkdir3, readdir as readdir2, readFile as readFile5 } from "fs/promises";
|
|
3422
3909
|
import { createServer as createServer2 } from "http";
|
|
3423
|
-
import { join as
|
|
3910
|
+
import { join as join7 } from "path";
|
|
3424
3911
|
import { z as z2 } from "zod";
|
|
3425
3912
|
|
|
3426
3913
|
// src/telemetry/admin-page.ts
|
|
@@ -3945,7 +4432,7 @@ var batchSchema = z2.object({
|
|
|
3945
4432
|
});
|
|
3946
4433
|
async function startCollector(options) {
|
|
3947
4434
|
const { dataDir } = options;
|
|
3948
|
-
await
|
|
4435
|
+
await mkdir3(dataDir, { recursive: true });
|
|
3949
4436
|
const registry = await loadRegistry(dataDir);
|
|
3950
4437
|
let summaryCache;
|
|
3951
4438
|
let npmCache;
|
|
@@ -3993,7 +4480,7 @@ async function startCollector(options) {
|
|
|
3993
4480
|
if (!registry.has(batch.installId)) {
|
|
3994
4481
|
registry.set(batch.installId, receivedAt);
|
|
3995
4482
|
await appendFile(
|
|
3996
|
-
|
|
4483
|
+
join7(dataDir, "installs.jsonl"),
|
|
3997
4484
|
`${JSON.stringify({ installId: batch.installId, firstSeen: receivedAt })}
|
|
3998
4485
|
`
|
|
3999
4486
|
);
|
|
@@ -4001,7 +4488,7 @@ async function startCollector(options) {
|
|
|
4001
4488
|
const { events, ...meta } = batch;
|
|
4002
4489
|
const lines = events.map((event) => JSON.stringify({ ...meta, ...event, receivedAt }));
|
|
4003
4490
|
await appendFile(
|
|
4004
|
-
|
|
4491
|
+
join7(dataDir, `events-${receivedAt.slice(0, 10)}.jsonl`),
|
|
4005
4492
|
`${lines.join("\n")}
|
|
4006
4493
|
`
|
|
4007
4494
|
);
|
|
@@ -4098,7 +4585,7 @@ function passwordMatches(request, password3) {
|
|
|
4098
4585
|
if (!header.startsWith("Basic ")) return false;
|
|
4099
4586
|
const decoded = Buffer.from(header.slice(6), "base64").toString("utf8");
|
|
4100
4587
|
const given = decoded.slice(decoded.indexOf(":") + 1);
|
|
4101
|
-
const digest = (value) =>
|
|
4588
|
+
const digest = (value) => createHash2("sha256").update(value).digest();
|
|
4102
4589
|
return timingSafeEqual(digest(given), digest(password3));
|
|
4103
4590
|
}
|
|
4104
4591
|
function readBody(request) {
|
|
@@ -4122,10 +4609,10 @@ function listen2(server, port) {
|
|
|
4122
4609
|
}
|
|
4123
4610
|
async function readEvents(dataDir, days, now = Date.now()) {
|
|
4124
4611
|
const since = new Date(now - (days - 1) * 864e5).toISOString().slice(0, 10);
|
|
4125
|
-
const files = (await
|
|
4612
|
+
const files = (await readdir2(dataDir).catch(() => [])).filter((file) => /^events-\d{4}-\d{2}-\d{2}\.jsonl$/.test(file) && file.slice(7, 17) >= since).sort();
|
|
4126
4613
|
const events = [];
|
|
4127
4614
|
for (const file of files) {
|
|
4128
|
-
for (const line of (await
|
|
4615
|
+
for (const line of (await readFile5(join7(dataDir, file), "utf8")).split("\n")) {
|
|
4129
4616
|
if (!line) continue;
|
|
4130
4617
|
try {
|
|
4131
4618
|
events.push(JSON.parse(line));
|
|
@@ -4400,8 +4887,8 @@ function installTrace(events, prefix) {
|
|
|
4400
4887
|
}
|
|
4401
4888
|
async function loadRegistry(dataDir) {
|
|
4402
4889
|
const registry = /* @__PURE__ */ new Map();
|
|
4403
|
-
const path =
|
|
4404
|
-
const raw = await
|
|
4890
|
+
const path = join7(dataDir, "installs.jsonl");
|
|
4891
|
+
const raw = await readFile5(path, "utf8").catch(() => null);
|
|
4405
4892
|
if (raw !== null) {
|
|
4406
4893
|
for (const line of raw.split("\n")) {
|
|
4407
4894
|
try {
|
|
@@ -4446,10 +4933,10 @@ function registerTelemetryCommands(program, runSafely2) {
|
|
|
4446
4933
|
const state = await readTelemetryState();
|
|
4447
4934
|
const blocked = telemetryBlocked(state);
|
|
4448
4935
|
console.log(
|
|
4449
|
-
blocked ? `${
|
|
4936
|
+
blocked ? `${pc21.yellow("\u25CB")} telemetry off (${blocked})` : `${pc21.green("\u25CF")} telemetry on`
|
|
4450
4937
|
);
|
|
4451
|
-
console.log(
|
|
4452
|
-
if (telemetryUrl()) console.log(
|
|
4938
|
+
console.log(pc21.dim(` install id ${state.installId} (${telemetryStatePath()})`));
|
|
4939
|
+
if (telemetryUrl()) console.log(pc21.dim(` collector ${telemetryUrl()}`));
|
|
4453
4940
|
});
|
|
4454
4941
|
});
|
|
4455
4942
|
telemetry.command("enable").description("Allow anonymous usage data from this machine.").action(async () => {
|
|
@@ -4460,7 +4947,7 @@ function registerTelemetryCommands(program, runSafely2) {
|
|
|
4460
4947
|
noticeAt: state.noticeAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
4461
4948
|
});
|
|
4462
4949
|
console.log(TELEMETRY_NOTICE);
|
|
4463
|
-
console.log(
|
|
4950
|
+
console.log(pc21.green("Telemetry enabled."));
|
|
4464
4951
|
});
|
|
4465
4952
|
});
|
|
4466
4953
|
telemetry.command("disable").description("Stop sending anonymous usage data from this machine.").action(async () => {
|
|
@@ -4488,14 +4975,14 @@ function registerTelemetryCommands(program, runSafely2) {
|
|
|
4488
4975
|
adminPort: opts.adminPort,
|
|
4489
4976
|
...process.env.CODEBUDDY_ADMIN_PASSWORD ? { adminPassword: process.env.CODEBUDDY_ADMIN_PASSWORD } : {}
|
|
4490
4977
|
});
|
|
4491
|
-
console.log(`${
|
|
4492
|
-
console.log(`${
|
|
4978
|
+
console.log(`${pc21.green("\u25CF")} ingest ${pc21.bold(collector.ingestUrl)}`);
|
|
4979
|
+
console.log(`${pc21.green("\u25CF")} admin ${pc21.bold(collector.adminUrl)}`);
|
|
4493
4980
|
console.log(
|
|
4494
|
-
|
|
4981
|
+
pc21.dim(
|
|
4495
4982
|
process.env.CODEBUDDY_ADMIN_PASSWORD ? " admin panel is password-protected (CODEBUDDY_ADMIN_PASSWORD)." : " admin panel has no password: keep it private, or set CODEBUDDY_ADMIN_PASSWORD."
|
|
4496
4983
|
)
|
|
4497
4984
|
);
|
|
4498
|
-
console.log(
|
|
4985
|
+
console.log(pc21.dim(` events in ${opts.data}. Ctrl+C to stop.`));
|
|
4499
4986
|
await new Promise((resolve6) => {
|
|
4500
4987
|
const stop = () => void collector.close().then(resolve6);
|
|
4501
4988
|
process.once("SIGINT", stop);
|
|
@@ -4506,54 +4993,10 @@ function registerTelemetryCommands(program, runSafely2) {
|
|
|
4506
4993
|
}
|
|
4507
4994
|
|
|
4508
4995
|
// src/cli/commands/use-command.ts
|
|
4996
|
+
import { execFile as execFile3, spawn as spawn7 } from "child_process";
|
|
4509
4997
|
import { basename as basename2 } from "path";
|
|
4510
4998
|
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";
|
|
4511
|
-
import
|
|
4512
|
-
|
|
4513
|
-
// src/core/global-config.ts
|
|
4514
|
-
import { chmod, mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
4515
|
-
import { homedir } from "os";
|
|
4516
|
-
import { dirname as dirname3, join as join7 } from "path";
|
|
4517
|
-
import { z as z3 } from "zod";
|
|
4518
|
-
var GLOBAL_DIR = ".codebuddy";
|
|
4519
|
-
var GLOBAL_FILE = "global.json";
|
|
4520
|
-
var globalConfigSchema = z3.object({
|
|
4521
|
-
hfToken: z3.string().min(1).optional(),
|
|
4522
|
-
databaseUrl: z3.string().min(1).optional(),
|
|
4523
|
-
// Where the codebuddy install lives on this machine. Cached so future
|
|
4524
|
-
// `codebuddy use` calls in unrelated projects can find the CLI entry
|
|
4525
|
-
// without re-resolving via process.argv every time.
|
|
4526
|
-
installPath: z3.string().optional()
|
|
4527
|
-
});
|
|
4528
|
-
function globalConfigPath(home = homedir()) {
|
|
4529
|
-
return join7(home, GLOBAL_DIR, GLOBAL_FILE);
|
|
4530
|
-
}
|
|
4531
|
-
async function readGlobalConfig(home = homedir()) {
|
|
4532
|
-
const path = globalConfigPath(home);
|
|
4533
|
-
try {
|
|
4534
|
-
const raw = await readFile5(path, "utf8");
|
|
4535
|
-
return globalConfigSchema.parse(JSON.parse(raw));
|
|
4536
|
-
} catch (error) {
|
|
4537
|
-
if (error.code === "ENOENT") return {};
|
|
4538
|
-
throw error;
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
async function writeGlobalConfig(next, home = homedir()) {
|
|
4542
|
-
const path = globalConfigPath(home);
|
|
4543
|
-
await mkdir3(dirname3(path), { recursive: true, mode: 448 });
|
|
4544
|
-
await writeFile3(path, `${JSON.stringify(next, null, 2)}
|
|
4545
|
-
`, { mode: 384 });
|
|
4546
|
-
await chmod(path, 384);
|
|
4547
|
-
return { path };
|
|
4548
|
-
}
|
|
4549
|
-
async function mergeGlobalConfig(patch, home = homedir()) {
|
|
4550
|
-
const current = await readGlobalConfig(home);
|
|
4551
|
-
const merged = { ...current, ...patch };
|
|
4552
|
-
const { path } = await writeGlobalConfig(merged, home);
|
|
4553
|
-
return { path, merged };
|
|
4554
|
-
}
|
|
4555
|
-
|
|
4556
|
-
// src/cli/commands/use-command.ts
|
|
4999
|
+
import pc22 from "picocolors";
|
|
4557
5000
|
function bail2() {
|
|
4558
5001
|
log2.warn("Aborted.");
|
|
4559
5002
|
process.exit(1);
|
|
@@ -4572,6 +5015,131 @@ async function pingOnce2(databaseUrl) {
|
|
|
4572
5015
|
await client.close().catch(() => void 0);
|
|
4573
5016
|
}
|
|
4574
5017
|
}
|
|
5018
|
+
async function setupZvec(configured) {
|
|
5019
|
+
if (configured === false) {
|
|
5020
|
+
log2.info(`zvec-grep: disabled in config (enable with ${pc22.cyan("codebuddy zvec enable")}).`);
|
|
5021
|
+
return;
|
|
5022
|
+
}
|
|
5023
|
+
let report = await zvecStatus(process.cwd()).catch(() => null);
|
|
5024
|
+
if (report && report.version === null && process.env.CODEBUDDY_ZVEC !== "0") {
|
|
5025
|
+
const install = spinner3();
|
|
5026
|
+
install.start(`Installing zvec-grep (${ZVEC_PACKAGE})`);
|
|
5027
|
+
const code2 = await npmGlobalInstall(ZVEC_PACKAGE);
|
|
5028
|
+
install.stop(code2 === 0 ? "zvec-grep installed." : `zvec-grep install failed (exit ${code2}).`);
|
|
5029
|
+
if (code2 === 0) report = await zvecStatus(process.cwd()).catch(() => null);
|
|
5030
|
+
}
|
|
5031
|
+
if (!report?.supported) {
|
|
5032
|
+
log2.warn(
|
|
5033
|
+
`zvec-grep: ${report?.reason ?? "unavailable"}; keyword-only file discovery. Install with ${pc22.cyan("npm install -g @zvec/zvec-grep")}, then ${pc22.cyan("codebuddy zvec index && codebuddy zvec enable")}.`
|
|
5034
|
+
);
|
|
5035
|
+
return;
|
|
5036
|
+
}
|
|
5037
|
+
const zvecSpinner = spinner3();
|
|
5038
|
+
zvecSpinner.start("Building the zvec-grep semantic index");
|
|
5039
|
+
const code = report.indexReady ? 0 : await buildZvecIndex(process.cwd(), { quiet: true });
|
|
5040
|
+
if (code !== 0) {
|
|
5041
|
+
zvecSpinner.stop(`zvec-grep index failed (exit ${code}); keyword-only file discovery.`);
|
|
5042
|
+
return;
|
|
5043
|
+
}
|
|
5044
|
+
await setZvecEnabled(true);
|
|
5045
|
+
zvecSpinner.stop("zvec-grep semantic file discovery enabled.");
|
|
5046
|
+
}
|
|
5047
|
+
var ZVEC_PACKAGE = "@zvec/zvec-grep@0.2";
|
|
5048
|
+
function npmGlobalInstall(spec) {
|
|
5049
|
+
return new Promise((resolve6) => {
|
|
5050
|
+
execFile3(
|
|
5051
|
+
"npm",
|
|
5052
|
+
["install", "-g", spec],
|
|
5053
|
+
{ timeout: 10 * 6e4 },
|
|
5054
|
+
(error) => resolve6(error ? typeof error.code === "number" ? error.code : 1 : 0)
|
|
5055
|
+
);
|
|
5056
|
+
});
|
|
5057
|
+
}
|
|
5058
|
+
async function setupEmbeddings(embeddings) {
|
|
5059
|
+
if (embeddings !== "local" || process.env.CODEBUDDY_SEMANTIC === "0") return;
|
|
5060
|
+
const pull = spinner3();
|
|
5061
|
+
pull.start("Downloading the local embedding model (~90 MB, once)");
|
|
5062
|
+
try {
|
|
5063
|
+
const { LocalModelProvider } = await import("./local-ST4WMRCG.js");
|
|
5064
|
+
await new LocalModelProvider().embed({ input: "warm up" });
|
|
5065
|
+
pull.stop("Local embedding model ready.");
|
|
5066
|
+
} catch (error) {
|
|
5067
|
+
pull.stop(
|
|
5068
|
+
`Embedding model unavailable (${error.message.split("\n")[0]}); recall stays lexical until ${pc22.cyan("codebuddy models pull")} succeeds.`
|
|
5069
|
+
);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
function startBackgroundIndex(withModel) {
|
|
5073
|
+
const entry = process.argv[1];
|
|
5074
|
+
if (!entry) return;
|
|
5075
|
+
const child = spawn7(process.execPath, [entry, "index", "--cards"], {
|
|
5076
|
+
cwd: process.cwd(),
|
|
5077
|
+
detached: true,
|
|
5078
|
+
stdio: "ignore"
|
|
5079
|
+
});
|
|
5080
|
+
child.unref();
|
|
5081
|
+
log2.info(
|
|
5082
|
+
withModel ? "Building file vectors, intent cards and project facts in the background." : "Building file vectors in the background."
|
|
5083
|
+
);
|
|
5084
|
+
}
|
|
5085
|
+
async function setupLocalLlm(choice, interactive) {
|
|
5086
|
+
if (choice === false || process.env.CODEBUDDY_LOCAL_LLM === "0") {
|
|
5087
|
+
log2.info(`Local LLM: skipped (add later with ${pc22.cyan("codebuddy models add local-llm")}).`);
|
|
5088
|
+
return false;
|
|
5089
|
+
}
|
|
5090
|
+
const { installedLocalModel, machineCanCard } = await import("./local-75DJECVR.js");
|
|
5091
|
+
let installed = await installedLocalModel() !== null;
|
|
5092
|
+
const machine = machineCanCard();
|
|
5093
|
+
if (!installed && !machine.ok) {
|
|
5094
|
+
if (!await setupCloudQwen(machine.reason, interactive)) return false;
|
|
5095
|
+
installed = true;
|
|
5096
|
+
}
|
|
5097
|
+
if (!installed) {
|
|
5098
|
+
const { addLocalLlm: addLocalLlm2 } = await import("./local-llm-MECVJTVU.js");
|
|
5099
|
+
installed = await addLocalLlm2({ yes: true, interactive });
|
|
5100
|
+
if (!installed) {
|
|
5101
|
+
log2.warn(
|
|
5102
|
+
`Local LLM not installed; file discovery uses keywords and vectors only. Retry with ${pc22.cyan("codebuddy models add local-llm")}.`
|
|
5103
|
+
);
|
|
5104
|
+
return false;
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
return installed;
|
|
5108
|
+
}
|
|
5109
|
+
async function setupCloudQwen(reason, interactive) {
|
|
5110
|
+
const existing = (await readConfigFile()).llm;
|
|
5111
|
+
if (existing && existing.provider !== "local") return existing.enabled;
|
|
5112
|
+
const fromEnv = process.env.GROQ_API_KEY ? "groq" : process.env.OPENROUTER_API_KEY ? "openrouter" : null;
|
|
5113
|
+
const saved = await readGlobalConfig().catch(
|
|
5114
|
+
() => ({})
|
|
5115
|
+
);
|
|
5116
|
+
let chosen = fromEnv ?? (saved.groqApiKey ? "groq" : saved.openrouterApiKey ? "openrouter" : null);
|
|
5117
|
+
if (!chosen && interactive) {
|
|
5118
|
+
log2.info(
|
|
5119
|
+
`This machine is too small for the local model (${reason}), so CodeBuddy can use Qwen in the cloud. Get a free Groq key at ${pc22.cyan("https://console.groq.com/keys")}.`
|
|
5120
|
+
);
|
|
5121
|
+
const key = unwrap2(
|
|
5122
|
+
await password2({
|
|
5123
|
+
message: "Groq API key (Enter to skip; saved to ~/.codebuddy/global.json)"
|
|
5124
|
+
})
|
|
5125
|
+
).trim();
|
|
5126
|
+
if (key) {
|
|
5127
|
+
await mergeGlobalConfig({ groqApiKey: key });
|
|
5128
|
+
chosen = "groq";
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
if (!chosen) {
|
|
5132
|
+
log2.warn(
|
|
5133
|
+
`Local LLM: this machine is too small for it (${reason}). For cloud Qwen, get a free key at ${pc22.cyan("https://console.groq.com/keys")} and rerun ${pc22.cyan("codebuddy use")} (or ${pc22.cyan("export GROQ_API_KEY=\u2026")} and ${pc22.cyan("codebuddy llm enable --provider groq")}). Keyword + vector discovery work meanwhile.`
|
|
5134
|
+
);
|
|
5135
|
+
return false;
|
|
5136
|
+
}
|
|
5137
|
+
await setLlmEnabled(true, { provider: chosen });
|
|
5138
|
+
log2.info(
|
|
5139
|
+
`LLM: this machine is too small for the local model (${reason}); using Qwen (qwen/qwen3.8-27b) on ${chosen}. Redacted file heads and signatures are sent to it; turn off with \`codebuddy llm disable\`.`
|
|
5140
|
+
);
|
|
5141
|
+
return true;
|
|
5142
|
+
}
|
|
4575
5143
|
function sanitizeNamespace(input) {
|
|
4576
5144
|
return input.replace(/[^a-zA-Z0-9_.:-]/g, "-").slice(0, 128) || "default";
|
|
4577
5145
|
}
|
|
@@ -4592,15 +5160,15 @@ async function runUseCommand(options = {}) {
|
|
|
4592
5160
|
})
|
|
4593
5161
|
));
|
|
4594
5162
|
note2(
|
|
4595
|
-
`Project folder: ${
|
|
4596
|
-
Namespace: ${
|
|
5163
|
+
`Project folder: ${pc22.bold(process.cwd())}
|
|
5164
|
+
Namespace: ${pc22.bold(namespace)}`,
|
|
4597
5165
|
"codebuddy use"
|
|
4598
5166
|
);
|
|
4599
5167
|
const stored = await readGlobalConfig();
|
|
4600
5168
|
const projectConfig = await readConfigFile();
|
|
4601
5169
|
let embeddings = projectConfig.provider?.type;
|
|
4602
5170
|
if (embeddings) {
|
|
4603
|
-
log2.info(`Embeddings: ${embeddings} (change with ${
|
|
5171
|
+
log2.info(`Embeddings: ${embeddings} (change with ${pc22.cyan("codebuddy models use")}).`);
|
|
4604
5172
|
} else if (!interactive) {
|
|
4605
5173
|
embeddings = "local";
|
|
4606
5174
|
} else {
|
|
@@ -4641,7 +5209,7 @@ Namespace: ${pc20.bold(namespace)}`,
|
|
|
4641
5209
|
log2.info("Using HF_TOKEN from environment.");
|
|
4642
5210
|
} else if (stored.hfToken) {
|
|
4643
5211
|
hfToken = stored.hfToken;
|
|
4644
|
-
log2.info(`Reusing saved Hugging Face token (${
|
|
5212
|
+
log2.info(`Reusing saved Hugging Face token (${pc22.dim("~/.codebuddy/global.json")}).`);
|
|
4645
5213
|
} else if (!interactive) {
|
|
4646
5214
|
throw new Error(
|
|
4647
5215
|
"Hugging Face embeddings need a token: set HF_TOKEN (no terminal to prompt for it)."
|
|
@@ -4757,6 +5325,9 @@ Namespace: ${pc20.bold(namespace)}`,
|
|
|
4757
5325
|
tokenBudget: existingConfig.tokenBudget ?? 4e3,
|
|
4758
5326
|
graphify: { enabled: graphifyConfigured, graphPath: "graphify-out/graph.json" }
|
|
4759
5327
|
});
|
|
5328
|
+
await setupEmbeddings(embeddings);
|
|
5329
|
+
await setupZvec(existingConfig.zvec?.enabled);
|
|
5330
|
+
startBackgroundIndex(await setupLocalLlm(options.localLlm, interactive));
|
|
4760
5331
|
const graphifyMessage = graphifySetup?.message ?? "Graphify disabled; CodeBuddy MCP remains available.";
|
|
4761
5332
|
await writeProjectMcpConfig({
|
|
4762
5333
|
repositoryRoot: process.cwd(),
|
|
@@ -4775,7 +5346,7 @@ Namespace: ${pc20.bold(namespace)}`,
|
|
|
4775
5346
|
databaseUrl
|
|
4776
5347
|
});
|
|
4777
5348
|
log2.success(
|
|
4778
|
-
`Registered ${
|
|
5349
|
+
`Registered ${pc22.bold(install.serverKey)} in Claude Desktop. Restart with \u2318Q to activate.`
|
|
4779
5350
|
);
|
|
4780
5351
|
} catch (error) {
|
|
4781
5352
|
log2.warn(`Claude Desktop registration skipped: ${error.message}`);
|
|
@@ -4790,7 +5361,7 @@ Namespace: ${pc20.bold(namespace)}`,
|
|
|
4790
5361
|
databaseUrl
|
|
4791
5362
|
});
|
|
4792
5363
|
log2.success(
|
|
4793
|
-
`Registered ${
|
|
5364
|
+
`Registered ${pc22.bold(install.serverKey)} in Codex. Restart Codex to activate.`
|
|
4794
5365
|
);
|
|
4795
5366
|
} catch (error) {
|
|
4796
5367
|
log2.warn(`Codex registration skipped: ${error.message}`);
|
|
@@ -4798,24 +5369,24 @@ Namespace: ${pc20.bold(namespace)}`,
|
|
|
4798
5369
|
}
|
|
4799
5370
|
note2(
|
|
4800
5371
|
[
|
|
4801
|
-
`${
|
|
4802
|
-
`${
|
|
4803
|
-
`${
|
|
4804
|
-
`${
|
|
5372
|
+
`${pc22.green("\u2713")} CodeBuddy is ready for ${pc22.bold(folderName)}.`,
|
|
5373
|
+
`${pc22.green("\u2713")} Scaffolded ${pc22.cyan(".codebuddy/policies.yaml")} for project-specific guardrails.`,
|
|
5374
|
+
`${pc22.green("\u2713")} ${graphifyMessage}`,
|
|
5375
|
+
`${pc22.green("\u2713")} Added agent workflow instructions to ${pc22.cyan("CLAUDE.md")} and ${pc22.cyan("AGENTS.md")}.`,
|
|
4805
5376
|
``,
|
|
4806
5377
|
`Next:`,
|
|
4807
5378
|
` 1. Restart Claude Desktop and Codex.`,
|
|
4808
|
-
` 2. Ask Claude or Codex to use the ${
|
|
5379
|
+
` 2. Ask Claude or Codex to use the ${pc22.cyan("context_pack")} tool for each coding task.`,
|
|
4809
5380
|
` 3. Indexing, Graphify, Markdown memory import, and embeddings now update automatically while the MCP server is running.`,
|
|
4810
|
-
` 4. Run ${
|
|
5381
|
+
` 4. Run ${pc22.cyan("codebuddy doctor")} or ${pc22.cyan("codebuddy db doctor")} if you want a full health check.`,
|
|
4811
5382
|
``,
|
|
4812
5383
|
`Manage:`,
|
|
4813
|
-
` codebuddy claude list ${
|
|
4814
|
-
` codebuddy codex list ${
|
|
4815
|
-
` codebuddy db test ${
|
|
5384
|
+
` codebuddy claude list ${pc22.dim("# see every project wired up")}`,
|
|
5385
|
+
` codebuddy codex list ${pc22.dim("# see Codex entries")}`,
|
|
5386
|
+
` codebuddy db test ${pc22.dim("# quick Postgres connectivity check")}`,
|
|
4816
5387
|
` codebuddy claude remove codebuddy-${namespace}`,
|
|
4817
5388
|
` codebuddy codex remove codebuddy-${namespace}`,
|
|
4818
|
-
` codebuddy postgres down ${
|
|
5389
|
+
` codebuddy postgres down ${pc22.dim("# stop the DB (data preserved)")}`
|
|
4819
5390
|
].join("\n"),
|
|
4820
5391
|
"Done"
|
|
4821
5392
|
);
|
|
@@ -4826,7 +5397,7 @@ Namespace: ${pc20.bold(namespace)}`,
|
|
|
4826
5397
|
|
|
4827
5398
|
// src/cli/commands/worklog.ts
|
|
4828
5399
|
import { relative as relative6 } from "path";
|
|
4829
|
-
import
|
|
5400
|
+
import pc23 from "picocolors";
|
|
4830
5401
|
function registerWorklogCommands(program, runSafely2) {
|
|
4831
5402
|
const worklog = program.command("worklog").description("Inspect the cross-chat worklog of completed turns.");
|
|
4832
5403
|
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) => {
|
|
@@ -4837,7 +5408,7 @@ function registerWorklogCommands(program, runSafely2) {
|
|
|
4837
5408
|
return;
|
|
4838
5409
|
}
|
|
4839
5410
|
if (entries.length === 0) {
|
|
4840
|
-
console.log(
|
|
5411
|
+
console.log(pc23.dim("no worklog entries yet"));
|
|
4841
5412
|
return;
|
|
4842
5413
|
}
|
|
4843
5414
|
for (const entry of entries) console.log(renderRow2(entry));
|
|
@@ -4851,36 +5422,36 @@ function registerWorklogCommands(program, runSafely2) {
|
|
|
4851
5422
|
worklog.command("latest").description("Show the most recent worklog entry.").action(async () => {
|
|
4852
5423
|
await runSafely2(async () => {
|
|
4853
5424
|
const [entry] = await new WorklogStore().list();
|
|
4854
|
-
console.log(entry ? renderEntry(entry) :
|
|
5425
|
+
console.log(entry ? renderEntry(entry) : pc23.dim("no worklog entries yet"));
|
|
4855
5426
|
});
|
|
4856
5427
|
});
|
|
4857
5428
|
}
|
|
4858
5429
|
function renderRow2(entry) {
|
|
4859
5430
|
const summary = entry.summary.replace(/\s+/g, " ");
|
|
4860
5431
|
const short2 = summary.length <= 70 ? summary : `${summary.slice(0, 69)}\u2026`;
|
|
4861
|
-
return `${
|
|
5432
|
+
return `${pc23.dim(entry.createdAt.slice(0, 16))} ${pc23.dim(entry.id)} ${short2} ${pc23.dim(`(${entry.files.length} file(s))`)}`;
|
|
4862
5433
|
}
|
|
4863
5434
|
function renderEntry(entry) {
|
|
4864
|
-
const lines = [`${
|
|
4865
|
-
if (entry.sourceSessionId) lines.push(`${
|
|
4866
|
-
if (entry.sourcePlanId) lines.push(`${
|
|
4867
|
-
if (entry.commitSha) lines.push(`${
|
|
4868
|
-
if (entry.files.length > 0) lines.push(`${
|
|
5435
|
+
const lines = [`${pc23.bold(entry.id)} ${pc23.dim(entry.createdAt)} ${pc23.dim(entry.namespace)}`];
|
|
5436
|
+
if (entry.sourceSessionId) lines.push(`${pc23.bold("session:")} ${entry.sourceSessionId}`);
|
|
5437
|
+
if (entry.sourcePlanId) lines.push(`${pc23.bold("plan:")} ${entry.sourcePlanId}`);
|
|
5438
|
+
if (entry.commitSha) lines.push(`${pc23.bold("commit:")} ${entry.commitSha}`);
|
|
5439
|
+
if (entry.files.length > 0) lines.push(`${pc23.bold("files:")} ${entry.files.join(", ")}`);
|
|
4869
5440
|
if (entry.verification.length > 0) {
|
|
4870
|
-
lines.push(`${
|
|
5441
|
+
lines.push(`${pc23.bold("verification:")}`, ...entry.verification.map((line) => ` - ${line}`));
|
|
4871
5442
|
}
|
|
4872
5443
|
if (entry.savedMemory.length > 0) {
|
|
4873
|
-
lines.push(`${
|
|
5444
|
+
lines.push(`${pc23.bold("saved memory:")} ${entry.savedMemory.join(", ")}`);
|
|
4874
5445
|
}
|
|
4875
5446
|
if (entry.queuedForReview.length > 0) {
|
|
4876
|
-
lines.push(`${
|
|
5447
|
+
lines.push(`${pc23.bold("queued for review:")} ${entry.queuedForReview.join(", ")}`);
|
|
4877
5448
|
}
|
|
4878
|
-
lines.push("", entry.summary, "",
|
|
5449
|
+
lines.push("", entry.summary, "", pc23.dim(relative6(process.cwd(), entry.path)));
|
|
4879
5450
|
return lines.join("\n");
|
|
4880
5451
|
}
|
|
4881
5452
|
|
|
4882
5453
|
// src/cli/commands/zvec.ts
|
|
4883
|
-
import
|
|
5454
|
+
import pc24 from "picocolors";
|
|
4884
5455
|
function registerZvecCommands(program, runSafely2) {
|
|
4885
5456
|
const zvec = program.command("zvec").description("Optional zvec-grep semantic file discovery for the context tools.");
|
|
4886
5457
|
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) => {
|
|
@@ -4890,22 +5461,22 @@ function registerZvecCommands(program, runSafely2) {
|
|
|
4890
5461
|
console.log(JSON.stringify(report, null, 2));
|
|
4891
5462
|
return;
|
|
4892
5463
|
}
|
|
4893
|
-
const mark = (ok2) => ok2 ?
|
|
5464
|
+
const mark = (ok2) => ok2 ? pc24.green("yes") : pc24.yellow("no");
|
|
4894
5465
|
console.log(`enabled: ${mark(report.enabled)}`);
|
|
4895
5466
|
console.log(`binary: ${report.binary}`);
|
|
4896
5467
|
console.log(
|
|
4897
|
-
`version: ${report.version ??
|
|
5468
|
+
`version: ${report.version ?? pc24.yellow("not found")}${report.version && !report.supported ? pc24.yellow(" (unsupported; need 0.2.x)") : ""}`
|
|
4898
5469
|
);
|
|
4899
5470
|
console.log(`index ready: ${mark(report.indexReady)}`);
|
|
4900
5471
|
console.log(
|
|
4901
|
-
`zg server: ${report.server} ${
|
|
5472
|
+
`zg server: ${report.server} ${pc24.dim("(running = faster queries; optional)")}`
|
|
4902
5473
|
);
|
|
4903
5474
|
console.log(`status: ${report.reason}`);
|
|
4904
|
-
if (!report.version) console.log(
|
|
5475
|
+
if (!report.version) console.log(pc24.dim("install: npm install -g @zvec/zvec-grep"));
|
|
4905
5476
|
else if (report.supported && !report.indexReady)
|
|
4906
|
-
console.log(
|
|
5477
|
+
console.log(pc24.dim("next: codebuddy zvec index"));
|
|
4907
5478
|
else if (report.indexReady && !report.enabled)
|
|
4908
|
-
console.log(
|
|
5479
|
+
console.log(pc24.dim("next: codebuddy zvec enable"));
|
|
4909
5480
|
});
|
|
4910
5481
|
});
|
|
4911
5482
|
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) => {
|
|
@@ -4915,21 +5486,21 @@ function registerZvecCommands(program, runSafely2) {
|
|
|
4915
5486
|
...opts.rebuild ? { rebuild: true } : {}
|
|
4916
5487
|
});
|
|
4917
5488
|
if (code !== 0) throw new Error(`zg index exited with code ${code}`);
|
|
4918
|
-
console.log(
|
|
5489
|
+
console.log(pc24.dim("next: codebuddy zvec enable (.zvec-grep/ ignores itself in git)"));
|
|
4919
5490
|
});
|
|
4920
5491
|
});
|
|
4921
5492
|
zvec.command("enable").description("Use zvec-grep candidates in context_pack / context_before_edit.").action(async () => {
|
|
4922
5493
|
await runSafely2(async () => {
|
|
4923
5494
|
await setZvecEnabled(true);
|
|
4924
5495
|
console.log(
|
|
4925
|
-
`${
|
|
5496
|
+
`${pc24.green("enabled")} zvec-grep retrieval (falls back to keywords if unavailable)`
|
|
4926
5497
|
);
|
|
4927
5498
|
});
|
|
4928
5499
|
});
|
|
4929
5500
|
zvec.command("disable").description("Stop using zvec-grep; keyword ranking only.").action(async () => {
|
|
4930
5501
|
await runSafely2(async () => {
|
|
4931
5502
|
await setZvecEnabled(false);
|
|
4932
|
-
console.log(`${
|
|
5503
|
+
console.log(`${pc24.yellow("disabled")} zvec-grep retrieval`);
|
|
4933
5504
|
});
|
|
4934
5505
|
});
|
|
4935
5506
|
}
|
|
@@ -4950,7 +5521,7 @@ function createCli() {
|
|
|
4950
5521
|
).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(
|
|
4951
5522
|
"--docker",
|
|
4952
5523
|
"Start CodeBuddy's bundled Postgres container when the default DB is unavailable."
|
|
4953
|
-
).option("--no-docker", "Do not offer to start CodeBuddy's bundled Postgres container.").option("-y, --yes", "Never prompt: use flags and defaults (automatic without a TTY).").option("--non-interactive", "Same as --yes.").action(
|
|
5524
|
+
).option("--no-docker", "Do not offer to start CodeBuddy's bundled Postgres container.").option("--no-local-llm", "Skip installing the local LLM (Qwen3-0.6B, ~400 MB).").option("-y, --yes", "Never prompt: use flags and defaults (automatic without a TTY).").option("--non-interactive", "Same as --yes.").action(
|
|
4954
5525
|
async (namespace, opts) => {
|
|
4955
5526
|
await runSafely(async () => {
|
|
4956
5527
|
await runUseCommand({
|
|
@@ -4960,6 +5531,7 @@ function createCli() {
|
|
|
4960
5531
|
...opts.skipCodex !== void 0 ? { skipCodex: opts.skipCodex } : {},
|
|
4961
5532
|
...opts.graphify !== void 0 ? { graphify: opts.graphify } : {},
|
|
4962
5533
|
...opts.docker !== void 0 ? { useDocker: opts.docker } : {},
|
|
5534
|
+
...opts.localLlm !== void 0 ? { localLlm: opts.localLlm } : {},
|
|
4963
5535
|
...opts.yes || opts.nonInteractive ? { interactive: false } : {}
|
|
4964
5536
|
});
|
|
4965
5537
|
});
|
|
@@ -5199,7 +5771,47 @@ function createCli() {
|
|
|
5199
5771
|
});
|
|
5200
5772
|
program.command("stats").description("Show CodeBuddy storage and usage stats.").action(async () => {
|
|
5201
5773
|
await withRuntime(async (runtime) => {
|
|
5202
|
-
console.log(
|
|
5774
|
+
console.log(
|
|
5775
|
+
JSON.stringify(
|
|
5776
|
+
{ ...await runtime.repository.getStats(), ...await indexModelStats(process.cwd()) },
|
|
5777
|
+
null,
|
|
5778
|
+
2
|
|
5779
|
+
)
|
|
5780
|
+
);
|
|
5781
|
+
});
|
|
5782
|
+
});
|
|
5783
|
+
program.command("storage").option("--json", "Print machine-readable JSON.").description(
|
|
5784
|
+
"Memory storage health: Markdown/database drift, backup coverage, embedding backlog and spaces, capture lag."
|
|
5785
|
+
).action(async (opts) => {
|
|
5786
|
+
await runSafely(async () => {
|
|
5787
|
+
const root = process.cwd();
|
|
5788
|
+
const capture2 = {
|
|
5789
|
+
stagedNotCaptured: await new HookStagingStore(root).pending(),
|
|
5790
|
+
journalBehind: (await journalLag(root)).filter((entry) => entry.behindBytes !== 0)
|
|
5791
|
+
};
|
|
5792
|
+
let database;
|
|
5793
|
+
try {
|
|
5794
|
+
const runtime = await createRuntime(await loadRuntimeConfig());
|
|
5795
|
+
try {
|
|
5796
|
+
database = await runtime.memory.storageReport();
|
|
5797
|
+
} finally {
|
|
5798
|
+
await runtime.close();
|
|
5799
|
+
}
|
|
5800
|
+
} catch (error) {
|
|
5801
|
+
database = { unavailable: error.message };
|
|
5802
|
+
}
|
|
5803
|
+
const index = await indexModelStats(root);
|
|
5804
|
+
if (opts.json) {
|
|
5805
|
+
console.log(JSON.stringify({ database, capture: capture2, ...index }, null, 2));
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
printStorage(database, capture2);
|
|
5809
|
+
console.log(
|
|
5810
|
+
`intent cards: ${index.cards.carded} / ${index.cards.eligible} source files; index facts: ${index.indexFacts}`
|
|
5811
|
+
);
|
|
5812
|
+
if ("unavailable" in database || database.markdownOnly.length + database.dbOnly.length > 0) {
|
|
5813
|
+
process.exitCode = 1;
|
|
5814
|
+
}
|
|
5203
5815
|
});
|
|
5204
5816
|
});
|
|
5205
5817
|
program.command("doctor").description("Check CodeBuddy environment health.").option("--skip-model-check", "Do not call Hugging Face models.").action(async (opts) => {
|
|
@@ -5228,6 +5840,8 @@ function createCli() {
|
|
|
5228
5840
|
registerLearnCommand(program, runSafely);
|
|
5229
5841
|
registerMemoryCommands(program, runSafely);
|
|
5230
5842
|
registerWorklogCommands(program, runSafely);
|
|
5843
|
+
registerJournalCommands(program, runSafely);
|
|
5844
|
+
registerOutputCommands(program, runSafely);
|
|
5231
5845
|
registerZvecCommands(program, runSafely);
|
|
5232
5846
|
registerLlmCommands(program, runSafely);
|
|
5233
5847
|
registerSuggestCommand(program, runSafely);
|
|
@@ -5255,7 +5869,20 @@ var HELP_GROUPS = [
|
|
|
5255
5869
|
["Get started:", ["use", "init", "doctor", "hooks", "clients", "models", "dashboard"]],
|
|
5256
5870
|
[
|
|
5257
5871
|
"Daily work:",
|
|
5258
|
-
[
|
|
5872
|
+
[
|
|
5873
|
+
"context",
|
|
5874
|
+
"plan",
|
|
5875
|
+
"change",
|
|
5876
|
+
"risk",
|
|
5877
|
+
"suggest",
|
|
5878
|
+
"memory",
|
|
5879
|
+
"worklog",
|
|
5880
|
+
"journal",
|
|
5881
|
+
"run",
|
|
5882
|
+
"output",
|
|
5883
|
+
"map",
|
|
5884
|
+
"symbols"
|
|
5885
|
+
]
|
|
5259
5886
|
],
|
|
5260
5887
|
["Indexing and search:", ["index", "watch", "reindex", "zvec", "graphify", "llm"]],
|
|
5261
5888
|
[
|
|
@@ -5265,6 +5892,7 @@ var HELP_GROUPS = [
|
|
|
5265
5892
|
"db",
|
|
5266
5893
|
"migrate",
|
|
5267
5894
|
"stats",
|
|
5895
|
+
"storage",
|
|
5268
5896
|
"inspect",
|
|
5269
5897
|
"namespaces",
|
|
5270
5898
|
"list-facts",
|
|
@@ -5301,6 +5929,14 @@ Quick start:
|
|
|
5301
5929
|
see exactly what an agent would be given`
|
|
5302
5930
|
);
|
|
5303
5931
|
}
|
|
5932
|
+
async function indexModelStats(root) {
|
|
5933
|
+
const { cardProgress } = await import("./summaries-QXWFFDUZ.js");
|
|
5934
|
+
const { listIndexFacts: listIndexFacts2 } = await import("./index-facts-4MTEHKMN.js");
|
|
5935
|
+
return {
|
|
5936
|
+
cards: await cardProgress(root).catch(() => ({ carded: 0, eligible: 0 })),
|
|
5937
|
+
indexFacts: (await listIndexFacts2(root).catch(() => [])).length
|
|
5938
|
+
};
|
|
5939
|
+
}
|
|
5304
5940
|
async function withRuntime(fn) {
|
|
5305
5941
|
await runSafely(async () => {
|
|
5306
5942
|
const runtime = await createRuntime(await loadRuntimeConfig());
|
|
@@ -5315,7 +5951,7 @@ async function runSafely(fn) {
|
|
|
5315
5951
|
try {
|
|
5316
5952
|
await fn();
|
|
5317
5953
|
} catch (error) {
|
|
5318
|
-
console.error(
|
|
5954
|
+
console.error(pc25.red(String(redactSecrets(error.message))));
|
|
5319
5955
|
process.exitCode = 1;
|
|
5320
5956
|
}
|
|
5321
5957
|
}
|
|
@@ -5329,9 +5965,9 @@ function parseDuration(value) {
|
|
|
5329
5965
|
return amount * 60 * 1e3;
|
|
5330
5966
|
}
|
|
5331
5967
|
function printDoctor(report) {
|
|
5332
|
-
const ok2 =
|
|
5333
|
-
const bad =
|
|
5334
|
-
const warn2 =
|
|
5968
|
+
const ok2 = pc25.green("ok");
|
|
5969
|
+
const bad = pc25.red("fail");
|
|
5970
|
+
const warn2 = pc25.yellow("warn");
|
|
5335
5971
|
console.log(`scaffold root: ${report.scaffold.codebuddyDir.ok ? ok2 : bad}`);
|
|
5336
5972
|
if (report.scaffold.codebuddyDir.warning)
|
|
5337
5973
|
console.log(` ${report.scaffold.codebuddyDir.warning}`);
|
|
@@ -5357,7 +5993,7 @@ function printDoctor(report) {
|
|
|
5357
5993
|
);
|
|
5358
5994
|
console.log(`estimated daily cap remaining: ${report.usage.estimatedDailyCapRemaining}`);
|
|
5359
5995
|
if (report.usage.warning80Percent)
|
|
5360
|
-
console.log(
|
|
5996
|
+
console.log(pc25.yellow("usage warning: daily cap is over 80% used"));
|
|
5361
5997
|
console.log(
|
|
5362
5998
|
`config permissions: ${report.config.exists ? report.config.mode : "missing"} ${report.config.ok ? ok2 : warn2}`
|
|
5363
5999
|
);
|
|
@@ -5367,9 +6003,9 @@ function printDoctor(report) {
|
|
|
5367
6003
|
}
|
|
5368
6004
|
}
|
|
5369
6005
|
function printDbDoctor(report) {
|
|
5370
|
-
const ok2 =
|
|
5371
|
-
const bad =
|
|
5372
|
-
const warn2 =
|
|
6006
|
+
const ok2 = pc25.green("ok");
|
|
6007
|
+
const bad = pc25.red("fail");
|
|
6008
|
+
const warn2 = pc25.yellow("warn");
|
|
5373
6009
|
console.log(`DB connectivity: ${report.db.ok ? ok2 : bad}`);
|
|
5374
6010
|
if (report.db.postgresUrlSource) console.log(` url source: ${report.db.postgresUrlSource}`);
|
|
5375
6011
|
if (report.db.error) console.log(` ${report.db.error}`);
|
|
@@ -5382,7 +6018,42 @@ function printDbDoctor(report) {
|
|
|
5382
6018
|
}
|
|
5383
6019
|
if (report.namespace.warning) console.log(` ${report.namespace.warning}`);
|
|
5384
6020
|
}
|
|
6021
|
+
function printStorage(database, capture2) {
|
|
6022
|
+
if ("unavailable" in database) {
|
|
6023
|
+
console.log(pc25.yellow(`database: unavailable (${database.unavailable})`));
|
|
6024
|
+
} else {
|
|
6025
|
+
const drift = database.markdownOnly.length + database.dbOnly.length;
|
|
6026
|
+
console.log(
|
|
6027
|
+
`facts: ${database.markdownFacts} Markdown, ${database.databaseFacts} database${database.invalidFiles > 0 ? `, ${database.invalidFiles} unreadable file(s)` : ""}`
|
|
6028
|
+
);
|
|
6029
|
+
console.log(
|
|
6030
|
+
drift === 0 ? pc25.green("sync: Markdown and database agree") : pc25.yellow(
|
|
6031
|
+
`sync: ${database.markdownOnly.length} file(s) not in the database, ${database.dbOnly.length} row(s) without a file (run \`codebuddy reindex\`)`
|
|
6032
|
+
)
|
|
6033
|
+
);
|
|
6034
|
+
if (database.unbacked.length > 0) {
|
|
6035
|
+
console.log(
|
|
6036
|
+
pc25.yellow(
|
|
6037
|
+
`backup: ${database.unbacked.length} database-only fact(s) have no Markdown copy`
|
|
6038
|
+
)
|
|
6039
|
+
);
|
|
6040
|
+
}
|
|
6041
|
+
const e = database.embeddings;
|
|
6042
|
+
console.log(
|
|
6043
|
+
`embeddings: ${e.ready} ready, ${e.pending} pending, ${e.processing} processing, ${e.failed} failed${database.oldestPendingAt ? ` (oldest pending ${database.oldestPendingAt})` : ""}`
|
|
6044
|
+
);
|
|
6045
|
+
for (const space of database.spaces) {
|
|
6046
|
+
console.log(` space ${space.model} ${space.space ?? "(legacy)"}: ${space.count}`);
|
|
6047
|
+
}
|
|
6048
|
+
if (database.spaces.length > 1) {
|
|
6049
|
+
console.log(pc25.yellow(" more than one embedding space: run `codebuddy reindex --respace`"));
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
console.log(
|
|
6053
|
+
capture2.stagedNotCaptured.length === 0 && capture2.journalBehind.length === 0 ? pc25.green("capture: nothing pending") : `capture: ${capture2.stagedNotCaptured.length} session(s) with uncaptured edits, ${capture2.journalBehind.length} journal(s) behind their transcript`
|
|
6054
|
+
);
|
|
6055
|
+
}
|
|
5385
6056
|
export {
|
|
5386
6057
|
createCli
|
|
5387
6058
|
};
|
|
5388
|
-
//# sourceMappingURL=main-
|
|
6059
|
+
//# sourceMappingURL=main-RLMKQZW4.js.map
|