@orquentra/codebuddy 2.9.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/ARCHITECTURE.md +98 -0
  2. package/CLAUDE_CODEX.md +33 -0
  3. package/README.md +114 -9
  4. package/dist/{adapters-S4HHTWJW.js → adapters-OJRSVFV3.js} +2 -2
  5. package/dist/{chunk-GXOML4UZ.js → chunk-4BSO3HST.js} +126 -18
  6. package/dist/chunk-4BSO3HST.js.map +1 -0
  7. package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
  8. package/dist/chunk-57642NGU.js.map +1 -0
  9. package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
  10. package/dist/chunk-6YI2D5F5.js.map +1 -0
  11. package/dist/{chunk-GKNYMNFM.js → chunk-7IPSDDO5.js} +10 -3
  12. package/dist/chunk-7IPSDDO5.js.map +1 -0
  13. package/dist/chunk-BTTQWSDS.js +372 -0
  14. package/dist/chunk-BTTQWSDS.js.map +1 -0
  15. package/dist/{chunk-2WH2Q4TA.js → chunk-D5E7SGI5.js} +304 -77
  16. package/dist/chunk-D5E7SGI5.js.map +1 -0
  17. package/dist/chunk-EMWGLKPR.js +45 -0
  18. package/dist/chunk-EMWGLKPR.js.map +1 -0
  19. package/dist/chunk-GQKWUHMA.js +20 -0
  20. package/dist/chunk-GQKWUHMA.js.map +1 -0
  21. package/dist/chunk-ITXE27SZ.js +358 -0
  22. package/dist/chunk-ITXE27SZ.js.map +1 -0
  23. package/dist/{chunk-UK6XTS2Y.js → chunk-JJQDVGF7.js} +23 -23
  24. package/dist/chunk-JJQDVGF7.js.map +1 -0
  25. package/dist/{chunk-F4BJ3DXW.js → chunk-KQCP3ZYH.js} +1273 -331
  26. package/dist/chunk-KQCP3ZYH.js.map +1 -0
  27. package/dist/{chunk-QN76UJP7.js → chunk-KYRM3HPP.js} +89 -21
  28. package/dist/chunk-KYRM3HPP.js.map +1 -0
  29. package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
  30. package/dist/chunk-M75SN4D4.js.map +1 -0
  31. package/dist/{chunk-J4T6HHI4.js → chunk-RZ266NHE.js} +538 -317
  32. package/dist/chunk-RZ266NHE.js.map +1 -0
  33. package/dist/chunk-U7KDM62P.js +43 -0
  34. package/dist/chunk-U7KDM62P.js.map +1 -0
  35. package/dist/chunk-UATJCD2T.js +368 -0
  36. package/dist/chunk-UATJCD2T.js.map +1 -0
  37. package/dist/{chunk-ENKDFLOO.js → chunk-UOTKHSQ5.js} +25 -8
  38. package/dist/chunk-UOTKHSQ5.js.map +1 -0
  39. package/dist/{chunk-TLWGZVPC.js → chunk-UR3DF74I.js} +1007 -132
  40. package/dist/chunk-UR3DF74I.js.map +1 -0
  41. package/dist/chunk-UTPC66MV.js +128 -0
  42. package/dist/chunk-UTPC66MV.js.map +1 -0
  43. package/dist/{chunk-W7GAXL6C.js → chunk-V7PT6ER5.js} +6 -5
  44. package/dist/chunk-V7PT6ER5.js.map +1 -0
  45. package/dist/chunk-WHKXUXHE.js +156 -0
  46. package/dist/chunk-WHKXUXHE.js.map +1 -0
  47. package/dist/{chunk-SRA4MDVG.js → chunk-YMXFSHXT.js} +978 -950
  48. package/dist/chunk-YMXFSHXT.js.map +1 -0
  49. package/dist/cli/index.js +23 -9
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/{codebuddy-DO7ZhoWn.d.ts → codebuddy-CHqMptZB.d.ts} +35 -0
  52. package/dist/config-TXQJCCVI.js +15 -0
  53. package/dist/expand-KWYGNCKZ.js +98 -0
  54. package/dist/expand-KWYGNCKZ.js.map +1 -0
  55. package/dist/file-vectors-MKXTSGGH.js +19 -0
  56. package/dist/index-facts-4MTEHKMN.js +38 -0
  57. package/dist/index-pass-TBDUEZCB.js +14 -0
  58. package/dist/index.d.ts +6 -4
  59. package/dist/index.js +25 -23
  60. package/dist/install-E3JWINTN.js +31 -0
  61. package/dist/langgraph/index.d.ts +1 -1
  62. package/dist/lean-MLTAFS5L.js +29 -0
  63. package/dist/local-75DJECVR.js +48 -0
  64. package/dist/local-llm-MECVJTVU.js +19 -0
  65. package/dist/{main-YJKSYFPT.js → main-RLMKQZW4.js} +999 -328
  66. package/dist/main-RLMKQZW4.js.map +1 -0
  67. package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
  68. package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
  69. package/dist/{operations-OG4ISWIM.js → operations-STR7S5UO.js} +8 -7
  70. package/dist/operations-STR7S5UO.js.map +1 -0
  71. package/dist/{pretool-command-PRFOIHMW.js → pretool-command-B5GY5T6E.js} +2 -2
  72. package/dist/pretool-command-B5GY5T6E.js.map +1 -0
  73. package/dist/prompt-gate-TDTFFQW2.js +8 -0
  74. package/dist/prompt-gate-TDTFFQW2.js.map +1 -0
  75. package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
  76. package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
  77. package/dist/{runtime-GCSEIAVD.js → runtime-SWFWGTQ4.js} +18 -9
  78. package/dist/runtime-SWFWGTQ4.js.map +1 -0
  79. package/dist/summaries-QXWFFDUZ.js +31 -0
  80. package/dist/summaries-QXWFFDUZ.js.map +1 -0
  81. package/dist/{zvec-XDZNZ2OL.js → zvec-F73IBWO5.js} +13 -6
  82. package/dist/zvec-F73IBWO5.js.map +1 -0
  83. package/package.json +13 -3
  84. package/dist/chunk-2WH2Q4TA.js.map +0 -1
  85. package/dist/chunk-6NZEZJYD.js +0 -206
  86. package/dist/chunk-6NZEZJYD.js.map +0 -1
  87. package/dist/chunk-CAEPHLHC.js.map +0 -1
  88. package/dist/chunk-ENKDFLOO.js.map +0 -1
  89. package/dist/chunk-ESZXCZIN.js.map +0 -1
  90. package/dist/chunk-F4BJ3DXW.js.map +0 -1
  91. package/dist/chunk-GKNYMNFM.js.map +0 -1
  92. package/dist/chunk-GXOML4UZ.js.map +0 -1
  93. package/dist/chunk-ITQO632W.js +0 -673
  94. package/dist/chunk-ITQO632W.js.map +0 -1
  95. package/dist/chunk-J4T6HHI4.js.map +0 -1
  96. package/dist/chunk-KNR2P2UY.js.map +0 -1
  97. package/dist/chunk-QN76UJP7.js.map +0 -1
  98. package/dist/chunk-SFMIMLXR.js +0 -63
  99. package/dist/chunk-SFMIMLXR.js.map +0 -1
  100. package/dist/chunk-SRA4MDVG.js.map +0 -1
  101. package/dist/chunk-TLWGZVPC.js.map +0 -1
  102. package/dist/chunk-UK6XTS2Y.js.map +0 -1
  103. package/dist/chunk-W7GAXL6C.js.map +0 -1
  104. package/dist/config-GEZFLXK6.js +0 -12
  105. package/dist/install-BXM56QCD.js +0 -19
  106. package/dist/lean-command-7DFW32MA.js +0 -19
  107. package/dist/main-YJKSYFPT.js.map +0 -1
  108. package/dist/summaries-ML53R7EL.js +0 -21
  109. /package/dist/{adapters-S4HHTWJW.js.map → adapters-OJRSVFV3.js.map} +0 -0
  110. /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
  111. /package/dist/{install-BXM56QCD.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
  112. /package/dist/{lean-command-7DFW32MA.js.map → index-facts-4MTEHKMN.js.map} +0 -0
  113. /package/dist/{operations-OG4ISWIM.js.map → index-pass-TBDUEZCB.js.map} +0 -0
  114. /package/dist/{pretool-command-PRFOIHMW.js.map → install-E3JWINTN.js.map} +0 -0
  115. /package/dist/{runtime-GCSEIAVD.js.map → lean-MLTAFS5L.js.map} +0 -0
  116. /package/dist/{summaries-ML53R7EL.js.map → local-75DJECVR.js.map} +0 -0
  117. /package/dist/{zvec-XDZNZ2OL.js.map → local-llm-MECVJTVU.js.map} +0 -0
@@ -1,6 +1,3 @@
1
- import {
2
- refreshZvecIndex
3
- } from "./chunk-GXOML4UZ.js";
4
1
  import {
5
2
  PlanFileStore,
6
3
  PlanLifecycle,
@@ -11,38 +8,45 @@ import {
11
8
  configuredReranker,
12
9
  enforceContextBudget,
13
10
  llmMemoryOptions,
11
+ rankSemanticReuse,
14
12
  readPolicyFile,
15
13
  resolveRiskPaths
16
- } from "./chunk-F4BJ3DXW.js";
17
- import {
18
- SessionCapsuleLedger,
19
- coveringTestCandidates,
20
- estimateTokens
21
- } from "./chunk-GKNYMNFM.js";
14
+ } from "./chunk-KQCP3ZYH.js";
22
15
  import {
23
16
  readTraces,
24
17
  recordTrace
25
18
  } from "./chunk-YN7TEIUH.js";
26
19
  import {
27
- codeBuddyConfigSchema,
28
- createRuntime,
29
- listFactsPage
30
- } from "./chunk-QN76UJP7.js";
31
- import {
32
- MemoryFileStore,
33
- VERSION
34
- } from "./chunk-SRA4MDVG.js";
20
+ SessionCapsuleLedger,
21
+ coveringTestCandidates,
22
+ estimateTokens,
23
+ findCoveringTest
24
+ } from "./chunk-7IPSDDO5.js";
35
25
  import {
36
- generateGraphifyGraph,
37
- loadProjectArchitectureMap
38
- } from "./chunk-TLWGZVPC.js";
26
+ ensureZvecServer,
27
+ refreshZvecIndex
28
+ } from "./chunk-4BSO3HST.js";
39
29
  import {
30
+ NON_SOURCE_PATH,
31
+ TEST_PATH,
40
32
  buildArchitectureMap,
33
+ generateGraphifyGraph,
41
34
  indexRepository,
42
35
  loadFreshIndex,
36
+ loadProjectArchitectureMap,
43
37
  neighbours,
44
- queryMap
45
- } from "./chunk-ITQO632W.js";
38
+ queryMap,
39
+ taskTerms
40
+ } from "./chunk-UR3DF74I.js";
41
+ import {
42
+ codeBuddyConfigSchema,
43
+ createRuntime,
44
+ listFactsPage
45
+ } from "./chunk-KYRM3HPP.js";
46
+ import {
47
+ MemoryFileStore,
48
+ VERSION
49
+ } from "./chunk-YMXFSHXT.js";
46
50
  import {
47
51
  redactPersisted
48
52
  } from "./chunk-AL6VBTNA.js";
@@ -51,7 +55,10 @@ import {
51
55
  loadPlanPolicy,
52
56
  loadRuntimeConfig,
53
57
  redactSecrets
54
- } from "./chunk-KNR2P2UY.js";
58
+ } from "./chunk-M75SN4D4.js";
59
+ import {
60
+ markForeground
61
+ } from "./chunk-BTTQWSDS.js";
55
62
 
56
63
  // src/core/migrate-to-files.ts
57
64
  import { createHash } from "crypto";
@@ -303,8 +310,8 @@ async function traceCliCommand(command, started, root = process.cwd()) {
303
310
  await flushTelemetry(root);
304
311
  }
305
312
  async function installedClients(repositoryRoot) {
306
- const { CLIENT_IDS } = await import("./adapters-S4HHTWJW.js");
307
- const { detectClient } = await import("./install-BXM56QCD.js");
313
+ const { CLIENT_IDS } = await import("./adapters-OJRSVFV3.js");
314
+ const { detectClient } = await import("./install-E3JWINTN.js");
308
315
  const found = [];
309
316
  for (const id of CLIENT_IDS) {
310
317
  const status = await detectClient(id, repositoryRoot).catch(() => null);
@@ -328,7 +335,8 @@ var OMITTABLE_SECTIONS = [
328
335
  "handoffs",
329
336
  "architecture",
330
337
  "symbols",
331
- "neighbours"
338
+ "neighbours",
339
+ "reuse"
332
340
  ];
333
341
  var SESSION_TTL_MS = 30 * 60 * 1e3;
334
342
  async function applySessionDelivery(repositoryRoot, payload, options) {
@@ -373,10 +381,88 @@ async function applySessionDelivery(repositoryRoot, payload, options) {
373
381
  }
374
382
  }
375
383
 
384
+ // src/context/reuse.ts
385
+ import { readFile as readFile2 } from "fs/promises";
386
+ import { join as join2 } from "path";
387
+ var REUSE_ADMISSION = { minMatched: 2, minCoverage: 0.67, limit: 3 };
388
+ var REUSABLE_KINDS = /* @__PURE__ */ new Set(["function", "class", "method", "const"]);
389
+ async function findReuse(repositoryRoot, task, manifest, map, admission = REUSE_ADMISSION, semanticRanker = rankSemanticReuse) {
390
+ const terms = new Set(taskTerms(task));
391
+ if (terms.size < admission.minMatched) return [];
392
+ const symbols = Object.values(manifest.entries).filter((entry) => !TEST_PATH.test(entry.path) && !NON_SOURCE_PATH.test(entry.path)).flatMap(
393
+ (entry) => (entry.symbolTable ?? []).filter((symbol) => symbol.exported && REUSABLE_KINDS.has(symbol.kind)).map((symbol) => ({ ...symbol, path: entry.path, words: new Set(taskTerms(symbol.name)) }))
394
+ );
395
+ const frequency = /* @__PURE__ */ new Map();
396
+ for (const symbol of symbols) {
397
+ for (const word of symbol.words) frequency.set(word, (frequency.get(word) ?? 0) + 1);
398
+ }
399
+ const idf = (word) => Math.log((symbols.length + 1) / ((frequency.get(word) ?? 0) + 1));
400
+ const ranked = symbols.map((symbol) => {
401
+ const matched = [...symbol.words].filter((word) => terms.has(word));
402
+ return {
403
+ symbol,
404
+ matched,
405
+ coverage: symbol.words.size > 0 ? matched.length / symbol.words.size : 0,
406
+ score: matched.reduce((sum, word) => sum + idf(word), 0)
407
+ };
408
+ }).filter(
409
+ (item) => item.matched.length >= admission.minMatched && item.coverage >= admission.minCoverage
410
+ ).sort((a, b) => b.score - a.score || a.symbol.path.localeCompare(b.symbol.path));
411
+ let semantic = false;
412
+ if (!ranked.length) {
413
+ const candidates = await semanticRanker(repositoryRoot, task, manifest);
414
+ const first = candidates[0];
415
+ if (first && first.score >= 0.84 && first.score - (candidates[1]?.score ?? 0) >= 0.04) {
416
+ const symbol = symbols.find((item) => item.path === first.path && item.name === first.name);
417
+ if (symbol) {
418
+ ranked.push({ symbol, matched: [], coverage: 0, score: first.score });
419
+ semantic = true;
420
+ }
421
+ }
422
+ }
423
+ const indexed = new Set(Object.keys(manifest.entries));
424
+ const seen = /* @__PURE__ */ new Set();
425
+ const picked = [];
426
+ for (const { symbol, matched, score } of ranked) {
427
+ const key = `${symbol.path}:${symbol.name}`;
428
+ if (seen.has(key)) continue;
429
+ seen.add(key);
430
+ const usage = await usageSite(repositoryRoot, symbol.path, symbol.name, map);
431
+ const test = findCoveringTest(symbol.path, indexed);
432
+ picked.push({
433
+ path: symbol.path,
434
+ name: symbol.name,
435
+ signature: symbol.signature,
436
+ startLine: symbol.startLine,
437
+ endLine: symbol.endLine,
438
+ matched,
439
+ score: Math.round(score * 100) / 100,
440
+ ...semantic ? { evidence: "semantic" } : {},
441
+ ...usage ? { usage } : {},
442
+ ...test ? { test } : {}
443
+ });
444
+ if (picked.length === admission.limit) break;
445
+ }
446
+ return picked;
447
+ }
448
+ async function usageSite(root, path, name, map) {
449
+ const short = name.split(".").at(-1) ?? name;
450
+ const pattern = new RegExp(`\\b${short.replace(/[^\w$]/g, "")}\\b`);
451
+ const importers = (map?.edges ?? []).filter((edge) => edge.to === path && !TEST_PATH.test(edge.from)).map((edge) => edge.from).sort();
452
+ for (const importer of importers.slice(0, 20)) {
453
+ const source = await readFile2(join2(root, importer), "utf8").catch(() => "");
454
+ const lines = source.split("\n");
455
+ const index = lines.findIndex((line) => pattern.test(line) && !/^\s*import\b/.test(line));
456
+ if (index !== -1)
457
+ return `${importer}:${index + 1}: ${(lines[index] ?? "").trim().slice(0, 140)}`;
458
+ }
459
+ return void 0;
460
+ }
461
+
376
462
  // src/suggest/engine.ts
377
463
  import { constants } from "fs";
378
464
  import { access } from "fs/promises";
379
- import { extname, join as join2, resolve } from "path";
465
+ import { extname, join as join3, resolve } from "path";
380
466
 
381
467
  // src/suggest/types.ts
382
468
  var SEVERITY_RANK = {
@@ -413,7 +499,7 @@ async function generateSuggestions(input) {
413
499
  resolveRelevantPlan(planStore, namespace, input.planId),
414
500
  memoryStore.findIncidentFactsForPaths({ namespace, paths: targetPaths }),
415
501
  buildArchitectureMap(repositoryRoot),
416
- readPolicyFile(join2(repositoryRoot, ".codebuddy", "policies.yaml"))
502
+ readPolicyFile(join3(repositoryRoot, ".codebuddy", "policies.yaml"))
417
503
  ]);
418
504
  const suggestions = [];
419
505
  for (const item of risks.items) {
@@ -493,7 +579,7 @@ async function generateSuggestions(input) {
493
579
  for (const rule of policyFile.rules) {
494
580
  const prefix = literalPrefix(rule.pattern);
495
581
  if (!prefix) continue;
496
- if (await pathExists(join2(repositoryRoot, prefix))) continue;
582
+ if (await pathExists(join3(repositoryRoot, prefix))) continue;
497
583
  suggestions.push({
498
584
  id: `stale_policy:${rule.id}`,
499
585
  category: "stale_policy",
@@ -537,7 +623,7 @@ function isTestableCode(path) {
537
623
  }
538
624
  async function hasSiblingTest(repositoryRoot, path) {
539
625
  for (const candidate of coveringTestCandidates(path)) {
540
- if (await pathExists(join2(repositoryRoot, candidate))) return true;
626
+ if (await pathExists(join3(repositoryRoot, candidate))) return true;
541
627
  }
542
628
  return false;
543
629
  }
@@ -586,6 +672,40 @@ function normalize(path) {
586
672
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
587
673
  }
588
674
 
675
+ // src/mcp/database-retry.ts
676
+ function isTransientDatabaseError(error) {
677
+ let current = error;
678
+ for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
679
+ const item = current;
680
+ if (typeof item.code === "string" && (/* @__PURE__ */ new Set([
681
+ "ECONNRESET",
682
+ "ECONNREFUSED",
683
+ "EPIPE",
684
+ "CONNECTION_CLOSED",
685
+ "CONNECTION_ENDED",
686
+ "08000",
687
+ "08001",
688
+ "08003",
689
+ "08006",
690
+ "57P01",
691
+ "57P02",
692
+ "57P03"
693
+ ])).has(item.code))
694
+ return true;
695
+ current = item.cause;
696
+ }
697
+ return false;
698
+ }
699
+ async function withDatabaseRetry(operation, readOnly, delayMs = 250) {
700
+ try {
701
+ return await operation();
702
+ } catch (error) {
703
+ if (!readOnly || !isTransientDatabaseError(error)) throw error;
704
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
705
+ return operation();
706
+ }
707
+ }
708
+
589
709
  // src/mcp/tool-result.ts
590
710
  function serializeToolResult(value, format = process.env.CODEBUDDY_RESPONSE_FORMAT ?? "both") {
591
711
  if (format !== "text" && format !== "both") {
@@ -631,7 +751,7 @@ var recallSchema = {
631
751
  budget: z.number().int().positive().optional(),
632
752
  callerModel: z.string().min(1).max(200).optional(),
633
753
  conflictMode: z.enum(["all", "latest", "highest_confidence"]).optional(),
634
- /** Return only system, message id + content and core stats (no provenance). */
754
+ /** Default true: a flat list of memories (id, content, date). false = full provenance + stats. */
635
755
  compact: z.boolean().optional()
636
756
  };
637
757
  var shareSchema = {
@@ -768,6 +888,30 @@ var FILE_TOOLS = Object.keys(mcpToolInputSchemas).filter(
768
888
  (name) => !DATABASE_TOOLS.includes(name)
769
889
  );
770
890
  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.";
891
+ var LEAN_TOOLS = {
892
+ context_pack: {
893
+ description: "Find the files, symbols and line ranges for a coding task. Call only when the injected CodeBuddy block names no files.",
894
+ defaults: { tokenBudget: 800 },
895
+ inputSchema: {
896
+ task: z.string(),
897
+ paths: z.array(z.string()).optional(),
898
+ sessionId: z.string().optional(),
899
+ ack: z.string().optional()
900
+ }
901
+ },
902
+ recall: {
903
+ description: "Recall stored project memory (conventions, decisions, gotchas) for a question.",
904
+ inputSchema: { query: z.string() },
905
+ defaults: { sessionId: "agent" }
906
+ },
907
+ remember: {
908
+ description: "Store one durable project fact (convention, decision, gotcha).",
909
+ inputSchema: { content: z.string() },
910
+ // A fact is Markdown-backed and survives reindex; the full profile's
911
+ // default (`interaction`) is a database-only conversation record.
912
+ defaults: { type: "fact" }
913
+ }
914
+ };
771
915
  var CORE_TOOLS = [
772
916
  "context_pack",
773
917
  "context_bootstrap",
@@ -777,9 +921,9 @@ var CORE_TOOLS = [
777
921
  "health"
778
922
  ];
779
923
  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}"`);
924
+ if (value === void 0 || value === "" || value === "lean") return "lean";
925
+ if (value === "core" || value === "full") return value;
926
+ throw new Error(`CODEBUDDY_TOOL_PROFILE must be "lean", "core" or "full", got "${value}"`);
783
927
  }
784
928
  var PLAN_BODY_LIMIT = 8e3;
785
929
  function serializePlan(plan) {
@@ -790,12 +934,13 @@ function serializePlan(plan) {
790
934
  return { plan: { ...plan, body } };
791
935
  }
792
936
  function registerCodeBuddyTools(server, options, profile = "full") {
793
- const exposed = (name) => profile === "full" || CORE_TOOLS.includes(name);
937
+ const exposed = (name) => profile === "full" || (profile === "lean" ? name in LEAN_TOOLS : CORE_TOOLS.includes(name));
794
938
  const config = "database" in options ? options.config : options.memory.config;
795
939
  const traceRoot = config.projectRoot ?? process.cwd();
796
940
  const register = (name, toolConfig, callback) => {
797
941
  const traced = (async (...args) => {
798
942
  const started = Date.now();
943
+ void markForeground(traceRoot);
799
944
  const input = args.length > 1 ? args[0] : void 0;
800
945
  const trace = (ok, output) => void recordTrace(traceRoot, {
801
946
  kind: "tool",
@@ -815,7 +960,29 @@ function registerCodeBuddyTools(server, options, profile = "full") {
815
960
  throw error;
816
961
  }
817
962
  });
818
- const tool = server.registerTool(name, toolConfig, traced);
963
+ const lean = profile === "lean" ? LEAN_TOOLS[name] : void 0;
964
+ const full = mcpToolInputSchemas[name];
965
+ const tool = lean ? server.registerTool(
966
+ name,
967
+ {
968
+ ...toolConfig,
969
+ description: lean.description,
970
+ inputSchema: lean.inputSchema
971
+ },
972
+ // The lean schema only lists fields; the full schema enforces limits.
973
+ (async (input, extra) => {
974
+ const parsed = full.safeParse({ ...lean.defaults, ...input });
975
+ if (!parsed.success) {
976
+ return {
977
+ content: [
978
+ { type: "text", text: `Invalid input: ${parsed.error.message}` }
979
+ ],
980
+ isError: true
981
+ };
982
+ }
983
+ return traced(parsed.data, extra);
984
+ })
985
+ ) : server.registerTool(name, toolConfig, traced);
819
986
  if (!exposed(name)) tool.remove();
820
987
  return tool;
821
988
  };
@@ -831,7 +998,25 @@ function registerCodeBuddyTools(server, options, profile = "full") {
831
998
  isError: true
832
999
  };
833
1000
  }
834
- return json(await run(db));
1001
+ try {
1002
+ return json(
1003
+ await withDatabaseRetry(
1004
+ () => run(db),
1005
+ ["recall", "list_facts", "list_namespaces"].includes(tool)
1006
+ )
1007
+ );
1008
+ } catch (error) {
1009
+ if (!isTransientDatabaseError(error)) throw error;
1010
+ return {
1011
+ ...json({
1012
+ degraded: true,
1013
+ tool,
1014
+ database: { ...database.status(), available: false, state: "unavailable" },
1015
+ fallback: "The database connection failed during this operation. A write may have committed; verify state before repeating it. File-based context remains available."
1016
+ }),
1017
+ isError: true
1018
+ };
1019
+ }
835
1020
  };
836
1021
  const namespace = config.namespace;
837
1022
  const projectRoot = config.projectRoot ?? process.cwd();
@@ -880,12 +1065,12 @@ function registerCodeBuddyTools(server, options, profile = "full") {
880
1065
  "recall",
881
1066
  {
882
1067
  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.",
1068
+ 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
1069
  inputSchema: mcpToolInputSchemas.recall.shape
885
1070
  },
886
1071
  async (input) => viaDatabase(
887
1072
  "recall",
888
- async (db) => withResponseTokens(await db.memory.recall(toRecallInput(input)), input.compact === true)
1073
+ async (db) => input.compact === false ? withResponseTokens(await db.memory.recall(toRecallInput(input))) : compactRecall(await db.memory.recall(toRecallInput(input)))
889
1074
  )
890
1075
  );
891
1076
  register(
@@ -1148,24 +1333,35 @@ function registerCodeBuddyTools(server, options, profile = "full") {
1148
1333
  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
1334
  inputSchema: mcpToolInputSchemas.context_pack.shape
1150
1335
  },
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
- )
1336
+ async (input) => {
1337
+ const context = await buildBeforeEditContext({
1338
+ repositoryRoot: projectRoot,
1339
+ namespace,
1340
+ task: input.task,
1341
+ semanticRanker: rankContextFacts,
1342
+ ...await rerankerOption(),
1343
+ ...input.evidence ? { evidence: input.evidence } : {},
1344
+ ...input.paths !== void 0 ? { paths: input.paths } : {},
1345
+ ...input.planId !== void 0 ? { planId: input.planId } : {},
1346
+ ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
1347
+ tokenBudget: input.tokenBudget ?? tokenBudget ?? 4e3
1348
+ });
1349
+ const reuse = await loadFreshIndex(projectRoot).then(
1350
+ async ({ manifest }) => findReuse(
1351
+ projectRoot,
1352
+ input.task,
1353
+ manifest,
1354
+ await loadProjectArchitectureMap(projectRoot, manifest)
1355
+ )
1356
+ ).catch(() => []);
1357
+ return json(
1358
+ await applySessionDelivery(
1359
+ projectRoot,
1360
+ enforceContextBudget({ ...context, ...reuse.length > 0 ? { reuse } : {} }),
1361
+ deliveryOptions(input)
1362
+ )
1363
+ );
1364
+ }
1169
1365
  );
1170
1366
  register(
1171
1367
  "context_after_turn",
@@ -1259,21 +1455,17 @@ function toRecallInput(input) {
1259
1455
  const { compact: _compact, ...rest } = input;
1260
1456
  return cleanOptional(rest);
1261
1457
  }
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 };
1458
+ function compactRecall(result) {
1459
+ const memories = result.messages.map((message) => ({
1460
+ ...message.fact ? { id: message.fact.id } : {},
1461
+ content: message.content,
1462
+ ...message.fact?.createdAt ? { date: message.fact.createdAt.slice(0, 10) } : {}
1463
+ }));
1464
+ return memories.length > 0 ? { memories } : { memories, note: "nothing relevant" };
1465
+ }
1466
+ function withResponseTokens(result) {
1467
+ const stats = { ...result.stats };
1468
+ const value = { ...result, stats };
1277
1469
  for (let pass = 0; pass < 2; pass++) {
1278
1470
  const serialized = serializeToolResult(value);
1279
1471
  stats.responseTokens = estimateTokens(serialized.content[0]?.text);
@@ -1292,7 +1484,7 @@ import pino from "pino";
1292
1484
 
1293
1485
  // src/automation/project-sync.ts
1294
1486
  import { watch as fsWatch2 } from "fs";
1295
- import { join as join3, relative, resolve as resolve3, sep } from "path";
1487
+ import { join as join4, relative, resolve as resolve3, sep } from "path";
1296
1488
 
1297
1489
  // src/indexer/watch.ts
1298
1490
  import { watch as fsWatch } from "fs";
@@ -1396,6 +1588,8 @@ function startProjectSync(options) {
1396
1588
  let closing = false;
1397
1589
  let memoryWatcher = null;
1398
1590
  let memoryRefresh = null;
1591
+ let stopZvecServer = async () => {
1592
+ };
1399
1593
  const backgroundRefresh = (task) => {
1400
1594
  let current = null;
1401
1595
  let pending = false;
@@ -1431,9 +1625,36 @@ function startProjectSync(options) {
1431
1625
  );
1432
1626
  if (result.status !== "disabled") logger.info(`[sync] ${result.reason}`);
1433
1627
  });
1628
+ const modelPass = backgroundRefresh(async () => {
1629
+ const { runIndexModelPass } = await import("./index-pass-TBDUEZCB.js");
1630
+ const result = await runIndexModelPass(repositoryRoot, { signal: controller.signal }).catch(
1631
+ (error) => {
1632
+ logger.error(`[sync] index model pass failed: ${error.message}`);
1633
+ return null;
1634
+ }
1635
+ );
1636
+ if (result?.cards && (result.cards.summarized > 0 || result.cards.remaining > 0)) {
1637
+ logger.info(
1638
+ `[sync] intent cards: ${result.cards.summarized} written, ${result.cards.remaining} left (${result.cards.stoppedBy})`
1639
+ );
1640
+ }
1641
+ if (result?.facts && result.facts.written + result.facts.removed > 0) {
1642
+ logger.info(
1643
+ `[sync] index facts: ${result.facts.written} written, ${result.facts.removed} replaced`
1644
+ );
1645
+ await options.onMemoryChange?.();
1646
+ }
1647
+ });
1434
1648
  const ready = (async () => {
1435
1649
  await indexRepository({ repositoryRoot });
1650
+ void ensureZvecServer(repositoryRoot).then((server) => {
1651
+ stopZvecServer = server.stop;
1652
+ if (server.reason !== "zvec-grep not enabled") logger.info(`[sync] ${server.reason}`);
1653
+ if (closing) void server.stop();
1654
+ }).catch(() => {
1655
+ });
1436
1656
  zvecRefresh.schedule();
1657
+ modelPass.schedule();
1437
1658
  watchMemory();
1438
1659
  if (options.graphify?.enabled) {
1439
1660
  await refreshGraph().catch(
@@ -1451,6 +1672,7 @@ function startProjectSync(options) {
1451
1672
  onIndex: (result) => {
1452
1673
  if (result.added + result.changed + result.removed === 0) return;
1453
1674
  zvecRefresh.schedule();
1675
+ modelPass.schedule();
1454
1676
  if (options.graphify?.enabled) graphRefresh.schedule();
1455
1677
  },
1456
1678
  onError: (error) => logger.error(`[sync] repository watch failed: ${error.message}`)
@@ -1473,7 +1695,7 @@ function startProjectSync(options) {
1473
1695
  300
1474
1696
  );
1475
1697
  memoryWatcher = fsWatch2(
1476
- join3(repositoryRoot, ".codebuddy", "memory"),
1698
+ join4(repositoryRoot, ".codebuddy", "memory"),
1477
1699
  { recursive: true },
1478
1700
  (_event, filename) => {
1479
1701
  const name = filename?.replace(/\\/g, "/") ?? "";
@@ -1500,7 +1722,11 @@ function startProjectSync(options) {
1500
1722
  memoryRefresh?.stop();
1501
1723
  memoryWatcher?.close();
1502
1724
  await watcher?.catch(() => void 0);
1503
- await Promise.all([graphRefresh.settled(), zvecRefresh.settled()]);
1725
+ await Promise.all([graphRefresh.settled(), zvecRefresh.settled(), modelPass.settled()]);
1726
+ await stopZvecServer().catch(() => {
1727
+ });
1728
+ await import("./local-75DJECVR.js").then(({ unloadLocalModel }) => unloadLocalModel()).catch(() => {
1729
+ });
1504
1730
  }
1505
1731
  };
1506
1732
  }
@@ -1676,7 +1902,7 @@ async function startMcpServer(options = {}) {
1676
1902
  };
1677
1903
  let stopping;
1678
1904
  const stop = () => {
1679
- stopping ??= shutdown().finally(() => process.exit(0));
1905
+ if (!stopping) stopping = shutdown().finally(() => process.exit(0));
1680
1906
  };
1681
1907
  process.once("SIGINT", stop);
1682
1908
  process.once("SIGTERM", stop);
@@ -1784,9 +2010,10 @@ export {
1784
2010
  showTelemetryNoticeOnce,
1785
2011
  flushTelemetry,
1786
2012
  traceCliCommand,
2013
+ findReuse,
1787
2014
  generateSuggestions,
1788
2015
  mcpToolInputSchemas,
1789
2016
  registerCodeBuddyTools,
1790
2017
  startMcpServer
1791
2018
  };
1792
- //# sourceMappingURL=chunk-2WH2Q4TA.js.map
2019
+ //# sourceMappingURL=chunk-D5E7SGI5.js.map