@orquentra/codebuddy 2.9.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 (70) 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-SRA4MDVG.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-ENKDFLOO.js → chunk-UOTKHSQ5.js} +25 -8
  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-F4BJ3DXW.js → chunk-XSNW6Y3X.js} +1253 -315
  22. package/dist/chunk-XSNW6Y3X.js.map +1 -0
  23. package/dist/{chunk-2WH2Q4TA.js → chunk-YJPCS2GP.js} +256 -68
  24. package/dist/chunk-YJPCS2GP.js.map +1 -0
  25. package/dist/{chunk-TLWGZVPC.js → chunk-YNSTIMCF.js} +746 -66
  26. package/dist/chunk-YNSTIMCF.js.map +1 -0
  27. package/dist/{chunk-QN76UJP7.js → chunk-YWDH6DQ6.js} +70 -4
  28. package/dist/chunk-YWDH6DQ6.js.map +1 -0
  29. package/dist/cli/index.js +23 -9
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  32. package/dist/index.d.ts +6 -4
  33. package/dist/index.js +8 -8
  34. package/dist/install-EU35ERK5.js +30 -0
  35. package/dist/langgraph/index.d.ts +1 -1
  36. package/dist/lean-43ADYTBW.js +28 -0
  37. package/dist/{main-YJKSYFPT.js → main-SFC5LV4E.js} +704 -242
  38. package/dist/main-SFC5LV4E.js.map +1 -0
  39. package/dist/{operations-OG4ISWIM.js → operations-6Z6ZDVBP.js} +3 -3
  40. package/dist/{pretool-command-PRFOIHMW.js → pretool-command-B5GY5T6E.js} +2 -2
  41. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  42. package/dist/{runtime-GCSEIAVD.js → runtime-W5AW32SD.js} +14 -6
  43. package/dist/{summaries-ML53R7EL.js → summaries-32PHOB6L.js} +3 -4
  44. package/dist/{zvec-XDZNZ2OL.js → zvec-24HLAFGN.js} +3 -4
  45. package/dist/zvec-24HLAFGN.js.map +1 -0
  46. package/package.json +11 -2
  47. package/dist/chunk-2WH2Q4TA.js.map +0 -1
  48. package/dist/chunk-ENKDFLOO.js.map +0 -1
  49. package/dist/chunk-F4BJ3DXW.js.map +0 -1
  50. package/dist/chunk-GKNYMNFM.js.map +0 -1
  51. package/dist/chunk-ITQO632W.js +0 -673
  52. package/dist/chunk-ITQO632W.js.map +0 -1
  53. package/dist/chunk-J4T6HHI4.js.map +0 -1
  54. package/dist/chunk-QN76UJP7.js.map +0 -1
  55. package/dist/chunk-SRA4MDVG.js.map +0 -1
  56. package/dist/chunk-TLWGZVPC.js.map +0 -1
  57. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  58. package/dist/chunk-W7GAXL6C.js.map +0 -1
  59. package/dist/install-BXM56QCD.js +0 -19
  60. package/dist/lean-command-7DFW32MA.js +0 -19
  61. package/dist/main-YJKSYFPT.js.map +0 -1
  62. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  63. /package/dist/{chunk-GXOML4UZ.js.map → chunk-LW75SKZR.js.map} +0 -0
  64. /package/dist/{install-BXM56QCD.js.map → install-EU35ERK5.js.map} +0 -0
  65. /package/dist/{lean-command-7DFW32MA.js.map → lean-43ADYTBW.js.map} +0 -0
  66. /package/dist/{operations-OG4ISWIM.js.map → operations-6Z6ZDVBP.js.map} +0 -0
  67. /package/dist/{pretool-command-PRFOIHMW.js.map → pretool-command-B5GY5T6E.js.map} +0 -0
  68. /package/dist/{runtime-GCSEIAVD.js.map → prompt-gate-TDTFFQW2.js.map} +0 -0
  69. /package/dist/{summaries-ML53R7EL.js.map → runtime-W5AW32SD.js.map} +0 -0
  70. /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-F4BJ3DXW.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
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-QN76UJP7.js";
32
+ } from "./chunk-YWDH6DQ6.js";
31
33
  import {
32
34
  MemoryFileStore,
33
35
  VERSION
34
- } from "./chunk-SRA4MDVG.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";
@@ -303,8 +306,8 @@ async function traceCliCommand(command, started, root = process.cwd()) {
303
306
  await flushTelemetry(root);
304
307
  }
305
308
  async function installedClients(repositoryRoot) {
306
- const { CLIENT_IDS } = await import("./adapters-S4HHTWJW.js");
307
- const { detectClient } = await import("./install-BXM56QCD.js");
309
+ const { CLIENT_IDS } = await import("./adapters-OJRSVFV3.js");
310
+ const { detectClient } = await import("./install-EU35ERK5.js");
308
311
  const found = [];
309
312
  for (const id of CLIENT_IDS) {
310
313
  const status = await detectClient(id, repositoryRoot).catch(() => null);
@@ -328,7 +331,8 @@ var OMITTABLE_SECTIONS = [
328
331
  "handoffs",
329
332
  "architecture",
330
333
  "symbols",
331
- "neighbours"
334
+ "neighbours",
335
+ "reuse"
332
336
  ];
333
337
  var SESSION_TTL_MS = 30 * 60 * 1e3;
334
338
  async function applySessionDelivery(repositoryRoot, payload, options) {
@@ -373,10 +377,88 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
373
377
  }
374
378
  }
375
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
+
376
458
  // src/suggest/engine.ts
377
459
  import { constants } from "fs";
378
460
  import { access } from "fs/promises";
379
- import { extname, join as join2, resolve } from "path";
461
+ import { extname, join as join3, resolve } from "path";
380
462
 
381
463
  // src/suggest/types.ts
382
464
  var SEVERITY_RANK = {
@@ -413,7 +495,7 @@ async function generateSuggestions(input) {
413
495
  resolveRelevantPlan(planStore, namespace, input.planId),
414
496
  memoryStore.findIncidentFactsForPaths({ namespace, paths: targetPaths }),
415
497
  buildArchitectureMap(repositoryRoot),
416
- readPolicyFile(join2(repositoryRoot, ".codebuddy", "policies.yaml"))
498
+ readPolicyFile(join3(repositoryRoot, ".codebuddy", "policies.yaml"))
417
499
  ]);
418
500
  const suggestions = [];
419
501
  for (const item of risks.items) {
@@ -493,7 +575,7 @@ async function generateSuggestions(input) {
493
575
  for (const rule of policyFile.rules) {
494
576
  const prefix = literalPrefix(rule.pattern);
495
577
  if (!prefix) continue;
496
- if (await pathExists(join2(repositoryRoot, prefix))) continue;
578
+ if (await pathExists(join3(repositoryRoot, prefix))) continue;
497
579
  suggestions.push({
498
580
  id: `stale_policy:${rule.id}`,
499
581
  category: "stale_policy",
@@ -537,7 +619,7 @@ function isTestableCode(path) {
537
619
  }
538
620
  async function hasSiblingTest(repositoryRoot, path) {
539
621
  for (const candidate of coveringTestCandidates(path)) {
540
- if (await pathExists(join2(repositoryRoot, candidate))) return true;
622
+ if (await pathExists(join3(repositoryRoot, candidate))) return true;
541
623
  }
542
624
  return false;
543
625
  }
@@ -586,6 +668,40 @@ function normalize(path) {
586
668
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
587
669
  }
588
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
+
589
705
  // src/mcp/tool-result.ts
590
706
  function serializeToolResult(value, format = process.env.CODEBUDDY_RESPONSE_FORMAT ?? "both") {
591
707
  if (format !== "text" && format !== "both") {
@@ -631,7 +747,7 @@ var recallSchema = {
631
747
  budget: z.number().int().positive().optional(),
632
748
  callerModel: z.string().min(1).max(200).optional(),
633
749
  conflictMode: z.enum(["all", "latest", "highest_confidence"]).optional(),
634
- /** 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. */
635
751
  compact: z.boolean().optional()
636
752
  };
637
753
  var shareSchema = {
@@ -768,6 +884,30 @@ var FILE_TOOLS = Object.keys(mcpToolInputSchemas).filter(
768
884
  (name) => !DATABASE_TOOLS.includes(name)
769
885
  );
770
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
+ };
771
911
  var CORE_TOOLS = [
772
912
  "context_pack",
773
913
  "context_bootstrap",
@@ -777,9 +917,9 @@ var CORE_TOOLS = [
777
917
  "health"
778
918
  ];
779
919
  function resolveToolProfile(value = process.env.CODEBUDDY_TOOL_PROFILE) {
780
- if (value === void 0 || value === "" || value === "full") return "full";
781
- if (value === "core") return "core";
782
- 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}"`);
783
923
  }
784
924
  var PLAN_BODY_LIMIT = 8e3;
785
925
  function serializePlan(plan) {
@@ -790,7 +930,7 @@ function serializePlan(plan) {
790
930
  return { plan: { ...plan, body } };
791
931
  }
792
932
  function registerCodeBuddyTools(server, options, profile = "full") {
793
- 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));
794
934
  const config = "database" in options ? options.config : options.memory.config;
795
935
  const traceRoot = config.projectRoot ?? process.cwd();
796
936
  const register = (name, toolConfig, callback) => {
@@ -815,7 +955,29 @@ function registerCodeBuddyTools(server, options, profile = "full") {
815
955
  throw error;
816
956
  }
817
957
  });
818
- 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);
819
981
  if (!exposed(name)) tool.remove();
820
982
  return tool;
821
983
  };
@@ -831,7 +993,25 @@ function registerCodeBuddyTools(server, options, profile = "full") {
831
993
  isError: true
832
994
  };
833
995
  }
834
- 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
+ }
835
1015
  };
836
1016
  const namespace = config.namespace;
837
1017
  const projectRoot = config.projectRoot ?? process.cwd();
@@ -880,12 +1060,12 @@ function registerCodeBuddyTools(server, options, profile = "full") {
880
1060
  "recall",
881
1061
  {
882
1062
  title: "Recall",
883
- 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.",
884
1064
  inputSchema: mcpToolInputSchemas.recall.shape
885
1065
  },
886
1066
  async (input) => viaDatabase(
887
1067
  "recall",
888
- 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)))
889
1069
  )
890
1070
  );
891
1071
  register(
@@ -1148,24 +1328,35 @@ function registerCodeBuddyTools(server, options, profile = "full") {
1148
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.",
1149
1329
  inputSchema: mcpToolInputSchemas.context_pack.shape
1150
1330
  },
1151
- async (input) => json(
1152
- await applySessionDelivery(
1153
- projectRoot,
1154
- await buildBeforeEditContext({
1155
- repositoryRoot: projectRoot,
1156
- namespace,
1157
- task: input.task,
1158
- semanticRanker: rankContextFacts,
1159
- ...await rerankerOption(),
1160
- ...input.evidence ? { evidence: input.evidence } : {},
1161
- ...input.paths !== void 0 ? { paths: input.paths } : {},
1162
- ...input.planId !== void 0 ? { planId: input.planId } : {},
1163
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
1164
- tokenBudget: input.tokenBudget ?? tokenBudget ?? 4e3
1165
- }),
1166
- deliveryOptions(input)
1167
- )
1168
- )
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
+ }
1169
1360
  );
1170
1361
  register(
1171
1362
  "context_after_turn",
@@ -1259,21 +1450,17 @@ function toRecallInput(input) {
1259
1450
  const { compact: _compact, ...rest } = input;
1260
1451
  return cleanOptional(rest);
1261
1452
  }
1262
- function withResponseTokens(result, compact) {
1263
- const stats = compact ? {
1264
- tokensUsed: result.stats.tokensUsed,
1265
- itemsIncluded: result.stats.itemsIncluded,
1266
- itemsSkipped: result.stats.itemsSkipped,
1267
- ...result.stats.budgetStatus ? { budgetStatus: result.stats.budgetStatus } : {}
1268
- } : { ...result.stats };
1269
- const value = compact ? {
1270
- system: result.system,
1271
- messages: result.messages.map((message) => ({
1272
- ...message.fact ? { id: message.fact.id } : {},
1273
- content: message.content
1274
- })),
1275
- stats
1276
- } : { ...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 };
1277
1464
  for (let pass = 0; pass < 2; pass++) {
1278
1465
  const serialized = serializeToolResult(value);
1279
1466
  stats.responseTokens = estimateTokens(serialized.content[0]?.text);
@@ -1292,7 +1479,7 @@ import pino from "pino";
1292
1479
 
1293
1480
  // src/automation/project-sync.ts
1294
1481
  import { watch as fsWatch2 } from "fs";
1295
- import { join as join3, relative, resolve as resolve3, sep } from "path";
1482
+ import { join as join4, relative, resolve as resolve3, sep } from "path";
1296
1483
 
1297
1484
  // src/indexer/watch.ts
1298
1485
  import { watch as fsWatch } from "fs";
@@ -1473,7 +1660,7 @@ function startProjectSync(options) {
1473
1660
  300
1474
1661
  );
1475
1662
  memoryWatcher = fsWatch2(
1476
- join3(repositoryRoot, ".codebuddy", "memory"),
1663
+ join4(repositoryRoot, ".codebuddy", "memory"),
1477
1664
  { recursive: true },
1478
1665
  (_event, filename) => {
1479
1666
  const name = filename?.replace(/\\/g, "/") ?? "";
@@ -1676,7 +1863,7 @@ async function startMcpServer(options = {}) {
1676
1863
  };
1677
1864
  let stopping;
1678
1865
  const stop = () => {
1679
- stopping ??= shutdown().finally(() => process.exit(0));
1866
+ if (!stopping) stopping = shutdown().finally(() => process.exit(0));
1680
1867
  };
1681
1868
  process.once("SIGINT", stop);
1682
1869
  process.once("SIGTERM", stop);
@@ -1784,9 +1971,10 @@ export {
1784
1971
  showTelemetryNoticeOnce,
1785
1972
  flushTelemetry,
1786
1973
  traceCliCommand,
1974
+ findReuse,
1787
1975
  generateSuggestions,
1788
1976
  mcpToolInputSchemas,
1789
1977
  registerCodeBuddyTools,
1790
1978
  startMcpServer
1791
1979
  };
1792
- //# sourceMappingURL=chunk-2WH2Q4TA.js.map
1980
+ //# sourceMappingURL=chunk-YJPCS2GP.js.map