@orquentra/codebuddy 2.6.0 → 2.6.2

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 (79) hide show
  1. package/CLAUDE_CODEX.md +6 -4
  2. package/WORKFLOW.md +5 -5
  3. package/dist/{chunk-UN57FKQX.js → chunk-2XDPKFUW.js} +111 -847
  4. package/dist/chunk-2XDPKFUW.js.map +1 -0
  5. package/dist/{chunk-QDBDYCM2.js → chunk-5FW3PMNP.js} +2 -2
  6. package/dist/{chunk-776EQXDV.js → chunk-5KZY6OTG.js} +82 -5
  7. package/dist/chunk-5KZY6OTG.js.map +1 -0
  8. package/dist/{chunk-UFZD3JAA.js → chunk-5MQOE4J6.js} +3 -3
  9. package/dist/chunk-5UH3BEQR.js +32 -0
  10. package/dist/chunk-5UH3BEQR.js.map +1 -0
  11. package/dist/{chunk-N34XHWBA.js → chunk-B6CO63SQ.js} +8 -7
  12. package/dist/chunk-B6CO63SQ.js.map +1 -0
  13. package/dist/chunk-CSRD5OQA.js +79 -0
  14. package/dist/chunk-CSRD5OQA.js.map +1 -0
  15. package/dist/{chunk-PYLH4MH5.js → chunk-FJOTP632.js} +1 -1
  16. package/dist/{chunk-U4HAJLIB.js → chunk-IBLJOHJL.js} +15 -2
  17. package/dist/chunk-IBLJOHJL.js.map +1 -0
  18. package/dist/chunk-IMFUYEZA.js +1607 -0
  19. package/dist/chunk-IMFUYEZA.js.map +1 -0
  20. package/dist/{chunk-SYWJG5NA.js → chunk-J2EAZ4MA.js} +20 -7
  21. package/dist/chunk-J2EAZ4MA.js.map +1 -0
  22. package/dist/{chunk-L5AYZC3F.js → chunk-NERKZSSE.js} +7 -7
  23. package/dist/{chunk-PCE4STAY.js → chunk-OWWCDRXW.js} +5 -5
  24. package/dist/{chunk-WHZSVY2L.js → chunk-QBDJXGG5.js} +2 -2
  25. package/dist/{chunk-VAKBJV26.js → chunk-R7CD6EXS.js} +307 -1822
  26. package/dist/chunk-R7CD6EXS.js.map +1 -0
  27. package/dist/{chunk-6MKEIDX5.js → chunk-RAJJSCIP.js} +2 -2
  28. package/dist/chunk-S6VEG4S2.js +991 -0
  29. package/dist/chunk-S6VEG4S2.js.map +1 -0
  30. package/dist/cli/index.js +14 -3
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/{codebuddy-DM-i0Fu5.d.ts → codebuddy-B_pCaQrV.d.ts} +22 -2
  33. package/dist/{config-KFEHLV6W.js → config-DEVHUDIU.js} +3 -3
  34. package/dist/docker-autostart-KDME7TVC.js +12 -0
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.js +19 -15
  37. package/dist/langgraph/index.d.ts +1 -1
  38. package/dist/lean-command-HTQCPH76.js +15 -0
  39. package/dist/{local-OTOY56B4.js → local-5MZURLCZ.js} +6 -2
  40. package/dist/{main-HFHLGID4.js → main-MTBQZGJV.js} +139 -171
  41. package/dist/main-MTBQZGJV.js.map +1 -0
  42. package/dist/{memory-4RGVLPKG.js → memory-KIC5FJVW.js} +4 -4
  43. package/dist/{operations-LOBAQRLO.js → operations-POCHY3WR.js} +5 -4
  44. package/dist/postgres-docker-QQZQ2ABK.js +16 -0
  45. package/dist/{pretool-command-MQQ2UNQM.js → pretool-command-AX6JXP25.js} +3 -3
  46. package/dist/{rerank-3P3C2UHN.js → rerank-5QZ3PNUW.js} +4 -4
  47. package/dist/runtime-CUBOOPZO.js +43 -0
  48. package/dist/runtime-CUBOOPZO.js.map +1 -0
  49. package/dist/stdio-HOV4P3DQ.js +12 -0
  50. package/dist/stdio-HOV4P3DQ.js.map +1 -0
  51. package/dist/{summaries-ZBV5IN7Y.js → summaries-SB5L2TSW.js} +6 -6
  52. package/dist/summaries-SB5L2TSW.js.map +1 -0
  53. package/dist/{zvec-MAPSJQSR.js → zvec-MSRKT7IV.js} +3 -3
  54. package/dist/zvec-MSRKT7IV.js.map +1 -0
  55. package/package.json +7 -10
  56. package/dist/chunk-776EQXDV.js.map +0 -1
  57. package/dist/chunk-N34XHWBA.js.map +0 -1
  58. package/dist/chunk-SYWJG5NA.js.map +0 -1
  59. package/dist/chunk-U4HAJLIB.js.map +0 -1
  60. package/dist/chunk-UN57FKQX.js.map +0 -1
  61. package/dist/chunk-VAKBJV26.js.map +0 -1
  62. package/dist/lean-command-C5YPFKHP.js +0 -15
  63. package/dist/main-HFHLGID4.js.map +0 -1
  64. /package/dist/{chunk-QDBDYCM2.js.map → chunk-5FW3PMNP.js.map} +0 -0
  65. /package/dist/{chunk-UFZD3JAA.js.map → chunk-5MQOE4J6.js.map} +0 -0
  66. /package/dist/{chunk-PYLH4MH5.js.map → chunk-FJOTP632.js.map} +0 -0
  67. /package/dist/{chunk-L5AYZC3F.js.map → chunk-NERKZSSE.js.map} +0 -0
  68. /package/dist/{chunk-PCE4STAY.js.map → chunk-OWWCDRXW.js.map} +0 -0
  69. /package/dist/{chunk-WHZSVY2L.js.map → chunk-QBDJXGG5.js.map} +0 -0
  70. /package/dist/{chunk-6MKEIDX5.js.map → chunk-RAJJSCIP.js.map} +0 -0
  71. /package/dist/{config-KFEHLV6W.js.map → config-DEVHUDIU.js.map} +0 -0
  72. /package/dist/{lean-command-C5YPFKHP.js.map → docker-autostart-KDME7TVC.js.map} +0 -0
  73. /package/dist/{local-OTOY56B4.js.map → lean-command-HTQCPH76.js.map} +0 -0
  74. /package/dist/{operations-LOBAQRLO.js.map → local-5MZURLCZ.js.map} +0 -0
  75. /package/dist/{memory-4RGVLPKG.js.map → memory-KIC5FJVW.js.map} +0 -0
  76. /package/dist/{pretool-command-MQQ2UNQM.js.map → operations-POCHY3WR.js.map} +0 -0
  77. /package/dist/{summaries-ZBV5IN7Y.js.map → postgres-docker-QQZQ2ABK.js.map} +0 -0
  78. /package/dist/{zvec-MAPSJQSR.js.map → pretool-command-AX6JXP25.js.map} +0 -0
  79. /package/dist/{rerank-3P3C2UHN.js.map → rerank-5QZ3PNUW.js.map} +0 -0
@@ -6,25 +6,20 @@ import {
6
6
  SessionCapsuleLedger,
7
7
  detectTestCommand,
8
8
  estimateTokens,
9
- findCoveringTest
10
- } from "./chunk-SYWJG5NA.js";
9
+ findCoveringTest,
10
+ hookRoot
11
+ } from "./chunk-J2EAZ4MA.js";
11
12
  import {
12
13
  TASK_PATH_LIMIT,
13
- buildArchitectureMap,
14
14
  countTerms,
15
15
  discoverTaskPaths,
16
- generateGraphifyGraph,
17
16
  identifierTokens,
18
- indexRepository,
19
- loadArchitectureMap,
20
17
  loadFreshIndex,
21
18
  loadProjectArchitectureMap,
22
19
  namedSegments,
23
- neighbours,
24
- queryMap,
25
20
  readBoundedSource,
26
21
  taskTerms
27
- } from "./chunk-L5AYZC3F.js";
22
+ } from "./chunk-NERKZSSE.js";
28
23
  import {
29
24
  extractSymbolTable,
30
25
  supportsSymbols
@@ -32,13 +27,10 @@ import {
32
27
  import {
33
28
  INCIDENT_SEVERITIES,
34
29
  MemoryFileStore,
35
- codeBuddyConfigSchema,
36
- createRuntime,
37
30
  isEligibleMemory,
38
31
  lexicalTerms,
39
- listFactsPage,
40
32
  termOverlap
41
- } from "./chunk-UN57FKQX.js";
33
+ } from "./chunk-S6VEG4S2.js";
42
34
  import {
43
35
  claimText,
44
36
  redactPersisted,
@@ -52,137 +44,26 @@ import {
52
44
  import {
53
45
  DEFAULT_RERANK_MODELS
54
46
  } from "./chunk-JPHJBCZB.js";
55
- import {
56
- refreshZvecIndex
57
- } from "./chunk-QDBDYCM2.js";
58
47
  import {
59
48
  assertNoSymlinkBetween,
60
49
  composeMarkdown,
61
- configPath,
62
50
  loadPlanPolicy,
63
51
  loadRuntimeConfig,
64
52
  parseFrontMatter,
65
53
  readConfigFile,
66
- redactSecrets,
67
54
  writeAtomic
68
- } from "./chunk-776EQXDV.js";
55
+ } from "./chunk-5KZY6OTG.js";
69
56
 
70
- // src/core/migrate-to-files.ts
71
- import { createHash } from "crypto";
72
- async function migrateMemoryToFiles(input) {
73
- const namespace = await input.repository.getNamespaceByName(input.namespace);
74
- if (!namespace) {
75
- return {
76
- namespace: input.namespace,
77
- dryRun: !input.write,
78
- facts: { found: 0, existing: 0, toWrite: 0, conflicts: 0, written: 0 },
79
- summaries: { found: 0, existing: 0, toWrite: 0, conflicts: 0, written: 0 },
80
- conflicts: [],
81
- failures: []
82
- };
83
- }
84
- const [facts, summaries, existingFacts, existingSummaries] = await Promise.all([
85
- input.repository.listFacts({ namespaceId: namespace.id, limit: 1e5 }),
86
- input.repository.listSummaries({ namespaceId: namespace.id, limit: 1e5 }),
87
- input.store.listFacts(),
88
- input.store.listSummaries()
89
- ]);
90
- const warnings = facts.length >= 1e5 || summaries.length >= 1e5 ? [
91
- "namespace is at or above the 100,000-row read cap; the migration may be partial \u2014 prune or export in batches"
92
- ] : [];
93
- const existingFactsById = new Map(existingFacts.map((fact) => [fact.id, fact]));
94
- const existingSummariesById = new Map(existingSummaries.map((summary) => [summary.id, summary]));
95
- const conflicts = [];
96
- const failures = [];
97
- const factsToWrite = facts.filter((fact) => {
98
- const existing = existingFactsById.get(fact.id);
99
- if (!existing) return true;
100
- const databaseChecksum = checksum(redactPersisted(fact).content);
101
- const fileChecksum = checksum(existing.content);
102
- if (databaseChecksum !== fileChecksum) {
103
- conflicts.push({ kind: "fact", id: fact.id, databaseChecksum, fileChecksum });
104
- }
105
- return false;
106
- });
107
- const summariesToWrite = summaries.filter((summary) => {
108
- const existing = existingSummariesById.get(summary.id);
109
- if (!existing) return true;
110
- const databaseChecksum = checksum(summary.content);
111
- const fileChecksum = checksum(existing.content);
112
- if (databaseChecksum !== fileChecksum) {
113
- conflicts.push({ kind: "summary", id: summary.id, databaseChecksum, fileChecksum });
114
- }
115
- return false;
116
- });
117
- let factsWritten = 0;
118
- let summariesWritten = 0;
119
- if (input.write) {
120
- for (const fact of factsToWrite) {
121
- try {
122
- await input.store.writeFact({
123
- id: fact.id,
124
- namespace: input.namespace,
125
- subject: fact.subject,
126
- predicate: fact.predicate,
127
- object: fact.object,
128
- confidence: fact.confidence,
129
- createdAt: fact.createdAt.toISOString(),
130
- createdByAgent: fact.createdByAgent,
131
- sourceInteractionId: fact.sourceInteractionId,
132
- sourceDeleted: fact.sourceDeleted,
133
- supersededBy: fact.supersededBy,
134
- content: fact.content
135
- });
136
- factsWritten += 1;
137
- } catch (error) {
138
- failures.push({ kind: "fact", id: fact.id, error: error.message });
139
- }
140
- }
141
- for (const summary of summariesToWrite) {
142
- try {
143
- await input.store.writeSummary({
144
- id: summary.id,
145
- namespace: input.namespace,
146
- sessionId: summary.sessionId,
147
- version: summary.version,
148
- tokenCount: summary.tokenCount,
149
- createdAt: summary.createdAt.toISOString(),
150
- createdByAgent: summary.createdByAgent,
151
- content: summary.content
152
- });
153
- summariesWritten += 1;
154
- } catch (error) {
155
- failures.push({ kind: "summary", id: summary.id, error: error.message });
156
- }
157
- }
158
- }
159
- const factConflicts = conflicts.filter((conflict) => conflict.kind === "fact").length;
160
- const summaryConflicts = conflicts.length - factConflicts;
161
- return {
162
- namespace: input.namespace,
163
- dryRun: !input.write,
164
- ...warnings.length > 0 ? { warnings } : {},
165
- facts: {
166
- found: facts.length,
167
- existing: facts.length - factsToWrite.length - factConflicts,
168
- toWrite: factsToWrite.length,
169
- conflicts: factConflicts,
170
- written: factsWritten
171
- },
172
- summaries: {
173
- found: summaries.length,
174
- existing: summaries.length - summariesToWrite.length - summaryConflicts,
175
- toWrite: summariesToWrite.length,
176
- conflicts: summaryConflicts,
177
- written: summariesWritten
178
- },
179
- conflicts,
180
- failures
181
- };
182
- }
183
- function checksum(content) {
184
- return createHash("sha256").update(content).digest("hex");
185
- }
57
+ // src/hooks/runtime.ts
58
+ import { mkdir as mkdir6, readFile as readFile8, rm as rm4, stat as stat5, writeFile } from "fs/promises";
59
+ import { basename, dirname as dirname5, isAbsolute, join as join8, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
60
+ import pTimeout2 from "p-timeout";
61
+
62
+ // src/context/engine.ts
63
+ import { execFile } from "child_process";
64
+ import { readFile as readFile4 } from "fs/promises";
65
+ import { join as join4, resolve as resolve5 } from "path";
66
+ import { promisify } from "util";
186
67
 
187
68
  // src/core/plan-file-store.ts
188
69
  import { mkdir, open, readdir, readFile, rm, stat } from "fs/promises";
@@ -413,7 +294,7 @@ function formatIssues(error) {
413
294
  });
414
295
  }
415
296
  function delay(ms) {
416
- return new Promise((resolve14) => setTimeout(resolve14, ms));
297
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
417
298
  }
418
299
  async function releaseOwnedLock(lockPath) {
419
300
  try {
@@ -609,124 +490,10 @@ var PlanLifecycle = class {
609
490
  }
610
491
  };
611
492
 
612
- // package.json
613
- var package_default = {
614
- name: "@orquentra/codebuddy",
615
- version: "2.6.0",
616
- description: "Local-first project context and memory for AI coding agents.",
617
- type: "module",
618
- license: "MIT",
619
- publishConfig: {
620
- access: "public"
621
- },
622
- author: "Orquentra Inc",
623
- repository: {
624
- type: "git",
625
- url: "https://github.com/Orquentra-Inc/CodeBuddy"
626
- },
627
- engines: {
628
- node: ">=20"
629
- },
630
- bin: {
631
- codebuddy: "./dist/cli/index.js"
632
- },
633
- exports: {
634
- ".": {
635
- types: "./dist/index.d.ts",
636
- import: "./dist/index.js"
637
- },
638
- "./langgraph": {
639
- types: "./dist/langgraph/index.d.ts",
640
- import: "./dist/langgraph/index.js"
641
- }
642
- },
643
- files: [
644
- "dist",
645
- "src/db/migrations",
646
- "README.md",
647
- "CLAUDE_CODEX.md",
648
- "ARCHITECTURE.md",
649
- "WORKFLOW.md",
650
- "LICENSE",
651
- "docker-compose.yml",
652
- "examples"
653
- ],
654
- scripts: {
655
- build: "tsup",
656
- dev: "tsx src/cli/index.ts",
657
- "db:generate": "drizzle-kit generate",
658
- "db:migrate": "drizzle-kit migrate",
659
- typecheck: "tsc --noEmit",
660
- test: "vitest run --passWithNoTests",
661
- "test:pg": "bash scripts/test-pg-recovery.sh",
662
- "eval:agent-ab": "tsx scripts/eval-agent-ab.mts",
663
- "eval:memory-ab": "tsx scripts/eval-memory-ab.mts",
664
- "eval:retrieval": "tsx scripts/eval-retrieval.mts",
665
- "eval:savings": "tsx scripts/eval-task-savings.mts",
666
- "branches:prune": "bash scripts/prune-merged-branches.sh",
667
- lint: "biome check .",
668
- format: "biome format --write .",
669
- prepublishOnly: "npm run typecheck && npm test && npm run build",
670
- "eval:semantic": "tsx scripts/eval-semantic.mts"
671
- },
672
- keywords: [
673
- "mcp",
674
- "memory",
675
- "agent",
676
- "multi-agent",
677
- "huggingface",
678
- "pgvector",
679
- "postgres",
680
- "langgraph",
681
- "claude"
682
- ],
683
- homepage: "https://github.com/Orquentra-Inc/CodeBuddy",
684
- bugs: {
685
- url: "https://github.com/Orquentra-Inc/CodeBuddy/issues"
686
- },
687
- dependencies: {
688
- "@clack/prompts": "^0.11.0",
689
- "@huggingface/inference": "^4.13.19",
690
- "@modelcontextprotocol/sdk": "^1.17.5",
691
- commander: "^14.0.0",
692
- "drizzle-orm": "^0.45.2",
693
- "js-tiktoken": "^1.0.21",
694
- langsmith: "^0.7.10",
695
- ora: "^8.2.0",
696
- "p-queue": "^8.1.0",
697
- "p-retry": "^6.2.1",
698
- "p-timeout": "^6.1.4",
699
- picocolors: "^1.1.1",
700
- pino: "^9.9.0",
701
- "pino-pretty": "^13.1.1",
702
- postgres: "^3.4.7",
703
- yaml: "^2.9.0",
704
- zod: "^4.6.7"
705
- },
706
- peerDependencies: {
707
- "@langchain/langgraph": "^0.2.0 || ^0.3.0"
708
- },
709
- peerDependenciesMeta: {
710
- "@langchain/langgraph": {
711
- optional: true
712
- }
713
- },
714
- devDependencies: {
715
- "@biomejs/biome": "^2.5.0",
716
- "@types/node": "^24.3.0",
717
- "drizzle-kit": "^0.31.10",
718
- tsup: "^8.5.0",
719
- tsx: "^4.20.5",
720
- typescript: "^5.9.2",
721
- vitest: "^4.1.12"
722
- },
723
- optionalDependencies: {
724
- "@huggingface/transformers": "^4.3.2"
725
- }
726
- };
727
-
728
- // src/version.ts
729
- var VERSION = package_default.version;
493
+ // src/risk/service.ts
494
+ import { spawn as spawn2 } from "child_process";
495
+ import { realpath as realpath2 } from "fs/promises";
496
+ import { relative as relative2, resolve as resolve3, sep as sep2 } from "path";
730
497
 
731
498
  // src/risk/assessor.ts
732
499
  var RiskAssessor = class {
@@ -893,7 +660,7 @@ async function gitLogForPath(repositoryRoot, path, since) {
893
660
  }).filter((record) => record.sha.length > 0);
894
661
  }
895
662
  function runGit(cwd, args) {
896
- return new Promise((resolve14, reject) => {
663
+ return new Promise((resolve10, reject) => {
897
664
  const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
898
665
  let stdout = "";
899
666
  let stderr = "";
@@ -904,7 +671,7 @@ function runGit(cwd, args) {
904
671
  stderr += String(chunk);
905
672
  });
906
673
  child.on("error", reject);
907
- child.on("close", (code) => resolve14({ code: code ?? 0, stdout, stderr }));
674
+ child.on("close", (code) => resolve10({ code: code ?? 0, stdout, stderr }));
908
675
  });
909
676
  }
910
677
 
@@ -1040,8 +807,6 @@ function escapeRegExp(value) {
1040
807
  }
1041
808
 
1042
809
  // src/risk/service.ts
1043
- import { spawn as spawn2 } from "child_process";
1044
- import { relative as relative2, resolve as resolve3, sep as sep2 } from "path";
1045
810
  async function assessRisk(input) {
1046
811
  const repositoryRoot = resolve3(input.repositoryRoot ?? process.cwd());
1047
812
  const paths = await resolveRiskPaths({
@@ -1092,7 +857,14 @@ async function resolveRiskPaths(input) {
1092
857
  if (contained) paths.add(contained);
1093
858
  }
1094
859
  }
1095
- const resolved = [...paths].sort();
860
+ const realRoot = await realpath2(input.repositoryRoot).catch(() => resolve3(input.repositoryRoot));
861
+ const inside = await Promise.all(
862
+ [...paths].map(async (path) => {
863
+ const real = await realpath2(resolve3(input.repositoryRoot, path)).catch(() => null);
864
+ return real === null || real === realRoot || real.startsWith(`${realRoot}${sep2}`);
865
+ })
866
+ );
867
+ const resolved = [...paths].filter((_, index) => inside[index]).sort();
1096
868
  return resolved.length > MAX_RESOLVED_PATHS ? resolved.slice(0, MAX_RESOLVED_PATHS) : resolved;
1097
869
  }
1098
870
  var MAX_RESOLVED_PATHS = 200;
@@ -1109,9 +881,7 @@ async function changedGitPaths(repositoryRoot) {
1109
881
  runGit2(repositoryRoot, ["diff", "--name-only", "HEAD"]),
1110
882
  runGit2(repositoryRoot, ["ls-files", "--others", "--exclude-standard"])
1111
883
  ]);
1112
- return [...tracked.stdout.split("\n"), ...untracked.stdout.split("\n")].map((path) => path.trim()).filter(
1113
- (path) => path && !path.startsWith(".codebuddy/cache/") && !path.startsWith(".codebuddy/worklog/")
1114
- );
884
+ return [...tracked.stdout.split("\n"), ...untracked.stdout.split("\n")].map((path) => path.trim()).filter((path) => path && !path.startsWith(".codebuddy/"));
1115
885
  }
1116
886
  function runGit2(cwd, args) {
1117
887
  return new Promise((resolveResult, reject) => {
@@ -1132,207 +902,8 @@ function normalizePath2(path) {
1132
902
  return path.replace(/\\/g, "/").replace(/^\.\//, "");
1133
903
  }
1134
904
 
1135
- // src/mcp/tools/index.ts
1136
- import { z as z6 } from "zod";
1137
-
1138
- // src/context/delivery.ts
1139
- import { createHash as createHash2 } from "crypto";
1140
-
1141
- // src/context/budget.ts
1142
- var WARNING = "Context incomplete: inspect omitted policies, risks and incidents before editing.";
1143
- function enforceContextBudget(input) {
1144
- const output = structuredClone(input);
1145
- if (!output.tokens) return input;
1146
- output.tokens.essentialOmitted ??= 0;
1147
- if (account(output) <= output.tokens.budget) return output;
1148
- const candidates = [];
1149
- const add = (section, value, priority, essential = false) => {
1150
- candidates.push({ section, value, priority, essential });
1151
- };
1152
- for (const [section, priority] of [
1153
- ["diffs", 10],
1154
- ["neighbours", 20],
1155
- ["handoffs", 30],
1156
- ["facts", 40],
1157
- ["rules", 45],
1158
- ["symbols", 50],
1159
- ["policyRules", 90],
1160
- ["incidents", 80]
1161
- ]) {
1162
- const values = output[section];
1163
- if (!Array.isArray(values)) continue;
1164
- for (const value of values) {
1165
- const essential = section === "policyRules" || section === "incidents" && !value.resolved;
1166
- const severity = section === "incidents" ? { critical: 4, high: 3, medium: 2, low: 1 }[value.severity] ?? 0 : 0;
1167
- add(section, value, priority + severity, essential);
1168
- }
1169
- output[section] = [];
1170
- }
1171
- const risks = output.risks;
1172
- if (risks) {
1173
- for (const value of risks.items)
1174
- add(
1175
- "risks",
1176
- value,
1177
- value.score >= 50 ? 100 + value.score : 60 + value.score / 100,
1178
- value.score > 0
1179
- );
1180
- risks.items = [];
1181
- }
1182
- if (output.plan) {
1183
- add("plan", output.plan, 55);
1184
- output.plan = null;
1185
- }
1186
- const architecture = output.architecture;
1187
- if (architecture) {
1188
- for (const value of architecture.hotspots) add("hotspots", value, 25);
1189
- architecture.hotspots = [];
1190
- }
1191
- output.explain = [];
1192
- if (typeof output.task === "string") output.task = compact(output.task, 160);
1193
- const riskStats = output.risks?.stats;
1194
- if (riskStats) riskStats.perSignal = [];
1195
- const previousOmitted = output.tokens.essentialOmitted;
1196
- output.tokens.essentialOmitted = previousOmitted + candidates.filter((c) => c.essential).length;
1197
- output.tokens.warning = WARNING;
1198
- if (account(output) > output.tokens.budget && output.task) output.task = null;
1199
- if (estimateTokens({ project: output.project, targetPaths: output.targetPaths }) > output.tokens.budget) {
1200
- account(output);
1201
- return output;
1202
- }
1203
- candidates.sort((a, b) => b.priority - a.priority);
1204
- for (const candidate of candidates) {
1205
- const insert = (value) => {
1206
- if (candidate.section === "plan") output.plan = value;
1207
- else if (candidate.section === "risks") risks?.items.push(value);
1208
- else if (candidate.section === "hotspots") architecture?.hotspots.push(value);
1209
- else output[candidate.section].push(value);
1210
- };
1211
- const remove = () => {
1212
- if (candidate.section === "plan") output.plan = null;
1213
- else if (candidate.section === "risks") risks?.items.pop();
1214
- else if (candidate.section === "hotspots") architecture?.hotspots.pop();
1215
- else output[candidate.section].pop();
1216
- };
1217
- insert(candidate.value);
1218
- if (candidate.essential) output.tokens.essentialOmitted--;
1219
- if (account(output) <= output.tokens.budget) continue;
1220
- remove();
1221
- if (candidate.essential) output.tokens.essentialOmitted++;
1222
- const smaller = candidate.essential ? compactSafety(candidate.value) : compact(candidate.value, 100);
1223
- insert(smaller);
1224
- if (account(output) <= output.tokens.budget) continue;
1225
- remove();
1226
- }
1227
- if (output.tokens.essentialOmitted === 0) delete output.tokens.warning;
1228
- account(output);
1229
- return output;
1230
- }
1231
- function compactSafety(value) {
1232
- if (!value || typeof value !== "object") return value;
1233
- const item = value;
1234
- return {
1235
- ...item,
1236
- ...typeof item.reason === "string" ? { reason: compact(item.reason, 100) } : {},
1237
- ...typeof item.summary === "string" ? { summary: compact(item.summary, 100) } : {},
1238
- ...Array.isArray(item.evidence) ? { evidence: item.evidence.slice(0, 1).map((entry) => compact(entry, 100)) } : {}
1239
- };
1240
- }
1241
- function compact(value, limit) {
1242
- if (typeof value === "string")
1243
- return value.length > limit ? `${value.slice(0, limit - 1)}\u2026` : value;
1244
- if (Array.isArray(value)) return value.slice(0, 3).map((item) => compact(item, limit));
1245
- if (value && typeof value === "object")
1246
- return Object.fromEntries(
1247
- Object.entries(value).map(([key, item]) => [key, compact(item, limit)])
1248
- );
1249
- return value;
1250
- }
1251
- function account(output) {
1252
- const tokens = output.tokens;
1253
- const set = (count) => {
1254
- tokens.returnedEstimate = count;
1255
- tokens.countedSurface = "compact_json";
1256
- tokens.status = count > tokens.budget ? "overflow" : tokens.essentialOmitted > 0 ? "minimum_envelope" : "within_budget";
1257
- if (tokens.status === "overflow") tokens.warning = WARNING;
1258
- };
1259
- for (let padding = 0; padding < 32; padding++) {
1260
- if (padding > 0) output.accountingPadding = " ".repeat(padding);
1261
- let count = estimateTokens(output);
1262
- for (let attempt = 0; attempt < 12; attempt++) {
1263
- set(count);
1264
- const measured = estimateTokens(output);
1265
- if (measured === count) return count;
1266
- count = measured;
1267
- }
1268
- }
1269
- throw new Error("Could not stabilize compact context token accounting");
1270
- }
1271
-
1272
- // src/context/delivery.ts
1273
- var DELIVERY_VERSION = 1;
1274
- var OMITTABLE_SECTIONS = [
1275
- "plan",
1276
- "policyRules",
1277
- "incidents",
1278
- "facts",
1279
- "rules",
1280
- "handoffs",
1281
- "architecture",
1282
- "symbols",
1283
- "neighbours"
1284
- ];
1285
- var SESSION_TTL_MS = 30 * 60 * 1e3;
1286
- async function applySessionDelivery(repositoryRoot, payload, options) {
1287
- let output = { ...payload };
1288
- if (!options.sessionId) return output;
1289
- const sections = OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
1290
- key: `mcp:${key}`,
1291
- hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
1292
- }));
1293
- try {
1294
- await new SessionCapsuleLedger(repositoryRoot).exchange(options.sessionId, sections, {
1295
- ...options.ack !== void 0 ? { ack: options.ack } : {},
1296
- ttlMs: SESSION_TTL_MS,
1297
- ...options.full ? { full: true } : {},
1298
- prepare: (exchange) => {
1299
- const unchanged = exchange.unchanged.map((key) => key.slice("mcp:".length));
1300
- let avoidedTokens = 0;
1301
- for (const key of unchanged) {
1302
- avoidedTokens += estimateTokens(output[key]);
1303
- delete output[key];
1304
- }
1305
- const delivery = {
1306
- version: DELIVERY_VERSION,
1307
- sessionId: options.sessionId,
1308
- deliveryId: exchange.deliveryId,
1309
- ack: exchange.ack,
1310
- unchanged,
1311
- avoidedTokens,
1312
- note: "Pass deliveryId as ack. Use full=true after losing context."
1313
- };
1314
- output.delivery = delivery;
1315
- output = enforceContextBudget(output);
1316
- return OMITTABLE_SECTIONS.filter((key) => output[key] !== void 0).map((key) => ({
1317
- key: `mcp:${key}`,
1318
- hash: createHash2("sha256").update(JSON.stringify(output[key])).digest("hex")
1319
- }));
1320
- }
1321
- });
1322
- return output;
1323
- } catch {
1324
- return enforceContextBudget({ ...payload });
1325
- }
1326
- }
1327
-
1328
- // src/context/engine.ts
1329
- import { execFile } from "child_process";
1330
- import { readFile as readFile4 } from "fs/promises";
1331
- import { join as join4, resolve as resolve5 } from "path";
1332
- import { promisify } from "util";
1333
-
1334
905
  // src/worklog/store.ts
1335
- import { createHash as createHash3 } from "crypto";
906
+ import { createHash } from "crypto";
1336
907
  import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, stat as stat2 } from "fs/promises";
1337
908
  import { dirname as dirname2, join as join3, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
1338
909
  import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
@@ -1375,7 +946,7 @@ var WorklogStore = class {
1375
946
  const files = [...new Set((input.files ?? []).map(normalizePath3))].filter(Boolean).sort().slice(0, LIMITS.files);
1376
947
  const summary = boundSummary(input.summary, files);
1377
948
  const verification = (input.verification ?? []).map((line) => line.trim().slice(0, LIMITS.verificationChars)).filter((line) => line && !scanSensitive(line).sensitive).slice(0, LIMITS.verification);
1378
- const fingerprint = createHash3("sha256").update(
949
+ const fingerprint = createHash("sha256").update(
1379
950
  JSON.stringify({
1380
951
  namespace: input.namespace.trim().toLowerCase(),
1381
952
  session: input.sessionId ?? null,
@@ -1522,45 +1093,176 @@ function words(text) {
1522
1093
  return new Set(text.toLowerCase().match(/[a-z0-9_]{4,}/g) ?? []);
1523
1094
  }
1524
1095
 
1525
- // src/context/engine.ts
1526
- var LIMITS2 = {
1527
- bootstrapIncidents: 5,
1528
- beforeEditIncidents: 8,
1529
- policyRules: 5,
1530
- hotspots: 5,
1531
- neighbourPaths: 10,
1532
- neighbourEdges: 10,
1533
- incidentSummaryChars: 140,
1534
- symbolFiles: TASK_PATH_LIMIT,
1535
- symbolsPerFile: 12,
1536
- spanFiles: 3,
1537
- excerptLines: 6,
1538
- excerptCharsPerSpan: 400,
1539
- excerptChars: 1200,
1540
- relevantFacts: 5
1541
- };
1542
- var DEFAULT_TOKEN_BUDGET = 4e3;
1543
- var MAX_DIFF_FILES = 8;
1544
- var MAX_DIFF_CHARS_PER_FILE = 2400;
1545
- var execFileAsync = promisify(execFile);
1546
- async function buildBootstrapContext(input) {
1547
- const repositoryRoot = resolve5(input.repositoryRoot ?? process.cwd());
1548
- const namespace = input.namespace;
1549
- const planStore = new PlanFileStore(repositoryRoot);
1550
- const memoryStore = new MemoryFileStore(repositoryRoot);
1551
- const freshPromise = loadFreshIndex(repositoryRoot);
1552
- const manifestPromise = freshPromise.then((fresh2) => fresh2.manifest);
1553
- const mapPromise = loadProjectArchitectureMap(repositoryRoot, manifestPromise);
1554
- const [activePlan, policy, policyFile, incidents, facts, fresh, map, handoffs] = await Promise.all([
1555
- new PlanLifecycle(planStore).current(namespace),
1556
- loadPlanPolicy(repositoryRoot),
1557
- readPolicyFile(join4(repositoryRoot, ".codebuddy", "policies.yaml")),
1558
- memoryStore.listIncidentFacts({ namespace }),
1559
- memoryStore.listFacts(),
1560
- freshPromise,
1561
- mapPromise,
1562
- recallHandoffs(repositoryRoot, { namespace })
1563
- ]);
1096
+ // src/context/budget.ts
1097
+ var WARNING = "Context incomplete: inspect omitted policies, risks and incidents before editing.";
1098
+ function enforceContextBudget(input) {
1099
+ const output = structuredClone(input);
1100
+ if (!output.tokens) return input;
1101
+ output.tokens.essentialOmitted ??= 0;
1102
+ if (account(output) <= output.tokens.budget) return output;
1103
+ const candidates = [];
1104
+ const add = (section, value, priority, essential = false) => {
1105
+ candidates.push({ section, value, priority, essential });
1106
+ };
1107
+ for (const [section, priority] of [
1108
+ ["diffs", 10],
1109
+ ["neighbours", 20],
1110
+ ["handoffs", 30],
1111
+ ["facts", 40],
1112
+ ["rules", 45],
1113
+ ["symbols", 50],
1114
+ ["policyRules", 90],
1115
+ ["incidents", 80]
1116
+ ]) {
1117
+ const values = output[section];
1118
+ if (!Array.isArray(values)) continue;
1119
+ for (const value of values) {
1120
+ const essential = section === "policyRules" || section === "incidents" && !value.resolved;
1121
+ const severity = section === "incidents" ? { critical: 4, high: 3, medium: 2, low: 1 }[value.severity] ?? 0 : 0;
1122
+ add(section, value, priority + severity, essential);
1123
+ }
1124
+ output[section] = [];
1125
+ }
1126
+ const risks = output.risks;
1127
+ if (risks) {
1128
+ for (const value of risks.items)
1129
+ add(
1130
+ "risks",
1131
+ value,
1132
+ value.score >= 50 ? 100 + value.score : 60 + value.score / 100,
1133
+ value.score > 0
1134
+ );
1135
+ risks.items = [];
1136
+ }
1137
+ if (output.plan) {
1138
+ add("plan", output.plan, 55);
1139
+ output.plan = null;
1140
+ }
1141
+ const architecture = output.architecture;
1142
+ if (architecture) {
1143
+ for (const value of architecture.hotspots) add("hotspots", value, 25);
1144
+ architecture.hotspots = [];
1145
+ }
1146
+ output.explain = [];
1147
+ if (typeof output.task === "string") output.task = compact(output.task, 160);
1148
+ const riskStats = output.risks?.stats;
1149
+ if (riskStats) riskStats.perSignal = [];
1150
+ const previousOmitted = output.tokens.essentialOmitted;
1151
+ output.tokens.essentialOmitted = previousOmitted + candidates.filter((c) => c.essential).length;
1152
+ output.tokens.warning = WARNING;
1153
+ if (account(output) > output.tokens.budget && output.task) output.task = null;
1154
+ if (estimateTokens({ project: output.project, targetPaths: output.targetPaths }) > output.tokens.budget) {
1155
+ account(output);
1156
+ return output;
1157
+ }
1158
+ candidates.sort((a, b) => b.priority - a.priority);
1159
+ for (const candidate of candidates) {
1160
+ const insert = (value) => {
1161
+ if (candidate.section === "plan") output.plan = value;
1162
+ else if (candidate.section === "risks") risks?.items.push(value);
1163
+ else if (candidate.section === "hotspots") architecture?.hotspots.push(value);
1164
+ else output[candidate.section].push(value);
1165
+ };
1166
+ const remove = () => {
1167
+ if (candidate.section === "plan") output.plan = null;
1168
+ else if (candidate.section === "risks") risks?.items.pop();
1169
+ else if (candidate.section === "hotspots") architecture?.hotspots.pop();
1170
+ else output[candidate.section].pop();
1171
+ };
1172
+ insert(candidate.value);
1173
+ if (candidate.essential) output.tokens.essentialOmitted--;
1174
+ if (account(output) <= output.tokens.budget) continue;
1175
+ remove();
1176
+ if (candidate.essential) output.tokens.essentialOmitted++;
1177
+ const smaller = candidate.essential ? compactSafety(candidate.value) : compact(candidate.value, 100);
1178
+ insert(smaller);
1179
+ if (account(output) <= output.tokens.budget) continue;
1180
+ remove();
1181
+ }
1182
+ if (output.tokens.essentialOmitted === 0) delete output.tokens.warning;
1183
+ account(output);
1184
+ return output;
1185
+ }
1186
+ function compactSafety(value) {
1187
+ if (!value || typeof value !== "object") return value;
1188
+ const item = value;
1189
+ return {
1190
+ ...item,
1191
+ ...typeof item.reason === "string" ? { reason: compact(item.reason, 100) } : {},
1192
+ ...typeof item.summary === "string" ? { summary: compact(item.summary, 100) } : {},
1193
+ ...Array.isArray(item.evidence) ? { evidence: item.evidence.slice(0, 1).map((entry) => compact(entry, 100)) } : {}
1194
+ };
1195
+ }
1196
+ function compact(value, limit) {
1197
+ if (typeof value === "string")
1198
+ return value.length > limit ? `${value.slice(0, limit - 1)}\u2026` : value;
1199
+ if (Array.isArray(value)) return value.slice(0, 3).map((item) => compact(item, limit));
1200
+ if (value && typeof value === "object")
1201
+ return Object.fromEntries(
1202
+ Object.entries(value).map(([key, item]) => [key, compact(item, limit)])
1203
+ );
1204
+ return value;
1205
+ }
1206
+ function account(output) {
1207
+ const tokens = output.tokens;
1208
+ const set = (count) => {
1209
+ tokens.returnedEstimate = count;
1210
+ tokens.countedSurface = "compact_json";
1211
+ tokens.status = count > tokens.budget ? "overflow" : tokens.essentialOmitted > 0 ? "minimum_envelope" : "within_budget";
1212
+ if (tokens.status === "overflow") tokens.warning = WARNING;
1213
+ };
1214
+ for (let padding = 0; padding < 32; padding++) {
1215
+ if (padding > 0) output.accountingPadding = " ".repeat(padding);
1216
+ let count = estimateTokens(output);
1217
+ for (let attempt = 0; attempt < 12; attempt++) {
1218
+ set(count);
1219
+ const measured = estimateTokens(output);
1220
+ if (measured === count) return count;
1221
+ count = measured;
1222
+ }
1223
+ }
1224
+ throw new Error("Could not stabilize compact context token accounting");
1225
+ }
1226
+
1227
+ // src/context/engine.ts
1228
+ var LIMITS2 = {
1229
+ bootstrapIncidents: 5,
1230
+ beforeEditIncidents: 8,
1231
+ policyRules: 5,
1232
+ hotspots: 5,
1233
+ neighbourPaths: 10,
1234
+ neighbourEdges: 10,
1235
+ incidentSummaryChars: 140,
1236
+ symbolFiles: TASK_PATH_LIMIT,
1237
+ symbolsPerFile: 12,
1238
+ spanFiles: 3,
1239
+ excerptLines: 6,
1240
+ excerptCharsPerSpan: 400,
1241
+ excerptChars: 1200,
1242
+ relevantFacts: 5
1243
+ };
1244
+ var DEFAULT_TOKEN_BUDGET = 4e3;
1245
+ var MAX_DIFF_FILES = 8;
1246
+ var MAX_DIFF_CHARS_PER_FILE = 2400;
1247
+ var execFileAsync = promisify(execFile);
1248
+ async function buildBootstrapContext(input) {
1249
+ const repositoryRoot = resolve5(input.repositoryRoot ?? process.cwd());
1250
+ const namespace = input.namespace;
1251
+ const planStore = new PlanFileStore(repositoryRoot);
1252
+ const memoryStore = new MemoryFileStore(repositoryRoot);
1253
+ const freshPromise = loadFreshIndex(repositoryRoot);
1254
+ const manifestPromise = freshPromise.then((fresh2) => fresh2.manifest);
1255
+ const mapPromise = loadProjectArchitectureMap(repositoryRoot, manifestPromise);
1256
+ const [activePlan, policy, policyFile, incidents, facts, fresh, map, handoffs] = await Promise.all([
1257
+ new PlanLifecycle(planStore).current(namespace),
1258
+ loadPlanPolicy(repositoryRoot),
1259
+ readPolicyFile(join4(repositoryRoot, ".codebuddy", "policies.yaml")),
1260
+ memoryStore.listIncidentFacts({ namespace }),
1261
+ memoryStore.listFacts(),
1262
+ freshPromise,
1263
+ mapPromise,
1264
+ recallHandoffs(repositoryRoot, { namespace })
1265
+ ]);
1564
1266
  const explain = [fresh.explain];
1565
1267
  const plan = summarizePlan(activePlan);
1566
1268
  explain.push(plan ? `plan: active plan ${plan.id} (${plan.status})` : "plan: no active plan");
@@ -1663,10 +1365,10 @@ async function buildBeforeEditContext(input) {
1663
1365
  explain.push(`incidents: ${incidentSummaries.length} incident(s) touch target paths`);
1664
1366
  }
1665
1367
  if (risks.items.length > 0) explain.push(`risks: ${risks.items.length} scored path(s)`);
1666
- const neighbours2 = computeNeighbours(map, targetPaths);
1667
- if (neighbours2.length > 0)
1368
+ const neighbours = computeNeighbours(map, targetPaths);
1369
+ if (neighbours.length > 0)
1668
1370
  explain.push(
1669
- `neighbours: import graph for ${neighbours2.length} file(s)` + (map.source ? ` (${map.source})` : "")
1371
+ `neighbours: import graph for ${neighbours.length} file(s)` + (map.source ? ` (${map.source})` : "")
1670
1372
  );
1671
1373
  const symbols = await computeTargetSymbols(
1672
1374
  repositoryRoot,
@@ -1720,7 +1422,7 @@ async function buildBeforeEditContext(input) {
1720
1422
  facts: relevantFacts,
1721
1423
  handoffs,
1722
1424
  risks,
1723
- neighbours: neighbours2,
1425
+ neighbours,
1724
1426
  symbols,
1725
1427
  tests,
1726
1428
  testCommand,
@@ -2025,8 +1727,10 @@ async function buildUntrackedPatch(repositoryRoot, path) {
2025
1727
  }
2026
1728
  var STANDING_PREDICATES = /* @__PURE__ */ new Set(["rule", "procedure"]);
2027
1729
  var STANDING_LIMITS = { rule: 5, procedure: 3, chars: 200 };
1730
+ var SHELL_CONTROL = /[;&|`$<>\n\r]/;
2028
1731
  function selectRules(facts, namespace) {
2029
- const newest = (predicate) => facts.filter((fact) => fact.predicate === predicate && isEligibleMemory(fact, { namespace })).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, STANDING_LIMITS[predicate]);
1732
+ const latest = new Date(Date.now() + 864e5).toISOString();
1733
+ const newest = (predicate) => facts.filter((fact) => fact.predicate === predicate && isEligibleMemory(fact, { namespace })).filter((fact) => fact.createdAt <= latest).filter((fact) => predicate !== "procedure" || !SHELL_CONTROL.test(fact.object)).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, STANDING_LIMITS[predicate]);
2030
1734
  return [...newest("rule"), ...newest("procedure")].map((fact) => ({
2031
1735
  id: fact.id,
2032
1736
  subject: fact.predicate,
@@ -2170,13 +1874,8 @@ function truncate(value, max) {
2170
1874
  return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max - 1)}\u2026`;
2171
1875
  }
2172
1876
 
2173
- // src/hooks/runtime.ts
2174
- import { readFile as readFile8 } from "fs/promises";
2175
- import { basename, isAbsolute, relative as relative5, resolve as resolve9, sep as sep5 } from "path";
2176
- import pTimeout2 from "p-timeout";
2177
-
2178
1877
  // src/memory-extract/engine.ts
2179
- import { createHash as createHash4 } from "crypto";
1878
+ import { createHash as createHash2 } from "crypto";
2180
1879
  import { resolve as resolve7 } from "path";
2181
1880
 
2182
1881
  // src/memory-extract/deterministic.ts
@@ -2972,7 +2671,7 @@ function candidateFingerprint(namespace, raw) {
2972
2671
  content: candidate.content.trim().replace(/\s+/g, " ").toLowerCase(),
2973
2672
  paths: [...new Set(candidate.paths.map((path) => path.replace(/\\/g, "/")))].sort()
2974
2673
  });
2975
- return createHash4("sha256").update(canonical).digest("hex");
2674
+ return createHash2("sha256").update(canonical).digest("hex");
2976
2675
  }
2977
2676
  async function approveReviewItem(id, options = {}) {
2978
2677
  const repositoryRoot = resolve7(options.repositoryRoot ?? process.cwd());
@@ -3022,7 +2721,7 @@ async function approveReviewItem(id, options = {}) {
3022
2721
  }
3023
2722
 
3024
2723
  // src/tracing/log.ts
3025
- import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat3, writeFile } from "fs/promises";
2724
+ import { appendFile, mkdir as mkdir4, readFile as readFile6, stat as stat3 } from "fs/promises";
3026
2725
  import { dirname as dirname4, join as join6 } from "path";
3027
2726
  var MAX_BYTES = 4 * 1024 * 1024;
3028
2727
  function tracePath(repositoryRoot) {
@@ -3037,7 +2736,7 @@ async function recordTrace(repositoryRoot, event) {
3037
2736
  `);
3038
2737
  if ((await stat3(path)).size > MAX_BYTES) {
3039
2738
  const lines = (await readFile6(path, "utf8")).split("\n").filter(Boolean);
3040
- await writeFile(path, `${lines.slice(Math.floor(lines.length / 2)).join("\n")}
2739
+ await writeAtomic(path, `${lines.slice(Math.floor(lines.length / 2)).join("\n")}
3041
2740
  `);
3042
2741
  }
3043
2742
  } catch {
@@ -3080,7 +2779,7 @@ async function configuredReranker(repositoryRoot) {
3080
2779
  if (provider === "local") {
3081
2780
  const revision = model === DEFAULT_RERANK_MODELS.local.id ? DEFAULT_RERANK_MODELS.local.revision : void 0;
3082
2781
  return async (task, documents) => {
3083
- const { rerankLocally } = await import("./local-OTOY56B4.js");
2782
+ const { rerankLocally } = await import("./local-5MZURLCZ.js");
3084
2783
  return bounded(
3085
2784
  rerankLocally({ id: model, ...revision ? { revision } : {} }, task, documents)
3086
2785
  );
@@ -3097,8 +2796,8 @@ async function openHookSemantics(repositoryRoot) {
3097
2796
  const connect = async () => {
3098
2797
  const config = await loadRuntimeConfig(repositoryRoot, { requireDatabase: false });
3099
2798
  if (!config.postgresUrl) return null;
3100
- const { createRuntime: createRuntime2 } = await import("./operations-LOBAQRLO.js");
3101
- const opening = createRuntime2(config, {
2799
+ const { createRuntime } = await import("./operations-POCHY3WR.js");
2800
+ const opening = createRuntime(config, {
3102
2801
  repositoryRoot,
3103
2802
  startWorker: false,
3104
2803
  connectTimeoutSeconds: 2
@@ -3134,6 +2833,7 @@ async function openHookSemantics(repositoryRoot) {
3134
2833
  // src/hooks/staging.ts
3135
2834
  import { mkdir as mkdir5, readFile as readFile7, rename as rename2, rm as rm3, stat as stat4 } from "fs/promises";
3136
2835
  import { join as join7, resolve as resolve8 } from "path";
2836
+ var STAGING_LOCK_STALE_MS = 1e4;
3137
2837
  var HookStagingStore = class {
3138
2838
  directory;
3139
2839
  operations = Promise.resolve();
@@ -3258,6 +2958,14 @@ var HookStagingStore = class {
3258
2958
  break;
3259
2959
  } catch (error) {
3260
2960
  if (error.code !== "EEXIST" || attempt >= 200) throw error;
2961
+ const age = await stat4(lock).then(
2962
+ (info) => Date.now() - info.mtimeMs,
2963
+ () => 0
2964
+ );
2965
+ if (age > STAGING_LOCK_STALE_MS) {
2966
+ await rm3(lock, { recursive: true, force: true });
2967
+ continue;
2968
+ }
3261
2969
  await new Promise((resolveWait) => setTimeout(resolveWait, 10));
3262
2970
  }
3263
2971
  }
@@ -3304,7 +3012,7 @@ async function resolveNamespace(repositoryRoot) {
3304
3012
  }
3305
3013
  async function runContextHook(input, emit, budgetMs = CONTEXT_HOOK_BUDGET_MS) {
3306
3014
  const started = Date.now();
3307
- const repositoryRoot = resolve9(input.cwd ?? process.cwd());
3015
+ const repositoryRoot = hookRoot(input.cwd);
3308
3016
  const namespace = await resolveNamespace(repositoryRoot);
3309
3017
  const bootstrap = await buildBootstrapContext({ repositoryRoot, namespace });
3310
3018
  let beforeEdit = null;
@@ -3369,14 +3077,14 @@ function recallTrace(input, beforeEdit, emitted, text, started, late) {
3369
3077
  };
3370
3078
  }
3371
3079
  async function runStageHook(input) {
3372
- const repositoryRoot = resolve9(input.cwd ?? process.cwd());
3080
+ const repositoryRoot = hookRoot(input.cwd);
3373
3081
  const paths = extractEditedPaths(input.tool_input).map((path) => toRepoRelative(repositoryRoot, path)).filter((path) => path !== null);
3374
3082
  await new HookStagingStore(repositoryRoot).add(input.session_id ?? "default", paths);
3375
3083
  return paths;
3376
3084
  }
3377
3085
  async function llmMemoryOptions(repositoryRoot) {
3378
3086
  try {
3379
- return await (await import("./memory-4RGVLPKG.js")).llmCaptureOptions(repositoryRoot);
3087
+ return await (await import("./memory-KIC5FJVW.js")).llmCaptureOptions(repositoryRoot);
3380
3088
  } catch {
3381
3089
  return {};
3382
3090
  }
@@ -3385,7 +3093,7 @@ var INVESTIGATION_MIN_CHARS = 200;
3385
3093
  var NAMES_CODE = /[\w.-]+\/[\w./-]*\.\w{1,5}\b|\b[\w-]+\.(?:ts|tsx|js|jsx|mjs|py|go|rs|java|rb|php|cs|c|cpp|h|swift|kt)\b|`[^`\s]+`/;
3386
3094
  async function runCaptureHook(input) {
3387
3095
  const captureStarted = Date.now();
3388
- const repositoryRoot = resolve9(input.cwd ?? process.cwd());
3096
+ const repositoryRoot = hookRoot(input.cwd);
3389
3097
  const staging = new HookStagingStore(repositoryRoot);
3390
3098
  const sessionId = input.session_id ?? "default";
3391
3099
  const claimed = await staging.claim(sessionId);
@@ -3455,39 +3163,78 @@ async function runCaptureHook(input) {
3455
3163
  }
3456
3164
  }
3457
3165
  var SYNC_BUDGET_MS = 5 * 6e4;
3458
- async function runSyncHook(repositoryRoot) {
3166
+ async function runSyncHook(repositoryRoot, options = {}) {
3459
3167
  const config = await loadRuntimeConfig(repositoryRoot, { requireDatabase: false });
3460
3168
  if (!config.postgresUrl) return null;
3461
- const { createRuntime: createRuntime2 } = await import("./operations-LOBAQRLO.js");
3462
- const started = Date.now();
3463
- const runtime = await createRuntime2(config, { repositoryRoot });
3169
+ const lock = join8(repositoryRoot, ".codebuddy", "cache", "sync.lock");
3170
+ const dirty = join8(repositoryRoot, ".codebuddy", "cache", "sync.dirty");
3171
+ await mkdir6(dirname5(lock), { recursive: true });
3172
+ if (!await takeLock(lock)) {
3173
+ await writeFile(dirty, "").catch(() => void 0);
3174
+ return null;
3175
+ }
3176
+ const { createRuntime } = await import("./operations-POCHY3WR.js");
3177
+ const runtime = await createRuntime(config, { repositoryRoot }).catch(async (error) => {
3178
+ await rm4(lock, { recursive: true, force: true });
3179
+ throw error;
3180
+ });
3464
3181
  try {
3465
- const reindex = await runtime.memory.reindexMemory();
3466
- const drained = await pTimeout2(runtime.memory.getWorker().drain(), {
3467
- milliseconds: SYNC_BUDGET_MS
3468
- }).then(
3182
+ let result;
3183
+ let round = 0;
3184
+ do {
3185
+ await rm4(dirty, { force: true });
3186
+ const started = Date.now();
3187
+ const reindex = await runtime.memory.reindexMemory(options.respace ? {} : { respace: false });
3188
+ const drained = await pTimeout2(runtime.memory.getWorker().drain(), {
3189
+ milliseconds: SYNC_BUDGET_MS
3190
+ }).then(
3191
+ () => true,
3192
+ () => false
3193
+ );
3194
+ await recordTrace(repositoryRoot, {
3195
+ kind: "sync",
3196
+ name: "sync",
3197
+ ms: Date.now() - started,
3198
+ ok: drained,
3199
+ detail: {
3200
+ imported: reindex.facts.imported,
3201
+ updated: reindex.facts.updated,
3202
+ reembedding: reindex.reembedding ?? 0,
3203
+ failures: reindex.failures.length
3204
+ }
3205
+ });
3206
+ result = { reindex, drained };
3207
+ round += 1;
3208
+ } while (round < 3 && await stat5(dirty).then(
3469
3209
  () => true,
3470
3210
  () => false
3471
- );
3472
- await recordTrace(repositoryRoot, {
3473
- kind: "sync",
3474
- name: "sync",
3475
- ms: Date.now() - started,
3476
- ok: drained,
3477
- detail: {
3478
- imported: reindex.facts.imported,
3479
- updated: reindex.facts.updated,
3480
- reembedding: reindex.reembedding ?? 0,
3481
- failures: reindex.failures.length
3482
- }
3483
- });
3484
- return { reindex, drained };
3211
+ ));
3212
+ return result;
3485
3213
  } finally {
3486
3214
  await runtime.close();
3215
+ await rm4(lock, { recursive: true, force: true });
3216
+ }
3217
+ }
3218
+ var SYNC_LOCK_STALE_MS = SYNC_BUDGET_MS + 6e4;
3219
+ async function takeLock(path) {
3220
+ try {
3221
+ await mkdir6(path);
3222
+ return true;
3223
+ } catch {
3224
+ const age = await stat5(path).then(
3225
+ (info) => Date.now() - info.mtimeMs,
3226
+ () => 0
3227
+ );
3228
+ if (age < SYNC_LOCK_STALE_MS) return false;
3229
+ await rm4(path, { recursive: true, force: true });
3230
+ return mkdir6(path).then(
3231
+ () => true,
3232
+ () => false
3233
+ );
3487
3234
  }
3488
3235
  }
3489
3236
  async function runResetHook(input) {
3490
- const repositoryRoot = resolve9(input.cwd ?? process.cwd());
3237
+ const repositoryRoot = hookRoot(input.cwd);
3491
3238
  await new SessionCapsuleLedger(repositoryRoot).reset(input.session_id ?? "default");
3492
3239
  }
3493
3240
  function extractEditedPaths(toolInput) {
@@ -3802,1275 +3549,7 @@ function describeKeys(keys) {
3802
3549
  }
3803
3550
  var kinds = /* @__PURE__ */ new Set(["fact", "incident", "handoff", "rule", "procedure"]);
3804
3551
 
3805
- // src/suggest/engine.ts
3806
- import { constants } from "fs";
3807
- import { access } from "fs/promises";
3808
- import { extname, join as join8, resolve as resolve10 } from "path";
3809
-
3810
- // src/suggest/types.ts
3811
- var SEVERITY_RANK = {
3812
- high: 3,
3813
- medium: 2,
3814
- low: 1,
3815
- info: 0
3816
- };
3817
-
3818
- // src/suggest/engine.ts
3819
- var RISK_SUGGEST_THRESHOLD = 50;
3820
- var BLAST_RADIUS_MEDIUM = 5;
3821
- var BLAST_RADIUS_HIGH = 10;
3822
- var CODE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
3823
- async function generateSuggestions(input) {
3824
- const repositoryRoot = resolve10(input.repositoryRoot ?? process.cwd());
3825
- const namespace = input.namespace;
3826
- const targetPaths = await resolveRiskPaths({
3827
- repositoryRoot,
3828
- ...input.paths !== void 0 ? { paths: input.paths } : {},
3829
- ...input.planId !== void 0 ? { planId: input.planId } : {},
3830
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {}
3831
- });
3832
- const planStore = new PlanFileStore(repositoryRoot);
3833
- const memoryStore = new MemoryFileStore(repositoryRoot);
3834
- const [risks, plan, incidents, map, policyFile] = await Promise.all([
3835
- assessRisk({
3836
- repositoryRoot,
3837
- namespace,
3838
- ...input.paths !== void 0 ? { paths: input.paths } : {},
3839
- ...input.planId !== void 0 ? { planId: input.planId } : {},
3840
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {}
3841
- }),
3842
- resolveRelevantPlan2(planStore, namespace, input.planId),
3843
- memoryStore.findIncidentFactsForPaths({ namespace, paths: targetPaths }),
3844
- buildArchitectureMap(repositoryRoot),
3845
- readPolicyFile(join8(repositoryRoot, ".codebuddy", "policies.yaml"))
3846
- ]);
3847
- const suggestions = [];
3848
- for (const item of risks.items) {
3849
- if (item.score < RISK_SUGGEST_THRESHOLD) continue;
3850
- suggestions.push({
3851
- id: `risk:${item.path}`,
3852
- category: "risk",
3853
- severity: item.score >= 75 ? "high" : "medium",
3854
- title: `Review high-risk change to ${item.path}`,
3855
- detail: item.reason,
3856
- path: item.path,
3857
- evidence: [
3858
- { kind: "risk_score", detail: `score ${item.score} (${item.category})` },
3859
- ...item.evidence.length > 0 ? [{ kind: "risk_evidence", detail: `${item.evidence.length} evidence item(s)` }] : []
3860
- ],
3861
- commandHint: `codebuddy risk assess --paths ${item.path} --explain`
3862
- });
3863
- }
3864
- for (const incident of incidents) {
3865
- const path = incident.paths[0] ?? "(unknown)";
3866
- suggestions.push({
3867
- id: `incident:${path}`,
3868
- category: "incident",
3869
- severity: "high",
3870
- title: `Recurring incident hotspot: ${path}`,
3871
- detail: `${incident.subject} \u2014 proceed carefully; this area has an unresolved incident.`,
3872
- path,
3873
- evidence: [{ kind: "incident", detail: `${incident.id} severity=${incident.severity}` }]
3874
- });
3875
- }
3876
- const inbound = inboundDegrees(map);
3877
- for (const path of targetPaths) {
3878
- const dependents = inbound.get(path) ?? 0;
3879
- if (dependents < BLAST_RADIUS_MEDIUM) continue;
3880
- suggestions.push({
3881
- id: `architecture:${path}`,
3882
- category: "architecture",
3883
- severity: dependents >= BLAST_RADIUS_HIGH ? "high" : "medium",
3884
- title: `High blast radius: ${path}`,
3885
- detail: `${dependents} module(s) import ${path}; a change here ripples widely.`,
3886
- path,
3887
- evidence: [{ kind: "dependents", detail: `${dependents} inbound import(s)` }],
3888
- commandHint: `codebuddy map dependents ${path}`
3889
- });
3890
- }
3891
- if (plan) {
3892
- const planned = new Set(plan.filesToTouch.map((file) => normalize3(file.path)));
3893
- const strays = targetPaths.filter((path) => !planned.has(normalize3(path)));
3894
- if (planned.size > 0 && strays.length > 0) {
3895
- suggestions.push({
3896
- id: `plan_divergence:${plan.id}`,
3897
- category: "plan_divergence",
3898
- severity: "medium",
3899
- title: `Change set diverges from plan ${plan.id}`,
3900
- detail: `${strays.length} target file(s) are outside the active plan's scope: ${strays.slice(0, 5).join(", ")}.`,
3901
- evidence: [
3902
- { kind: "plan", detail: `plan ${plan.id} (${plan.status})` },
3903
- { kind: "out_of_scope", detail: strays.slice(0, 5).join(", ") }
3904
- ],
3905
- commandHint: `codebuddy plan show ${plan.id}`
3906
- });
3907
- }
3908
- }
3909
- for (const path of targetPaths) {
3910
- if (!isTestableCode(path)) continue;
3911
- if (await hasSiblingTest(repositoryRoot, path)) continue;
3912
- suggestions.push({
3913
- id: `missing_test:${path}`,
3914
- category: "missing_test",
3915
- severity: "low",
3916
- title: `No test found for ${path}`,
3917
- detail: "Consider adding a test alongside this change.",
3918
- path,
3919
- evidence: [{ kind: "no_test", detail: "no *.test.* / *.spec.* sibling found" }]
3920
- });
3921
- }
3922
- for (const rule of policyFile.rules) {
3923
- const prefix = literalPrefix(rule.pattern);
3924
- if (!prefix) continue;
3925
- if (await pathExists(join8(repositoryRoot, prefix))) continue;
3926
- suggestions.push({
3927
- id: `stale_policy:${rule.id}`,
3928
- category: "stale_policy",
3929
- severity: "low",
3930
- title: `Stale policy rule "${rule.id}"`,
3931
- detail: `Pattern "${rule.pattern}" targets "${prefix}", which does not exist.`,
3932
- evidence: [{ kind: "policy_rule", detail: `${rule.id} \u2192 ${rule.pattern}` }]
3933
- });
3934
- }
3935
- const deduped = dedupe(suggestions).sort(compareSuggestions);
3936
- const limited = input.limit ? deduped.slice(0, input.limit) : deduped;
3937
- return {
3938
- suggestions: limited,
3939
- stats: buildStats(targetPaths.length, limited)
3940
- };
3941
- }
3942
- async function resolveRelevantPlan2(store, namespace, planId) {
3943
- if (planId) {
3944
- try {
3945
- return await store.readPlan(planId);
3946
- } catch (error) {
3947
- if (error.code === "ENOENT") return null;
3948
- throw error;
3949
- }
3950
- }
3951
- return new PlanLifecycle(store).current(namespace);
3952
- }
3953
- function inboundDegrees(map) {
3954
- const inbound = /* @__PURE__ */ new Map();
3955
- for (const edge of map.edges) inbound.set(edge.to, (inbound.get(edge.to) ?? 0) + 1);
3956
- return inbound;
3957
- }
3958
- function isTestableCode(path) {
3959
- if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(path)) return false;
3960
- if (path.endsWith(".d.ts")) return false;
3961
- if (path.includes("__tests__/")) return false;
3962
- return CODE_EXTENSIONS.has(extname(path).toLowerCase());
3963
- }
3964
- async function hasSiblingTest(repositoryRoot, path) {
3965
- const ext = extname(path);
3966
- const base = path.slice(0, path.length - ext.length);
3967
- const dir = base.includes("/") ? base.slice(0, base.lastIndexOf("/")) : "";
3968
- const name = base.includes("/") ? base.slice(base.lastIndexOf("/") + 1) : base;
3969
- const candidates = [
3970
- `${base}.test${ext}`,
3971
- `${base}.spec${ext}`,
3972
- `${dir ? `${dir}/` : ""}__tests__/${name}${ext}`,
3973
- `${dir ? `${dir}/` : ""}__tests__/${name}.test${ext}`
3974
- ];
3975
- for (const candidate of candidates) {
3976
- if (await pathExists(join8(repositoryRoot, candidate))) return true;
3977
- }
3978
- return false;
3979
- }
3980
- function literalPrefix(pattern) {
3981
- const normalized = normalize3(pattern);
3982
- const wildcard = normalized.search(/[*?[]/);
3983
- if (wildcard === -1) return normalized.replace(/\/+$/, "");
3984
- const beforeWildcard = normalized.slice(0, wildcard);
3985
- const lastSlash = beforeWildcard.lastIndexOf("/");
3986
- return lastSlash === -1 ? "" : beforeWildcard.slice(0, lastSlash);
3987
- }
3988
- function dedupe(suggestions) {
3989
- const byId = /* @__PURE__ */ new Map();
3990
- for (const suggestion of suggestions) {
3991
- const existing = byId.get(suggestion.id);
3992
- if (!existing || SEVERITY_RANK[suggestion.severity] > SEVERITY_RANK[existing.severity]) {
3993
- byId.set(suggestion.id, suggestion);
3994
- }
3995
- }
3996
- return [...byId.values()];
3997
- }
3998
- function compareSuggestions(left, right) {
3999
- const severity = SEVERITY_RANK[right.severity] - SEVERITY_RANK[left.severity];
4000
- if (severity !== 0) return severity;
4001
- if (left.category !== right.category) return left.category.localeCompare(right.category);
4002
- return (left.path ?? "").localeCompare(right.path ?? "");
4003
- }
4004
- function buildStats(pathsConsidered, suggestions) {
4005
- const byCategory = {};
4006
- const bySeverity = {};
4007
- for (const suggestion of suggestions) {
4008
- byCategory[suggestion.category] = (byCategory[suggestion.category] ?? 0) + 1;
4009
- bySeverity[suggestion.severity] = (bySeverity[suggestion.severity] ?? 0) + 1;
4010
- }
4011
- return { pathsConsidered, produced: suggestions.length, byCategory, bySeverity };
4012
- }
4013
- async function pathExists(path) {
4014
- try {
4015
- await access(path, constants.F_OK);
4016
- return true;
4017
- } catch {
4018
- return false;
4019
- }
4020
- }
4021
- function normalize3(path) {
4022
- return path.replace(/\\/g, "/").replace(/^\.\//, "");
4023
- }
4024
-
4025
- // src/mcp/tool-result.ts
4026
- function serializeToolResult(value, format = process.env.CODEBUDDY_RESPONSE_FORMAT ?? "both") {
4027
- if (format !== "text" && format !== "both") {
4028
- throw new Error('CODEBUDDY_RESPONSE_FORMAT must be "text" or "both"');
4029
- }
4030
- return {
4031
- content: [{ type: "text", text: JSON.stringify(value) }],
4032
- ...format === "both" && value !== null && typeof value === "object" && !Array.isArray(value) ? { structuredContent: value } : {}
4033
- };
4034
- }
4035
-
4036
- // src/mcp/tools/index.ts
4037
- var INPUT_LIMITS = {
4038
- contentChars: 32e3,
4039
- batchItems: 100,
4040
- summaryChars: 32e3,
4041
- planBodyChars: 64e3,
4042
- planBriefChars: 4e3,
4043
- titleChars: 300,
4044
- arrayItems: 100,
4045
- pathChars: 1024,
4046
- taskChars: 2e3,
4047
- agentIdChars: 200
4048
- };
4049
- var deliverySchema = {
4050
- sessionId: z6.string().min(1).max(200).optional(),
4051
- ack: z6.string().regex(/^dlv_[a-z0-9]+$/).optional(),
4052
- full: z6.boolean().optional()
4053
- };
4054
- var rememberSchema = {
4055
- sessionId: z6.string().min(1).max(200).optional(),
4056
- content: z6.string().min(1).max(INPUT_LIMITS.contentChars),
4057
- type: z6.enum(["interaction", "fact", "summary"]).optional(),
4058
- idempotencyKey: z6.string().min(1).max(200).optional(),
4059
- agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
4060
- };
4061
- var recallSchema = {
4062
- sessionId: z6.string().min(1).max(200),
4063
- query: z6.string().min(1).max(INPUT_LIMITS.taskChars),
4064
- budget: z6.number().int().positive().optional(),
4065
- callerModel: z6.string().min(1).max(200).optional(),
4066
- conflictMode: z6.enum(["all", "latest", "highest_confidence"]).optional()
4067
- };
4068
- var shareSchema = {
4069
- from: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional(),
4070
- to_namespace: z6.string().min(1).max(INPUT_LIMITS.agentIdChars),
4071
- factIds: z6.array(z6.string().min(1).max(100)).min(1).max(INPUT_LIMITS.arrayItems),
4072
- mode: z6.enum(["reference", "snapshot"]).optional(),
4073
- /** Snapshot mode: the target project's root; its Markdown store receives the copy. */
4074
- to_project_root: z6.string().min(1).max(INPUT_LIMITS.pathChars).optional(),
4075
- agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
4076
- };
4077
- var unshareSchema = {
4078
- to_namespace: z6.string().min(1).max(INPUT_LIMITS.agentIdChars),
4079
- factIds: z6.array(z6.string().min(1).max(100)).min(1).max(INPUT_LIMITS.arrayItems),
4080
- agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
4081
- };
4082
- var plannedFileSchema2 = z6.object({
4083
- path: z6.string().min(1).max(INPUT_LIMITS.pathChars),
4084
- action: z6.enum(["create", "edit", "delete"]),
4085
- notes: z6.string().max(INPUT_LIMITS.taskChars).optional()
4086
- });
4087
- var plannedTestSchema2 = z6.object({
4088
- path: z6.string().min(1).max(INPUT_LIMITS.pathChars),
4089
- description: z6.string().max(INPUT_LIMITS.taskChars).optional()
4090
- });
4091
- var planPatchSchema = {
4092
- title: z6.string().min(1).max(INPUT_LIMITS.titleChars).optional(),
4093
- brief: z6.string().max(INPUT_LIMITS.planBriefChars).optional(),
4094
- body: z6.string().max(INPUT_LIMITS.planBodyChars).optional(),
4095
- filesToTouch: z6.array(plannedFileSchema2).max(INPUT_LIMITS.arrayItems).optional(),
4096
- testsToAdd: z6.array(plannedTestSchema2).max(INPUT_LIMITS.arrayItems).optional(),
4097
- outOfScope: z6.array(z6.string().max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
4098
- risks: z6.array(z6.string().max(INPUT_LIMITS.taskChars)).max(INPUT_LIMITS.arrayItems).optional()
4099
- };
4100
- var mcpToolInputSchemas = {
4101
- remember: z6.object(rememberSchema),
4102
- remember_batch: z6.object({ items: z6.array(z6.object(rememberSchema)).min(1) }),
4103
- recall: z6.object(recallSchema),
4104
- list_facts: z6.object({
4105
- subject: z6.string().optional(),
4106
- limit: z6.number().int().positive().max(100).default(50),
4107
- cursor: z6.string().optional()
4108
- }),
4109
- list_namespaces: z6.object({}),
4110
- forget: z6.object({ id: z6.string().min(1) }),
4111
- share: z6.object(shareSchema),
4112
- unshare: z6.object(unshareSchema),
4113
- plan_current: z6.object({}),
4114
- plan_create: z6.object({
4115
- title: z6.string().min(1).max(INPUT_LIMITS.titleChars),
4116
- brief: z6.string().min(1).max(INPUT_LIMITS.planBriefChars),
4117
- body: z6.string().max(INPUT_LIMITS.planBodyChars).optional(),
4118
- filesToTouch: z6.array(plannedFileSchema2).max(INPUT_LIMITS.arrayItems).optional(),
4119
- testsToAdd: z6.array(plannedTestSchema2).max(INPUT_LIMITS.arrayItems).optional(),
4120
- outOfScope: z6.array(z6.string().max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
4121
- risks: z6.array(z6.string().max(INPUT_LIMITS.taskChars)).max(INPUT_LIMITS.arrayItems).optional(),
4122
- agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional()
4123
- }),
4124
- plan_amend: z6.object({ id: z6.string().min(1), patch: z6.object(planPatchSchema) }),
4125
- plan_status: z6.object({
4126
- id: z6.string().min(1),
4127
- status: z6.enum(["approved", "executing", "complete", "abandoned"]),
4128
- commitSha: z6.string().optional(),
4129
- notes: z6.string().optional(),
4130
- reason: z6.string().optional()
4131
- }),
4132
- risk_assess: z6.object({
4133
- planId: z6.string().min(1).max(100).optional(),
4134
- paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
4135
- evidence: z6.enum(["overview", "source"]).optional(),
4136
- useGit: z6.boolean().optional(),
4137
- limit: z6.number().int().positive().max(100).optional()
4138
- }),
4139
- map_query: z6.object({
4140
- from: z6.string().min(1).max(INPUT_LIMITS.pathChars).optional(),
4141
- to: z6.string().min(1).max(INPUT_LIMITS.pathChars).optional(),
4142
- limit: z6.number().int().positive().max(100).optional()
4143
- }),
4144
- map_neighbours: z6.object({
4145
- module: z6.string().min(1).max(INPUT_LIMITS.pathChars),
4146
- direction: z6.enum(["in", "out", "both"]).default("both")
4147
- }),
4148
- context_bootstrap: z6.object({ ...deliverySchema }),
4149
- context_before_edit: z6.object({
4150
- task: z6.string().min(1).max(INPUT_LIMITS.taskChars).optional(),
4151
- paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
4152
- planId: z6.string().min(1).max(100).optional(),
4153
- evidence: z6.enum(["overview", "source"]).optional(),
4154
- useGit: z6.boolean().optional(),
4155
- tokenBudget: z6.number().int().positive().max(1e5).optional(),
4156
- ...deliverySchema
4157
- }),
4158
- context_pack: z6.object({
4159
- task: z6.string().min(1).max(INPUT_LIMITS.taskChars),
4160
- paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
4161
- planId: z6.string().min(1).max(100).optional(),
4162
- evidence: z6.enum(["overview", "source"]).optional(),
4163
- useGit: z6.boolean().optional(),
4164
- tokenBudget: z6.number().int().positive().max(1e5).optional(),
4165
- ...deliverySchema
4166
- }),
4167
- context_after_turn: z6.object({
4168
- summary: z6.string().min(1).max(INPUT_LIMITS.summaryChars),
4169
- changedFiles: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
4170
- planId: z6.string().min(1).max(100).optional(),
4171
- taskType: z6.string().min(1).max(100).optional(),
4172
- agentId: z6.string().min(1).max(INPUT_LIMITS.agentIdChars).optional(),
4173
- sessionId: z6.string().min(1).max(200).optional(),
4174
- verification: z6.array(z6.string().min(1).max(500)).max(20).optional(),
4175
- commitSha: z6.string().min(7).max(64).optional()
4176
- }),
4177
- code_suggestions: z6.object({
4178
- paths: z6.array(z6.string().min(1).max(INPUT_LIMITS.pathChars)).max(INPUT_LIMITS.arrayItems).optional(),
4179
- planId: z6.string().min(1).max(100).optional(),
4180
- evidence: z6.enum(["overview", "source"]).optional(),
4181
- useGit: z6.boolean().optional(),
4182
- limit: z6.number().int().positive().max(100).optional()
4183
- }),
4184
- health: z6.object({})
4185
- };
4186
- var DATABASE_TOOLS = [
4187
- "remember",
4188
- "remember_batch",
4189
- "recall",
4190
- "list_facts",
4191
- "list_namespaces",
4192
- "forget",
4193
- "share",
4194
- "unshare"
4195
- ];
4196
- var FILE_TOOLS = Object.keys(mcpToolInputSchemas).filter(
4197
- (name) => !DATABASE_TOOLS.includes(name)
4198
- );
4199
- 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.";
4200
- var CORE_TOOLS = [
4201
- "context_pack",
4202
- "context_bootstrap",
4203
- "context_before_edit",
4204
- "context_after_turn",
4205
- "code_suggestions",
4206
- "health"
4207
- ];
4208
- function resolveToolProfile(value = process.env.CODEBUDDY_TOOL_PROFILE) {
4209
- if (value === void 0 || value === "" || value === "full") return "full";
4210
- if (value === "core") return "core";
4211
- throw new Error(`CODEBUDDY_TOOL_PROFILE must be "core" or "full", got "${value}"`);
4212
- }
4213
- var PLAN_BODY_LIMIT = 8e3;
4214
- function serializePlan(plan) {
4215
- if (!plan) return { plan: null };
4216
- const body = plan.body.length > PLAN_BODY_LIMIT ? `${plan.body.slice(0, PLAN_BODY_LIMIT)}
4217
-
4218
- \u2026[truncated]` : plan.body;
4219
- return { plan: { ...plan, body } };
4220
- }
4221
- function registerCodeBuddyTools(server, options, profile = "full") {
4222
- const exposed = (name) => profile === "full" || CORE_TOOLS.includes(name);
4223
- const config = "database" in options ? options.config : options.memory.config;
4224
- const traceRoot = config.projectRoot ?? process.cwd();
4225
- const register = (name, toolConfig, callback) => {
4226
- const traced = (async (...args) => {
4227
- const started = Date.now();
4228
- const input = args.length > 1 ? args[0] : void 0;
4229
- const trace = (ok, output) => void recordTrace(traceRoot, {
4230
- kind: "tool",
4231
- name,
4232
- ms: Date.now() - started,
4233
- ok,
4234
- tokensIn: estimateTokens(input),
4235
- tokensOut: estimateTokens(output),
4236
- ...typeof input?.sessionId === "string" ? { session: input.sessionId } : {}
4237
- });
4238
- try {
4239
- const result = await callback(...args);
4240
- trace(result?.isError !== true, result?.content?.map((part) => part.text ?? "").join(""));
4241
- return result;
4242
- } catch (error) {
4243
- trace(false);
4244
- throw error;
4245
- }
4246
- });
4247
- const tool = server.registerTool(name, toolConfig, traced);
4248
- if (!exposed(name)) tool.remove();
4249
- return tool;
4250
- };
4251
- const json = serializeToolResult;
4252
- const database = "database" in options ? options.database : { get: async () => options, status: () => ({ available: true, state: "ready" }) };
4253
- const viaDatabase = async (tool, run) => {
4254
- let db;
4255
- try {
4256
- db = await database.get();
4257
- } catch {
4258
- return {
4259
- ...json({ degraded: true, tool, database: database.status(), fallback: DEGRADED_FALLBACK }),
4260
- isError: true
4261
- };
4262
- }
4263
- return json(await run(db));
4264
- };
4265
- const namespace = config.namespace;
4266
- const projectRoot = config.projectRoot ?? process.cwd();
4267
- const rankContextFacts = async (task, candidates) => {
4268
- if (database.status().state !== "ready") return {};
4269
- try {
4270
- const { memory } = await database.get();
4271
- return await memory.rankContextFacts(
4272
- task,
4273
- candidates.map(({ id }) => id)
4274
- );
4275
- } catch {
4276
- return {};
4277
- }
4278
- };
4279
- const reranker = configuredReranker(projectRoot).catch(() => void 0);
4280
- const rerankerOption = async () => {
4281
- const configured = await reranker;
4282
- return configured ? { reranker: configured } : {};
4283
- };
4284
- const planStore = new PlanFileStore(projectRoot);
4285
- const planLifecycle = new PlanLifecycle(planStore);
4286
- const toFiles = (items) => items?.map((item) => ({ path: item.path, action: item.action, notes: item.notes ?? "" }));
4287
- const toTests = (items) => items?.map((item) => ({ path: item.path, description: item.description ?? "" }));
4288
- register(
4289
- "remember",
4290
- {
4291
- title: "Remember",
4292
- description: "Store one CodeBuddy memory item.",
4293
- inputSchema: mcpToolInputSchemas.remember.shape
4294
- },
4295
- async (input) => viaDatabase("remember", (db) => db.memory.remember(toRememberInput(input)))
4296
- );
4297
- register(
4298
- "remember_batch",
4299
- {
4300
- title: "Remember batch",
4301
- description: "Store multiple CodeBuddy memory items.",
4302
- inputSchema: mcpToolInputSchemas.remember_batch.shape
4303
- },
4304
- async (input) => viaDatabase("remember_batch", async (db) => ({
4305
- items: await db.memory.rememberBatch(input.items.map(toRememberInput))
4306
- }))
4307
- );
4308
- register(
4309
- "recall",
4310
- {
4311
- title: "Recall",
4312
- description: "Recall prompt-ready CodeBuddy context.",
4313
- inputSchema: mcpToolInputSchemas.recall.shape
4314
- },
4315
- async (input) => viaDatabase("recall", (db) => db.memory.recall(toRecallInput(input)))
4316
- );
4317
- register(
4318
- "list_facts",
4319
- {
4320
- title: "List facts",
4321
- description: "List facts in the configured namespace.",
4322
- inputSchema: mcpToolInputSchemas.list_facts.shape
4323
- },
4324
- async (input) => viaDatabase(
4325
- "list_facts",
4326
- (db) => listFactsPage(
4327
- db.memory,
4328
- db.repository,
4329
- cleanOptional(input)
4330
- )
4331
- )
4332
- );
4333
- register(
4334
- "list_namespaces",
4335
- {
4336
- title: "List namespaces",
4337
- description: "List CodeBuddy namespaces.",
4338
- inputSchema: mcpToolInputSchemas.list_namespaces.shape
4339
- },
4340
- async () => viaDatabase("list_namespaces", async (db) => ({
4341
- namespaces: await db.repository.listNamespaces()
4342
- }))
4343
- );
4344
- register(
4345
- "forget",
4346
- {
4347
- title: "Forget",
4348
- description: "Delete an interaction or fact by id.",
4349
- inputSchema: mcpToolInputSchemas.forget.shape
4350
- },
4351
- async (input) => viaDatabase("forget", (db) => db.memory.forget(input.id))
4352
- );
4353
- register(
4354
- "share",
4355
- {
4356
- title: "Share",
4357
- description: "Share facts from the configured namespace to another namespace. Reference (default) is a live, revocable grant; snapshot copies the fact into the target project's Markdown store (requires to_project_root).",
4358
- inputSchema: mcpToolInputSchemas.share.shape
4359
- },
4360
- async (input) => viaDatabase(
4361
- "share",
4362
- (db) => db.memory.share({
4363
- from: input.from ?? namespace,
4364
- to: input.to_namespace,
4365
- factIds: input.factIds,
4366
- ...input.mode ? { mode: input.mode } : {},
4367
- ...input.to_project_root ? { targetProjectRoot: input.to_project_root } : {},
4368
- ...input.agentId ? { agentId: input.agentId } : {}
4369
- })
4370
- )
4371
- );
4372
- register(
4373
- "unshare",
4374
- {
4375
- title: "Unshare",
4376
- description: "Revoke live reference shares of configured-namespace facts to another namespace.",
4377
- inputSchema: mcpToolInputSchemas.unshare.shape
4378
- },
4379
- async (input) => viaDatabase(
4380
- "unshare",
4381
- (db) => db.memory.unshare({
4382
- to: input.to_namespace,
4383
- factIds: input.factIds,
4384
- ...input.agentId ? { agentId: input.agentId } : {}
4385
- })
4386
- )
4387
- );
4388
- register(
4389
- "plan_current",
4390
- {
4391
- title: "Current plan",
4392
- description: "Return the active (approved or executing) plan for this namespace, or null.",
4393
- inputSchema: mcpToolInputSchemas.plan_current.shape
4394
- },
4395
- async () => {
4396
- const [current, policy] = await Promise.all([
4397
- planLifecycle.current(namespace),
4398
- loadPlanPolicy(projectRoot)
4399
- ]);
4400
- return json({ ...serializePlan(current), policy });
4401
- }
4402
- );
4403
- register(
4404
- "plan_create",
4405
- {
4406
- title: "Create plan",
4407
- description: "Create a draft plan in this namespace.",
4408
- inputSchema: mcpToolInputSchemas.plan_create.shape
4409
- },
4410
- async (input) => {
4411
- const files = toFiles(input.filesToTouch);
4412
- const tests = toTests(input.testsToAdd);
4413
- const created = await planLifecycle.create({
4414
- namespace,
4415
- title: input.title,
4416
- brief: input.brief,
4417
- ...input.body !== void 0 ? { body: input.body } : {},
4418
- ...files ? { filesToTouch: files } : {},
4419
- ...tests ? { testsToAdd: tests } : {},
4420
- ...input.outOfScope !== void 0 ? { outOfScope: input.outOfScope } : {},
4421
- ...input.risks !== void 0 ? { risks: input.risks } : {},
4422
- ...input.agentId !== void 0 ? { createdByAgent: input.agentId } : {}
4423
- });
4424
- return json(serializePlan(created));
4425
- }
4426
- );
4427
- register(
4428
- "plan_amend",
4429
- {
4430
- title: "Amend plan",
4431
- description: "Amend a draft or executing plan's content fields.",
4432
- inputSchema: mcpToolInputSchemas.plan_amend.shape
4433
- },
4434
- async (input) => {
4435
- const patch = input.patch;
4436
- const files = toFiles(patch.filesToTouch);
4437
- const tests = toTests(patch.testsToAdd);
4438
- const amended = await planLifecycle.amend(input.id, {
4439
- ...patch.title !== void 0 ? { title: patch.title } : {},
4440
- ...patch.brief !== void 0 ? { brief: patch.brief } : {},
4441
- ...patch.body !== void 0 ? { body: patch.body } : {},
4442
- ...files ? { filesToTouch: files } : {},
4443
- ...tests ? { testsToAdd: tests } : {},
4444
- ...patch.outOfScope !== void 0 ? { outOfScope: patch.outOfScope } : {},
4445
- ...patch.risks !== void 0 ? { risks: patch.risks } : {}
4446
- });
4447
- return json(serializePlan(amended));
4448
- }
4449
- );
4450
- register(
4451
- "plan_status",
4452
- {
4453
- title: "Transition plan",
4454
- description: "Move a plan through its lifecycle: approved, executing, complete, abandoned.",
4455
- inputSchema: mcpToolInputSchemas.plan_status.shape
4456
- },
4457
- async (input) => {
4458
- let next;
4459
- if (input.status === "approved") next = await planLifecycle.approve(input.id);
4460
- else if (input.status === "executing") next = await planLifecycle.start(input.id);
4461
- else if (input.status === "complete")
4462
- next = await planLifecycle.complete(input.id, {
4463
- ...input.commitSha !== void 0 ? { commitSha: input.commitSha } : {},
4464
- ...input.notes !== void 0 ? { notes: input.notes } : {}
4465
- });
4466
- else
4467
- next = await planLifecycle.abandon(
4468
- input.id,
4469
- input.reason !== void 0 ? { reason: input.reason } : {}
4470
- );
4471
- return json(serializePlan(next));
4472
- }
4473
- );
4474
- register(
4475
- "risk_assess",
4476
- {
4477
- title: "Assess risk",
4478
- description: "Assess evidence-backed risk for a plan, explicit paths, or git changes.",
4479
- inputSchema: mcpToolInputSchemas.risk_assess.shape
4480
- },
4481
- async (input) => json(
4482
- await assessRisk({
4483
- repositoryRoot: projectRoot,
4484
- namespace,
4485
- ...input.planId !== void 0 ? { planId: input.planId } : {},
4486
- ...input.paths !== void 0 ? { paths: input.paths } : {},
4487
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
4488
- ...input.limit !== void 0 ? { limit: input.limit } : {}
4489
- })
4490
- )
4491
- );
4492
- register(
4493
- "map_query",
4494
- {
4495
- title: "Query architecture map",
4496
- description: "Query lightweight module import edges in the current repository.",
4497
- inputSchema: mcpToolInputSchemas.map_query.shape
4498
- },
4499
- async (input) => {
4500
- const graph = await cachedMap();
4501
- return json(
4502
- queryMap(graph, {
4503
- ...input.from !== void 0 ? { from: input.from } : {},
4504
- ...input.to !== void 0 ? { to: input.to } : {},
4505
- ...input.limit !== void 0 ? { limit: input.limit } : {}
4506
- })
4507
- );
4508
- }
4509
- );
4510
- register(
4511
- "map_neighbours",
4512
- {
4513
- title: "Module neighbours",
4514
- description: "Return inbound/outbound neighbours for a repository module.",
4515
- inputSchema: mcpToolInputSchemas.map_neighbours.shape
4516
- },
4517
- async (input) => json(neighbours(await cachedMap(), input.module, input.direction))
4518
- );
4519
- const tokenBudget = config.tokenBudget;
4520
- const cachedMap = async () => {
4521
- const { manifest } = await loadFreshIndex(projectRoot);
4522
- const cached = await loadArchitectureMap(projectRoot, manifest);
4523
- if (cached.map.modules.length > 0) return cached.map;
4524
- return buildArchitectureMap(projectRoot);
4525
- };
4526
- register(
4527
- "context_bootstrap",
4528
- {
4529
- title: "Bootstrap project context",
4530
- description: "Return compact project awareness for the start of a turn: identity, active plan and policy, top policy rules, incident hotspots, and an architecture summary. Prefer this over a manual recall at session start. 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.",
4531
- inputSchema: mcpToolInputSchemas.context_bootstrap.shape
4532
- },
4533
- async (input) => json(
4534
- await applySessionDelivery(
4535
- projectRoot,
4536
- await buildBootstrapContext({
4537
- repositoryRoot: projectRoot,
4538
- namespace,
4539
- ...tokenBudget !== void 0 ? { tokenBudget } : {}
4540
- }),
4541
- deliveryOptions(input)
4542
- )
4543
- )
4544
- );
4545
- register(
4546
- "context_before_edit",
4547
- {
4548
- title: "Context before edit",
4549
- description: "Assemble relevant context before changing code: resolves target files from paths, a plan, or git, then returns the relevant plan, matching policy rules, incident memory, an evidence-backed risk assessment, and import neighbours. 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.",
4550
- inputSchema: mcpToolInputSchemas.context_before_edit.shape
4551
- },
4552
- async (input) => json(
4553
- await applySessionDelivery(
4554
- projectRoot,
4555
- await buildBeforeEditContext({
4556
- repositoryRoot: projectRoot,
4557
- namespace,
4558
- semanticRanker: rankContextFacts,
4559
- ...await rerankerOption(),
4560
- ...input.evidence ? { evidence: input.evidence } : {},
4561
- ...input.task !== void 0 ? { task: input.task } : {},
4562
- ...input.paths !== void 0 ? { paths: input.paths } : {},
4563
- ...input.planId !== void 0 ? { planId: input.planId } : {},
4564
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
4565
- ...input.tokenBudget !== void 0 ? { tokenBudget: input.tokenBudget } : {},
4566
- ...tokenBudget !== void 0 && input.tokenBudget === void 0 ? { tokenBudget } : {}
4567
- }),
4568
- deliveryOptions(input)
4569
- )
4570
- )
4571
- );
4572
- register(
4573
- "context_pack",
4574
- {
4575
- title: "Build task context pack",
4576
- 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.",
4577
- inputSchema: mcpToolInputSchemas.context_pack.shape
4578
- },
4579
- async (input) => json(
4580
- await applySessionDelivery(
4581
- projectRoot,
4582
- await buildBeforeEditContext({
4583
- repositoryRoot: projectRoot,
4584
- namespace,
4585
- task: input.task,
4586
- semanticRanker: rankContextFacts,
4587
- ...await rerankerOption(),
4588
- ...input.evidence ? { evidence: input.evidence } : {},
4589
- ...input.paths !== void 0 ? { paths: input.paths } : {},
4590
- ...input.planId !== void 0 ? { planId: input.planId } : {},
4591
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
4592
- tokenBudget: input.tokenBudget ?? tokenBudget ?? 4e3
4593
- }),
4594
- deliveryOptions(input)
4595
- )
4596
- )
4597
- );
4598
- register(
4599
- "context_after_turn",
4600
- {
4601
- title: "Capture context after a turn",
4602
- description: "Extract durable knowledge from a turn summary. Confident facts/decisions/incidents are auto-saved as Markdown; temporary notes, low-confidence, and sensitive items are queued for review under .codebuddy/memory/review/; chatter is ignored. Never saves sensitive content automatically. Turns that changed files or report verification also record a bounded worklog handoff under .codebuddy/worklog/ so later chats can recover the work.",
4603
- inputSchema: mcpToolInputSchemas.context_after_turn.shape
4604
- },
4605
- async (input) => {
4606
- const result = await captureAfterTurn(
4607
- {
4608
- summary: input.summary,
4609
- namespace,
4610
- ...input.changedFiles !== void 0 ? { changedFiles: input.changedFiles } : {},
4611
- ...input.planId !== void 0 ? { planId: input.planId } : {},
4612
- ...input.taskType !== void 0 ? { taskType: input.taskType } : {},
4613
- ...input.agentId !== void 0 ? { agentId: input.agentId } : {},
4614
- ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
4615
- ...input.verification !== void 0 ? { verification: input.verification } : {},
4616
- ...input.commitSha !== void 0 ? { commitSha: input.commitSha } : {}
4617
- },
4618
- {
4619
- repositoryRoot: projectRoot,
4620
- ...(input.changedFiles?.length ?? 0) > 0 ? await llmMemoryOptions(projectRoot) : {}
4621
- }
4622
- );
4623
- const db = await database.get().catch(() => void 0);
4624
- await db?.memory.reindexMemory();
4625
- return json({
4626
- saved: result.saved.map((d) => ({
4627
- id: d.ref,
4628
- class: d.candidate.class,
4629
- subject: d.candidate.subject
4630
- })),
4631
- queuedForReview: result.queuedForReview.map((d) => ({
4632
- id: d.ref,
4633
- class: d.candidate.class,
4634
- reason: d.reason,
4635
- sensitive: d.sensitive
4636
- })),
4637
- ignored: result.ignored.length,
4638
- reasons: result.reasons,
4639
- extractor: result.extractor,
4640
- worklogId: result.worklogId,
4641
- ...db ? {} : { degraded: true, database: database.status() }
4642
- });
4643
- }
4644
- );
4645
- register(
4646
- "code_suggestions",
4647
- {
4648
- title: "Code suggestions",
4649
- description: "Return read-only, evidence-backed code-quality suggestions for a change set (paths, a plan, or git). Composes risk, plan divergence, architecture blast radius, incident history, missing tests, and stale policies. Never edits code.",
4650
- inputSchema: mcpToolInputSchemas.code_suggestions.shape
4651
- },
4652
- async (input) => json(
4653
- await generateSuggestions({
4654
- repositoryRoot: projectRoot,
4655
- namespace,
4656
- ...input.paths !== void 0 ? { paths: input.paths } : {},
4657
- ...input.planId !== void 0 ? { planId: input.planId } : {},
4658
- ...input.useGit !== void 0 ? { useGit: input.useGit } : {},
4659
- ...input.limit !== void 0 ? { limit: input.limit } : {}
4660
- })
4661
- )
4662
- );
4663
- register(
4664
- "health",
4665
- {
4666
- title: "Health",
4667
- description: "Report which CodeBuddy tools are available right now: file-based tools always work; database tools (recall, remember, facts) need PostgreSQL. Does not wait on a connection attempt.",
4668
- inputSchema: mcpToolInputSchemas.health.shape
4669
- },
4670
- async () => json({
4671
- database: database.status(),
4672
- tools: {
4673
- profile,
4674
- file: FILE_TOOLS.filter(exposed),
4675
- database: DATABASE_TOOLS.filter(exposed)
4676
- }
4677
- })
4678
- );
4679
- }
4680
- function deliveryOptions(input) {
4681
- return cleanOptional(input);
4682
- }
4683
- function toRememberInput(input) {
4684
- return cleanOptional(input);
4685
- }
4686
- function toRecallInput(input) {
4687
- return cleanOptional(input);
4688
- }
4689
- function cleanOptional(input) {
4690
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0));
4691
- }
4692
-
4693
- // src/mcp/server.ts
4694
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4695
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4696
- import pino from "pino";
4697
-
4698
- // src/automation/project-sync.ts
4699
- import { watch as fsWatch2 } from "fs";
4700
- import { join as join9, relative as relative6, resolve as resolve12, sep as sep6 } from "path";
4701
-
4702
- // src/indexer/watch.ts
4703
- import { watch as fsWatch } from "fs";
4704
- import { resolve as resolve11 } from "path";
4705
- var DEFAULT_DEBOUNCE_MS = 300;
4706
- function createCoalescer(fn, delayMs) {
4707
- let timer = null;
4708
- let running = null;
4709
- let dirty = false;
4710
- let stopped = false;
4711
- let firstTriggerAt = null;
4712
- let maxWaitTimer = null;
4713
- const clearTimers = () => {
4714
- if (timer) {
4715
- clearTimeout(timer);
4716
- timer = null;
4717
- }
4718
- if (maxWaitTimer) {
4719
- clearTimeout(maxWaitTimer);
4720
- maxWaitTimer = null;
4721
- }
4722
- firstTriggerAt = null;
4723
- };
4724
- const run = () => {
4725
- if (stopped || running) return;
4726
- clearTimers();
4727
- dirty = false;
4728
- running = (async () => {
4729
- try {
4730
- await fn();
4731
- } finally {
4732
- running = null;
4733
- if (dirty) schedule();
4734
- }
4735
- })();
4736
- };
4737
- const schedule = () => {
4738
- if (stopped) return;
4739
- dirty = true;
4740
- if (firstTriggerAt === null) {
4741
- firstTriggerAt = Date.now();
4742
- maxWaitTimer = setTimeout(run, MAX_WAIT_MS);
4743
- }
4744
- if (timer) clearTimeout(timer);
4745
- timer = setTimeout(run, delayMs);
4746
- };
4747
- return {
4748
- trigger: schedule,
4749
- stop: async () => {
4750
- stopped = true;
4751
- clearTimers();
4752
- await running?.catch(() => void 0);
4753
- }
4754
- };
4755
- }
4756
- var MAX_WAIT_MS = 5e3;
4757
- function isIgnoredEvent(filename, ignoredPaths = []) {
4758
- if (!filename) return false;
4759
- const normalized = filename.replace(/\\/g, "/");
4760
- return ignoredPaths.some((path) => normalized === path || normalized.startsWith(`${path}/`)) || normalized.startsWith(".codebuddy/") || normalized.includes("/.git/") || normalized.startsWith(".git/") || normalized.includes("/node_modules/") || normalized.startsWith("node_modules/") || normalized.endsWith(".tmp");
4761
- }
4762
- async function watchRepository(options = {}) {
4763
- const repositoryRoot = resolve11(options.repositoryRoot ?? process.cwd());
4764
- const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
4765
- const runIndex = async () => {
4766
- try {
4767
- const result = await indexRepository({
4768
- repositoryRoot,
4769
- ...options.now ? { now: options.now } : {}
4770
- });
4771
- options.onIndex?.(result);
4772
- } catch (error) {
4773
- options.onError?.(error);
4774
- }
4775
- };
4776
- await runIndex();
4777
- const coalescer = createCoalescer(runIndex, debounceMs);
4778
- const watcher = fsWatch(repositoryRoot, { recursive: true }, (_event, filename) => {
4779
- if (isIgnoredEvent(filename, options.ignoredPaths)) return;
4780
- coalescer.trigger();
4781
- });
4782
- return new Promise((resolvePromise) => {
4783
- const stop = () => {
4784
- watcher.close();
4785
- void coalescer.stop().then(() => resolvePromise());
4786
- };
4787
- if (options.signal) {
4788
- if (options.signal.aborted) return stop();
4789
- options.signal.addEventListener("abort", stop, { once: true });
4790
- }
4791
- watcher.on("error", () => stop());
4792
- });
4793
- }
4794
-
4795
- // src/automation/project-sync.ts
4796
- function startProjectSync(options) {
4797
- const repositoryRoot = resolve12(options.repositoryRoot);
4798
- const logger = options.logger ?? console;
4799
- const controller = new AbortController();
4800
- let watcher = null;
4801
- let closing = false;
4802
- let memoryWatcher = null;
4803
- let memoryRefresh = null;
4804
- const backgroundRefresh = (task) => {
4805
- let current = null;
4806
- let pending = false;
4807
- const schedule = () => {
4808
- if (closing) return;
4809
- if (current) {
4810
- pending = true;
4811
- return;
4812
- }
4813
- current = (async () => {
4814
- do {
4815
- pending = false;
4816
- await task();
4817
- } while (pending && !closing);
4818
- })().finally(() => {
4819
- current = null;
4820
- if (pending && !closing) schedule();
4821
- });
4822
- };
4823
- return { schedule, settled: async () => current?.catch(() => void 0) };
4824
- };
4825
- const graphRefresh = backgroundRefresh(
4826
- () => refreshGraph().catch(
4827
- (error) => logger.error(`[sync] Graphify refresh failed: ${error.message}`)
4828
- )
4829
- );
4830
- const zvecRefresh = backgroundRefresh(async () => {
4831
- const result = await refreshZvecIndex(repositoryRoot, { signal: controller.signal }).catch(
4832
- (error) => ({
4833
- status: "failed",
4834
- reason: error.message
4835
- })
4836
- );
4837
- if (result.status !== "disabled") logger.info(`[sync] ${result.reason}`);
4838
- });
4839
- const ready = (async () => {
4840
- await indexRepository({ repositoryRoot });
4841
- zvecRefresh.schedule();
4842
- watchMemory();
4843
- if (options.graphify?.enabled) {
4844
- await refreshGraph().catch(
4845
- (error) => logger.error(`[sync] Graphify startup refresh failed: ${error.message}`)
4846
- );
4847
- }
4848
- watcher = watchRepository({
4849
- repositoryRoot,
4850
- signal: controller.signal,
4851
- ...options.graphify?.enabled ? {
4852
- ignoredPaths: [
4853
- options.graphify.graphPath ? relative6(repositoryRoot, resolve12(repositoryRoot, options.graphify.graphPath)).split(sep6).join("/") : "graphify-out"
4854
- ]
4855
- } : {},
4856
- onIndex: (result) => {
4857
- if (result.added + result.changed + result.removed === 0) return;
4858
- zvecRefresh.schedule();
4859
- if (options.graphify?.enabled) graphRefresh.schedule();
4860
- },
4861
- onError: (error) => logger.error(`[sync] repository watch failed: ${error.message}`)
4862
- });
4863
- void watcher.catch(
4864
- (error) => logger.error(`[sync] live repository watch unavailable: ${error.message}`)
4865
- );
4866
- })().catch((error) => {
4867
- logger.error(`[sync] startup synchronization failed: ${error.message}`);
4868
- throw error;
4869
- });
4870
- function watchMemory() {
4871
- const onMemoryChange = options.onMemoryChange;
4872
- if (closing || !onMemoryChange) return;
4873
- try {
4874
- memoryRefresh = createCoalescer(
4875
- () => onMemoryChange().catch(
4876
- (error) => logger.error(`[sync] memory reindex failed: ${error.message}`)
4877
- ),
4878
- 300
4879
- );
4880
- memoryWatcher = fsWatch2(
4881
- join9(repositoryRoot, ".codebuddy", "memory"),
4882
- { recursive: true },
4883
- (_event, filename) => {
4884
- const name = filename?.replace(/\\/g, "/") ?? "";
4885
- if (/^(facts\/fact|summaries\/sum)_[a-z0-9]+\.md$/.test(name)) memoryRefresh?.trigger();
4886
- }
4887
- );
4888
- memoryWatcher.on("error", () => memoryWatcher?.close());
4889
- } catch (error) {
4890
- logger.error(`[sync] live memory watch unavailable: ${error.message}`);
4891
- }
4892
- }
4893
- async function refreshGraph() {
4894
- const result = await generateGraphifyGraph({
4895
- repositoryRoot,
4896
- ...options.graphify?.graphPath ? { graphPath: options.graphify.graphPath } : {}
4897
- });
4898
- logger.info(`[sync] Graphify updated (${result.nodes} nodes, ${result.edges} edges)`);
4899
- }
4900
- return {
4901
- ready,
4902
- async close() {
4903
- closing = true;
4904
- controller.abort();
4905
- memoryRefresh?.stop();
4906
- memoryWatcher?.close();
4907
- await watcher?.catch(() => void 0);
4908
- await Promise.all([graphRefresh.settled(), zvecRefresh.settled()]);
4909
- }
4910
- };
4911
- }
4912
-
4913
- // src/core/project-root.ts
4914
- import { realpath as realpath2, stat as stat5 } from "fs/promises";
4915
- import { homedir } from "os";
4916
- import { isAbsolute as isAbsolute2, parse, resolve as resolve13 } from "path";
4917
- async function resolveInitializedProjectRoot(configuredRoot, cwd = process.cwd()) {
4918
- if (configuredRoot !== void 0 && !isAbsolute2(configuredRoot)) {
4919
- throw new Error("CODEBUDDY_PROJECT_ROOT must be an absolute path to an initialized project.");
4920
- }
4921
- const selected = resolve13(configuredRoot ?? cwd);
4922
- const root = await realpath2(selected);
4923
- if (root === parse(root).root || root === await realpath2(homedir())) {
4924
- throw new Error(
4925
- "Select an initialized project folder, not the filesystem root or home folder."
4926
- );
4927
- }
4928
- const initialized = await stat5(configPath(root)).then(
4929
- (info) => info.isFile(),
4930
- () => false
4931
- );
4932
- if (!initialized) {
4933
- throw new Error(
4934
- `CodeBuddy will not scan ${root}: run codebuddy init in your project folder first. For a desktop client, re-register CodeBuddy from that folder to save its project path.`
4935
- );
4936
- }
4937
- return root;
4938
- }
4939
-
4940
- // src/mcp/database.ts
4941
- function createLazyDatabase(connect, options = {}) {
4942
- const retryMs = options.retryMs ?? 5e3;
4943
- let runtime;
4944
- let pending;
4945
- let failure;
4946
- let closed = false;
4947
- return {
4948
- get() {
4949
- if (runtime) return Promise.resolve(runtime);
4950
- if (closed) return Promise.reject(new Error("CodeBuddy MCP server is shutting down."));
4951
- if (pending) return pending;
4952
- if (failure && Date.now() - failure.at < retryMs) {
4953
- return Promise.reject(new Error(failure.error));
4954
- }
4955
- pending = connect().then(
4956
- async (handle) => {
4957
- if (closed) {
4958
- await handle.close();
4959
- throw new Error("CodeBuddy MCP server is shutting down.");
4960
- }
4961
- runtime = handle;
4962
- failure = void 0;
4963
- return handle;
4964
- },
4965
- (error) => {
4966
- failure = { error: describeError(error), at: Date.now() };
4967
- throw error;
4968
- }
4969
- ).finally(() => {
4970
- pending = void 0;
4971
- });
4972
- return pending;
4973
- },
4974
- status() {
4975
- if (runtime) return { available: true, state: "ready" };
4976
- if (pending) return { available: false, state: "connecting" };
4977
- if (!failure) return { available: false, state: "idle" };
4978
- return {
4979
- available: false,
4980
- state: "unavailable",
4981
- error: failure.error,
4982
- retryInMs: Math.max(0, failure.at + retryMs - Date.now())
4983
- };
4984
- },
4985
- async close() {
4986
- closed = true;
4987
- await pending?.catch(() => void 0);
4988
- const current = runtime;
4989
- runtime = void 0;
4990
- await current?.close();
4991
- }
4992
- };
4993
- }
4994
- function describeError(error) {
4995
- const err = error;
4996
- const text = err?.message || err?.code || String(error);
4997
- return String(redactSecrets(text)).slice(0, 300);
4998
- }
4999
-
5000
- // src/mcp/server.ts
5001
- async function startMcpServer(options = {}) {
5002
- const logger = pino({ level: process.env.LOG_LEVEL ?? "info" }, process.stderr);
5003
- const projectRoot = await resolveInitializedProjectRoot(
5004
- options.config?.projectRoot ?? process.env.CODEBUDDY_PROJECT_ROOT
5005
- );
5006
- const config = {
5007
- ...options.config ?? await loadRuntimeConfig(projectRoot, { requireDatabase: false }),
5008
- projectRoot
5009
- };
5010
- (config.postgresUrl ? codeBuddyConfigSchema : codeBuddyConfigSchema.omit({ postgresUrl: true })).parse(config);
5011
- const toolProfile = resolveToolProfile();
5012
- const importMemory = async (memory) => {
5013
- try {
5014
- const result = await memory.reindexMemory();
5015
- for (const failure of result.failures) {
5016
- logger.error(`[sync] memory file skipped (${failure.id}): ${failure.error}`);
5017
- }
5018
- } catch (error) {
5019
- logger.error(`[sync] Markdown memory import failed: ${error.message}`);
5020
- }
5021
- };
5022
- const database = createLazyDatabase(async () => {
5023
- try {
5024
- const runtime = await createRuntime(config, { autoBootstrap: true });
5025
- await importMemory(runtime.memory);
5026
- logger.info("PostgreSQL connected; memory tools available");
5027
- return runtime;
5028
- } catch (error) {
5029
- logger.warn(
5030
- `PostgreSQL unavailable; serving file-based tools only: ${error.message}`
5031
- );
5032
- throw error;
5033
- }
5034
- });
5035
- void database.get().catch(() => void 0);
5036
- const sync = startProjectSync({
5037
- repositoryRoot: projectRoot,
5038
- ...config.graphify ? { graphify: config.graphify } : {},
5039
- logger,
5040
- // Hand edits re-import live once PostgreSQL is connected; until then the
5041
- // next successful connect imports everything.
5042
- onMemoryChange: async () => {
5043
- if (database.status().state === "ready") await importMemory((await database.get()).memory);
5044
- }
5045
- });
5046
- const server = new McpServer({ name: "codebuddy", version: VERSION });
5047
- registerCodeBuddyTools(server, { config, database }, toolProfile);
5048
- const shutdown = async () => {
5049
- logger.info("codebuddy MCP server shutting down");
5050
- await server.close();
5051
- await sync.close();
5052
- await database.close();
5053
- };
5054
- let stopping;
5055
- const stop = () => {
5056
- stopping ??= shutdown().finally(() => process.exit(0));
5057
- };
5058
- process.once("SIGINT", stop);
5059
- process.once("SIGTERM", stop);
5060
- process.stdin.once("end", stop);
5061
- try {
5062
- await sync.ready;
5063
- await server.connect(new StdioServerTransport());
5064
- } catch (error) {
5065
- await sync.close();
5066
- await database.close();
5067
- throw error;
5068
- }
5069
- logger.info(`codebuddy MCP server started over stdio (tool profile: ${toolProfile})`);
5070
- }
5071
-
5072
3552
  export {
5073
- migrateMemoryToFiles,
5074
3553
  PLAN_STATUSES,
5075
3554
  ACTIVE_PLAN_STATUSES,
5076
3555
  PlanLockError,
@@ -5080,8 +3559,7 @@ export {
5080
3559
  PlanNotFoundError,
5081
3560
  ActivePlanExistsError,
5082
3561
  PlanLifecycle,
5083
- watchRepository,
5084
- VERSION,
3562
+ enforceContextBudget,
5085
3563
  RiskAssessor,
5086
3564
  createChurnSignal,
5087
3565
  createIncidentSignal,
@@ -5093,20 +3571,27 @@ export {
5093
3571
  buildBootstrapContext,
5094
3572
  buildBeforeEditContext,
5095
3573
  ReviewStore,
3574
+ captureAfterTurn,
5096
3575
  approveReviewItem,
5097
3576
  tracePath,
3577
+ recordTrace,
5098
3578
  readTraces,
5099
3579
  parseTraceLines,
3580
+ configuredReranker,
5100
3581
  openHookSemantics,
3582
+ CONTEXT_HOOK_BUDGET_MS,
5101
3583
  runContextHook,
5102
3584
  runStageHook,
3585
+ llmMemoryOptions,
5103
3586
  runCaptureHook,
5104
3587
  runSyncHook,
5105
3588
  runResetHook,
5106
- renderContextBlock,
5107
- generateSuggestions,
5108
- mcpToolInputSchemas,
5109
- registerCodeBuddyTools,
5110
- startMcpServer
3589
+ extractEditedPaths,
3590
+ deriveSummary,
3591
+ turnVerification,
3592
+ verificationLines,
3593
+ turnUserPrompts,
3594
+ lastAssistantMessage,
3595
+ renderContextBlock
5111
3596
  };
5112
- //# sourceMappingURL=chunk-VAKBJV26.js.map
3597
+ //# sourceMappingURL=chunk-R7CD6EXS.js.map