@orquentra/codebuddy 2.7.0 → 2.9.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 (52) hide show
  1. package/README.md +10 -0
  2. package/dist/adapters-S4HHTWJW.js +24 -0
  3. package/dist/{chunk-EEUFU7KV.js → chunk-2WH2Q4TA.js} +180 -22
  4. package/dist/chunk-2WH2Q4TA.js.map +1 -0
  5. package/dist/{chunk-BQM7FJ7D.js → chunk-6NZEZJYD.js} +5 -3
  6. package/dist/{chunk-BQM7FJ7D.js.map → chunk-6NZEZJYD.js.map} +1 -1
  7. package/dist/{chunk-AQHPEJR6.js → chunk-ENKDFLOO.js} +2 -2
  8. package/dist/{chunk-YJIA2MFL.js → chunk-F4BJ3DXW.js} +15 -13
  9. package/dist/{chunk-YJIA2MFL.js.map → chunk-F4BJ3DXW.js.map} +1 -1
  10. package/dist/{chunk-MSUAVR3R.js → chunk-GXOML4UZ.js} +2 -2
  11. package/dist/chunk-ITQO632W.js +673 -0
  12. package/dist/chunk-ITQO632W.js.map +1 -0
  13. package/dist/chunk-J4T6HHI4.js +1444 -0
  14. package/dist/chunk-J4T6HHI4.js.map +1 -0
  15. package/dist/{chunk-AK2UAE32.js → chunk-QN76UJP7.js} +2 -2
  16. package/dist/{chunk-JUAFCLRB.js → chunk-SRA4MDVG.js} +2 -2
  17. package/dist/{chunk-JUAFCLRB.js.map → chunk-SRA4MDVG.js.map} +1 -1
  18. package/dist/{chunk-5EDGAZQN.js → chunk-TLWGZVPC.js} +64 -725
  19. package/dist/chunk-TLWGZVPC.js.map +1 -0
  20. package/dist/{chunk-LCSBL5TT.js → chunk-UK6XTS2Y.js} +5 -3
  21. package/dist/{chunk-LCSBL5TT.js.map → chunk-UK6XTS2Y.js.map} +1 -1
  22. package/dist/chunk-W7GAXL6C.js +104 -0
  23. package/dist/chunk-W7GAXL6C.js.map +1 -0
  24. package/dist/{chunk-TUR6YKEE.js → chunk-YN7TEIUH.js} +1 -1
  25. package/dist/chunk-YN7TEIUH.js.map +1 -0
  26. package/dist/cli/index.js +4 -4
  27. package/dist/index.js +9 -8
  28. package/dist/install-BXM56QCD.js +19 -0
  29. package/dist/{lean-command-CWJAVQ5G.js → lean-command-7DFW32MA.js} +4 -3
  30. package/dist/{main-AHNGHC4L.js → main-YJKSYFPT.js} +1422 -1772
  31. package/dist/main-YJKSYFPT.js.map +1 -0
  32. package/dist/{operations-7BTAAUHF.js → operations-OG4ISWIM.js} +3 -3
  33. package/dist/{pretool-command-3SXQSMAO.js → pretool-command-PRFOIHMW.js} +3 -3
  34. package/dist/{runtime-E7JQZ2BZ.js → runtime-GCSEIAVD.js} +7 -6
  35. package/dist/{summaries-XV3JGGCC.js → summaries-ML53R7EL.js} +4 -3
  36. package/dist/summaries-ML53R7EL.js.map +1 -0
  37. package/dist/{zvec-KP4KNB7Y.js → zvec-XDZNZ2OL.js} +4 -3
  38. package/dist/zvec-XDZNZ2OL.js.map +1 -0
  39. package/package.json +1 -1
  40. package/dist/chunk-5EDGAZQN.js.map +0 -1
  41. package/dist/chunk-EEUFU7KV.js.map +0 -1
  42. package/dist/chunk-TUR6YKEE.js.map +0 -1
  43. package/dist/main-AHNGHC4L.js.map +0 -1
  44. /package/dist/{lean-command-CWJAVQ5G.js.map → adapters-S4HHTWJW.js.map} +0 -0
  45. /package/dist/{chunk-AQHPEJR6.js.map → chunk-ENKDFLOO.js.map} +0 -0
  46. /package/dist/{chunk-MSUAVR3R.js.map → chunk-GXOML4UZ.js.map} +0 -0
  47. /package/dist/{chunk-AK2UAE32.js.map → chunk-QN76UJP7.js.map} +0 -0
  48. /package/dist/{operations-7BTAAUHF.js.map → install-BXM56QCD.js.map} +0 -0
  49. /package/dist/{pretool-command-3SXQSMAO.js.map → lean-command-7DFW32MA.js.map} +0 -0
  50. /package/dist/{runtime-E7JQZ2BZ.js.map → operations-OG4ISWIM.js.map} +0 -0
  51. /package/dist/{summaries-XV3JGGCC.js.map → pretool-command-PRFOIHMW.js.map} +0 -0
  52. /package/dist/{zvec-KP4KNB7Y.js.map → runtime-GCSEIAVD.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
  refreshZvecIndex
3
- } from "./chunk-MSUAVR3R.js";
3
+ } from "./chunk-GXOML4UZ.js";
4
4
  import {
5
5
  PlanFileStore,
6
6
  PlanLifecycle,
@@ -13,33 +13,36 @@ import {
13
13
  llmMemoryOptions,
14
14
  readPolicyFile,
15
15
  resolveRiskPaths
16
- } from "./chunk-YJIA2MFL.js";
17
- import {
18
- recordTrace
19
- } from "./chunk-TUR6YKEE.js";
16
+ } from "./chunk-F4BJ3DXW.js";
20
17
  import {
21
18
  SessionCapsuleLedger,
22
19
  coveringTestCandidates,
23
20
  estimateTokens
24
21
  } from "./chunk-GKNYMNFM.js";
22
+ import {
23
+ readTraces,
24
+ recordTrace
25
+ } from "./chunk-YN7TEIUH.js";
25
26
  import {
26
27
  codeBuddyConfigSchema,
27
28
  createRuntime,
28
29
  listFactsPage
29
- } from "./chunk-AK2UAE32.js";
30
+ } from "./chunk-QN76UJP7.js";
30
31
  import {
31
32
  MemoryFileStore,
32
33
  VERSION
33
- } from "./chunk-JUAFCLRB.js";
34
+ } from "./chunk-SRA4MDVG.js";
34
35
  import {
35
- buildArchitectureMap,
36
36
  generateGraphifyGraph,
37
+ loadProjectArchitectureMap
38
+ } from "./chunk-TLWGZVPC.js";
39
+ import {
40
+ buildArchitectureMap,
37
41
  indexRepository,
38
42
  loadFreshIndex,
39
- loadProjectArchitectureMap,
40
43
  neighbours,
41
44
  queryMap
42
- } from "./chunk-5EDGAZQN.js";
45
+ } from "./chunk-ITQO632W.js";
43
46
  import {
44
47
  redactPersisted
45
48
  } from "./chunk-AL6VBTNA.js";
@@ -167,11 +170,154 @@ function checksum(content) {
167
170
  return createHash("sha256").update(content).digest("hex");
168
171
  }
169
172
 
173
+ // src/telemetry/telemetry.ts
174
+ import { createHash as createHash2, randomUUID } from "crypto";
175
+ import { existsSync } from "fs";
176
+ import { mkdir, readFile, writeFile } from "fs/promises";
177
+ import { homedir } from "os";
178
+ import { dirname, join } from "path";
179
+ var TELEMETRY_URL = "https://tanmay-hp-linux-server.tail41864f.ts.net/v1/events";
180
+ var FLUSH_INTERVAL_MS = 10 * 6e4;
181
+ var MAX_EVENTS = 500;
182
+ var SEND_TIMEOUT_MS = 3e3;
183
+ var NAME = /^[A-Za-z][A-Za-z0-9_:.-]{0,63}$/;
184
+ function telemetryStatePath(home = homedir()) {
185
+ return join(home, ".codebuddy", "telemetry.json");
186
+ }
187
+ async function readTelemetryState(home = homedir()) {
188
+ try {
189
+ const state = JSON.parse(await readFile(telemetryStatePath(home), "utf8"));
190
+ if (typeof state.installId === "string" && state.installId) return state;
191
+ } catch {
192
+ }
193
+ return writeTelemetryState({ installId: randomUUID() }, home);
194
+ }
195
+ async function writeTelemetryState(state, home = homedir()) {
196
+ const path = telemetryStatePath(home);
197
+ await mkdir(dirname(path), { recursive: true, mode: 448 });
198
+ await writeFile(path, `${JSON.stringify(state, null, 2)}
199
+ `, { mode: 384 });
200
+ return state;
201
+ }
202
+ function telemetryBlocked(state, env = process.env) {
203
+ if (env.CODEBUDDY_TELEMETRY === "0") return "CODEBUDDY_TELEMETRY=0";
204
+ if (env.DO_NOT_TRACK === "1" || env.DO_NOT_TRACK === "true") return "DO_NOT_TRACK";
205
+ if (env.CI) return "CI";
206
+ if (state.disabled) return "disabled with `codebuddy telemetry disable`";
207
+ if (!telemetryUrl(env)) return "no collector configured";
208
+ if (!state.noticeAt) return "first-run notice not shown yet";
209
+ return null;
210
+ }
211
+ function telemetryUrl(env = process.env) {
212
+ return env.CODEBUDDY_TELEMETRY_URL ?? TELEMETRY_URL;
213
+ }
214
+ var TELEMETRY_NOTICE = `CodeBuddy collects anonymous usage data (which tools run, how long they take,
215
+ success/failure, version, OS, agent client) to improve the product.
216
+ Prompts, code, file paths and repository names are never sent.
217
+ Opt out any time: \`codebuddy telemetry disable\` or CODEBUDDY_TELEMETRY=0.
218
+ Details: https://github.com/Orquentra-Inc/CodeBuddy/blob/main/docs/telemetry.md`;
219
+ async function showTelemetryNoticeOnce(write = (text) => process.stderr.write(text), options = {}) {
220
+ const env = options.env ?? process.env;
221
+ if (!(options.tty ?? process.stderr.isTTY)) return;
222
+ try {
223
+ const state = await readTelemetryState(options.home);
224
+ if (state.noticeAt || state.disabled || env.CODEBUDDY_TELEMETRY === "0" || env.CI) return;
225
+ if (!telemetryUrl(env)) return;
226
+ write(`
227
+ ${TELEMETRY_NOTICE}
228
+
229
+ `);
230
+ await writeTelemetryState({ ...state, noticeAt: (/* @__PURE__ */ new Date()).toISOString() }, options.home);
231
+ } catch {
232
+ }
233
+ }
234
+ function anonymize(installId, value) {
235
+ return createHash2("sha256").update(`${installId}\0${value}`).digest("hex").slice(0, 16);
236
+ }
237
+ function toTelemetryEvents(installId, traces) {
238
+ return traces.map((trace) => ({
239
+ ts: trace.ts,
240
+ kind: NAME.test(trace.kind) ? trace.kind : "other",
241
+ name: NAME.test(trace.name) ? trace.name : "other",
242
+ ms: Math.max(0, Math.round(trace.ms) || 0),
243
+ ok: trace.ok === true,
244
+ ...typeof trace.tokensIn === "number" ? { tokensIn: trace.tokensIn } : {},
245
+ ...typeof trace.tokensOut === "number" ? { tokensOut: trace.tokensOut } : {},
246
+ ...trace.session ? { session: anonymize(installId, trace.session) } : {}
247
+ }));
248
+ }
249
+ async function flushTelemetry(repositoryRoot, options = {}) {
250
+ const env = options.env ?? process.env;
251
+ const now = options.now ?? Date.now();
252
+ try {
253
+ const state = await readTelemetryState(options.home);
254
+ if (telemetryBlocked(state, env)) return 0;
255
+ const cursorPath = join(repositoryRoot, ".codebuddy", "cache", "telemetry.json");
256
+ const cursor = await readFile(cursorPath, "utf8").then((raw) => JSON.parse(raw)).catch(() => ({}));
257
+ if (cursor.flushedAt && now - cursor.flushedAt < FLUSH_INTERVAL_MS) return 0;
258
+ const save = (next) => mkdir(dirname(cursorPath), { recursive: true }).then(
259
+ () => writeFile(cursorPath, JSON.stringify(next))
260
+ );
261
+ await save({ ...cursor, flushedAt: now });
262
+ const pending = (await readTraces(repositoryRoot, 1e4)).filter((trace) => !cursor.sentTs || trace.ts > cursor.sentTs).slice(-MAX_EVENTS);
263
+ const last = pending.at(-1);
264
+ if (!last) return 0;
265
+ const batch = {
266
+ installId: state.installId,
267
+ project: anonymize(state.installId, repositoryRoot),
268
+ version: VERSION,
269
+ platform: process.platform,
270
+ arch: process.arch,
271
+ node: process.versions.node.split(".")[0] ?? "",
272
+ clients: await (options.clients ?? (() => installedClients(repositoryRoot)))().catch(
273
+ () => []
274
+ ),
275
+ events: toTelemetryEvents(state.installId, pending)
276
+ };
277
+ const response = await (options.fetchImpl ?? fetch)(telemetryUrl(env), {
278
+ method: "POST",
279
+ headers: { "content-type": "application/json" },
280
+ body: JSON.stringify(batch),
281
+ signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
282
+ });
283
+ if (!response.ok) return 0;
284
+ await save({ sentTs: last.ts, flushedAt: now });
285
+ return batch.events.length;
286
+ } catch {
287
+ return 0;
288
+ }
289
+ }
290
+ async function traceCliCommand(command, started, root = process.cwd()) {
291
+ if (/^\[(hook|admin)\]/.test(command.description()) || command.name() === "serve") return;
292
+ if (!existsSync(join(root, ".codebuddy"))) return;
293
+ const path = [];
294
+ for (let current = command; current?.parent; current = current.parent) {
295
+ path.unshift(current.name());
296
+ }
297
+ await recordTrace(root, {
298
+ kind: "cli",
299
+ name: path.join(".") || "use",
300
+ ms: Date.now() - started,
301
+ ok: !process.exitCode
302
+ });
303
+ await flushTelemetry(root);
304
+ }
305
+ async function installedClients(repositoryRoot) {
306
+ const { CLIENT_IDS } = await import("./adapters-S4HHTWJW.js");
307
+ const { detectClient } = await import("./install-BXM56QCD.js");
308
+ const found = [];
309
+ for (const id of CLIENT_IDS) {
310
+ const status = await detectClient(id, repositoryRoot).catch(() => null);
311
+ if (status && (status.hooks || status.mcp)) found.push(id);
312
+ }
313
+ return found;
314
+ }
315
+
170
316
  // src/mcp/tools/index.ts
171
317
  import { z } from "zod";
172
318
 
173
319
  // src/context/delivery.ts
174
- import { createHash as createHash2 } from "crypto";
320
+ import { createHash as createHash3 } from "crypto";
175
321
  var DELIVERY_VERSION = 1;
176
322
  var OMITTABLE_SECTIONS = [
177
323
  "plan",
@@ -190,7 +336,7 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
190
336
  if (!options.sessionId) return output;
191
337
  const sections = OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
192
338
  key: `mcp:${key}`,
193
- hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
339
+ hash: createHash3("sha256").update(JSON.stringify(output[key])).digest("hex")
194
340
  }));
195
341
  try {
196
342
  await new SessionCapsuleLedger(repositoryRoot).exchange(options.sessionId, sections, {
@@ -217,7 +363,7 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
217
363
  output = enforceContextBudget(output);
218
364
  return OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
219
365
  key: `mcp:${key}`,
220
- hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
366
+ hash: createHash3("sha256").update(JSON.stringify(output[key])).digest("hex")
221
367
  }));
222
368
  }
223
369
  });
@@ -230,7 +376,7 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
230
376
  // src/suggest/engine.ts
231
377
  import { constants } from "fs";
232
378
  import { access } from "fs/promises";
233
- import { extname, join, resolve } from "path";
379
+ import { extname, join as join2, resolve } from "path";
234
380
 
235
381
  // src/suggest/types.ts
236
382
  var SEVERITY_RANK = {
@@ -267,7 +413,7 @@ async function generateSuggestions(input) {
267
413
  resolveRelevantPlan(planStore, namespace, input.planId),
268
414
  memoryStore.findIncidentFactsForPaths({ namespace, paths: targetPaths }),
269
415
  buildArchitectureMap(repositoryRoot),
270
- readPolicyFile(join(repositoryRoot, ".codebuddy", "policies.yaml"))
416
+ readPolicyFile(join2(repositoryRoot, ".codebuddy", "policies.yaml"))
271
417
  ]);
272
418
  const suggestions = [];
273
419
  for (const item of risks.items) {
@@ -347,7 +493,7 @@ async function generateSuggestions(input) {
347
493
  for (const rule of policyFile.rules) {
348
494
  const prefix = literalPrefix(rule.pattern);
349
495
  if (!prefix) continue;
350
- if (await pathExists(join(repositoryRoot, prefix))) continue;
496
+ if (await pathExists(join2(repositoryRoot, prefix))) continue;
351
497
  suggestions.push({
352
498
  id: `stale_policy:${rule.id}`,
353
499
  category: "stale_policy",
@@ -391,7 +537,7 @@ function isTestableCode(path) {
391
537
  }
392
538
  async function hasSiblingTest(repositoryRoot, path) {
393
539
  for (const candidate of coveringTestCandidates(path)) {
394
- if (await pathExists(join(repositoryRoot, candidate))) return true;
540
+ if (await pathExists(join2(repositoryRoot, candidate))) return true;
395
541
  }
396
542
  return false;
397
543
  }
@@ -1146,7 +1292,7 @@ import pino from "pino";
1146
1292
 
1147
1293
  // src/automation/project-sync.ts
1148
1294
  import { watch as fsWatch2 } from "fs";
1149
- import { join as join2, relative, resolve as resolve3, sep } from "path";
1295
+ import { join as join3, relative, resolve as resolve3, sep } from "path";
1150
1296
 
1151
1297
  // src/indexer/watch.ts
1152
1298
  import { watch as fsWatch } from "fs";
@@ -1327,7 +1473,7 @@ function startProjectSync(options) {
1327
1473
  300
1328
1474
  );
1329
1475
  memoryWatcher = fsWatch2(
1330
- join2(repositoryRoot, ".codebuddy", "memory"),
1476
+ join3(repositoryRoot, ".codebuddy", "memory"),
1331
1477
  { recursive: true },
1332
1478
  (_event, filename) => {
1333
1479
  const name = filename?.replace(/\\/g, "/") ?? "";
@@ -1361,7 +1507,7 @@ function startProjectSync(options) {
1361
1507
 
1362
1508
  // src/core/project-root.ts
1363
1509
  import { realpath, stat } from "fs/promises";
1364
- import { homedir } from "os";
1510
+ import { homedir as homedir2 } from "os";
1365
1511
  import { isAbsolute, parse, resolve as resolve4 } from "path";
1366
1512
  async function resolveInitializedProjectRoot(configuredRoot, cwd = process.cwd()) {
1367
1513
  if (configuredRoot !== void 0 && !isAbsolute(configuredRoot)) {
@@ -1369,7 +1515,7 @@ async function resolveInitializedProjectRoot(configuredRoot, cwd = process.cwd()
1369
1515
  }
1370
1516
  const selected = resolve4(configuredRoot ?? cwd);
1371
1517
  const root = await realpath(selected);
1372
- if (root === parse(root).root || root === await realpath(homedir())) {
1518
+ if (root === parse(root).root || root === await realpath(homedir2())) {
1373
1519
  throw new Error(
1374
1520
  "Select an initialized project folder, not the filesystem root or home folder."
1375
1521
  );
@@ -1519,8 +1665,11 @@ async function startMcpServer(options = {}) {
1519
1665
  });
1520
1666
  const server = new McpServer({ name: "codebuddy", version: VERSION });
1521
1667
  registerCodeBuddyTools(server, { config, database }, toolProfile);
1668
+ const telemetry = setInterval(() => void flushTelemetry(projectRoot), 6e4);
1669
+ telemetry.unref();
1522
1670
  const shutdown = async () => {
1523
1671
  logger.info("codebuddy MCP server shutting down");
1672
+ clearInterval(telemetry);
1524
1673
  await server.close();
1525
1674
  await sync.close();
1526
1675
  await database.close();
@@ -1626,9 +1775,18 @@ function isConnectionRefused(error) {
1626
1775
  export {
1627
1776
  migrateMemoryToFiles,
1628
1777
  watchRepository,
1778
+ telemetryStatePath,
1779
+ readTelemetryState,
1780
+ writeTelemetryState,
1781
+ telemetryBlocked,
1782
+ telemetryUrl,
1783
+ TELEMETRY_NOTICE,
1784
+ showTelemetryNoticeOnce,
1785
+ flushTelemetry,
1786
+ traceCliCommand,
1629
1787
  generateSuggestions,
1630
1788
  mcpToolInputSchemas,
1631
1789
  registerCodeBuddyTools,
1632
1790
  startMcpServer
1633
1791
  };
1634
- //# sourceMappingURL=chunk-EEUFU7KV.js.map
1792
+ //# sourceMappingURL=chunk-2WH2Q4TA.js.map