@orquentra/codebuddy 2.8.0 → 2.14.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 (73) hide show
  1. package/CLAUDE_CODEX.md +33 -0
  2. package/README.md +27 -3
  3. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  4. package/dist/{chunk-GKNYMNFM.js → chunk-4DKYKDHI.js} +6 -1
  5. package/dist/chunk-4DKYKDHI.js.map +1 -0
  6. package/dist/{chunk-6KGYAFG7.js → chunk-7QMHIJBA.js} +30 -3
  7. package/dist/chunk-7QMHIJBA.js.map +1 -0
  8. package/dist/{chunk-6NZEZJYD.js → chunk-ESC4Y3L7.js} +3 -5
  9. package/dist/{chunk-6NZEZJYD.js.map → chunk-ESC4Y3L7.js.map} +1 -1
  10. package/dist/chunk-GQKWUHMA.js +20 -0
  11. package/dist/chunk-GQKWUHMA.js.map +1 -0
  12. package/dist/{chunk-J4T6HHI4.js → chunk-L6BRYZ6G.js} +537 -316
  13. package/dist/chunk-L6BRYZ6G.js.map +1 -0
  14. package/dist/{chunk-UK6XTS2Y.js → chunk-LRQNPYCK.js} +13 -19
  15. package/dist/chunk-LRQNPYCK.js.map +1 -0
  16. package/dist/{chunk-GXOML4UZ.js → chunk-LW75SKZR.js} +2 -2
  17. package/dist/{chunk-EJQVGFH2.js → chunk-UOTKHSQ5.js} +26 -9
  18. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  19. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  20. package/dist/chunk-V7PT6ER5.js.map +1 -0
  21. package/dist/{chunk-ZNLSCV3J.js → chunk-XSNW6Y3X.js} +1253 -315
  22. package/dist/chunk-XSNW6Y3X.js.map +1 -0
  23. package/dist/{chunk-6DONJW3W.js → chunk-YJPCS2GP.js} +274 -69
  24. package/dist/chunk-YJPCS2GP.js.map +1 -0
  25. package/dist/{chunk-XL2HLACE.js → chunk-YN7TEIUH.js} +1 -1
  26. package/dist/chunk-YN7TEIUH.js.map +1 -0
  27. package/dist/{chunk-TLWGZVPC.js → chunk-YNSTIMCF.js} +746 -66
  28. package/dist/chunk-YNSTIMCF.js.map +1 -0
  29. package/dist/{chunk-NY2YXP3R.js → chunk-YWDH6DQ6.js} +70 -4
  30. package/dist/chunk-YWDH6DQ6.js.map +1 -0
  31. package/dist/cli/index.js +23 -9
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  34. package/dist/index.d.ts +6 -4
  35. package/dist/index.js +9 -9
  36. package/dist/install-EU35ERK5.js +30 -0
  37. package/dist/langgraph/index.d.ts +1 -1
  38. package/dist/lean-43ADYTBW.js +28 -0
  39. package/dist/{main-QUASSFP7.js → main-SFC5LV4E.js} +1333 -437
  40. package/dist/main-SFC5LV4E.js.map +1 -0
  41. package/dist/{operations-WJR7AZZF.js → operations-6Z6ZDVBP.js} +3 -3
  42. package/dist/{pretool-command-JTKX6G3M.js → pretool-command-B5GY5T6E.js} +3 -3
  43. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  44. package/dist/{runtime-OQQ22VJF.js → runtime-W5AW32SD.js} +15 -7
  45. package/dist/{summaries-ML53R7EL.js → summaries-32PHOB6L.js} +3 -4
  46. package/dist/{zvec-XDZNZ2OL.js → zvec-24HLAFGN.js} +3 -4
  47. package/dist/zvec-24HLAFGN.js.map +1 -0
  48. package/package.json +11 -2
  49. package/dist/chunk-6DONJW3W.js.map +0 -1
  50. package/dist/chunk-6KGYAFG7.js.map +0 -1
  51. package/dist/chunk-EJQVGFH2.js.map +0 -1
  52. package/dist/chunk-GKNYMNFM.js.map +0 -1
  53. package/dist/chunk-ITQO632W.js +0 -673
  54. package/dist/chunk-ITQO632W.js.map +0 -1
  55. package/dist/chunk-J4T6HHI4.js.map +0 -1
  56. package/dist/chunk-NY2YXP3R.js.map +0 -1
  57. package/dist/chunk-TLWGZVPC.js.map +0 -1
  58. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  59. package/dist/chunk-W7GAXL6C.js.map +0 -1
  60. package/dist/chunk-XL2HLACE.js.map +0 -1
  61. package/dist/chunk-ZNLSCV3J.js.map +0 -1
  62. package/dist/install-BXM56QCD.js +0 -19
  63. package/dist/lean-command-7DFW32MA.js +0 -19
  64. package/dist/main-QUASSFP7.js.map +0 -1
  65. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  66. /package/dist/{chunk-GXOML4UZ.js.map → chunk-LW75SKZR.js.map} +0 -0
  67. /package/dist/{install-BXM56QCD.js.map → install-EU35ERK5.js.map} +0 -0
  68. /package/dist/{lean-command-7DFW32MA.js.map → lean-43ADYTBW.js.map} +0 -0
  69. /package/dist/{operations-WJR7AZZF.js.map → operations-6Z6ZDVBP.js.map} +0 -0
  70. /package/dist/{pretool-command-JTKX6G3M.js.map → pretool-command-B5GY5T6E.js.map} +0 -0
  71. /package/dist/{runtime-OQQ22VJF.js.map → prompt-gate-TDTFFQW2.js.map} +0 -0
  72. /package/dist/{summaries-ML53R7EL.js.map → runtime-W5AW32SD.js.map} +0 -0
  73. /package/dist/{zvec-XDZNZ2OL.js.map → summaries-32PHOB6L.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  refreshZvecIndex
3
- } from "./chunk-GXOML4UZ.js";
3
+ } from "./chunk-LW75SKZR.js";
4
4
  import {
5
5
  PlanFileStore,
6
6
  PlanLifecycle,
@@ -11,38 +11,41 @@ import {
11
11
  configuredReranker,
12
12
  enforceContextBudget,
13
13
  llmMemoryOptions,
14
+ rankSemanticReuse,
14
15
  readPolicyFile,
15
16
  resolveRiskPaths
16
- } from "./chunk-ZNLSCV3J.js";
17
- import {
18
- SessionCapsuleLedger,
19
- coveringTestCandidates,
20
- estimateTokens
21
- } from "./chunk-GKNYMNFM.js";
17
+ } from "./chunk-XSNW6Y3X.js";
22
18
  import {
23
19
  readTraces,
24
20
  recordTrace
25
- } from "./chunk-XL2HLACE.js";
21
+ } from "./chunk-YN7TEIUH.js";
22
+ import {
23
+ SessionCapsuleLedger,
24
+ coveringTestCandidates,
25
+ estimateTokens,
26
+ findCoveringTest
27
+ } from "./chunk-4DKYKDHI.js";
26
28
  import {
27
29
  codeBuddyConfigSchema,
28
30
  createRuntime,
29
31
  listFactsPage
30
- } from "./chunk-NY2YXP3R.js";
32
+ } from "./chunk-YWDH6DQ6.js";
31
33
  import {
32
34
  MemoryFileStore,
33
35
  VERSION
34
- } from "./chunk-6KGYAFG7.js";
35
- import {
36
- generateGraphifyGraph,
37
- loadProjectArchitectureMap
38
- } from "./chunk-TLWGZVPC.js";
36
+ } from "./chunk-7QMHIJBA.js";
39
37
  import {
38
+ NON_SOURCE_PATH,
39
+ TEST_PATH,
40
40
  buildArchitectureMap,
41
+ generateGraphifyGraph,
41
42
  indexRepository,
42
43
  loadFreshIndex,
44
+ loadProjectArchitectureMap,
43
45
  neighbours,
44
- queryMap
45
- } from "./chunk-ITQO632W.js";
46
+ queryMap,
47
+ taskTerms
48
+ } from "./chunk-YNSTIMCF.js";
46
49
  import {
47
50
  redactPersisted
48
51
  } from "./chunk-AL6VBTNA.js";
@@ -172,6 +175,7 @@ function checksum(content) {
172
175
 
173
176
  // src/telemetry/telemetry.ts
174
177
  import { createHash as createHash2, randomUUID } from "crypto";
178
+ import { existsSync } from "fs";
175
179
  import { mkdir, readFile, writeFile } from "fs/promises";
176
180
  import { homedir } from "os";
177
181
  import { dirname, join } from "path";
@@ -286,9 +290,24 @@ async function flushTelemetry(repositoryRoot, options = {}) {
286
290
  return 0;
287
291
  }
288
292
  }
293
+ async function traceCliCommand(command, started, root = process.cwd()) {
294
+ if (/^\[(hook|admin)\]/.test(command.description()) || command.name() === "serve") return;
295
+ if (!existsSync(join(root, ".codebuddy"))) return;
296
+ const path = [];
297
+ for (let current = command; current?.parent; current = current.parent) {
298
+ path.unshift(current.name());
299
+ }
300
+ await recordTrace(root, {
301
+ kind: "cli",
302
+ name: path.join(".") || "use",
303
+ ms: Date.now() - started,
304
+ ok: !process.exitCode
305
+ });
306
+ await flushTelemetry(root);
307
+ }
289
308
  async function installedClients(repositoryRoot) {
290
- const { CLIENT_IDS } = await import("./adapters-S4HHTWJW.js");
291
- const { detectClient } = await import("./install-BXM56QCD.js");
309
+ const { CLIENT_IDS } = await import("./adapters-OJRSVFV3.js");
310
+ const { detectClient } = await import("./install-EU35ERK5.js");
292
311
  const found = [];
293
312
  for (const id of CLIENT_IDS) {
294
313
  const status = await detectClient(id, repositoryRoot).catch(() => null);
@@ -312,7 +331,8 @@ var OMITTABLE_SECTIONS = [
312
331
  "handoffs",
313
332
  "architecture",
314
333
  "symbols",
315
- "neighbours"
334
+ "neighbours",
335
+ "reuse"
316
336
  ];
317
337
  var SESSION_TTL_MS = 30 * 60 * 1e3;
318
338
  async function applySessionDelivery(repositoryRoot, payload, options) {
@@ -357,10 +377,88 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
357
377
  }
358
378
  }
359
379
 
380
+ // src/context/reuse.ts
381
+ import { readFile as readFile2 } from "fs/promises";
382
+ import { join as join2 } from "path";
383
+ var REUSE_ADMISSION = { minMatched: 2, minCoverage: 0.67, limit: 3 };
384
+ var REUSABLE_KINDS = /* @__PURE__ */ new Set(["function", "class", "method", "const"]);
385
+ async function findReuse(repositoryRoot, task, manifest, map, admission = REUSE_ADMISSION, semanticRanker = rankSemanticReuse) {
386
+ const terms = new Set(taskTerms(task));
387
+ if (terms.size < admission.minMatched) return [];
388
+ const symbols = Object.values(manifest.entries).filter((entry) => !TEST_PATH.test(entry.path) && !NON_SOURCE_PATH.test(entry.path)).flatMap(
389
+ (entry) => (entry.symbolTable ?? []).filter((symbol) => symbol.exported && REUSABLE_KINDS.has(symbol.kind)).map((symbol) => ({ ...symbol, path: entry.path, words: new Set(taskTerms(symbol.name)) }))
390
+ );
391
+ const frequency = /* @__PURE__ */ new Map();
392
+ for (const symbol of symbols) {
393
+ for (const word of symbol.words) frequency.set(word, (frequency.get(word) ?? 0) + 1);
394
+ }
395
+ const idf = (word) => Math.log((symbols.length + 1) / ((frequency.get(word) ?? 0) + 1));
396
+ const ranked = symbols.map((symbol) => {
397
+ const matched = [...symbol.words].filter((word) => terms.has(word));
398
+ return {
399
+ symbol,
400
+ matched,
401
+ coverage: symbol.words.size > 0 ? matched.length / symbol.words.size : 0,
402
+ score: matched.reduce((sum, word) => sum + idf(word), 0)
403
+ };
404
+ }).filter(
405
+ (item) => item.matched.length >= admission.minMatched && item.coverage >= admission.minCoverage
406
+ ).sort((a, b) => b.score - a.score || a.symbol.path.localeCompare(b.symbol.path));
407
+ let semantic = false;
408
+ if (!ranked.length) {
409
+ const candidates = await semanticRanker(repositoryRoot, task, manifest);
410
+ const first = candidates[0];
411
+ if (first && first.score >= 0.84 && first.score - (candidates[1]?.score ?? 0) >= 0.04) {
412
+ const symbol = symbols.find((item) => item.path === first.path && item.name === first.name);
413
+ if (symbol) {
414
+ ranked.push({ symbol, matched: [], coverage: 0, score: first.score });
415
+ semantic = true;
416
+ }
417
+ }
418
+ }
419
+ const indexed = new Set(Object.keys(manifest.entries));
420
+ const seen = /* @__PURE__ */ new Set();
421
+ const picked = [];
422
+ for (const { symbol, matched, score } of ranked) {
423
+ const key = `${symbol.path}:${symbol.name}`;
424
+ if (seen.has(key)) continue;
425
+ seen.add(key);
426
+ const usage = await usageSite(repositoryRoot, symbol.path, symbol.name, map);
427
+ const test = findCoveringTest(symbol.path, indexed);
428
+ picked.push({
429
+ path: symbol.path,
430
+ name: symbol.name,
431
+ signature: symbol.signature,
432
+ startLine: symbol.startLine,
433
+ endLine: symbol.endLine,
434
+ matched,
435
+ score: Math.round(score * 100) / 100,
436
+ ...semantic ? { evidence: "semantic" } : {},
437
+ ...usage ? { usage } : {},
438
+ ...test ? { test } : {}
439
+ });
440
+ if (picked.length === admission.limit) break;
441
+ }
442
+ return picked;
443
+ }
444
+ async function usageSite(root, path, name, map) {
445
+ const short = name.split(".").at(-1) ?? name;
446
+ const pattern = new RegExp(`\\b${short.replace(/[^\w$]/g, "")}\\b`);
447
+ const importers = (map?.edges ?? []).filter((edge) => edge.to === path && !TEST_PATH.test(edge.from)).map((edge) => edge.from).sort();
448
+ for (const importer of importers.slice(0, 20)) {
449
+ const source = await readFile2(join2(root, importer), "utf8").catch(() => "");
450
+ const lines = source.split("\n");
451
+ const index = lines.findIndex((line) => pattern.test(line) && !/^\s*import\b/.test(line));
452
+ if (index !== -1)
453
+ return `${importer}:${index + 1}: ${(lines[index] ?? "").trim().slice(0, 140)}`;
454
+ }
455
+ return void 0;
456
+ }
457
+
360
458
  // src/suggest/engine.ts
361
459
  import { constants } from "fs";
362
460
  import { access } from "fs/promises";
363
- import { extname, join as join2, resolve } from "path";
461
+ import { extname, join as join3, resolve } from "path";
364
462
 
365
463
  // src/suggest/types.ts
366
464
  var SEVERITY_RANK = {
@@ -397,7 +495,7 @@ async function generateSuggestions(input) {
397
495
  resolveRelevantPlan(planStore, namespace, input.planId),
398
496
  memoryStore.findIncidentFactsForPaths({ namespace, paths: targetPaths }),
399
497
  buildArchitectureMap(repositoryRoot),
400
- readPolicyFile(join2(repositoryRoot, ".codebuddy", "policies.yaml"))
498
+ readPolicyFile(join3(repositoryRoot, ".codebuddy", "policies.yaml"))
401
499
  ]);
402
500
  const suggestions = [];
403
501
  for (const item of risks.items) {
@@ -477,7 +575,7 @@ async function generateSuggestions(input) {
477
575
  for (const rule of policyFile.rules) {
478
576
  const prefix = literalPrefix(rule.pattern);
479
577
  if (!prefix) continue;
480
- if (await pathExists(join2(repositoryRoot, prefix))) continue;
578
+ if (await pathExists(join3(repositoryRoot, prefix))) continue;
481
579
  suggestions.push({
482
580
  id: `stale_policy:${rule.id}`,
483
581
  category: "stale_policy",
@@ -521,7 +619,7 @@ function isTestableCode(path) {
521
619
  }
522
620
  async function hasSiblingTest(repositoryRoot, path) {
523
621
  for (const candidate of coveringTestCandidates(path)) {
524
- if (await pathExists(join2(repositoryRoot, candidate))) return true;
622
+ if (await pathExists(join3(repositoryRoot, candidate))) return true;
525
623
  }
526
624
  return false;
527
625
  }
@@ -570,6 +668,40 @@ function normalize(path) {
570
668
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
571
669
  }
572
670
 
671
+ // src/mcp/database-retry.ts
672
+ function isTransientDatabaseError(error) {
673
+ let current = error;
674
+ for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
675
+ const item = current;
676
+ if (typeof item.code === "string" && (/* @__PURE__ */ new Set([
677
+ "ECONNRESET",
678
+ "ECONNREFUSED",
679
+ "EPIPE",
680
+ "CONNECTION_CLOSED",
681
+ "CONNECTION_ENDED",
682
+ "08000",
683
+ "08001",
684
+ "08003",
685
+ "08006",
686
+ "57P01",
687
+ "57P02",
688
+ "57P03"
689
+ ])).has(item.code))
690
+ return true;
691
+ current = item.cause;
692
+ }
693
+ return false;
694
+ }
695
+ async function withDatabaseRetry(operation, readOnly, delayMs = 250) {
696
+ try {
697
+ return await operation();
698
+ } catch (error) {
699
+ if (!readOnly || !isTransientDatabaseError(error)) throw error;
700
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
701
+ return operation();
702
+ }
703
+ }
704
+
573
705
  // src/mcp/tool-result.ts
574
706
  function serializeToolResult(value, format = process.env.CODEBUDDY_RESPONSE_FORMAT ?? "both") {
575
707
  if (format !== "text" && format !== "both") {
@@ -615,7 +747,7 @@ var recallSchema = {
615
747
  budget: z.number().int().positive().optional(),
616
748
  callerModel: z.string().min(1).max(200).optional(),
617
749
  conflictMode: z.enum(["all", "latest", "highest_confidence"]).optional(),
618
- /** Return only system, message id + content and core stats (no provenance). */
750
+ /** Default true: a flat list of memories (id, content, date). false = full provenance + stats. */
619
751
  compact: z.boolean().optional()
620
752
  };
621
753
  var shareSchema = {
@@ -752,6 +884,30 @@ var FILE_TOOLS = Object.keys(mcpToolInputSchemas).filter(
752
884
  (name) => !DATABASE_TOOLS.includes(name)
753
885
  );
754
886
  var DEGRADED_FALLBACK = "PostgreSQL is unavailable, so this call did nothing. File-based tools still work; read the repository directly and say recall is unavailable instead of guessing. Run `codebuddy db doctor` to diagnose; the next call after retryInMs reconnects.";
887
+ var LEAN_TOOLS = {
888
+ context_pack: {
889
+ description: "Find the files, symbols and line ranges for a coding task. Call only when the injected CodeBuddy block names no files.",
890
+ defaults: { tokenBudget: 800 },
891
+ inputSchema: {
892
+ task: z.string(),
893
+ paths: z.array(z.string()).optional(),
894
+ sessionId: z.string().optional(),
895
+ ack: z.string().optional()
896
+ }
897
+ },
898
+ recall: {
899
+ description: "Recall stored project memory (conventions, decisions, gotchas) for a question.",
900
+ inputSchema: { query: z.string() },
901
+ defaults: { sessionId: "agent" }
902
+ },
903
+ remember: {
904
+ description: "Store one durable project fact (convention, decision, gotcha).",
905
+ inputSchema: { content: z.string() },
906
+ // A fact is Markdown-backed and survives reindex; the full profile's
907
+ // default (`interaction`) is a database-only conversation record.
908
+ defaults: { type: "fact" }
909
+ }
910
+ };
755
911
  var CORE_TOOLS = [
756
912
  "context_pack",
757
913
  "context_bootstrap",
@@ -761,9 +917,9 @@ var CORE_TOOLS = [
761
917
  "health"
762
918
  ];
763
919
  function resolveToolProfile(value = process.env.CODEBUDDY_TOOL_PROFILE) {
764
- if (value === void 0 || value === "" || value === "full") return "full";
765
- if (value === "core") return "core";
766
- throw new Error(`CODEBUDDY_TOOL_PROFILE must be "core" or "full", got "${value}"`);
920
+ if (value === void 0 || value === "" || value === "lean") return "lean";
921
+ if (value === "core" || value === "full") return value;
922
+ throw new Error(`CODEBUDDY_TOOL_PROFILE must be "lean", "core" or "full", got "${value}"`);
767
923
  }
768
924
  var PLAN_BODY_LIMIT = 8e3;
769
925
  function serializePlan(plan) {
@@ -774,7 +930,7 @@ function serializePlan(plan) {
774
930
  return { plan: { ...plan, body } };
775
931
  }
776
932
  function registerCodeBuddyTools(server, options, profile = "full") {
777
- const exposed = (name) => profile === "full" || CORE_TOOLS.includes(name);
933
+ const exposed = (name) => profile === "full" || (profile === "lean" ? name in LEAN_TOOLS : CORE_TOOLS.includes(name));
778
934
  const config = "database" in options ? options.config : options.memory.config;
779
935
  const traceRoot = config.projectRoot ?? process.cwd();
780
936
  const register = (name, toolConfig, callback) => {
@@ -799,7 +955,29 @@ function registerCodeBuddyTools(server, options, profile = "full") {
799
955
  throw error;
800
956
  }
801
957
  });
802
- const tool = server.registerTool(name, toolConfig, traced);
958
+ const lean = profile === "lean" ? LEAN_TOOLS[name] : void 0;
959
+ const full = mcpToolInputSchemas[name];
960
+ const tool = lean ? server.registerTool(
961
+ name,
962
+ {
963
+ ...toolConfig,
964
+ description: lean.description,
965
+ inputSchema: lean.inputSchema
966
+ },
967
+ // The lean schema only lists fields; the full schema enforces limits.
968
+ (async (input, extra) => {
969
+ const parsed = full.safeParse({ ...lean.defaults, ...input });
970
+ if (!parsed.success) {
971
+ return {
972
+ content: [
973
+ { type: "text", text: `Invalid input: ${parsed.error.message}` }
974
+ ],
975
+ isError: true
976
+ };
977
+ }
978
+ return traced(parsed.data, extra);
979
+ })
980
+ ) : server.registerTool(name, toolConfig, traced);
803
981
  if (!exposed(name)) tool.remove();
804
982
  return tool;
805
983
  };
@@ -815,7 +993,25 @@ function registerCodeBuddyTools(server, options, profile = "full") {
815
993
  isError: true
816
994
  };
817
995
  }
818
- return json(await run(db));
996
+ try {
997
+ return json(
998
+ await withDatabaseRetry(
999
+ () => run(db),
1000
+ ["recall", "list_facts", "list_namespaces"].includes(tool)
1001
+ )
1002
+ );
1003
+ } catch (error) {
1004
+ if (!isTransientDatabaseError(error)) throw error;
1005
+ return {
1006
+ ...json({
1007
+ degraded: true,
1008
+ tool,
1009
+ database: { ...database.status(), available: false, state: "unavailable" },
1010
+ fallback: "The database connection failed during this operation. A write may have committed; verify state before repeating it. File-based context remains available."
1011
+ }),
1012
+ isError: true
1013
+ };
1014
+ }
819
1015
  };
820
1016
  const namespace = config.namespace;
821
1017
  const projectRoot = config.projectRoot ?? process.cwd();
@@ -864,12 +1060,12 @@ function registerCodeBuddyTools(server, options, profile = "full") {
864
1060
  "recall",
865
1061
  {
866
1062
  title: "Recall",
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.",
1063
+ description: "Recall project memory for a query. Returns a flat list of memories (id, content, date), or an empty list when nothing is relevant. Pass compact=false for full provenance and token stats.",
868
1064
  inputSchema: mcpToolInputSchemas.recall.shape
869
1065
  },
870
1066
  async (input) => viaDatabase(
871
1067
  "recall",
872
- async (db) => withResponseTokens(await db.memory.recall(toRecallInput(input)), input.compact === true)
1068
+ async (db) => input.compact === false ? withResponseTokens(await db.memory.recall(toRecallInput(input))) : compactRecall(await db.memory.recall(toRecallInput(input)))
873
1069
  )
874
1070
  );
875
1071
  register(
@@ -1132,24 +1328,35 @@ function registerCodeBuddyTools(server, options, profile = "full") {
1132
1328
  description: "Build the smallest useful context pack for a coding task. Automatically finds relevant files from the local index when paths are omitted, then includes only bounded symbols, policies, risks, incidents, architecture neighbours, and relevant team facts within the requested token budget. Use this once at the start of a task instead of manually chaining context, risk, map, and memory tools. Request evidence=source with exact paths when short excerpts omit needed behavior; source excerpts remain bounded by the token budget. Optional sessionId enables session-aware delivery: pass the previous response's delivery.deliveryId as ack and unchanged sections are omitted (listed in delivery.unchanged); pass full=true to get everything again.",
1133
1329
  inputSchema: mcpToolInputSchemas.context_pack.shape
1134
1330
  },
1135
- async (input) => json(
1136
- await applySessionDelivery(
1137
- projectRoot,
1138
- await buildBeforeEditContext({
1139
- repositoryRoot: projectRoot,
1140
- namespace,
1141
- task: input.task,
1142
- semanticRanker: rankContextFacts,
1143
- ...await rerankerOption(),
1144
- ...input.evidence ? { evidence: input.evidence } : {},
1145
- ...input.paths !== void 0 ? { paths: input.paths } : {},
1146
- ...input.planId !== void 0 ? { planId: input.planId } : {},
1147
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
1148
- tokenBudget: input.tokenBudget ?? tokenBudget ?? 4e3
1149
- }),
1150
- deliveryOptions(input)
1151
- )
1152
- )
1331
+ async (input) => {
1332
+ const context = await buildBeforeEditContext({
1333
+ repositoryRoot: projectRoot,
1334
+ namespace,
1335
+ task: input.task,
1336
+ semanticRanker: rankContextFacts,
1337
+ ...await rerankerOption(),
1338
+ ...input.evidence ? { evidence: input.evidence } : {},
1339
+ ...input.paths !== void 0 ? { paths: input.paths } : {},
1340
+ ...input.planId !== void 0 ? { planId: input.planId } : {},
1341
+ ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
1342
+ tokenBudget: input.tokenBudget ?? tokenBudget ?? 4e3
1343
+ });
1344
+ const reuse = await loadFreshIndex(projectRoot).then(
1345
+ async ({ manifest }) => findReuse(
1346
+ projectRoot,
1347
+ input.task,
1348
+ manifest,
1349
+ await loadProjectArchitectureMap(projectRoot, manifest)
1350
+ )
1351
+ ).catch(() => []);
1352
+ return json(
1353
+ await applySessionDelivery(
1354
+ projectRoot,
1355
+ enforceContextBudget({ ...context, ...reuse.length > 0 ? { reuse } : {} }),
1356
+ deliveryOptions(input)
1357
+ )
1358
+ );
1359
+ }
1153
1360
  );
1154
1361
  register(
1155
1362
  "context_after_turn",
@@ -1243,21 +1450,17 @@ function toRecallInput(input) {
1243
1450
  const { compact: _compact, ...rest } = input;
1244
1451
  return cleanOptional(rest);
1245
1452
  }
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 };
1453
+ function compactRecall(result) {
1454
+ const memories = result.messages.map((message) => ({
1455
+ ...message.fact ? { id: message.fact.id } : {},
1456
+ content: message.content,
1457
+ ...message.fact?.createdAt ? { date: message.fact.createdAt.slice(0, 10) } : {}
1458
+ }));
1459
+ return memories.length > 0 ? { memories } : { memories, note: "nothing relevant" };
1460
+ }
1461
+ function withResponseTokens(result) {
1462
+ const stats = { ...result.stats };
1463
+ const value = { ...result, stats };
1261
1464
  for (let pass = 0; pass < 2; pass++) {
1262
1465
  const serialized = serializeToolResult(value);
1263
1466
  stats.responseTokens = estimateTokens(serialized.content[0]?.text);
@@ -1276,7 +1479,7 @@ import pino from "pino";
1276
1479
 
1277
1480
  // src/automation/project-sync.ts
1278
1481
  import { watch as fsWatch2 } from "fs";
1279
- import { join as join3, relative, resolve as resolve3, sep } from "path";
1482
+ import { join as join4, relative, resolve as resolve3, sep } from "path";
1280
1483
 
1281
1484
  // src/indexer/watch.ts
1282
1485
  import { watch as fsWatch } from "fs";
@@ -1457,7 +1660,7 @@ function startProjectSync(options) {
1457
1660
  300
1458
1661
  );
1459
1662
  memoryWatcher = fsWatch2(
1460
- join3(repositoryRoot, ".codebuddy", "memory"),
1663
+ join4(repositoryRoot, ".codebuddy", "memory"),
1461
1664
  { recursive: true },
1462
1665
  (_event, filename) => {
1463
1666
  const name = filename?.replace(/\\/g, "/") ?? "";
@@ -1660,7 +1863,7 @@ async function startMcpServer(options = {}) {
1660
1863
  };
1661
1864
  let stopping;
1662
1865
  const stop = () => {
1663
- stopping ??= shutdown().finally(() => process.exit(0));
1866
+ if (!stopping) stopping = shutdown().finally(() => process.exit(0));
1664
1867
  };
1665
1868
  process.once("SIGINT", stop);
1666
1869
  process.once("SIGTERM", stop);
@@ -1767,9 +1970,11 @@ export {
1767
1970
  TELEMETRY_NOTICE,
1768
1971
  showTelemetryNoticeOnce,
1769
1972
  flushTelemetry,
1973
+ traceCliCommand,
1974
+ findReuse,
1770
1975
  generateSuggestions,
1771
1976
  mcpToolInputSchemas,
1772
1977
  registerCodeBuddyTools,
1773
1978
  startMcpServer
1774
1979
  };
1775
- //# sourceMappingURL=chunk-6DONJW3W.js.map
1980
+ //# sourceMappingURL=chunk-YJPCS2GP.js.map