@orquentra/codebuddy 2.6.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/{chunk-IBLJOHJL.js → chunk-3QGW53GD.js} +2 -2
  2. package/dist/{chunk-NERKZSSE.js → chunk-5EDGAZQN.js} +438 -68
  3. package/dist/chunk-5EDGAZQN.js.map +1 -0
  4. package/dist/{chunk-JFDRBPYF.js → chunk-6JCKMIQY.js} +2 -2
  5. package/dist/{chunk-2XDPKFUW.js → chunk-AK2UAE32.js} +46 -20
  6. package/dist/chunk-AK2UAE32.js.map +1 -0
  7. package/dist/{chunk-GDJH7XJC.js → chunk-AL6VBTNA.js} +77 -3
  8. package/dist/chunk-AL6VBTNA.js.map +1 -0
  9. package/dist/{chunk-QBDJXGG5.js → chunk-AQHPEJR6.js} +93 -22
  10. package/dist/chunk-AQHPEJR6.js.map +1 -0
  11. package/dist/{chunk-OWWCDRXW.js → chunk-BQM7FJ7D.js} +6 -6
  12. package/dist/chunk-BQM7FJ7D.js.map +1 -0
  13. package/dist/{chunk-XBJQKCLU.js → chunk-CAEPHLHC.js} +13 -9
  14. package/dist/chunk-CAEPHLHC.js.map +1 -0
  15. package/dist/chunk-CG5B3LYG.js +66 -0
  16. package/dist/chunk-CG5B3LYG.js.map +1 -0
  17. package/dist/{chunk-IHQM2ZDV.js → chunk-DFV2SK6C.js} +1 -1
  18. package/dist/{chunk-IHQM2ZDV.js.map → chunk-DFV2SK6C.js.map} +1 -1
  19. package/dist/{chunk-IMFUYEZA.js → chunk-EEUFU7KV.js} +81 -54
  20. package/dist/chunk-EEUFU7KV.js.map +1 -0
  21. package/dist/{chunk-5MQOE4J6.js → chunk-ESZXCZIN.js} +3 -3
  22. package/dist/{chunk-J2EAZ4MA.js → chunk-GKNYMNFM.js} +22 -25
  23. package/dist/chunk-GKNYMNFM.js.map +1 -0
  24. package/dist/{chunk-JPHJBCZB.js → chunk-HPOR6YXA.js} +3 -2
  25. package/dist/chunk-HPOR6YXA.js.map +1 -0
  26. package/dist/{chunk-S6VEG4S2.js → chunk-JUAFCLRB.js} +28 -5
  27. package/dist/chunk-JUAFCLRB.js.map +1 -0
  28. package/dist/{chunk-5KZY6OTG.js → chunk-KNR2P2UY.js} +33 -83
  29. package/dist/chunk-KNR2P2UY.js.map +1 -0
  30. package/dist/{chunk-B6CO63SQ.js → chunk-LCSBL5TT.js} +21 -9
  31. package/dist/chunk-LCSBL5TT.js.map +1 -0
  32. package/dist/{chunk-5FW3PMNP.js → chunk-MSUAVR3R.js} +76 -11
  33. package/dist/chunk-MSUAVR3R.js.map +1 -0
  34. package/dist/{chunk-RAJJSCIP.js → chunk-SFMIMLXR.js} +2 -2
  35. package/dist/chunk-TGFTKWOR.js +16 -0
  36. package/dist/chunk-TGFTKWOR.js.map +1 -0
  37. package/dist/chunk-TUR6YKEE.js +55 -0
  38. package/dist/chunk-TUR6YKEE.js.map +1 -0
  39. package/dist/chunk-Y7D7PUBE.js +321 -0
  40. package/dist/chunk-Y7D7PUBE.js.map +1 -0
  41. package/dist/{chunk-R7CD6EXS.js → chunk-YJIA2MFL.js} +572 -377
  42. package/dist/chunk-YJIA2MFL.js.map +1 -0
  43. package/dist/cli/index.js +5 -5
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/{cloud-7WM3YLI4.js → cloud-LXZUR3U2.js} +3 -3
  46. package/dist/{codebuddy-B_pCaQrV.d.ts → codebuddy-DO7ZhoWn.d.ts} +15 -1
  47. package/dist/config-GEZFLXK6.js +12 -0
  48. package/dist/index.d.ts +46 -3
  49. package/dist/index.js +22 -19
  50. package/dist/langgraph/index.d.ts +1 -1
  51. package/dist/lean-command-CWJAVQ5G.js +18 -0
  52. package/dist/{local-5MZURLCZ.js → local-ST4WMRCG.js} +3 -3
  53. package/dist/{main-MTBQZGJV.js → main-AHNGHC4L.js} +213 -183
  54. package/dist/main-AHNGHC4L.js.map +1 -0
  55. package/dist/{memory-KIC5FJVW.js → memory-TGHVTBME.js} +9 -8
  56. package/dist/memory-TGHVTBME.js.map +1 -0
  57. package/dist/operations-7BTAAUHF.js +26 -0
  58. package/dist/pretool-command-3SXQSMAO.js +13 -0
  59. package/dist/{rerank-5QZ3PNUW.js → rerank-IQAEWLQR.js} +6 -5
  60. package/dist/rerank-IQAEWLQR.js.map +1 -0
  61. package/dist/{runtime-CUBOOPZO.js → runtime-E7JQZ2BZ.js} +14 -11
  62. package/dist/summaries-XV3JGGCC.js +20 -0
  63. package/dist/{zvec-MSRKT7IV.js → zvec-KP4KNB7Y.js} +10 -3
  64. package/package.json +1 -1
  65. package/src/db/migrations/0006_fact_idempotency_key.sql +2 -0
  66. package/src/db/migrations/meta/0006_snapshot.json +1258 -0
  67. package/src/db/migrations/meta/_journal.json +7 -0
  68. package/dist/chunk-2XDPKFUW.js.map +0 -1
  69. package/dist/chunk-5FW3PMNP.js.map +0 -1
  70. package/dist/chunk-5KZY6OTG.js.map +0 -1
  71. package/dist/chunk-B6CO63SQ.js.map +0 -1
  72. package/dist/chunk-GDJH7XJC.js.map +0 -1
  73. package/dist/chunk-IMFUYEZA.js.map +0 -1
  74. package/dist/chunk-J2EAZ4MA.js.map +0 -1
  75. package/dist/chunk-JPHJBCZB.js.map +0 -1
  76. package/dist/chunk-NERKZSSE.js.map +0 -1
  77. package/dist/chunk-OWWCDRXW.js.map +0 -1
  78. package/dist/chunk-PQK3GKZH.js +0 -98
  79. package/dist/chunk-PQK3GKZH.js.map +0 -1
  80. package/dist/chunk-QBDJXGG5.js.map +0 -1
  81. package/dist/chunk-R7CD6EXS.js.map +0 -1
  82. package/dist/chunk-S6VEG4S2.js.map +0 -1
  83. package/dist/chunk-XBJQKCLU.js.map +0 -1
  84. package/dist/config-DEVHUDIU.js +0 -11
  85. package/dist/lean-command-HTQCPH76.js +0 -15
  86. package/dist/main-MTBQZGJV.js.map +0 -1
  87. package/dist/memory-KIC5FJVW.js.map +0 -1
  88. package/dist/operations-POCHY3WR.js +0 -25
  89. package/dist/pretool-command-AX6JXP25.js +0 -10
  90. package/dist/rerank-5QZ3PNUW.js.map +0 -1
  91. package/dist/summaries-SB5L2TSW.js +0 -18
  92. /package/dist/{chunk-IBLJOHJL.js.map → chunk-3QGW53GD.js.map} +0 -0
  93. /package/dist/{chunk-JFDRBPYF.js.map → chunk-6JCKMIQY.js.map} +0 -0
  94. /package/dist/{chunk-5MQOE4J6.js.map → chunk-ESZXCZIN.js.map} +0 -0
  95. /package/dist/{chunk-RAJJSCIP.js.map → chunk-SFMIMLXR.js.map} +0 -0
  96. /package/dist/{cloud-7WM3YLI4.js.map → cloud-LXZUR3U2.js.map} +0 -0
  97. /package/dist/{config-DEVHUDIU.js.map → config-GEZFLXK6.js.map} +0 -0
  98. /package/dist/{lean-command-HTQCPH76.js.map → lean-command-CWJAVQ5G.js.map} +0 -0
  99. /package/dist/{local-5MZURLCZ.js.map → local-ST4WMRCG.js.map} +0 -0
  100. /package/dist/{operations-POCHY3WR.js.map → operations-7BTAAUHF.js.map} +0 -0
  101. /package/dist/{pretool-command-AX6JXP25.js.map → pretool-command-3SXQSMAO.js.map} +0 -0
  102. /package/dist/{runtime-CUBOOPZO.js.map → runtime-E7JQZ2BZ.js.map} +0 -0
  103. /package/dist/{summaries-SB5L2TSW.js.map → summaries-XV3JGGCC.js.map} +0 -0
  104. /package/dist/{zvec-MSRKT7IV.js.map → zvec-KP4KNB7Y.js.map} +0 -0
@@ -1,67 +1,12 @@
1
- // src/core/markdown-store-fs.ts
2
- import { lstat, open, rename } from "fs/promises";
3
- import { join, relative, resolve, sep } from "path";
4
- async function assertNoSymlinkBetween(root, target) {
5
- const absoluteRoot = resolve(root);
6
- const absoluteTarget = resolve(target);
7
- const relativeTarget = relative(absoluteRoot, absoluteTarget);
8
- if (relativeTarget === ".." || relativeTarget.startsWith(`..${sep}`)) {
9
- throw new Error(`Path escapes repository root: ${target}`);
10
- }
11
- let current = absoluteRoot;
12
- const parts = relativeTarget.split(sep).filter(Boolean);
13
- for (const part of parts) {
14
- current = join(current, part);
15
- try {
16
- const info = await lstat(current);
17
- if (info.isSymbolicLink()) {
18
- throw new Error(`Refusing to use symlinked path: ${current}`);
19
- }
20
- } catch (error) {
21
- if (error.code === "ENOENT") return;
22
- throw error;
23
- }
24
- }
25
- }
26
- async function writeAtomic(path, document, options) {
27
- const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;
28
- const handle = await open(temporaryPath, "wx", options?.mode ?? 384);
29
- try {
30
- await handle.writeFile(document, "utf8");
31
- await handle.sync();
32
- } finally {
33
- await handle.close();
34
- }
35
- await rename(temporaryPath, path);
36
- }
37
- function parseFrontMatter(document) {
38
- if (!document.startsWith("---\n")) {
39
- throw new Error("Markdown file must start with YAML front matter.");
40
- }
41
- const closingIndex = document.indexOf("\n---\n", 4);
42
- if (closingIndex === -1) {
43
- throw new Error("Markdown file has unterminated YAML front matter.");
44
- }
45
- return {
46
- frontMatter: document.slice(4, closingIndex),
47
- content: document.slice(closingIndex + 5)
48
- };
49
- }
50
- function composeMarkdown(frontMatterYaml, body) {
51
- const trimmedFrontMatter = frontMatterYaml.trimEnd();
52
- const content = body.endsWith("\n") ? body : `${body}
53
- `;
54
- return `---
55
- ${trimmedFrontMatter}
56
- ---
57
- ${content}`;
58
- }
1
+ import {
2
+ writeAtomic
3
+ } from "./chunk-CG5B3LYG.js";
59
4
 
60
5
  // src/core/config-file.ts
61
6
  import { execFile } from "child_process";
62
7
  import { constants } from "fs";
63
8
  import { access, chmod, mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
64
- import { basename, join as join2, resolve as resolve2 } from "path";
9
+ import { basename, join, resolve } from "path";
65
10
  import { z } from "zod";
66
11
  var CONFIG_DIR = ".codebuddy";
67
12
  var CONFIG_FILE = "config.json";
@@ -99,7 +44,8 @@ var fileSchema = z.object({
99
44
  type: z.enum(["local", "huggingface", "openrouter"]).default("local"),
100
45
  apiKey: z.string().min(1).optional(),
101
46
  model: z.string().min(1).optional(),
102
- relevanceFloor: z.number().min(-1).max(1).optional()
47
+ relevanceFloor: z.number().min(-1).max(1).optional(),
48
+ recallFloor: z.number().min(-1).max(1).optional()
103
49
  }).optional(),
104
50
  /** Optional cross-encoder that orders recalled facts (off by default). */
105
51
  rerank: z.object({
@@ -113,6 +59,11 @@ var fileSchema = z.object({
113
59
  enabled: z.boolean(),
114
60
  graphPath: z.string().min(1).optional()
115
61
  }).optional(),
62
+ /** Git churn risk signal (`risk/signals/churn.ts`); defaults keep the 30-day window. */
63
+ risk: z.object({
64
+ churnWindowDays: z.number().int().positive().max(3650).optional(),
65
+ churnMode: z.enum(["window", "lifetime-size"]).optional()
66
+ }).optional(),
116
67
  /**
117
68
  * Optional hosted small-model layer (bring your own key). Keys are read from
118
69
  * the environment only, never from this file (`src/llm/config.ts`).
@@ -134,18 +85,20 @@ var fileSchema = z.object({
134
85
  zvec: z.object({
135
86
  enabled: z.boolean(),
136
87
  binary: z.string().min(1).optional(),
137
- timeoutMs: z.number().int().positive().max(1e4).optional()
88
+ timeoutMs: z.number().int().positive().max(1e4).optional(),
89
+ /** Task IDF coverage a vector-only file needs (0..1, default 0.3; docs/zvec-floor.md). */
90
+ minScore: z.number().min(0).max(1).optional()
138
91
  }).optional()
139
92
  });
140
93
  function configPath(cwd = process.cwd()) {
141
- return join2(cwd, CONFIG_DIR, CONFIG_FILE);
94
+ return join(cwd, CONFIG_DIR, CONFIG_FILE);
142
95
  }
143
96
  async function initProjectScaffold(cwd = process.cwd()) {
144
- const codebuddyDir = join2(cwd, CONFIG_DIR);
145
- await mkdir(join2(codebuddyDir, "memory", "facts"), { recursive: true, mode: 448 });
146
- await mkdir(join2(codebuddyDir, "memory", "summaries"), { recursive: true, mode: 448 });
147
- await mkdir(join2(codebuddyDir, "plans"), { recursive: true, mode: 448 });
148
- const gitignorePath = join2(codebuddyDir, ".gitignore");
97
+ const codebuddyDir = join(cwd, CONFIG_DIR);
98
+ await mkdir(join(codebuddyDir, "memory", "facts"), { recursive: true, mode: 448 });
99
+ await mkdir(join(codebuddyDir, "memory", "summaries"), { recursive: true, mode: 448 });
100
+ await mkdir(join(codebuddyDir, "plans"), { recursive: true, mode: 448 });
101
+ const gitignorePath = join(codebuddyDir, ".gitignore");
149
102
  try {
150
103
  await access(gitignorePath, constants.F_OK);
151
104
  let gitignore = await readFile(gitignorePath, "utf8");
@@ -162,7 +115,7 @@ ${missing.join("\n")}
162
115
  } catch {
163
116
  await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 420 });
164
117
  }
165
- const policyPath = join2(codebuddyDir, POLICY_FILE);
118
+ const policyPath = join(codebuddyDir, POLICY_FILE);
166
119
  try {
167
120
  await access(policyPath, constants.F_OK);
168
121
  } catch {
@@ -235,7 +188,7 @@ function withoutRepositorySuppliedSecrets(config, path) {
235
188
  var warnedTracked = /* @__PURE__ */ new Set();
236
189
  var trackedCache = /* @__PURE__ */ new Map();
237
190
  function trackedByGit(root) {
238
- const key = resolve2(root);
191
+ const key = resolve(root);
239
192
  let pending = trackedCache.get(key);
240
193
  if (!pending) {
241
194
  pending = new Promise((done) => {
@@ -281,7 +234,7 @@ async function migrateLegacyNamespace(cwd = process.cwd()) {
281
234
  return target;
282
235
  }
283
236
  async function hasMemoryInNamespace(cwd, namespace) {
284
- const memoryDir = join2(cwd, ".codebuddy", "memory");
237
+ const memoryDir = join(cwd, ".codebuddy", "memory");
285
238
  let entries;
286
239
  try {
287
240
  entries = await readdir(memoryDir, { recursive: true });
@@ -291,7 +244,7 @@ async function hasMemoryInNamespace(cwd, namespace) {
291
244
  const line = new RegExp(`^namespace: ['"]?${namespace}['"]?$`, "m");
292
245
  for (const entry of entries) {
293
246
  if (!entry.endsWith(".md")) continue;
294
- const text = await readFile(join2(memoryDir, entry), "utf8").catch(() => "");
247
+ const text = await readFile(join(memoryDir, entry), "utf8").catch(() => "");
295
248
  if (line.test(text)) return true;
296
249
  }
297
250
  return false;
@@ -313,7 +266,8 @@ async function loadRuntimeConfig(cwd = process.cwd(), options = {}) {
313
266
  type: file.provider?.type ?? "local",
314
267
  ...hfToken ? { apiKey: hfToken } : {},
315
268
  ...file.provider?.model ? { model: file.provider.model } : {},
316
- ...file.provider?.relevanceFloor !== void 0 ? { relevanceFloor: file.provider.relevanceFloor } : {}
269
+ ...file.provider?.relevanceFloor !== void 0 ? { relevanceFloor: file.provider.relevanceFloor } : {},
270
+ ...file.provider?.recallFloor !== void 0 ? { recallFloor: file.provider.recallFloor } : {}
317
271
  },
318
272
  ...file.tokenBudget ? { tokenBudget: file.tokenBudget } : {},
319
273
  ...file.graphify ? { graphify: file.graphify } : {}
@@ -341,12 +295,12 @@ async function checkConfigPermissions(cwd = process.cwd()) {
341
295
  }
342
296
  }
343
297
  async function checkProjectScaffold(cwd = process.cwd()) {
344
- const codebuddyDir = join2(cwd, CONFIG_DIR);
345
- const memoryFactsDir = join2(codebuddyDir, "memory", "facts");
346
- const memorySummariesDir = join2(codebuddyDir, "memory", "summaries");
347
- const plansDir = join2(codebuddyDir, "plans");
348
- const gitignorePath = join2(codebuddyDir, ".gitignore");
349
- const policyPath = join2(codebuddyDir, POLICY_FILE);
298
+ const codebuddyDir = join(cwd, CONFIG_DIR);
299
+ const memoryFactsDir = join(codebuddyDir, "memory", "facts");
300
+ const memorySummariesDir = join(codebuddyDir, "memory", "summaries");
301
+ const plansDir = join(codebuddyDir, "plans");
302
+ const gitignorePath = join(codebuddyDir, ".gitignore");
303
+ const policyPath = join(codebuddyDir, POLICY_FILE);
350
304
  const [
351
305
  codebuddyDirExists,
352
306
  memoryFactsDirExists,
@@ -485,10 +439,6 @@ async function pathExists(path) {
485
439
  }
486
440
 
487
441
  export {
488
- assertNoSymlinkBetween,
489
- writeAtomic,
490
- parseFrontMatter,
491
- composeMarkdown,
492
442
  PLAN_POLICIES,
493
443
  configPath,
494
444
  initProjectScaffold,
@@ -509,4 +459,4 @@ export {
509
459
  setConfigBlock,
510
460
  redactSecrets
511
461
  };
512
- //# sourceMappingURL=chunk-5KZY6OTG.js.map
462
+ //# sourceMappingURL=chunk-KNR2P2UY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/config-file.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { constants } from \"node:fs\";\nimport { access, chmod, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join, resolve } from \"node:path\";\nimport { z } from \"zod\";\nimport { writeAtomic } from \"./markdown-store-fs.js\";\nimport type { CodeBuddyConfig } from \"./types.js\";\n\nexport const CONFIG_DIR = \".codebuddy\";\nexport const CONFIG_FILE = \"config.json\";\nexport const POLICY_FILE = \"policies.yaml\";\n\nconst PROJECT_GITIGNORE = [\n \"# CodeBuddy local-only files\",\n \"/config.json\",\n \"/cache/\",\n \"/memory/review/\",\n \"/memory/review-archive/\",\n \"/plans/.locks/\",\n \"/memory/.locks/\",\n \"\",\n \"# Keep these reviewable project artifacts shareable by default:\",\n \"# /memory/facts/*.md\",\n \"# /memory/summaries/*.md\",\n \"# /plans/*.md\",\n \"# If your project memory is private, ignore /memory/ in the repo root .gitignore.\",\n \"\",\n].join(\"\\n\");\n\nconst DEFAULT_POLICY_FILE = [\n \"rules:\",\n \" - id: auth-sensitive\",\n ' pattern: \"src/auth/**\"',\n ' message: \"Auth changes need careful review.\"',\n \" weight: 70\",\n \"\",\n].join(\"\\n\");\n\n/**\n * Plan policy controls how strongly CodeBuddy nudges agents toward writing\n * a plan before substantive work:\n * - off: plans are never suggested or required.\n * - suggest: agents are reminded to plan, but small tasks stay lightweight (default).\n * - required: agents should refuse substantive changes without an active plan.\n */\nexport const PLAN_POLICIES = [\"off\", \"suggest\", \"required\"] as const;\nexport type PlanPolicy = (typeof PLAN_POLICIES)[number];\n\nconst fileSchema = z.object({\n postgresUrl: z.string().min(1).optional(),\n namespace: z.string().min(1).optional(),\n tokenBudget: z.number().int().positive().optional(),\n provider: z\n .object({\n type: z.enum([\"local\", \"huggingface\", \"openrouter\"]).default(\"local\"),\n apiKey: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n relevanceFloor: z.number().min(-1).max(1).optional(),\n recallFloor: z.number().min(-1).max(1).optional(),\n })\n .optional(),\n /** Optional cross-encoder that orders recalled facts (off by default). */\n rerank: z\n .object({\n provider: z.enum([\"off\", \"local\", \"huggingface\", \"openrouter\"]).default(\"off\"),\n model: z.string().min(1).optional(),\n })\n .optional(),\n plan: z\n .object({\n policy: z.enum(PLAN_POLICIES).default(\"suggest\"),\n })\n .optional(),\n graphify: z\n .object({\n enabled: z.boolean(),\n graphPath: z.string().min(1).optional(),\n })\n .optional(),\n /** Git churn risk signal (`risk/signals/churn.ts`); defaults keep the 30-day window. */\n risk: z\n .object({\n churnWindowDays: z.number().int().positive().max(3650).optional(),\n churnMode: z.enum([\"window\", \"lifetime-size\"]).optional(),\n })\n .optional(),\n /**\n * Optional hosted small-model layer (bring your own key). Keys are read from\n * the environment only, never from this file (`src/llm/config.ts`).\n */\n llm: z\n .object({\n enabled: z.boolean(),\n provider: z.enum([\"groq\", \"openrouter\"]).optional(),\n model: z.string().min(1).optional(),\n fallback: z\n .object({ provider: z.enum([\"groq\", \"openrouter\"]), model: z.string().min(1) })\n .optional(),\n timeoutMs: z.number().int().min(100).max(5_000).optional(),\n dailyTokenBudget: z.number().int().positive().optional(),\n features: z\n .object({\n summaries: z.boolean().optional(),\n rerank: z.boolean().optional(),\n memory: z.boolean().optional(),\n })\n .optional(),\n })\n .optional(),\n /** Optional local semantic retrieval via zvec-grep (`zg`). Off unless enabled. */\n zvec: z\n .object({\n enabled: z.boolean(),\n binary: z.string().min(1).optional(),\n timeoutMs: z.number().int().positive().max(10_000).optional(),\n /** Task IDF coverage a vector-only file needs (0..1, default 0.3; docs/zvec-floor.md). */\n minScore: z.number().min(0).max(1).optional(),\n })\n .optional(),\n});\n\nexport type CodeBuddyFileConfig = z.infer<typeof fileSchema>;\n\nexport type ConfigPermissionReport = {\n path: string;\n exists: boolean;\n mode: string | null;\n ok: boolean;\n warning?: string;\n};\n\nexport type ProjectScaffoldReport = {\n codebuddyDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memoryFactsDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memorySummariesDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n plansDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n gitignore: { path: string; exists: boolean; ok: boolean; warning?: string };\n policies: { path: string; exists: boolean; ok: boolean; warning?: string };\n};\n\nexport function configPath(cwd = process.cwd()): string {\n return join(cwd, CONFIG_DIR, CONFIG_FILE);\n}\n\nexport async function initProjectScaffold(cwd = process.cwd()): Promise<{\n codebuddyDir: string;\n gitignorePath: string;\n policyPath: string;\n}> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n await mkdir(join(codebuddyDir, \"memory\", \"facts\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"memory\", \"summaries\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"plans\"), { recursive: true, mode: 0o700 });\n\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n try {\n await access(gitignorePath, constants.F_OK);\n let gitignore = await readFile(gitignorePath, \"utf8\");\n const lines = new Set(gitignore.split(/\\r?\\n/));\n const missing = [\"/memory/review/\", \"/memory/review-archive/\"].filter(\n (line) => !lines.has(line),\n );\n if (missing.length > 0) {\n gitignore = `${gitignore.trimEnd()}\\n${missing.join(\"\\n\")}\\n`;\n await writeFile(gitignorePath, gitignore, { mode: 0o644 });\n }\n } catch {\n await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 0o644 });\n }\n\n const policyPath = join(codebuddyDir, POLICY_FILE);\n try {\n await access(policyPath, constants.F_OK);\n } catch {\n await writeFile(policyPath, DEFAULT_POLICY_FILE, { mode: 0o644 });\n }\n\n return { codebuddyDir, gitignorePath, policyPath };\n}\n\nexport async function initConfigFile(\n cwd = process.cwd(),\n): Promise<{ path: string; created: boolean }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n try {\n await access(path, constants.F_OK);\n await chmod(path, 0o600);\n return { path, created: false };\n } catch {\n const template: CodeBuddyFileConfig = {\n postgresUrl: process.env.DATABASE_URL ?? \"postgres://localhost:5432/codebuddy\",\n // Default to the folder name (sanitized) so a fresh scaffold agrees with\n // doctor's expected default instead of warning immediately after init.\n namespace: defaultNamespaceForCwd(cwd),\n provider: { type: \"local\" },\n tokenBudget: 4000,\n };\n await writeFile(path, `${JSON.stringify(template, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path, created: true };\n }\n}\n\n/** Persist a complete project config without changing its private permissions. */\nexport async function writeConfigFile(\n config: CodeBuddyFileConfig,\n cwd = process.cwd(),\n): Promise<{ path: string }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path };\n}\n\nexport async function readConfigFile(cwd = process.cwd()): Promise<CodeBuddyFileConfig> {\n const path = configPath(cwd);\n let parsed: CodeBuddyFileConfig;\n try {\n const raw = await readFile(path, \"utf8\");\n parsed = fileSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n if ((parsed.zvec?.binary !== undefined || parsed.postgresUrl) && (await trackedByGit(cwd))) {\n return withoutRepositorySuppliedSecrets(parsed, path);\n }\n return parsed;\n}\n\n/**\n * `init` gitignores `.codebuddy/config.json`, so a tracked one came with the\n * repository, not from this user. A cloned repository must not choose a\n * program for CodeBuddy to run (`zvec.binary`) or a database to send project\n * memory to (`postgresUrl`); those two keys are ignored in that case and must\n * come from the environment (DATABASE_URL) or an untracked config.\n */\nfunction withoutRepositorySuppliedSecrets(\n config: CodeBuddyFileConfig,\n path: string,\n): CodeBuddyFileConfig {\n const { postgresUrl: _postgresUrl, ...rest } = config;\n const ignored = [\n ...(config.postgresUrl ? [\"postgresUrl\"] : []),\n ...(config.zvec?.binary !== undefined ? [\"zvec.binary\"] : []),\n ];\n if (!warnedTracked.has(path)) {\n warnedTracked.add(path);\n process.stderr.write(\n `codebuddy: ignoring ${ignored.join(\" and \")} in ${path}: the file is tracked by git, so it came with the repository. ` +\n \"Untrack it (git rm --cached) or set DATABASE_URL.\\n\",\n );\n }\n if (!rest.zvec) return rest;\n const { binary: _binary, ...zvec } = rest.zvec;\n return { ...rest, zvec };\n}\n\nconst warnedTracked = new Set<string>();\nconst trackedCache = new Map<string, Promise<boolean>>();\n\n/** Whether `.codebuddy/config.json` under `root` is tracked by git (cached per process). */\nfunction trackedByGit(root: string): Promise<boolean> {\n const key = resolve(root);\n let pending = trackedCache.get(key);\n if (!pending) {\n pending = new Promise((done) => {\n execFile(\n \"git\",\n [\"ls-files\", \"--error-unmatch\", \"--\", \".codebuddy/config.json\"],\n { cwd: key, timeout: 5_000 },\n (error) => done(error === null),\n );\n });\n trackedCache.set(key, pending);\n }\n return pending;\n}\n\n/**\n * The ONE namespace-resolution rule, shared by the DB/MCP runtime, every\n * file-based CLI command, and doctor. Precedence:\n * 1. CODEBUDDY_NAMESPACE env\n * 2. `.codebuddy/config.json` `namespace`\n * 3. the current folder name (sanitized) — matching what doctor reports as\n * the expected default.\n */\nexport function sanitizeNamespace(value: string): string {\n return value.replace(/[^a-zA-Z0-9_.:-]/g, \"-\").slice(0, 128);\n}\n\n/** Folder-name fallback used when neither env nor config specify a namespace. */\nexport function defaultNamespaceForCwd(cwd = process.cwd()): string {\n return sanitizeNamespace(basename(cwd)) || \"default\";\n}\n\nexport async function resolveNamespaceFrom(cwd = process.cwd()): Promise<string> {\n if (process.env.CODEBUDDY_NAMESPACE) return process.env.CODEBUDDY_NAMESPACE;\n const file = await readConfigFile(cwd);\n return file.namespace ?? defaultNamespaceForCwd(cwd);\n}\n\n/** Scaffolds before 2.0 wrote this literal, so every project shared one namespace. */\nconst LEGACY_NAMESPACE = \"default\";\n\n/**\n * Rewrites a legacy `\"namespace\": \"default\"` in `.codebuddy/config.json` to the\n * folder's default and returns the new namespace, or null when nothing changed.\n * Skipped when the file is tracked by git (it belongs to the repository) or\n * when Markdown memory was saved under \"default\" (it would drop out of recall).\n */\nexport async function migrateLegacyNamespace(cwd = process.cwd()): Promise<string | null> {\n const target = defaultNamespaceForCwd(cwd);\n if (target === LEGACY_NAMESPACE) return null;\n const path = configPath(cwd);\n let raw: Record<string, unknown>;\n try {\n raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n if (raw.namespace !== LEGACY_NAMESPACE) return null;\n if ((await trackedByGit(cwd)) || (await hasMemoryInNamespace(cwd, LEGACY_NAMESPACE))) {\n return null;\n }\n await writeAtomic(path, `${JSON.stringify({ ...raw, namespace: target }, null, 2)}\\n`);\n return target;\n}\n\nasync function hasMemoryInNamespace(cwd: string, namespace: string): Promise<boolean> {\n const memoryDir = join(cwd, \".codebuddy\", \"memory\");\n let entries: string[];\n try {\n entries = await readdir(memoryDir, { recursive: true });\n } catch {\n return false;\n }\n const line = new RegExp(`^namespace: ['\"]?${namespace}['\"]?$`, \"m\");\n for (const entry of entries) {\n if (!entry.endsWith(\".md\")) continue;\n const text = await readFile(join(memoryDir, entry), \"utf8\").catch(() => \"\");\n if (line.test(text)) return true;\n }\n return false;\n}\n\nexport const MISSING_DATABASE_URL =\n \"DATABASE_URL or .codebuddy/config.json postgresUrl is required.\";\n\n/**\n * `requireDatabase: false` (the MCP server) returns an empty `postgresUrl`\n * instead of throwing, so file-based tools still run without PostgreSQL;\n * `createRuntime` rejects the empty URL with the same message.\n */\nexport async function loadRuntimeConfig(\n cwd = process.cwd(),\n options: { requireDatabase?: boolean } = {},\n): Promise<CodeBuddyConfig> {\n const projectRoot = process.env.CODEBUDDY_PROJECT_ROOT ?? cwd;\n const file = await readConfigFile(projectRoot);\n const postgresUrl = process.env.DATABASE_URL ?? file.postgresUrl ?? \"\";\n if (!postgresUrl && options.requireDatabase !== false) {\n throw new Error(MISSING_DATABASE_URL);\n }\n const hfToken = process.env.HF_TOKEN ?? file.provider?.apiKey;\n return {\n postgresUrl,\n namespace:\n process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),\n projectRoot,\n provider: {\n type: file.provider?.type ?? \"local\",\n ...(hfToken ? { apiKey: hfToken } : {}),\n ...(file.provider?.model ? { model: file.provider.model } : {}),\n ...(file.provider?.relevanceFloor !== undefined\n ? { relevanceFloor: file.provider.relevanceFloor }\n : {}),\n ...(file.provider?.recallFloor !== undefined\n ? { recallFloor: file.provider.recallFloor }\n : {}),\n },\n ...(file.tokenBudget ? { tokenBudget: file.tokenBudget } : {}),\n ...(file.graphify ? { graphify: file.graphify } : {}),\n };\n}\n\nexport async function checkConfigPermissions(cwd = process.cwd()): Promise<ConfigPermissionReport> {\n const path = configPath(cwd);\n try {\n const info = await stat(path);\n const modeNumber = info.mode & 0o777;\n const mode = modeNumber.toString(8).padStart(4, \"0\");\n // Windows has no POSIX mode bits (stat reports 0o666/0o444 whatever chmod\n // did); the file is protected by the user profile's ACL instead.\n const ok = process.platform === \"win32\" || modeNumber === 0o600;\n return {\n path,\n exists: true,\n mode,\n ok,\n ...(ok ? {} : { warning: \".codebuddy/config.json should be chmod 0600.\" }),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { path, exists: false, mode: null, ok: true };\n }\n throw error;\n }\n}\n\nexport async function checkProjectScaffold(cwd = process.cwd()): Promise<ProjectScaffoldReport> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n const memoryFactsDir = join(codebuddyDir, \"memory\", \"facts\");\n const memorySummariesDir = join(codebuddyDir, \"memory\", \"summaries\");\n const plansDir = join(codebuddyDir, \"plans\");\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n const policyPath = join(codebuddyDir, POLICY_FILE);\n\n const [\n codebuddyDirExists,\n memoryFactsDirExists,\n memorySummariesDirExists,\n plansDirExists,\n gitignoreExists,\n policiesExist,\n ] = await Promise.all([\n pathExists(codebuddyDir),\n pathExists(memoryFactsDir),\n pathExists(memorySummariesDir),\n pathExists(plansDir),\n pathExists(gitignorePath),\n pathExists(policyPath),\n ]);\n\n return {\n codebuddyDir: {\n path: codebuddyDir,\n exists: codebuddyDirExists,\n ok: codebuddyDirExists,\n ...(codebuddyDirExists\n ? {}\n : { warning: \"Run `codebuddy use` or `codebuddy init` to create the project scaffold.\" }),\n },\n memoryFactsDir: {\n path: memoryFactsDir,\n exists: memoryFactsDirExists,\n ok: memoryFactsDirExists,\n ...(memoryFactsDirExists ? {} : { warning: \"Missing .codebuddy/memory/facts directory.\" }),\n },\n memorySummariesDir: {\n path: memorySummariesDir,\n exists: memorySummariesDirExists,\n ok: memorySummariesDirExists,\n ...(memorySummariesDirExists\n ? {}\n : { warning: \"Missing .codebuddy/memory/summaries directory.\" }),\n },\n plansDir: {\n path: plansDir,\n exists: plansDirExists,\n ok: plansDirExists,\n ...(plansDirExists ? {} : { warning: \"Missing .codebuddy/plans directory.\" }),\n },\n gitignore: {\n path: gitignorePath,\n exists: gitignoreExists,\n ok: gitignoreExists,\n ...(gitignoreExists ? {} : { warning: \"Missing .codebuddy/.gitignore.\" }),\n },\n policies: {\n path: policyPath,\n exists: policiesExist,\n ok: policiesExist,\n ...(policiesExist\n ? {}\n : {\n warning:\n \"Missing .codebuddy/policies.yaml. Run `codebuddy use` or `codebuddy init` to scaffold it.\",\n }),\n },\n };\n}\n\n/** Read the plan policy, honoring CODEBUDDY_PLAN_POLICY then the config file. */\nexport async function loadPlanPolicy(cwd = process.cwd()): Promise<PlanPolicy> {\n const fromEnv = process.env.CODEBUDDY_PLAN_POLICY;\n if (fromEnv && (PLAN_POLICIES as readonly string[]).includes(fromEnv)) {\n return fromEnv as PlanPolicy;\n }\n const file = await readConfigFile(cwd);\n return file.plan?.policy ?? \"suggest\";\n}\n\n/** Persist the plan policy into `.codebuddy/config.json`, creating it if needed. */\nexport async function setPlanPolicy(policy: PlanPolicy, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.plan = { ...(raw.plan as Record<string, unknown> | undefined), policy };\n // Atomic rewrite: this file holds the postgresUrl and provider apiKey.\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn optional zvec-grep retrieval on or off in `.codebuddy/config.json`. */\nexport async function setZvecEnabled(enabled: boolean, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.zvec = { ...(raw.zvec as Record<string, unknown> | undefined), enabled };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn the BYOK LLM layer on or off; never writes a key (keys live in the environment). */\nexport async function setLlmEnabled(\n enabled: boolean,\n options: { provider?: \"groq\" | \"openrouter\"; model?: string } = {},\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.llm = {\n ...(raw.llm as Record<string, unknown> | undefined),\n enabled,\n ...(options.provider ? { provider: options.provider } : {}),\n ...(options.model ? { model: options.model } : {}),\n };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/**\n * Replace one top-level block of `.codebuddy/config.json` (`provider`,\n * `rerank`), keeping every other key and the private file mode. The result\n * must still parse, so a bad value never reaches disk.\n */\nexport async function setConfigBlock(\n key: \"provider\" | \"rerank\",\n value: Record<string, unknown>,\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw[key] = value;\n fileSchema.parse(raw);\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function redactSecrets(value: unknown): unknown {\n if (typeof value === \"string\") {\n let redacted = value;\n // Provider keys (HF, BYOK LLM) literally anywhere in a string.\n for (const key of [\n process.env.HF_TOKEN,\n process.env.GROQ_API_KEY,\n process.env.OPENROUTER_API_KEY,\n process.env.CODEBUDDY_LLM_API_KEY,\n ]) {\n if (key) redacted = redacted.split(key).join(\"[REDACTED]\");\n }\n // Credentials embedded in connection URLs (postgres://user:pass@host…).\n // The password — not the username — is the secret.\n redacted = redacted.replace(\n /(\\b[a-z][a-z0-9+.-]*:\\/\\/[^:/\\s@]+:)[^/\\s@]+(@)/gi,\n \"$1[REDACTED]$2\",\n );\n return redacted;\n }\n if (Array.isArray(value)) return value.map(redactSecrets);\n if (!value || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).map(([key, nested]) => [\n key,\n /token|apiKey|authorization|secret/i.test(key) ? \"[REDACTED]\" : redactSecrets(nested),\n ]),\n );\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":[]}
@@ -7,9 +7,11 @@ import {
7
7
  SessionCapsuleLedger,
8
8
  detectTestCommand,
9
9
  estimateTokens,
10
- findCoveringTest,
10
+ findCoveringTest
11
+ } from "./chunk-GKNYMNFM.js";
12
+ import {
11
13
  hookRoot
12
- } from "./chunk-J2EAZ4MA.js";
14
+ } from "./chunk-TGFTKWOR.js";
13
15
  import {
14
16
  NON_SOURCE_PATH,
15
17
  discoverTaskPaths,
@@ -17,7 +19,11 @@ import {
17
19
  loadProjectArchitectureMap,
18
20
  readBoundedSource,
19
21
  taskTerms
20
- } from "./chunk-NERKZSSE.js";
22
+ } from "./chunk-5EDGAZQN.js";
23
+ import {
24
+ loadKnownSecretValues,
25
+ redactSourceText
26
+ } from "./chunk-AL6VBTNA.js";
21
27
 
22
28
  // src/hooks/lean.ts
23
29
  import { createHash } from "crypto";
@@ -45,8 +51,13 @@ async function runLeanHook(input, emit, budgetMs = LEAN_HOOK_BUDGET_MS) {
45
51
  prompt,
46
52
  manifest,
47
53
  loadProjectArchitectureMap(repositoryRoot, manifest),
48
- // The rerank must answer while rendering still fits in the hook budget.
49
- { semantic: false, llmDeadlineAt: started + budgetMs - RENDER_RESERVE_MS }
54
+ // The rerank, and zvec-grep (opt-in, only with a running `zg server`),
55
+ // must answer while rendering still fits in the hook budget.
56
+ {
57
+ semantic: false,
58
+ llmDeadlineAt: started + budgetMs - RENDER_RESERVE_MS,
59
+ zvecDeadlineAt: started + budgetMs - RENDER_RESERVE_MS
60
+ }
50
61
  );
51
62
  const terms = taskTerms(prompt);
52
63
  const indexed = new Set(Object.keys(manifest.entries));
@@ -110,6 +121,7 @@ async function renderLeanBlock(input) {
110
121
  });
111
122
  }
112
123
  if (loaded.length === 0) return { text: "", emitted: [] };
124
+ const knownSecrets = await loadKnownSecretValues(input.repositoryRoot);
113
125
  const capsules = [
114
126
  {
115
127
  key: "lean:pointers",
@@ -119,7 +131,7 @@ async function renderLeanBlock(input) {
119
131
  ];
120
132
  if (isReadOnlyPrompt(input.prompt)) {
121
133
  let tokens = 0;
122
- for (const span of loaded.flatMap(spansOf)) {
134
+ for (const span of loaded.flatMap((pointer) => spansOf(pointer, knownSecrets))) {
123
135
  const cost = estimateTokens(span.text);
124
136
  if (tokens + cost > LEAN_LIMITS.spanTokens) break;
125
137
  tokens += cost;
@@ -136,11 +148,11 @@ function describePointer(pointer) {
136
148
  const detail = [ranges, test].filter(Boolean).join("; ");
137
149
  return detail ? `${pointer.path} (${detail})` : pointer.path;
138
150
  }
139
- function spansOf(pointer) {
151
+ function spansOf(pointer, knownSecrets) {
140
152
  const ranges = pointer.lines.length <= LEAN_LIMITS.wholeFileLines ? [{ start: 1, end: pointer.lines.length }] : pointer.symbols.map((s) => ({ start: s.startLine, end: s.endLine }));
141
153
  const language = extname(pointer.path).slice(1);
142
154
  return ranges.map(({ start, end }) => {
143
- const body = pointer.lines.slice(start - 1, end).join("\n");
155
+ const body = redactSourceText(pointer.lines.slice(start - 1, end).join("\n"), knownSecrets);
144
156
  const hash = createHash("sha1").update(body).digest("hex").slice(0, 12);
145
157
  return {
146
158
  key: `lean:span:${pointer.path}:${start}-${end}:${hash}`,
@@ -173,4 +185,4 @@ export {
173
185
  renderLeanBlock,
174
186
  runLeanHookCommand
175
187
  };
176
- //# sourceMappingURL=chunk-B6CO63SQ.js.map
188
+ //# sourceMappingURL=chunk-LCSBL5TT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/lean.ts","../src/hooks/lean-command.ts"],"sourcesContent":["/**\n * Lean mode: the prompt hook's only job is to cut the agent's token spend. It\n * injects ranked file pointers (and code spans for read-only questions) and\n * nothing else — no MCP tools, rules block, or memory capture.\n */\nimport { createHash } from \"node:crypto\";\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { extname, join } from \"node:path\";\nimport {\n discoverTaskPaths,\n loadProjectArchitectureMap,\n NON_SOURCE_PATH,\n readBoundedSource,\n taskTerms,\n} from \"../context/discovery.js\";\nimport { type ContextCapsule, SessionCapsuleLedger } from \"../context/session-ledger.js\";\nimport { detectTestCommand, findCoveringTest } from \"../context/tests.js\";\nimport { loadKnownSecretValues, redactSourceText } from \"../core/sensitive.js\";\nimport { loadFreshIndex } from \"../indexer/engine.js\";\nimport { estimateTokens } from \"../savings/tokens.js\";\nimport { hookRoot } from \"./root.js\";\nimport type { HookInput } from \"./runtime.js\";\n\nexport const LEAN_POINTER_PREFIX = \"CodeBuddy likely files: \";\nexport const LEAN_LIMITS = {\n paths: 6,\n symbolsPerFile: 3,\n wholeFileLines: 80,\n spanTokens: 1200,\n} as const;\n\nexport type LeanPointer = {\n path: string;\n symbols: Array<{ name: string; startLine: number; endLine: number }>;\n /** The test file that covers this path, when one follows a known convention. */\n test?: string;\n};\n\ntype LoadedPointer = LeanPointer & { lines: string[] };\n\n/** A lean block finished later than this is dropped: the host may already have moved on. */\nexport const LEAN_HOOK_BUDGET_MS = 1500;\n/** Rendering after the BYOK rerank needs this much of the budget. */\nconst RENDER_RESERVE_MS = 300;\n\n/**\n * UserPromptSubmit in lean mode → ranked file pointers, or nothing. Never\n * throws: an error is appended to `.codebuddy/cache/hooks/errors.log` and the\n * turn proceeds as if CodeBuddy were not installed.\n */\nexport async function runLeanHook(\n input: HookInput,\n emit?: (block: string) => Promise<void>,\n budgetMs = LEAN_HOOK_BUDGET_MS,\n): Promise<string> {\n const started = Date.now();\n const repositoryRoot = hookRoot(input.cwd);\n const prompt = input.prompt?.trim() ?? \"\";\n if (prompt === \"\" || prompt.startsWith(\"/\")) return \"\";\n const sessionId = input.session_id ?? \"default\";\n try {\n const { manifest } = await loadFreshIndex(repositoryRoot);\n const discovery = await discoverTaskPaths(\n repositoryRoot,\n prompt,\n manifest,\n loadProjectArchitectureMap(repositoryRoot, manifest),\n // The rerank, and zvec-grep (opt-in, only with a running `zg server`),\n // must answer while rendering still fits in the hook budget.\n {\n semantic: false,\n llmDeadlineAt: started + budgetMs - RENDER_RESERVE_MS,\n zvecDeadlineAt: started + budgetMs - RENDER_RESERVE_MS,\n },\n );\n const terms = taskTerms(prompt);\n const indexed = new Set(Object.keys(manifest.entries));\n const pointers: LeanPointer[] = discovery.paths\n .filter((path) => !NON_SOURCE_PATH.test(path))\n .map((path) => {\n const test = findCoveringTest(path, indexed);\n const table = manifest.entries[path]?.symbolTable ?? [];\n // Symbols the BYOK rerank named come first, then term matches.\n const picked = discovery.symbols?.[path] ?? [];\n const ordered = [\n ...picked.flatMap((name) => table.filter((symbol) => symbol.name === name)),\n ...table.filter(\n (symbol) =>\n !picked.includes(symbol.name) &&\n terms.some((term) => symbol.name.toLowerCase().includes(term)),\n ),\n ];\n return {\n path,\n symbols: ordered.map(({ name, startLine, endLine }) => ({ name, startLine, endLine })),\n ...(test ? { test } : {}),\n };\n });\n const ledger = new SessionCapsuleLedger(repositoryRoot);\n // The host keeps the block for this turn only, so nothing counts as already sent.\n if (input.ephemeral_context) await ledger.reset(sessionId);\n const { text, emitted } = await renderLeanBlock({\n repositoryRoot,\n prompt,\n pointers,\n ledger,\n sessionId,\n testCommand: pointers.length > 0 ? await detectTestCommand(repositoryRoot) : null,\n });\n if (Date.now() - started >= budgetMs) return \"\";\n if (text) await emit?.(text);\n await ledger.markSent(sessionId, emitted);\n return text;\n } catch (error) {\n await logHookError(repositoryRoot, sessionId, error).catch(() => {});\n return \"\";\n }\n}\n\nasync function logHookError(root: string, sessionId: string, error: unknown): Promise<void> {\n const directory = join(root, \".codebuddy\", \"cache\", \"hooks\");\n await mkdir(directory, { recursive: true });\n const message = (error as Error)?.message ?? String(error);\n await appendFile(\n join(directory, \"errors.log\"),\n `${new Date().toISOString()} ${message.replace(/\\s+/g, \" \")} session=${sessionId}\\n`,\n );\n}\n\n/**\n * The lean block for one prompt: a single pointer line, plus current-source\n * spans when the prompt is read-only. Capsules already delivered this session\n * are left out; the caller marks `emitted` as sent once the block is written.\n */\nexport async function renderLeanBlock(input: {\n repositoryRoot: string;\n prompt: string;\n pointers: LeanPointer[];\n ledger: SessionCapsuleLedger;\n sessionId: string;\n /** Sent once per session, after the pointers. */\n testCommand?: string | null;\n}): Promise<{ text: string; emitted: ContextCapsule[] }> {\n const loaded: LoadedPointer[] = [];\n for (const pointer of input.pointers) {\n if (loaded.length === LEAN_LIMITS.paths) break;\n const source = await readBoundedSource(input.repositoryRoot, pointer.path);\n if (source === null) continue; // deleted, oversized or binary since indexing\n loaded.push({\n path: pointer.path,\n symbols: pointer.symbols.slice(0, LEAN_LIMITS.symbolsPerFile),\n ...(pointer.test ? { test: pointer.test } : {}),\n lines: source.split(/\\r?\\n/),\n });\n }\n if (loaded.length === 0) return { text: \"\", emitted: [] };\n const knownSecrets = await loadKnownSecretValues(input.repositoryRoot);\n\n const capsules: ContextCapsule[] = [\n {\n key: \"lean:pointers\",\n text: LEAN_POINTER_PREFIX + loaded.map(describePointer).join(\", \"),\n },\n ...(input.testCommand\n ? [{ key: \"lean:test-command\", text: `CodeBuddy test command: ${input.testCommand}` }]\n : []),\n ];\n if (isReadOnlyPrompt(input.prompt)) {\n let tokens = 0;\n for (const span of loaded.flatMap((pointer) => spansOf(pointer, knownSecrets))) {\n const cost = estimateTokens(span.text);\n if (tokens + cost > LEAN_LIMITS.spanTokens) break;\n tokens += cost;\n capsules.push(span);\n }\n }\n const seen = await input.ledger.seen(input.sessionId, capsules);\n const emitted = capsules.filter((capsule) => !seen.has(capsule.key));\n return { text: emitted.map((capsule) => capsule.text).join(\"\\n\"), emitted };\n}\n\nfunction describePointer(pointer: LoadedPointer): string {\n const ranges = pointer.symbols.map((s) => `${s.name} L${s.startLine}-${s.endLine}`).join(\", \");\n const test = pointer.test ? `test: ${pointer.test}` : \"\";\n const detail = [ranges, test].filter(Boolean).join(\"; \");\n return detail ? `${pointer.path} (${detail})` : pointer.path;\n}\n\n/** Whole small files; otherwise the matched symbol spans. */\nfunction spansOf(pointer: LoadedPointer, knownSecrets: readonly string[]): ContextCapsule[] {\n const ranges =\n pointer.lines.length <= LEAN_LIMITS.wholeFileLines\n ? [{ start: 1, end: pointer.lines.length }]\n : pointer.symbols.map((s) => ({ start: s.startLine, end: s.endLine }));\n const language = extname(pointer.path).slice(1);\n return ranges.map(({ start, end }) => {\n const body = redactSourceText(pointer.lines.slice(start - 1, end).join(\"\\n\"), knownSecrets);\n const hash = createHash(\"sha1\").update(body).digest(\"hex\").slice(0, 12);\n return {\n key: `lean:span:${pointer.path}:${start}-${end}:${hash}`,\n text: `${pointer.path}:L${start}-${end}\\n\\`\\`\\`${language}\\n${body}\\n\\`\\`\\``,\n };\n });\n}\n\nconst FORCED_READ_ONLY =\n /\\b(without editing|without changing|don't change|do not change|don't edit|do not edit)\\b/i;\nconst QUESTION_START = /^(how|what|why|where|which|who|explain|describe|summarize)\\b/i;\nconst EDIT_VERB =\n /\\b(add|change|fix|implement|update|remove|delete|rename|refactor|write|create|make|edit|replace|move|migrate|support|build)\\b/i;\n\n/**\n * Whether a prompt asks a question without asking for edits. Conservative: a\n * false positive costs up to the span budget, a false negative costs nothing.\n */\nexport function isReadOnlyPrompt(prompt: string): boolean {\n const text = prompt.trim();\n if (text === \"\" || text.startsWith(\"/\")) return false;\n if (FORCED_READ_ONLY.test(text)) return true;\n const asks = QUESTION_START.test(text) || text.endsWith(\"?\");\n return asks && !EDIT_VERB.test(text);\n}\n","import { runLeanHook } from \"./lean.js\";\nimport { failSoft, readHookInput, writeStdout } from \"./stdio.js\";\n\n/** `codebuddy hooks lean`: read the hook payload, print the lean block, never fail. */\nexport async function runLeanHookCommand(): Promise<void> {\n await failSoft(async () => {\n await runLeanHook(await readHookInput(), writeStdout);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,aAAa;AAClC,SAAS,SAAS,YAAY;AAgBvB,IAAM,sBAAsB;AAC5B,IAAM,cAAc;AAAA,EACzB,OAAO;AAAA,EACP,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,YAAY;AACd;AAYO,IAAM,sBAAsB;AAEnC,IAAM,oBAAoB;AAO1B,eAAsB,YACpB,OACA,MACA,WAAW,qBACM;AACjB,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,iBAAiB,SAAS,MAAM,GAAG;AACzC,QAAM,SAAS,MAAM,QAAQ,KAAK,KAAK;AACvC,MAAI,WAAW,MAAM,OAAO,WAAW,GAAG,EAAG,QAAO;AACpD,QAAM,YAAY,MAAM,cAAc;AACtC,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,eAAe,cAAc;AACxD,UAAM,YAAY,MAAM;AAAA,MACtB;AAAA,MACA;AAAA,MACA;AAAA,MACA,2BAA2B,gBAAgB,QAAQ;AAAA;AAAA;AAAA,MAGnD;AAAA,QACE,UAAU;AAAA,QACV,eAAe,UAAU,WAAW;AAAA,QACpC,gBAAgB,UAAU,WAAW;AAAA,MACvC;AAAA,IACF;AACA,UAAM,QAAQ,UAAU,MAAM;AAC9B,UAAM,UAAU,IAAI,IAAI,OAAO,KAAK,SAAS,OAAO,CAAC;AACrD,UAAM,WAA0B,UAAU,MACvC,OAAO,CAAC,SAAS,CAAC,gBAAgB,KAAK,IAAI,CAAC,EAC5C,IAAI,CAAC,SAAS;AACb,YAAM,OAAO,iBAAiB,MAAM,OAAO;AAC3C,YAAM,QAAQ,SAAS,QAAQ,IAAI,GAAG,eAAe,CAAC;AAEtD,YAAM,SAAS,UAAU,UAAU,IAAI,KAAK,CAAC;AAC7C,YAAM,UAAU;AAAA,QACd,GAAG,OAAO,QAAQ,CAAC,SAAS,MAAM,OAAO,CAAC,WAAW,OAAO,SAAS,IAAI,CAAC;AAAA,QAC1E,GAAG,MAAM;AAAA,UACP,CAAC,WACC,CAAC,OAAO,SAAS,OAAO,IAAI,KAC5B,MAAM,KAAK,CAAC,SAAS,OAAO,KAAK,YAAY,EAAE,SAAS,IAAI,CAAC;AAAA,QACjE;AAAA,MACF;AACA,aAAO;AAAA,QACL;AAAA,QACA,SAAS,QAAQ,IAAI,CAAC,EAAE,MAAM,WAAW,QAAQ,OAAO,EAAE,MAAM,WAAW,QAAQ,EAAE;AAAA,QACrF,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACzB;AAAA,IACF,CAAC;AACH,UAAM,SAAS,IAAI,qBAAqB,cAAc;AAEtD,QAAI,MAAM,kBAAmB,OAAM,OAAO,MAAM,SAAS;AACzD,UAAM,EAAE,MAAM,QAAQ,IAAI,MAAM,gBAAgB;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,SAAS,SAAS,IAAI,MAAM,kBAAkB,cAAc,IAAI;AAAA,IAC/E,CAAC;AACD,QAAI,KAAK,IAAI,IAAI,WAAW,SAAU,QAAO;AAC7C,QAAI,KAAM,OAAM,OAAO,IAAI;AAC3B,UAAM,OAAO,SAAS,WAAW,OAAO;AACxC,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,aAAa,gBAAgB,WAAW,KAAK,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACnE,WAAO;AAAA,EACT;AACF;AAEA,eAAe,aAAa,MAAc,WAAmB,OAA+B;AAC1F,QAAM,YAAY,KAAK,MAAM,cAAc,SAAS,OAAO;AAC3D,QAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAM,UAAW,OAAiB,WAAW,OAAO,KAAK;AACzD,QAAM;AAAA,IACJ,KAAK,WAAW,YAAY;AAAA,IAC5B,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,QAAQ,QAAQ,QAAQ,GAAG,CAAC,YAAY,SAAS;AAAA;AAAA,EAClF;AACF;AAOA,eAAsB,gBAAgB,OAQmB;AACvD,QAAM,SAA0B,CAAC;AACjC,aAAW,WAAW,MAAM,UAAU;AACpC,QAAI,OAAO,WAAW,YAAY,MAAO;AACzC,UAAM,SAAS,MAAM,kBAAkB,MAAM,gBAAgB,QAAQ,IAAI;AACzE,QAAI,WAAW,KAAM;AACrB,WAAO,KAAK;AAAA,MACV,MAAM,QAAQ;AAAA,MACd,SAAS,QAAQ,QAAQ,MAAM,GAAG,YAAY,cAAc;AAAA,MAC5D,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;AAAA,MAC7C,OAAO,OAAO,MAAM,OAAO;AAAA,IAC7B,CAAC;AAAA,EACH;AACA,MAAI,OAAO,WAAW,EAAG,QAAO,EAAE,MAAM,IAAI,SAAS,CAAC,EAAE;AACxD,QAAM,eAAe,MAAM,sBAAsB,MAAM,cAAc;AAErE,QAAM,WAA6B;AAAA,IACjC;AAAA,MACE,KAAK;AAAA,MACL,MAAM,sBAAsB,OAAO,IAAI,eAAe,EAAE,KAAK,IAAI;AAAA,IACnE;AAAA,IACA,GAAI,MAAM,cACN,CAAC,EAAE,KAAK,qBAAqB,MAAM,2BAA2B,MAAM,WAAW,GAAG,CAAC,IACnF,CAAC;AAAA,EACP;AACA,MAAI,iBAAiB,MAAM,MAAM,GAAG;AAClC,QAAI,SAAS;AACb,eAAW,QAAQ,OAAO,QAAQ,CAAC,YAAY,QAAQ,SAAS,YAAY,CAAC,GAAG;AAC9E,YAAM,OAAO,eAAe,KAAK,IAAI;AACrC,UAAI,SAAS,OAAO,YAAY,WAAY;AAC5C,gBAAU;AACV,eAAS,KAAK,IAAI;AAAA,IACpB;AAAA,EACF;AACA,QAAM,OAAO,MAAM,MAAM,OAAO,KAAK,MAAM,WAAW,QAAQ;AAC9D,QAAM,UAAU,SAAS,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC;AACnE,SAAO,EAAE,MAAM,QAAQ,IAAI,CAAC,YAAY,QAAQ,IAAI,EAAE,KAAK,IAAI,GAAG,QAAQ;AAC5E;AAEA,SAAS,gBAAgB,SAAgC;AACvD,QAAM,SAAS,QAAQ,QAAQ,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,SAAS,IAAI,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI;AAC7F,QAAM,OAAO,QAAQ,OAAO,SAAS,QAAQ,IAAI,KAAK;AACtD,QAAM,SAAS,CAAC,QAAQ,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AACvD,SAAO,SAAS,GAAG,QAAQ,IAAI,KAAK,MAAM,MAAM,QAAQ;AAC1D;AAGA,SAAS,QAAQ,SAAwB,cAAmD;AAC1F,QAAM,SACJ,QAAQ,MAAM,UAAU,YAAY,iBAChC,CAAC,EAAE,OAAO,GAAG,KAAK,QAAQ,MAAM,OAAO,CAAC,IACxC,QAAQ,QAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,KAAK,EAAE,QAAQ,EAAE;AACzE,QAAM,WAAW,QAAQ,QAAQ,IAAI,EAAE,MAAM,CAAC;AAC9C,SAAO,OAAO,IAAI,CAAC,EAAE,OAAO,IAAI,MAAM;AACpC,UAAM,OAAO,iBAAiB,QAAQ,MAAM,MAAM,QAAQ,GAAG,GAAG,EAAE,KAAK,IAAI,GAAG,YAAY;AAC1F,UAAM,OAAO,WAAW,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACtE,WAAO;AAAA,MACL,KAAK,aAAa,QAAQ,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI;AAAA,MACtD,MAAM,GAAG,QAAQ,IAAI,KAAK,KAAK,IAAI,GAAG;AAAA,QAAW,QAAQ;AAAA,EAAK,IAAI;AAAA;AAAA,IACpE;AAAA,EACF,CAAC;AACH;AAEA,IAAM,mBACJ;AACF,IAAM,iBAAiB;AACvB,IAAM,YACJ;AAMK,SAAS,iBAAiB,QAAyB;AACxD,QAAM,OAAO,OAAO,KAAK;AACzB,MAAI,SAAS,MAAM,KAAK,WAAW,GAAG,EAAG,QAAO;AAChD,MAAI,iBAAiB,KAAK,IAAI,EAAG,QAAO;AACxC,QAAM,OAAO,eAAe,KAAK,IAAI,KAAK,KAAK,SAAS,GAAG;AAC3D,SAAO,QAAQ,CAAC,UAAU,KAAK,IAAI;AACrC;;;ACzNA,eAAsB,qBAAoC;AACxD,QAAM,SAAS,YAAY;AACzB,UAAM,YAAY,MAAM,cAAc,GAAG,WAAW;AAAA,EACtD,CAAC;AACH;","names":[]}
@@ -1,11 +1,16 @@
1
1
  import {
2
- readConfigFile,
2
+ asksForChangelog
3
+ } from "./chunk-5EDGAZQN.js";
4
+ import {
5
+ readConfigFile
6
+ } from "./chunk-KNR2P2UY.js";
7
+ import {
3
8
  writeAtomic
4
- } from "./chunk-5KZY6OTG.js";
9
+ } from "./chunk-CG5B3LYG.js";
5
10
 
6
11
  // src/retrieval/zvec.ts
7
12
  import { execFile, spawn } from "child_process";
8
- import { mkdir, readFile, rm, stat } from "fs/promises";
13
+ import { mkdir, readFile, rm, stat, writeFile } from "fs/promises";
9
14
  import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
10
15
  var SUPPORTED = /^0\.2\.\d+$/;
11
16
  var DEFAULT_TIMEOUT_MS = 1500;
@@ -13,7 +18,18 @@ var READY_CACHE_MS = 10 * 6e4;
13
18
  var NOT_READY_CACHE_MS = 3e4;
14
19
  var REFRESH_TIMEOUT_MS = 10 * 6e4;
15
20
  var DEFAULT_ZVEC_MODEL = "local/potion-code-16m-v2";
16
- var DOC_GLOBS = ["!**/*.md", "!**/*.mdx", "!docs/**"];
21
+ var DEFAULT_ZVEC_MIN_SCORE = 0.3;
22
+ var DOC_GLOBS = ["!**/*.md", "!**/*.mdx", "!**/*.rst", "!**/*.adoc", "!docs/**"];
23
+ var CHANGELOG_GLOBS = [
24
+ "!**/CHANGES*",
25
+ "!**/CHANGELOG*",
26
+ "!**/HISTORY*",
27
+ "!**/NEWS*",
28
+ "!**/RELEASE*",
29
+ "!**/changelog*",
30
+ "!**/Changelog*",
31
+ "!**/ChangeLog*"
32
+ ];
17
33
  var TEST_GLOBS = [
18
34
  "!**/*.test.*",
19
35
  "!**/*.spec.*",
@@ -26,6 +42,7 @@ function zvecExcludeGlobs(task) {
26
42
  return [
27
43
  .../\b(docs?|readme|documentation|markdown)\b/i.test(task) ? [] : DOC_GLOBS,
28
44
  .../\b(tests?|spec)\b/i.test(task) ? [] : TEST_GLOBS,
45
+ ...asksForChangelog(task) ? [] : CHANGELOG_GLOBS,
29
46
  ...ALWAYS_EXCLUDED_GLOBS
30
47
  ];
31
48
  }
@@ -34,14 +51,25 @@ async function zvecCandidates(repositoryRoot, task, options = {}) {
34
51
  const root = resolve(repositoryRoot);
35
52
  const config = await zvecConfig(root);
36
53
  if (!config.enabled) return none("disabled", "zvec-grep not enabled");
37
- const run = (args) => runZg(config.binary, args, root, config.timeoutMs);
54
+ const { deadlineAt } = options;
55
+ const run = (args) => {
56
+ const ms = deadlineAt === void 0 ? config.timeoutMs : Math.min(config.timeoutMs, deadlineAt - Date.now());
57
+ if (ms <= 0) return Promise.resolve({ stdout: "", error: deadlineError() });
58
+ return runZg(config.binary, args, root, ms);
59
+ };
60
+ const missedDeadline = (status) => deadlineAt !== void 0 && status === "timeout" && Date.now() >= deadlineAt - 5;
38
61
  const cached = await readCachedReadiness(root, config.binary);
39
62
  if (cached !== "ready") {
40
63
  if (cached) return cached;
41
64
  const readiness = await checkReadiness(run);
42
- await markReadiness(root, config.binary, readiness);
65
+ if (!(readiness && missedDeadline(readiness.status))) {
66
+ await markReadiness(root, config.binary, readiness);
67
+ }
43
68
  if (readiness) return readiness;
44
69
  }
70
+ if (options.requireServer && !await serverRunning(root, config.binary, run)) {
71
+ return none("no-server", "no running `zg server` (this path never starts one)");
72
+ }
45
73
  const limit = Math.min(20, Math.max(1, Math.floor(options.limit ?? 10)));
46
74
  const chunkLimit = limit * 8;
47
75
  const globs = zvecExcludeGlobs(task);
@@ -58,8 +86,9 @@ async function zvecCandidates(repositoryRoot, task, options = {}) {
58
86
  ...globs.flatMap((glob) => ["-g", glob])
59
87
  ]);
60
88
  if (result.error) {
61
- await resetZvecCache(root);
62
- return fromError(result.error, "zvec-grep query");
89
+ const failure = fromError(result.error, "zvec-grep query");
90
+ if (!missedDeadline(failure.status)) await resetZvecCache(root);
91
+ return failure;
63
92
  }
64
93
  const hits = parseHits(result.stdout).slice(0, chunkLimit);
65
94
  if (hits.length === 0 && /\bzg index\b/.test(result.stdout)) {
@@ -103,6 +132,7 @@ async function zvecCandidates(repositoryRoot, task, options = {}) {
103
132
  hits: candidates,
104
133
  stale,
105
134
  changed,
135
+ minScore: config.minScore,
106
136
  reason: `zvec-grep vector search: ${paths.length} file(s)` + (globs.length > 0 ? "; non-source paths excluded" : "") + (stale > 0 ? `, ${stale} stale hit(s) dropped` : "") + (changed > 0 ? `, ${changed} hit(s) changed since the zg index dropped` : "") + (indexedAt === null ? "; index age unknown, hits unverified (run `codebuddy zvec index`)" : "")
107
137
  };
108
138
  }
@@ -191,7 +221,11 @@ async function buildZvecIndex(repositoryRoot, options = {}) {
191
221
  child.on("close", (exit) => resolveRun(exit ?? 1));
192
222
  });
193
223
  await resetZvecCache(root);
194
- if (code === 0) await markIndexed(root, startedAt);
224
+ if (code === 0) {
225
+ await writeFile(join(root, ".zvec-grep", ".gitignore"), "*\n", { flag: "wx" }).catch(() => {
226
+ });
227
+ await markIndexed(root, startedAt);
228
+ }
195
229
  return code;
196
230
  }
197
231
  var NOT_READY = "zvec-grep index missing or not ready; run `codebuddy zvec index`";
@@ -271,7 +305,8 @@ async function zvecConfig(root) {
271
305
  return {
272
306
  enabled: env !== void 0 ? env === "1" || env === "true" : file.zvec?.enabled ?? false,
273
307
  binary: file.zvec?.binary ?? "zg",
274
- timeoutMs: file.zvec?.timeoutMs ?? DEFAULT_TIMEOUT_MS
308
+ timeoutMs: file.zvec?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
309
+ minScore: file.zvec?.minScore ?? DEFAULT_ZVEC_MIN_SCORE
275
310
  };
276
311
  }
277
312
  function runZg(binary, args, cwd, timeoutMs, signal) {
@@ -296,6 +331,35 @@ function runZg(binary, args, cwd, timeoutMs, signal) {
296
331
  );
297
332
  });
298
333
  }
334
+ function deadlineError() {
335
+ return Object.assign(new Error("hook deadline reached"), { code: "ETIMEDOUT" });
336
+ }
337
+ function serverPath(root) {
338
+ return join(root, ".codebuddy", "cache", "zvec-server.json");
339
+ }
340
+ async function serverRunning(root, binary, run) {
341
+ try {
342
+ const cached = JSON.parse(await readFile(serverPath(root), "utf8"));
343
+ const age = Date.now() - (cached.checkedAt ?? 0);
344
+ if (cached.binary === binary && age >= 0 && age <= NOT_READY_CACHE_MS) {
345
+ return cached.running === true;
346
+ }
347
+ } catch {
348
+ }
349
+ const status = await run(["server", "status"]);
350
+ if (status.error?.code === "ETIMEDOUT") return false;
351
+ const running = !status.error && /^Server: ready\b/m.test(status.stdout);
352
+ try {
353
+ await mkdir(dirname(serverPath(root)), { recursive: true, mode: 448 });
354
+ await writeAtomic(
355
+ serverPath(root),
356
+ `${JSON.stringify({ binary, checkedAt: Date.now(), running })}
357
+ `
358
+ );
359
+ } catch {
360
+ }
361
+ return running;
362
+ }
299
363
  function fromError(error, what) {
300
364
  if (error.code === "ENOENT") return none("not-installed", "zvec-grep (`zg`) not found on PATH");
301
365
  if (error.code === "ETIMEDOUT") return none("timeout", `${what} timed out`);
@@ -313,6 +377,7 @@ function toRepoRelative(root, path) {
313
377
 
314
378
  export {
315
379
  DEFAULT_ZVEC_MODEL,
380
+ DEFAULT_ZVEC_MIN_SCORE,
316
381
  zvecExcludeGlobs,
317
382
  zvecCandidates,
318
383
  refreshZvecIndex,
@@ -321,4 +386,4 @@ export {
321
386
  zvecStatus,
322
387
  buildZvecIndex
323
388
  };
324
- //# sourceMappingURL=chunk-5FW3PMNP.js.map
389
+ //# sourceMappingURL=chunk-MSUAVR3R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/retrieval/zvec.ts"],"sourcesContent":["import { execFile, spawn } from \"node:child_process\";\nimport { mkdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { asksForChangelog } from \"../context/discovery.js\";\nimport { readConfigFile } from \"../core/config-file.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\n\n/**\n * Optional semantic candidate source backed by zvec-grep (`zg`), a local\n * code-search CLI. It only *suggests* files: hits are normalized to\n * repository-relative paths, checked against the working tree, and fused with\n * the keyword ranking by the caller. Risk, policy and incident evidence are\n * never derived from it.\n *\n * Off unless `.codebuddy/config.json` sets `zvec.enabled` (or\n * `CODEBUDDY_ZVEC=1`). It never builds an index implicitly: an unready index,\n * a missing or unsupported binary, a timeout, or unparseable output all return\n * no candidates plus a reason, and callers fall back to keyword ranking.\n *\n * Queries exclude non-source paths (docs, Markdown, evals, scripts, tests,\n * CodeBuddy's own state) unless the task asks about them: otherwise prose that\n * quotes a task, such as audits or eval prompts, outranks the code it describes.\n *\n * Benchmarked: vector-only queries beat zg's default hybrid route on code\n * tasks, so only `--vector` is used. Results are parsed from zg's agent\n * markdown (`#N matchedBy=… path:start-end`), as 0.2.x has no JSON output.\n *\n * Queries use `--mode auto`: a running `zg server` answers in ~200ms, otherwise\n * zg searches directly (~400ms). Auto mode never starts the daemon. The\n * version/readiness check (~400ms) is cached on disk for a few minutes so the\n * per-prompt hook process doesn't repeat it. A failed check (not installed,\n * index not ready, timeout) is cached too, but only for 30 s and only until the\n * workspace's `.zvec-grep/` changes, so an unready index costs one probe per\n * window instead of one per call while a fresh `zg index` is still picked up.\n *\n * Freshness: queries keep `--refresh off` so the prompt path never waits on\n * embedding. Instead `refreshZvecIndex` (run by project sync after index\n * passes, off the request path) and `codebuddy zvec index` record when the zg\n * index was last brought up to date, and hits whose file changed after that are\n * dropped rather than presented with their old meaning.\n */\n\n/** zvec-grep versions whose CLI and output format this adapter was tested against. */\nconst SUPPORTED = /^0\\.2\\.\\d+$/;\nconst DEFAULT_TIMEOUT_MS = 1_500;\nconst READY_CACHE_MS = 10 * 60_000;\n/** A failed readiness check is reused this long, so a fresh `zg index` is noticed quickly. */\nconst NOT_READY_CACHE_MS = 30_000;\n/** An incremental `zg index` runs in the background; only a runaway is killed. */\nconst REFRESH_TIMEOUT_MS = 10 * 60_000;\n/** Small, fast local model; indexes this repo in seconds. */\nexport const DEFAULT_ZVEC_MODEL = \"local/potion-code-16m-v2\";\n/**\n * Default `zvec.minScore`: a vector-only file must cover this share of the\n * task's IDF mass in its current source. zg 0.2.x prints no similarity (its\n * `--trace` score is 1/(60+rank)), so the floor is CodeBuddy's own measure. 0.3\n * is the content gate's value (`MIN_IDF_COVERAGE`), not calibrated on vector\n * hits: see docs/zvec-floor.md to calibrate it.\n */\nexport const DEFAULT_ZVEC_MIN_SCORE = 0.3;\nconst DOC_GLOBS = [\"!**/*.md\", \"!**/*.mdx\", \"!**/*.rst\", \"!**/*.adoc\", \"!docs/**\"];\n/** zg globs are case-sensitive: the usual spellings of changelogs and release notes. */\nconst CHANGELOG_GLOBS = [\n \"!**/CHANGES*\",\n \"!**/CHANGELOG*\",\n \"!**/HISTORY*\",\n \"!**/NEWS*\",\n \"!**/RELEASE*\",\n \"!**/changelog*\",\n \"!**/Changelog*\",\n \"!**/ChangeLog*\",\n];\nconst TEST_GLOBS = [\n \"!**/*.test.*\",\n \"!**/*.spec.*\",\n \"!**/__tests__/**\",\n \"!**/test/**\",\n \"!**/tests/**\",\n];\nconst ALWAYS_EXCLUDED_GLOBS = [\"!evals/**\", \"!scripts/**\", \"!.codebuddy/**\", \"!**/fixtures/**\"];\n\n/** zg `-g` exclusions for a task: docs and tests stay searchable when the task names them. */\nexport function zvecExcludeGlobs(task: string): string[] {\n return [\n ...(/\\b(docs?|readme|documentation|markdown)\\b/i.test(task) ? [] : DOC_GLOBS),\n ...(/\\b(tests?|spec)\\b/i.test(task) ? [] : TEST_GLOBS),\n ...(asksForChangelog(task) ? [] : CHANGELOG_GLOBS),\n ...ALWAYS_EXCLUDED_GLOBS,\n ];\n}\n\nconst EXCLUDED_PREFIXES = [\".codebuddy/cache/\", \".zvec-grep/\", \".git/\", \"node_modules/\"];\n\nexport type ZvecStatus =\n | \"ok\"\n | \"disabled\"\n | \"not-installed\"\n | \"unsupported-version\"\n | \"index-not-ready\"\n | \"timeout\"\n | \"failed\"\n | \"no-server\";\n\n/** zg provides no content hash: a valid range alone cannot prove index freshness. */\nexport type RetrievalHit = {\n path: string;\n startLine: number;\n endLine: number;\n rank: number;\n /** `anchor`: a line the task itself names (path:line, stack frame, error string). */\n source: \"zvec\" | \"anchor\";\n freshness: \"unverified\";\n};\n\nexport type ZvecCandidates = {\n status: ZvecStatus;\n /** Repo-relative paths of existing files, best first. */\n paths: string[];\n /** Every chunk hit inside `paths`, in rank order (several per file). */\n hits: RetrievalHit[];\n reason: string;\n /** Hits dropped because the file no longer exists (stale index). */\n stale: number;\n /** Hits dropped because the file changed after the zg index was refreshed. */\n changed: number;\n /** `zvec.minScore` for vector-only files; set on every `ok` result. */\n minScore?: number;\n};\n\nexport async function zvecCandidates(\n repositoryRoot: string,\n task: string,\n options: {\n limit?: number;\n /**\n * Hard stop (epoch ms) for every zg call: each one is killed at the\n * deadline and the caller falls back to keyword ranking. A miss is not\n * cached, so a slow prompt does not turn zvec off for the next one.\n */\n deadlineAt?: number;\n /** Query only while a `zg server` is running (checked at most every 30 s). */\n requireServer?: boolean;\n } = {},\n): Promise<ZvecCandidates> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n if (!config.enabled) return none(\"disabled\", \"zvec-grep not enabled\");\n const { deadlineAt } = options;\n const run = (args: string[]): Promise<RunResult> => {\n const ms =\n deadlineAt === undefined\n ? config.timeoutMs\n : Math.min(config.timeoutMs, deadlineAt - Date.now());\n if (ms <= 0) return Promise.resolve({ stdout: \"\", error: deadlineError() });\n return runZg(config.binary, args, root, ms);\n };\n const missedDeadline = (status: ZvecStatus) =>\n deadlineAt !== undefined && status === \"timeout\" && Date.now() >= deadlineAt - 5;\n\n const cached = await readCachedReadiness(root, config.binary);\n if (cached !== \"ready\") {\n if (cached) return cached;\n const readiness = await checkReadiness(run);\n if (!(readiness && missedDeadline(readiness.status))) {\n await markReadiness(root, config.binary, readiness);\n }\n if (readiness) return readiness;\n }\n if (options.requireServer && !(await serverRunning(root, config.binary, run))) {\n return none(\"no-server\", \"no running `zg server` (this path never starts one)\");\n }\n\n // zg ranks chunks, and one file can own many of them: oversample chunks so\n // `limit` distinct files survive de-duplication.\n const limit = Math.min(20, Math.max(1, Math.floor(options.limit ?? 10)));\n const chunkLimit = limit * 8;\n const globs = zvecExcludeGlobs(task);\n const result = await run([\n \"query\",\n \"--vector\",\n task,\n \"--mode\",\n \"auto\",\n \"--refresh\",\n \"off\",\n \"--limit\",\n String(chunkLimit),\n ...globs.flatMap((glob) => [\"-g\", glob]),\n ]);\n if (result.error) {\n const failure = fromError(result.error, \"zvec-grep query\");\n if (!missedDeadline(failure.status)) await resetZvecCache(root);\n return failure;\n }\n\n const hits = parseHits(result.stdout).slice(0, chunkLimit);\n // With no index, zg answers with a \"run zg index\" hint instead of hits.\n if (hits.length === 0 && /\\bzg index\\b/.test(result.stdout)) {\n const notReady = none(\"index-not-ready\", NOT_READY);\n await markReadiness(root, config.binary, notReady);\n return notReady;\n }\n const indexedAt = await readIndexedAt(root);\n const paths: string[] = [];\n const candidates: RetrievalHit[] = [];\n const seen = new Set<string>();\n let stale = 0;\n let changed = 0;\n for (const hit of hits) {\n const path = toRepoRelative(root, hit.path);\n if (!path) continue;\n // Later chunks of an accepted file keep their spans; a rejected file stays rejected.\n if (seen.has(path)) {\n if (paths.includes(path)) candidates.push({ ...hit, path });\n continue;\n }\n if (paths.length >= limit) continue;\n seen.add(path);\n if (EXCLUDED_PREFIXES.some((prefix) => path.startsWith(prefix))) continue;\n try {\n const info = await stat(resolve(root, path));\n if (!info.isFile()) continue;\n // ctime too: a rename over the path keeps the old mtime.\n if (indexedAt !== null && Math.max(info.mtimeMs, info.ctimeMs) > indexedAt) {\n changed++;\n continue;\n }\n } catch {\n stale++;\n continue;\n }\n paths.push(path);\n candidates.push({ ...hit, path });\n }\n return {\n status: \"ok\",\n paths,\n hits: candidates,\n stale,\n changed,\n minScore: config.minScore,\n reason:\n `zvec-grep vector search: ${paths.length} file(s)` +\n (globs.length > 0 ? \"; non-source paths excluded\" : \"\") +\n (stale > 0 ? `, ${stale} stale hit(s) dropped` : \"\") +\n (changed > 0 ? `, ${changed} hit(s) changed since the zg index dropped` : \"\") +\n (indexedAt === null\n ? \"; index age unknown, hits unverified (run `codebuddy zvec index`)\"\n : \"\"),\n };\n}\n\nexport type ZvecRefresh = { status: ZvecStatus; reason: string };\n\n/**\n * Incrementally refresh an existing zg index (no `--rebuild`, no embedding\n * flag) and record when it started, which is the freshness bound applied to\n * later hits. Never builds a missing index and never starts a zg server; every\n * failure is returned as a status for the caller to log.\n */\nexport async function refreshZvecIndex(\n repositoryRoot: string,\n options: { signal?: AbortSignal } = {},\n): Promise<ZvecRefresh> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n if (!config.enabled) return { status: \"disabled\", reason: \"zvec-grep not enabled\" };\n const readiness = await checkReadiness((args) =>\n runZg(config.binary, args, root, config.timeoutMs),\n );\n if (readiness) return { status: readiness.status, reason: readiness.reason };\n const startedAt = Date.now();\n const result = await runZg(\n config.binary,\n [\"index\", root, \"--mode\", \"auto\"],\n root,\n REFRESH_TIMEOUT_MS,\n options.signal,\n );\n if (result.error) {\n const failure = fromError(result.error, \"zvec-grep refresh\");\n return { status: failure.status, reason: failure.reason };\n }\n await markIndexed(root, startedAt);\n return { status: \"ok\", reason: \"zvec-grep index refreshed\" };\n}\n\n/** Extract bounded, validated line ranges in retrieval rank order. */\nexport function parseHits(output: string): RetrievalHit[] {\n return [...output.matchAll(/^#(\\d+) matchedBy=\\S+ (.+?):(\\d+)(?:-(\\d+))?\\s*$/gm)]\n .flatMap((match) => {\n const rank = Number(match[1]);\n const startLine = Number(match[3]);\n const endLine = Number(match[4] ?? match[3]);\n if (startLine < 1 || endLine < startLine) return [];\n return [\n {\n path: match[2] as string,\n rank,\n startLine,\n endLine,\n source: \"zvec\" as const,\n freshness: \"unverified\" as const,\n },\n ];\n })\n .sort((left, right) => left.rank - right.rank);\n}\n\n/** Forget the cached readiness check, e.g. after `zg index` or a failed query. */\nexport async function resetZvecCache(repositoryRoot: string): Promise<void> {\n await rm(readyPath(resolve(repositoryRoot)), { force: true });\n}\n\nexport type ZvecReport = {\n enabled: boolean;\n binary: string;\n version: string | null;\n supported: boolean;\n indexReady: boolean;\n server: \"running\" | \"stopped\" | \"unknown\";\n reason: string;\n};\n\n/** Everything `codebuddy zvec status` shows. Read-only; never builds an index. */\nexport async function zvecStatus(repositoryRoot: string): Promise<ZvecReport> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n const run = (args: string[]) =>\n runZg(config.binary, args, root, Math.max(config.timeoutMs, 5_000));\n const version = await run([\"version\"]);\n const found = version.error ? null : (version.stdout.trim().split(/\\s+/).pop() ?? null);\n const supported = found !== null && SUPPORTED.test(found);\n const readiness = supported ? await checkReadiness(run) : null;\n const server = supported ? await run([\"server\", \"status\"]) : null;\n return {\n enabled: config.enabled,\n binary: config.binary,\n version: found,\n supported,\n indexReady: supported && readiness === null,\n server:\n server === null || server.error\n ? \"unknown\"\n : /running|ready/i.test(server.stdout)\n ? \"running\"\n : \"stopped\",\n reason: version.error\n ? fromError(version.error, \"zvec-grep version check\").reason\n : !supported\n ? `zvec-grep ${found ?? \"unknown\"} is not 0.2.x`\n : (readiness?.reason ?? \"ready\"),\n };\n}\n\n/**\n * Build or incrementally refresh the zg index for this repository, streaming\n * zg's progress. Explicit by design: CodeBuddy never indexes implicitly.\n */\nexport async function buildZvecIndex(\n repositoryRoot: string,\n options: {\n model?: string;\n rebuild?: boolean /** Discard zg's progress output. */;\n quiet?: boolean;\n } = {},\n): Promise<number> {\n const root = resolve(repositoryRoot);\n const config = await zvecConfig(root);\n const args = [\n \"index\",\n root,\n \"--embedding\",\n options.model ?? DEFAULT_ZVEC_MODEL,\n \"--mode\",\n \"auto\",\n ];\n if (options.rebuild) args.push(\"--rebuild\");\n const startedAt = Date.now();\n const code = await new Promise<number>((resolveRun, reject) => {\n const child = spawn(config.binary, args, {\n cwd: root,\n stdio: options.quiet ? \"ignore\" : \"inherit\",\n });\n child.on(\"error\", reject);\n child.on(\"close\", (exit) => resolveRun(exit ?? 1));\n });\n await resetZvecCache(root);\n if (code === 0) {\n // zg's index is binary and rebuildable: keep it out of git (and so out of\n // the uncommitted-file targets) without touching the project's .gitignore.\n await writeFile(join(root, \".zvec-grep\", \".gitignore\"), \"*\\n\", { flag: \"wx\" }).catch(() => {});\n await markIndexed(root, startedAt);\n }\n return code;\n}\n\nconst NOT_READY = \"zvec-grep index missing or not ready; run `codebuddy zvec index`\";\n\n/** Version + index readiness. Returns null when ready, else the failure. */\nasync function checkReadiness(\n run: (args: string[]) => Promise<RunResult>,\n): Promise<ZvecCandidates | null> {\n const version = await run([\"version\"]);\n if (version.error) return fromError(version.error, \"zvec-grep version check\");\n const found = version.stdout.trim().split(/\\s+/).pop() ?? \"\";\n if (!SUPPORTED.test(found)) {\n return none(\"unsupported-version\", `zvec-grep ${found || \"unknown\"} is not 0.2.x`);\n }\n const ready = await run([\"status\", \"--check-ready\", \"--mode\", \"auto\"]);\n if (!ready.error) return null;\n return ready.error.code === \"ETIMEDOUT\"\n ? none(\"timeout\", \"zvec-grep status timed out\")\n : none(\"index-not-ready\", NOT_READY);\n}\n\nfunction readyPath(root: string): string {\n return join(root, \".codebuddy\", \"cache\", \"zvec-ready.json\");\n}\n\ntype CachedReadiness = {\n binary?: string;\n checkedAt?: string;\n /** Absent in caches written before failures were cached: those were always ready. */\n failure?: { status: ZvecStatus; reason: string };\n};\n\n/**\n * \"ready\", a cached failure (reason marked as cached), or null when the check\n * must run. A failure is dropped once `.zvec-grep/` changed after it was\n * recorded, i.e. someone (re)built the index outside CodeBuddy.\n */\nasync function readCachedReadiness(\n root: string,\n binary: string,\n): Promise<\"ready\" | ZvecCandidates | null> {\n let cached: CachedReadiness;\n try {\n cached = JSON.parse(await readFile(readyPath(root), \"utf8\")) as CachedReadiness;\n } catch {\n return null;\n }\n const checkedAt = Date.parse(cached.checkedAt ?? \"\");\n const age = Date.now() - checkedAt;\n if (cached.binary !== binary || !(age >= 0)) return null;\n if (!cached.failure) return age <= READY_CACHE_MS ? \"ready\" : null;\n if (age > NOT_READY_CACHE_MS) return null;\n const workspace = await stat(join(root, \".zvec-grep\")).catch(() => null);\n if (workspace && workspace.mtimeMs > checkedAt) return null;\n const { status, reason } = cached.failure;\n return none(status, `${reason}; cached check from ${Math.round(age / 1000)}s ago`);\n}\n\n/** Cache a readiness result: null = ready. */\nasync function markReadiness(\n root: string,\n binary: string,\n failure: ZvecCandidates | null,\n): Promise<void> {\n try {\n const path = readyPath(root);\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n const record: CachedReadiness = {\n binary,\n checkedAt: new Date().toISOString(),\n ...(failure ? { failure: { status: failure.status, reason: failure.reason } } : {}),\n };\n await writeAtomic(path, `${JSON.stringify(record)}\\n`);\n } catch {\n // A read-only cache only costs a repeated readiness check.\n }\n}\n\nfunction indexedPath(root: string): string {\n return join(root, \".codebuddy\", \"cache\", \"zvec-index.json\");\n}\n\n/** When the zg index was last refreshed by CodeBuddy, or null if unknown. */\nasync function readIndexedAt(root: string): Promise<number | null> {\n try {\n const parsed = JSON.parse(await readFile(indexedPath(root), \"utf8\")) as { indexedAt?: string };\n const at = Date.parse(parsed.indexedAt ?? \"\");\n return Number.isFinite(at) ? at : null;\n } catch {\n return null;\n }\n}\n\nasync function markIndexed(root: string, at: number): Promise<void> {\n try {\n const path = indexedPath(root);\n await mkdir(dirname(path), { recursive: true, mode: 0o700 });\n await writeAtomic(path, `${JSON.stringify({ indexedAt: new Date(at).toISOString() })}\\n`);\n } catch {\n // Without the record, later hits are reported as unverified, not trusted.\n }\n}\n\nasync function zvecConfig(\n root: string,\n): Promise<{ enabled: boolean; binary: string; timeoutMs: number; minScore: number }> {\n let file: Awaited<ReturnType<typeof readConfigFile>> = {};\n try {\n file = await readConfigFile(root);\n } catch {\n // A broken config must not break context assembly; treat zvec as disabled.\n }\n const env = process.env.CODEBUDDY_ZVEC;\n return {\n enabled: env !== undefined ? env === \"1\" || env === \"true\" : (file.zvec?.enabled ?? false),\n binary: file.zvec?.binary ?? \"zg\",\n timeoutMs: file.zvec?.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n minScore: file.zvec?.minScore ?? DEFAULT_ZVEC_MIN_SCORE,\n };\n}\n\ntype RunResult = {\n stdout: string;\n error: (Error & { code?: string | number | null | undefined }) | null;\n};\n\nfunction runZg(\n binary: string,\n args: string[],\n cwd: string,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<RunResult> {\n return new Promise((resolveRun) => {\n execFile(\n binary,\n args,\n {\n cwd,\n timeout: timeoutMs,\n maxBuffer: 1024 * 1024,\n killSignal: \"SIGKILL\",\n ...(signal ? { signal } : {}),\n },\n (error, stdout) => {\n if (error && (error as { killed?: boolean }).killed) {\n resolveRun({ stdout: \"\", error: Object.assign(error, { code: \"ETIMEDOUT\" }) });\n return;\n }\n resolveRun({ stdout: String(stdout), error });\n },\n );\n });\n}\n\nfunction deadlineError(): NonNullable<RunResult[\"error\"]> {\n return Object.assign(new Error(\"hook deadline reached\"), { code: \"ETIMEDOUT\" });\n}\n\nfunction serverPath(root: string): string {\n return join(root, \".codebuddy\", \"cache\", \"zvec-server.json\");\n}\n\n/** `zg server status` says ready; the answer (either way) is reused for 30 s. */\nasync function serverRunning(\n root: string,\n binary: string,\n run: (args: string[]) => Promise<RunResult>,\n): Promise<boolean> {\n try {\n const cached = JSON.parse(await readFile(serverPath(root), \"utf8\")) as {\n binary?: string;\n checkedAt?: number;\n running?: boolean;\n };\n const age = Date.now() - (cached.checkedAt ?? 0);\n if (cached.binary === binary && age >= 0 && age <= NOT_READY_CACHE_MS) {\n return cached.running === true;\n }\n } catch {\n // No record yet: ask zg.\n }\n const status = await run([\"server\", \"status\"]);\n // A deadline miss says nothing about the server: not cached.\n if (status.error?.code === \"ETIMEDOUT\") return false;\n const running = !status.error && /^Server: ready\\b/m.test(status.stdout);\n try {\n await mkdir(dirname(serverPath(root)), { recursive: true, mode: 0o700 });\n await writeAtomic(\n serverPath(root),\n `${JSON.stringify({ binary, checkedAt: Date.now(), running })}\\n`,\n );\n } catch {\n // An unwritable cache only repeats the check.\n }\n return running;\n}\n\nfunction fromError(error: NonNullable<RunResult[\"error\"]>, what: string): ZvecCandidates {\n if (error.code === \"ENOENT\") return none(\"not-installed\", \"zvec-grep (`zg`) not found on PATH\");\n if (error.code === \"ETIMEDOUT\") return none(\"timeout\", `${what} timed out`);\n return none(\"failed\", `${what} failed`);\n}\n\nfunction none(status: ZvecStatus, reason: string): ZvecCandidates {\n return { status, paths: [], hits: [], reason, stale: 0, changed: 0 };\n}\n\nfunction toRepoRelative(root: string, path: string): string | null {\n const absolute = isAbsolute(path) ? path : resolve(root, path);\n const rel = relative(root, absolute);\n if (rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join(\"/\");\n}\n"],"mappings":"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","names":[]}