@orquentra/codebuddy 2.6.2 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +10 -0
  2. package/dist/adapters-S4HHTWJW.js +24 -0
  3. package/dist/{chunk-IBLJOHJL.js → chunk-3QGW53GD.js} +2 -2
  4. package/dist/{chunk-IMFUYEZA.js → chunk-6DONJW3W.js} +232 -64
  5. package/dist/chunk-6DONJW3W.js.map +1 -0
  6. package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
  7. package/dist/{chunk-S6VEG4S2.js → chunk-6KGYAFG7.js} +28 -5
  8. package/dist/chunk-6KGYAFG7.js.map +1 -0
  9. package/dist/{chunk-OWWCDRXW.js → chunk-6NZEZJYD.js} +9 -7
  10. package/dist/chunk-6NZEZJYD.js.map +1 -0
  11. package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
  12. package/dist/chunk-AL6VBTNA.js.map +1 -0
  13. package/dist/{chunk-XBJQKCLU.js → chunk-CAEPHLHC.js} +13 -9
  14. package/dist/chunk-CAEPHLHC.js.map +1 -0
  15. package/dist/chunk-CG5B3LYG.js +66 -0
  16. package/dist/chunk-CG5B3LYG.js.map +1 -0
  17. package/dist/{chunk-IHQM2ZDV.js → chunk-DFV2SK6C.js} +1 -1
  18. package/dist/{chunk-IHQM2ZDV.js.map → chunk-DFV2SK6C.js.map} +1 -1
  19. package/dist/{chunk-QBDJXGG5.js → chunk-EJQVGFH2.js} +93 -22
  20. package/dist/chunk-EJQVGFH2.js.map +1 -0
  21. package/dist/{chunk-5MQOE4J6.js → chunk-ESZXCZIN.js} +3 -3
  22. package/dist/{chunk-J2EAZ4MA.js → chunk-GKNYMNFM.js} +22 -25
  23. package/dist/chunk-GKNYMNFM.js.map +1 -0
  24. package/dist/{chunk-5FW3PMNP.js → chunk-GXOML4UZ.js} +76 -11
  25. package/dist/chunk-GXOML4UZ.js.map +1 -0
  26. package/dist/{chunk-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
  27. package/dist/chunk-HPOR6YXA.js.map +1 -0
  28. package/dist/chunk-ITQO632W.js +673 -0
  29. package/dist/chunk-ITQO632W.js.map +1 -0
  30. package/dist/chunk-J4T6HHI4.js +1444 -0
  31. package/dist/chunk-J4T6HHI4.js.map +1 -0
  32. package/dist/{chunk-5KZY6OTG.js → chunk-KNR2P2UY.js} +33 -83
  33. package/dist/chunk-KNR2P2UY.js.map +1 -0
  34. package/dist/{chunk-2XDPKFUW.js → chunk-NY2YXP3R.js} +46 -20
  35. package/dist/chunk-NY2YXP3R.js.map +1 -0
  36. package/dist/{chunk-RAJJSCIP.js → chunk-SFMIMLXR.js} +2 -2
  37. package/dist/chunk-TGFTKWOR.js +16 -0
  38. package/dist/chunk-TGFTKWOR.js.map +1 -0
  39. package/dist/{chunk-NERKZSSE.js → chunk-TLWGZVPC.js} +395 -686
  40. package/dist/chunk-TLWGZVPC.js.map +1 -0
  41. package/dist/{chunk-B6CO63SQ.js → chunk-UK6XTS2Y.js} +24 -10
  42. package/dist/chunk-UK6XTS2Y.js.map +1 -0
  43. package/dist/chunk-W7GAXL6C.js +104 -0
  44. package/dist/chunk-W7GAXL6C.js.map +1 -0
  45. package/dist/chunk-XL2HLACE.js +55 -0
  46. package/dist/chunk-XL2HLACE.js.map +1 -0
  47. package/dist/chunk-Y7D7PUBE.js +321 -0
  48. package/dist/chunk-Y7D7PUBE.js.map +1 -0
  49. package/dist/{chunk-R7CD6EXS.js → chunk-ZNLSCV3J.js} +578 -381
  50. package/dist/chunk-ZNLSCV3J.js.map +1 -0
  51. package/dist/cli/index.js +5 -5
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
  54. package/dist/{codebuddy-B_pCaQrV.d.ts → codebuddy-DO7ZhoWn.d.ts} +15 -1
  55. package/dist/config-GEZFLXK6.js +12 -0
  56. package/dist/index.d.ts +46 -3
  57. package/dist/index.js +23 -19
  58. package/dist/install-BXM56QCD.js +19 -0
  59. package/dist/langgraph/index.d.ts +1 -1
  60. package/dist/lean-command-7DFW32MA.js +19 -0
  61. package/dist/{local-5MZURLCZ.js → local-ST4WMRCG.js} +3 -3
  62. package/dist/{main-MTBQZGJV.js → main-QUASSFP7.js} +987 -1741
  63. package/dist/main-QUASSFP7.js.map +1 -0
  64. package/dist/{memory-KIC5FJVW.js → memory-TGHVTBME.js} +9 -8
  65. package/dist/memory-TGHVTBME.js.map +1 -0
  66. package/dist/operations-WJR7AZZF.js +26 -0
  67. package/dist/pretool-command-JTKX6G3M.js +13 -0
  68. package/dist/{rerank-5QZ3PNUW.js → rerank-IQAEWLQR.js} +6 -5
  69. package/dist/rerank-IQAEWLQR.js.map +1 -0
  70. package/dist/{runtime-CUBOOPZO.js → runtime-OQQ22VJF.js} +15 -11
  71. package/dist/summaries-ML53R7EL.js +21 -0
  72. package/dist/summaries-ML53R7EL.js.map +1 -0
  73. package/dist/{zvec-MSRKT7IV.js → zvec-XDZNZ2OL.js} +11 -3
  74. package/dist/zvec-XDZNZ2OL.js.map +1 -0
  75. package/package.json +1 -1
  76. package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
  77. package/src/db/migrations/meta/0006_snapshot.json +1258 -0
  78. package/src/db/migrations/meta/_journal.json +7 -0
  79. package/dist/chunk-2XDPKFUW.js.map +0 -1
  80. package/dist/chunk-5FW3PMNP.js.map +0 -1
  81. package/dist/chunk-5KZY6OTG.js.map +0 -1
  82. package/dist/chunk-B6CO63SQ.js.map +0 -1
  83. package/dist/chunk-GDJH7XJC.js.map +0 -1
  84. package/dist/chunk-IMFUYEZA.js.map +0 -1
  85. package/dist/chunk-J2EAZ4MA.js.map +0 -1
  86. package/dist/chunk-JPHJBCZB.js.map +0 -1
  87. package/dist/chunk-NERKZSSE.js.map +0 -1
  88. package/dist/chunk-OWWCDRXW.js.map +0 -1
  89. package/dist/chunk-PQK3GKZH.js +0 -98
  90. package/dist/chunk-PQK3GKZH.js.map +0 -1
  91. package/dist/chunk-QBDJXGG5.js.map +0 -1
  92. package/dist/chunk-R7CD6EXS.js.map +0 -1
  93. package/dist/chunk-S6VEG4S2.js.map +0 -1
  94. package/dist/chunk-XBJQKCLU.js.map +0 -1
  95. package/dist/config-DEVHUDIU.js +0 -11
  96. package/dist/lean-command-HTQCPH76.js +0 -15
  97. package/dist/main-MTBQZGJV.js.map +0 -1
  98. package/dist/memory-KIC5FJVW.js.map +0 -1
  99. package/dist/operations-POCHY3WR.js +0 -25
  100. package/dist/pretool-command-AX6JXP25.js +0 -10
  101. package/dist/rerank-5QZ3PNUW.js.map +0 -1
  102. package/dist/summaries-SB5L2TSW.js +0 -18
  103. /package/dist/{cloud-7WM3YLI4.js.map → adapters-S4HHTWJW.js.map} +0 -0
  104. /package/dist/{chunk-IBLJOHJL.js.map → chunk-3QGW53GD.js.map} +0 -0
  105. /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
  106. /package/dist/{chunk-5MQOE4J6.js.map → chunk-ESZXCZIN.js.map} +0 -0
  107. /package/dist/{chunk-RAJJSCIP.js.map → chunk-SFMIMLXR.js.map} +0 -0
  108. /package/dist/{config-DEVHUDIU.js.map → cloud-LXZUR3U2.js.map} +0 -0
  109. /package/dist/{lean-command-HTQCPH76.js.map → config-GEZFLXK6.js.map} +0 -0
  110. /package/dist/{local-5MZURLCZ.js.map → install-BXM56QCD.js.map} +0 -0
  111. /package/dist/{operations-POCHY3WR.js.map → lean-command-7DFW32MA.js.map} +0 -0
  112. /package/dist/{pretool-command-AX6JXP25.js.map → local-ST4WMRCG.js.map} +0 -0
  113. /package/dist/{runtime-CUBOOPZO.js.map → operations-WJR7AZZF.js.map} +0 -0
  114. /package/dist/{summaries-SB5L2TSW.js.map → pretool-command-JTKX6G3M.js.map} +0 -0
  115. /package/dist/{zvec-MSRKT7IV.js.map → runtime-OQQ22VJF.js.map} +0 -0
package/README.md CHANGED
@@ -52,6 +52,15 @@ This opens a live local page showing:
52
52
 
53
53
  It updates as you work, and the traces never leave your machine.
54
54
 
55
+ ## Telemetry
56
+
57
+ CodeBuddy sends anonymous usage counts (tool names, timings, success, version,
58
+ OS, agent client) so we can see which features are used. Prompts, code, file
59
+ paths and repository names are never sent, and nothing is sent until a notice
60
+ has been shown once in your terminal. Opt out with `codebuddy telemetry disable`,
61
+ `CODEBUDDY_TELEMETRY=0` or `DO_NOT_TRACK=1`; CI never sends. Details:
62
+ [docs/telemetry.md](https://github.com/Orquentra-Inc/CodeBuddy/blob/main/docs/telemetry.md).
63
+
55
64
  ## Try it without a database
56
65
 
57
66
  ```bash
@@ -66,6 +75,7 @@ needs more detail. Inspect missing or truncated evidence before editing.
66
75
  ## Documentation
67
76
 
68
77
  - [Full guide and examples](https://github.com/Orquentra-Inc/CodeBuddy/blob/348bb7c/README.md)
78
+ - [Telemetry and the admin panel](https://github.com/Orquentra-Inc/CodeBuddy/blob/main/docs/telemetry.md)
69
79
  - [Token budgets](https://github.com/Orquentra-Inc/CodeBuddy/blob/main/docs/token-budgets.md)
70
80
  - [Optional LLM setup](https://github.com/Orquentra-Inc/CodeBuddy/blob/main/docs/llm-byok.md)
71
81
  - [Source and issues](https://github.com/Orquentra-Inc/CodeBuddy)
@@ -0,0 +1,24 @@
1
+ import {
2
+ AUTOMATION_MODES,
3
+ CLIENT_ADAPTERS,
4
+ CLIENT_IDS,
5
+ LIFECYCLE_EVENTS,
6
+ MODE_LABELS,
7
+ parseClient,
8
+ parseMode,
9
+ sharedRulesReaders,
10
+ supportedModes
11
+ } from "./chunk-W7GAXL6C.js";
12
+ import "./chunk-PZ5AY32C.js";
13
+ export {
14
+ AUTOMATION_MODES,
15
+ CLIENT_ADAPTERS,
16
+ CLIENT_IDS,
17
+ LIFECYCLE_EVENTS,
18
+ MODE_LABELS,
19
+ parseClient,
20
+ parseMode,
21
+ sharedRulesReaders,
22
+ supportedModes
23
+ };
24
+ //# sourceMappingURL=adapters-S4HHTWJW.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LOCAL_EMBEDDING_MODEL
3
- } from "./chunk-JPHJBCZB.js";
3
+ } from "./chunk-HPOR6YXA.js";
4
4
 
5
5
  // src/providers/local.ts
6
6
  import { homedir } from "os";
@@ -101,4 +101,4 @@ export {
101
101
  LocalModelProvider,
102
102
  rerankLocally
103
103
  };
104
- //# sourceMappingURL=chunk-IBLJOHJL.js.map
104
+ //# sourceMappingURL=chunk-3QGW53GD.js.map
@@ -1,3 +1,6 @@
1
+ import {
2
+ refreshZvecIndex
3
+ } from "./chunk-GXOML4UZ.js";
1
4
  import {
2
5
  PlanFileStore,
3
6
  PlanLifecycle,
@@ -9,43 +12,46 @@ import {
9
12
  enforceContextBudget,
10
13
  llmMemoryOptions,
11
14
  readPolicyFile,
12
- recordTrace,
13
15
  resolveRiskPaths
14
- } from "./chunk-R7CD6EXS.js";
16
+ } from "./chunk-ZNLSCV3J.js";
15
17
  import {
16
18
  SessionCapsuleLedger,
19
+ coveringTestCandidates,
17
20
  estimateTokens
18
- } from "./chunk-J2EAZ4MA.js";
21
+ } from "./chunk-GKNYMNFM.js";
19
22
  import {
20
- buildArchitectureMap,
21
- generateGraphifyGraph,
22
- indexRepository,
23
- loadArchitectureMap,
24
- loadFreshIndex,
25
- neighbours,
26
- queryMap
27
- } from "./chunk-NERKZSSE.js";
23
+ readTraces,
24
+ recordTrace
25
+ } from "./chunk-XL2HLACE.js";
28
26
  import {
29
27
  codeBuddyConfigSchema,
30
28
  createRuntime,
31
29
  listFactsPage
32
- } from "./chunk-2XDPKFUW.js";
30
+ } from "./chunk-NY2YXP3R.js";
33
31
  import {
34
32
  MemoryFileStore,
35
33
  VERSION
36
- } from "./chunk-S6VEG4S2.js";
34
+ } from "./chunk-6KGYAFG7.js";
37
35
  import {
38
- redactPersisted
39
- } from "./chunk-GDJH7XJC.js";
36
+ generateGraphifyGraph,
37
+ loadProjectArchitectureMap
38
+ } from "./chunk-TLWGZVPC.js";
40
39
  import {
41
- refreshZvecIndex
42
- } from "./chunk-5FW3PMNP.js";
40
+ buildArchitectureMap,
41
+ indexRepository,
42
+ loadFreshIndex,
43
+ neighbours,
44
+ queryMap
45
+ } from "./chunk-ITQO632W.js";
46
+ import {
47
+ redactPersisted
48
+ } from "./chunk-AL6VBTNA.js";
43
49
  import {
44
50
  configPath,
45
51
  loadPlanPolicy,
46
52
  loadRuntimeConfig,
47
53
  redactSecrets
48
- } from "./chunk-5KZY6OTG.js";
54
+ } from "./chunk-KNR2P2UY.js";
49
55
 
50
56
  // src/core/migrate-to-files.ts
51
57
  import { createHash } from "crypto";
@@ -164,11 +170,138 @@ function checksum(content) {
164
170
  return createHash("sha256").update(content).digest("hex");
165
171
  }
166
172
 
173
+ // src/telemetry/telemetry.ts
174
+ import { createHash as createHash2, randomUUID } from "crypto";
175
+ import { mkdir, readFile, writeFile } from "fs/promises";
176
+ import { homedir } from "os";
177
+ import { dirname, join } from "path";
178
+ var TELEMETRY_URL = "https://tanmay-hp-linux-server.tail41864f.ts.net/v1/events";
179
+ var FLUSH_INTERVAL_MS = 10 * 6e4;
180
+ var MAX_EVENTS = 500;
181
+ var SEND_TIMEOUT_MS = 3e3;
182
+ var NAME = /^[A-Za-z][A-Za-z0-9_:.-]{0,63}$/;
183
+ function telemetryStatePath(home = homedir()) {
184
+ return join(home, ".codebuddy", "telemetry.json");
185
+ }
186
+ async function readTelemetryState(home = homedir()) {
187
+ try {
188
+ const state = JSON.parse(await readFile(telemetryStatePath(home), "utf8"));
189
+ if (typeof state.installId === "string" && state.installId) return state;
190
+ } catch {
191
+ }
192
+ return writeTelemetryState({ installId: randomUUID() }, home);
193
+ }
194
+ async function writeTelemetryState(state, home = homedir()) {
195
+ const path = telemetryStatePath(home);
196
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
197
+ await writeFile(path, `${JSON.stringify(state, null, 2)}
198
+ `, { mode: 384 });
199
+ return state;
200
+ }
201
+ function telemetryBlocked(state, env = process.env) {
202
+ if (env.CODEBUDDY_TELEMETRY === "0") return "CODEBUDDY_TELEMETRY=0";
203
+ if (env.DO_NOT_TRACK === "1" || env.DO_NOT_TRACK === "true") return "DO_NOT_TRACK";
204
+ if (env.CI) return "CI";
205
+ if (state.disabled) return "disabled with `codebuddy telemetry disable`";
206
+ if (!telemetryUrl(env)) return "no collector configured";
207
+ if (!state.noticeAt) return "first-run notice not shown yet";
208
+ return null;
209
+ }
210
+ function telemetryUrl(env = process.env) {
211
+ return env.CODEBUDDY_TELEMETRY_URL ?? TELEMETRY_URL;
212
+ }
213
+ var TELEMETRY_NOTICE = `CodeBuddy collects anonymous usage data (which tools run, how long they take,
214
+ success/failure, version, OS, agent client) to improve the product.
215
+ Prompts, code, file paths and repository names are never sent.
216
+ Opt out any time: \`codebuddy telemetry disable\` or CODEBUDDY_TELEMETRY=0.
217
+ Details: https://github.com/Orquentra-Inc/CodeBuddy/blob/main/docs/telemetry.md`;
218
+ async function showTelemetryNoticeOnce(write = (text) => process.stderr.write(text), options = {}) {
219
+ const env = options.env ?? process.env;
220
+ if (!(options.tty ?? process.stderr.isTTY)) return;
221
+ try {
222
+ const state = await readTelemetryState(options.home);
223
+ if (state.noticeAt || state.disabled || env.CODEBUDDY_TELEMETRY === "0" || env.CI) return;
224
+ if (!telemetryUrl(env)) return;
225
+ write(`
226
+ ${TELEMETRY_NOTICE}
227
+
228
+ `);
229
+ await writeTelemetryState({ ...state, noticeAt: (/* @__PURE__ */ new Date()).toISOString() }, options.home);
230
+ } catch {
231
+ }
232
+ }
233
+ function anonymize(installId, value) {
234
+ return createHash2("sha256").update(`${installId}\0${value}`).digest("hex").slice(0, 16);
235
+ }
236
+ function toTelemetryEvents(installId, traces) {
237
+ return traces.map((trace) => ({
238
+ ts: trace.ts,
239
+ kind: NAME.test(trace.kind) ? trace.kind : "other",
240
+ name: NAME.test(trace.name) ? trace.name : "other",
241
+ ms: Math.max(0, Math.round(trace.ms) || 0),
242
+ ok: trace.ok === true,
243
+ ...typeof trace.tokensIn === "number" ? { tokensIn: trace.tokensIn } : {},
244
+ ...typeof trace.tokensOut === "number" ? { tokensOut: trace.tokensOut } : {},
245
+ ...trace.session ? { session: anonymize(installId, trace.session) } : {}
246
+ }));
247
+ }
248
+ async function flushTelemetry(repositoryRoot, options = {}) {
249
+ const env = options.env ?? process.env;
250
+ const now = options.now ?? Date.now();
251
+ try {
252
+ const state = await readTelemetryState(options.home);
253
+ if (telemetryBlocked(state, env)) return 0;
254
+ const cursorPath = join(repositoryRoot, ".codebuddy", "cache", "telemetry.json");
255
+ const cursor = await readFile(cursorPath, "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
256
+ if (cursor.flushedAt && now - cursor.flushedAt < FLUSH_INTERVAL_MS) return 0;
257
+ const save = (next) => mkdir(dirname(cursorPath), { recursive: true }).then(
258
+ () => writeFile(cursorPath, JSON.stringify(next))
259
+ );
260
+ await save({ ...cursor, flushedAt: now });
261
+ const pending = (await readTraces(repositoryRoot, 1e4)).filter((trace) => !cursor.sentTs || trace.ts > cursor.sentTs).slice(-MAX_EVENTS);
262
+ const last = pending.at(-1);
263
+ if (!last) return 0;
264
+ const batch = {
265
+ installId: state.installId,
266
+ project: anonymize(state.installId, repositoryRoot),
267
+ version: VERSION,
268
+ platform: process.platform,
269
+ arch: process.arch,
270
+ node: process.versions.node.split(".")[0] ?? "",
271
+ clients: await (options.clients ?? (() => installedClients(repositoryRoot)))().catch(
272
+ () => []
273
+ ),
274
+ events: toTelemetryEvents(state.installId, pending)
275
+ };
276
+ const response = await (options.fetchImpl ?? fetch)(telemetryUrl(env), {
277
+ method: "POST",
278
+ headers: { "content-type": "application/json" },
279
+ body: JSON.stringify(batch),
280
+ signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
281
+ });
282
+ if (!response.ok) return 0;
283
+ await save({ sentTs: last.ts, flushedAt: now });
284
+ return batch.events.length;
285
+ } catch {
286
+ return 0;
287
+ }
288
+ }
289
+ async function installedClients(repositoryRoot) {
290
+ const { CLIENT_IDS } = await import("./adapters-S4HHTWJW.js");
291
+ const { detectClient } = await import("./install-BXM56QCD.js");
292
+ const found = [];
293
+ for (const id of CLIENT_IDS) {
294
+ const status = await detectClient(id, repositoryRoot).catch(() => null);
295
+ if (status && (status.hooks || status.mcp)) found.push(id);
296
+ }
297
+ return found;
298
+ }
299
+
167
300
  // src/mcp/tools/index.ts
168
301
  import { z } from "zod";
169
302
 
170
303
  // src/context/delivery.ts
171
- import { createHash as createHash2 } from "crypto";
304
+ import { createHash as createHash3 } from "crypto";
172
305
  var DELIVERY_VERSION = 1;
173
306
  var OMITTABLE_SECTIONS = [
174
307
  "plan",
@@ -187,7 +320,7 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
187
320
  if (!options.sessionId) return output;
188
321
  const sections = OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
189
322
  key: `mcp:${key}`,
190
- hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
323
+ hash: createHash3("sha256").update(JSON.stringify(output[key])).digest("hex")
191
324
  }));
192
325
  try {
193
326
  await new SessionCapsuleLedger(repositoryRoot).exchange(options.sessionId, sections, {
@@ -214,7 +347,7 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
214
347
  output = enforceContextBudget(output);
215
348
  return OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
216
349
  key: `mcp:${key}`,
217
- hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
350
+ hash: createHash3("sha256").update(JSON.stringify(output[key])).digest("hex")
218
351
  }));
219
352
  }
220
353
  });
@@ -227,7 +360,7 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
227
360
  // src/suggest/engine.ts
228
361
  import { constants } from "fs";
229
362
  import { access } from "fs/promises";
230
- import { extname, join, resolve } from "path";
363
+ import { extname, join as join2, resolve } from "path";
231
364
 
232
365
  // src/suggest/types.ts
233
366
  var SEVERITY_RANK = {
@@ -241,7 +374,7 @@ var SEVERITY_RANK = {
241
374
  var RISK_SUGGEST_THRESHOLD = 50;
242
375
  var BLAST_RADIUS_MEDIUM = 5;
243
376
  var BLAST_RADIUS_HIGH = 10;
244
- var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
377
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
245
378
  async function generateSuggestions(input) {
246
379
  const repositoryRoot = resolve(input.repositoryRoot ?? process.cwd());
247
380
  const namespace = input.namespace;
@@ -264,7 +397,7 @@ async function generateSuggestions(input) {
264
397
  resolveRelevantPlan(planStore, namespace, input.planId),
265
398
  memoryStore.findIncidentFactsForPaths({ namespace, paths: targetPaths }),
266
399
  buildArchitectureMap(repositoryRoot),
267
- readPolicyFile(join(repositoryRoot, ".codebuddy", "policies.yaml"))
400
+ readPolicyFile(join2(repositoryRoot, ".codebuddy", "policies.yaml"))
268
401
  ]);
269
402
  const suggestions = [];
270
403
  for (const item of risks.items) {
@@ -338,13 +471,13 @@ async function generateSuggestions(input) {
338
471
  title: `No test found for ${path}`,
339
472
  detail: "Consider adding a test alongside this change.",
340
473
  path,
341
- evidence: [{ kind: "no_test", detail: "no *.test.* / *.spec.* sibling found" }]
474
+ evidence: [{ kind: "no_test", detail: "no conventional test file found" }]
342
475
  });
343
476
  }
344
477
  for (const rule of policyFile.rules) {
345
478
  const prefix = literalPrefix(rule.pattern);
346
479
  if (!prefix) continue;
347
- if (await pathExists(join(repositoryRoot, prefix))) continue;
480
+ if (await pathExists(join2(repositoryRoot, prefix))) continue;
348
481
  suggestions.push({
349
482
  id: `stale_policy:${rule.id}`,
350
483
  category: "stale_policy",
@@ -381,21 +514,14 @@ function isTestableCode(path) {
381
514
  if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(path)) return false;
382
515
  if (path.endsWith(".d.ts")) return false;
383
516
  if (path.includes("__tests__/")) return false;
517
+ if (/(^|\/)(test_[^/]*|[^/]*_test|conftest|__init__)\.py$|(^|\/)tests?\/.*\.py$/.test(path)) {
518
+ return false;
519
+ }
384
520
  return CODE_EXTENSIONS.has(extname(path).toLowerCase());
385
521
  }
386
522
  async function hasSiblingTest(repositoryRoot, path) {
387
- const ext = extname(path);
388
- const base = path.slice(0, path.length - ext.length);
389
- const dir = base.includes("/") ? base.slice(0, base.lastIndexOf("/")) : "";
390
- const name = base.includes("/") ? base.slice(base.lastIndexOf("/") + 1) : base;
391
- const candidates = [
392
- `${base}.test${ext}`,
393
- `${base}.spec${ext}`,
394
- `${dir ? `${dir}/` : ""}__tests__/${name}${ext}`,
395
- `${dir ? `${dir}/` : ""}__tests__/${name}.test${ext}`
396
- ];
397
- for (const candidate of candidates) {
398
- if (await pathExists(join(repositoryRoot, candidate))) return true;
523
+ for (const candidate of coveringTestCandidates(path)) {
524
+ if (await pathExists(join2(repositoryRoot, candidate))) return true;
399
525
  }
400
526
  return false;
401
527
  }
@@ -466,26 +592,31 @@ var INPUT_LIMITS = {
466
592
  arrayItems: 100,
467
593
  pathChars: 1024,
468
594
  taskChars: 2e3,
469
- agentIdChars: 200
595
+ agentIdChars: 200,
596
+ /** Entity ids and list cursors are short; a megabyte one is never valid. */
597
+ idChars: 512
470
598
  };
471
599
  var deliverySchema = {
472
600
  sessionId: z.string().min(1).max(200).optional(),
473
601
  ack: z.string().regex(/^dlv_[a-z0-9]+$/).optional(),
474
602
  full: z.boolean().optional()
475
603
  };
604
+ var pgText = (max) => z.string().min(1).max(max).refine((value) => !value.includes("\0"), "must not contain NUL bytes");
476
605
  var rememberSchema = {
477
- sessionId: z.string().min(1).max(200).optional(),
478
- content: z.string().min(1).max(INPUT_LIMITS.contentChars),
606
+ sessionId: pgText(200).optional(),
607
+ content: pgText(INPUT_LIMITS.contentChars),
479
608
  type: z.enum(["interaction", "fact", "summary"]).optional(),
480
- idempotencyKey: z.string().min(1).max(200).optional(),
481
- agentId: z.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
609
+ idempotencyKey: pgText(200).optional(),
610
+ agentId: pgText(INPUT_LIMITS.agentIdChars).optional()
482
611
  };
483
612
  var recallSchema = {
484
- sessionId: z.string().min(1).max(200),
485
- query: z.string().min(1).max(INPUT_LIMITS.taskChars),
613
+ sessionId: pgText(200),
614
+ query: pgText(INPUT_LIMITS.taskChars),
486
615
  budget: z.number().int().positive().optional(),
487
616
  callerModel: z.string().min(1).max(200).optional(),
488
- conflictMode: z.enum(["all", "latest", "highest_confidence"]).optional()
617
+ conflictMode: z.enum(["all", "latest", "highest_confidence"]).optional(),
618
+ /** Return only system, message id + content and core stats (no provenance). */
619
+ compact: z.boolean().optional()
489
620
  };
490
621
  var shareSchema = {
491
622
  from: z.string().min(1).max(INPUT_LIMITS.agentIdChars).optional(),
@@ -521,15 +652,17 @@ var planPatchSchema = {
521
652
  };
522
653
  var mcpToolInputSchemas = {
523
654
  remember: z.object(rememberSchema),
524
- remember_batch: z.object({ items: z.array(z.object(rememberSchema)).min(1) }),
655
+ remember_batch: z.object({
656
+ items: z.array(z.object(rememberSchema)).min(1).max(INPUT_LIMITS.batchItems)
657
+ }),
525
658
  recall: z.object(recallSchema),
526
659
  list_facts: z.object({
527
- subject: z.string().optional(),
660
+ subject: z.string().max(INPUT_LIMITS.titleChars).optional(),
528
661
  limit: z.number().int().positive().max(100).default(50),
529
- cursor: z.string().optional()
662
+ cursor: z.string().max(INPUT_LIMITS.idChars).optional()
530
663
  }),
531
664
  list_namespaces: z.object({}),
532
- forget: z.object({ id: z.string().min(1) }),
665
+ forget: z.object({ id: z.string().min(1).max(INPUT_LIMITS.idChars) }),
533
666
  share: z.object(shareSchema),
534
667
  unshare: z.object(unshareSchema),
535
668
  plan_current: z.object({}),
@@ -731,10 +864,13 @@ function registerCodeBuddyTools(server, options, profile = "full") {
731
864
  "recall",
732
865
  {
733
866
  title: "Recall",
734
- description: "Recall prompt-ready CodeBuddy context.",
867
+ description: "Recall prompt-ready CodeBuddy context. `budget` bounds message content (stats.tokensUsed); stats.responseTokens counts the whole JSON text returned (provenance and stats included) and stats.transportTokens the full tool result, which also repeats it as structuredContent. Pass compact=true for only message id + content.",
735
868
  inputSchema: mcpToolInputSchemas.recall.shape
736
869
  },
737
- async (input) => viaDatabase("recall", (db) => db.memory.recall(toRecallInput(input)))
870
+ async (input) => viaDatabase(
871
+ "recall",
872
+ async (db) => withResponseTokens(await db.memory.recall(toRecallInput(input)), input.compact === true)
873
+ )
738
874
  );
739
875
  register(
740
876
  "list_facts",
@@ -939,12 +1075,10 @@ function registerCodeBuddyTools(server, options, profile = "full") {
939
1075
  async (input) => json(neighbours(await cachedMap(), input.module, input.direction))
940
1076
  );
941
1077
  const tokenBudget = config.tokenBudget;
942
- const cachedMap = async () => {
943
- const { manifest } = await loadFreshIndex(projectRoot);
944
- const cached = await loadArchitectureMap(projectRoot, manifest);
945
- if (cached.map.modules.length > 0) return cached.map;
946
- return buildArchitectureMap(projectRoot);
947
- };
1078
+ const cachedMap = async () => loadProjectArchitectureMap(
1079
+ projectRoot,
1080
+ loadFreshIndex(projectRoot).then(({ manifest }) => manifest)
1081
+ );
948
1082
  register(
949
1083
  "context_bootstrap",
950
1084
  {
@@ -1106,7 +1240,30 @@ function toRememberInput(input) {
1106
1240
  return cleanOptional(input);
1107
1241
  }
1108
1242
  function toRecallInput(input) {
1109
- return cleanOptional(input);
1243
+ const { compact: _compact, ...rest } = input;
1244
+ return cleanOptional(rest);
1245
+ }
1246
+ function withResponseTokens(result, compact) {
1247
+ const stats = compact ? {
1248
+ tokensUsed: result.stats.tokensUsed,
1249
+ itemsIncluded: result.stats.itemsIncluded,
1250
+ itemsSkipped: result.stats.itemsSkipped,
1251
+ ...result.stats.budgetStatus ? { budgetStatus: result.stats.budgetStatus } : {}
1252
+ } : { ...result.stats };
1253
+ const value = compact ? {
1254
+ system: result.system,
1255
+ messages: result.messages.map((message) => ({
1256
+ ...message.fact ? { id: message.fact.id } : {},
1257
+ content: message.content
1258
+ })),
1259
+ stats
1260
+ } : { ...result, stats };
1261
+ for (let pass = 0; pass < 2; pass++) {
1262
+ const serialized = serializeToolResult(value);
1263
+ stats.responseTokens = estimateTokens(serialized.content[0]?.text);
1264
+ stats.transportTokens = estimateTokens(serialized);
1265
+ }
1266
+ return value;
1110
1267
  }
1111
1268
  function cleanOptional(input) {
1112
1269
  return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
@@ -1119,7 +1276,7 @@ import pino from "pino";
1119
1276
 
1120
1277
  // src/automation/project-sync.ts
1121
1278
  import { watch as fsWatch2 } from "fs";
1122
- import { join as join2, relative, resolve as resolve3, sep } from "path";
1279
+ import { join as join3, relative, resolve as resolve3, sep } from "path";
1123
1280
 
1124
1281
  // src/indexer/watch.ts
1125
1282
  import { watch as fsWatch } from "fs";
@@ -1300,7 +1457,7 @@ function startProjectSync(options) {
1300
1457
  300
1301
1458
  );
1302
1459
  memoryWatcher = fsWatch2(
1303
- join2(repositoryRoot, ".codebuddy", "memory"),
1460
+ join3(repositoryRoot, ".codebuddy", "memory"),
1304
1461
  { recursive: true },
1305
1462
  (_event, filename) => {
1306
1463
  const name = filename?.replace(/\\/g, "/") ?? "";
@@ -1334,7 +1491,7 @@ function startProjectSync(options) {
1334
1491
 
1335
1492
  // src/core/project-root.ts
1336
1493
  import { realpath, stat } from "fs/promises";
1337
- import { homedir } from "os";
1494
+ import { homedir as homedir2 } from "os";
1338
1495
  import { isAbsolute, parse, resolve as resolve4 } from "path";
1339
1496
  async function resolveInitializedProjectRoot(configuredRoot, cwd = process.cwd()) {
1340
1497
  if (configuredRoot !== void 0 && !isAbsolute(configuredRoot)) {
@@ -1342,7 +1499,7 @@ async function resolveInitializedProjectRoot(configuredRoot, cwd = process.cwd()
1342
1499
  }
1343
1500
  const selected = resolve4(configuredRoot ?? cwd);
1344
1501
  const root = await realpath(selected);
1345
- if (root === parse(root).root || root === await realpath(homedir())) {
1502
+ if (root === parse(root).root || root === await realpath(homedir2())) {
1346
1503
  throw new Error(
1347
1504
  "Select an initialized project folder, not the filesystem root or home folder."
1348
1505
  );
@@ -1492,8 +1649,11 @@ async function startMcpServer(options = {}) {
1492
1649
  });
1493
1650
  const server = new McpServer({ name: "codebuddy", version: VERSION });
1494
1651
  registerCodeBuddyTools(server, { config, database }, toolProfile);
1652
+ const telemetry = setInterval(() => void flushTelemetry(projectRoot), 6e4);
1653
+ telemetry.unref();
1495
1654
  const shutdown = async () => {
1496
1655
  logger.info("codebuddy MCP server shutting down");
1656
+ clearInterval(telemetry);
1497
1657
  await server.close();
1498
1658
  await sync.close();
1499
1659
  await database.close();
@@ -1599,9 +1759,17 @@ function isConnectionRefused(error) {
1599
1759
  export {
1600
1760
  migrateMemoryToFiles,
1601
1761
  watchRepository,
1762
+ telemetryStatePath,
1763
+ readTelemetryState,
1764
+ writeTelemetryState,
1765
+ telemetryBlocked,
1766
+ telemetryUrl,
1767
+ TELEMETRY_NOTICE,
1768
+ showTelemetryNoticeOnce,
1769
+ flushTelemetry,
1602
1770
  generateSuggestions,
1603
1771
  mcpToolInputSchemas,
1604
1772
  registerCodeBuddyTools,
1605
1773
  startMcpServer
1606
1774
  };
1607
- //# sourceMappingURL=chunk-IMFUYEZA.js.map
1775
+ //# sourceMappingURL=chunk-6DONJW3W.js.map