@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
@@ -58,9 +58,10 @@ ${content}`;
58
58
  }
59
59
 
60
60
  // src/core/config-file.ts
61
+ import { execFile } from "child_process";
61
62
  import { constants } from "fs";
62
- import { access, chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
63
- import { basename, join as join2 } from "path";
63
+ import { access, chmod, mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
64
+ import { basename, join as join2, resolve as resolve2 } from "path";
64
65
  import { z } from "zod";
65
66
  var CONFIG_DIR = ".codebuddy";
66
67
  var CONFIG_FILE = "config.json";
@@ -201,13 +202,53 @@ async function writeConfigFile(config, cwd = process.cwd()) {
201
202
  }
202
203
  async function readConfigFile(cwd = process.cwd()) {
203
204
  const path = configPath(cwd);
205
+ let parsed;
204
206
  try {
205
207
  const raw = await readFile(path, "utf8");
206
- return fileSchema.parse(JSON.parse(raw));
208
+ parsed = fileSchema.parse(JSON.parse(raw));
207
209
  } catch (error) {
208
210
  if (error.code === "ENOENT") return {};
209
211
  throw error;
210
212
  }
213
+ if ((parsed.zvec?.binary !== void 0 || parsed.postgresUrl) && await trackedByGit(cwd)) {
214
+ return withoutRepositorySuppliedSecrets(parsed, path);
215
+ }
216
+ return parsed;
217
+ }
218
+ function withoutRepositorySuppliedSecrets(config, path) {
219
+ const { postgresUrl: _postgresUrl, ...rest } = config;
220
+ const ignored = [
221
+ ...config.postgresUrl ? ["postgresUrl"] : [],
222
+ ...config.zvec?.binary !== void 0 ? ["zvec.binary"] : []
223
+ ];
224
+ if (!warnedTracked.has(path)) {
225
+ warnedTracked.add(path);
226
+ process.stderr.write(
227
+ `codebuddy: ignoring ${ignored.join(" and ")} in ${path}: the file is tracked by git, so it came with the repository. Untrack it (git rm --cached) or set DATABASE_URL.
228
+ `
229
+ );
230
+ }
231
+ if (!rest.zvec) return rest;
232
+ const { binary: _binary, ...zvec } = rest.zvec;
233
+ return { ...rest, zvec };
234
+ }
235
+ var warnedTracked = /* @__PURE__ */ new Set();
236
+ var trackedCache = /* @__PURE__ */ new Map();
237
+ function trackedByGit(root) {
238
+ const key = resolve2(root);
239
+ let pending = trackedCache.get(key);
240
+ if (!pending) {
241
+ pending = new Promise((done) => {
242
+ execFile(
243
+ "git",
244
+ ["ls-files", "--error-unmatch", "--", ".codebuddy/config.json"],
245
+ { cwd: key, timeout: 5e3 },
246
+ (error) => done(error === null)
247
+ );
248
+ });
249
+ trackedCache.set(key, pending);
250
+ }
251
+ return pending;
211
252
  }
212
253
  function sanitizeNamespace(value) {
213
254
  return value.replace(/[^a-zA-Z0-9_.:-]/g, "-").slice(0, 128);
@@ -220,6 +261,41 @@ async function resolveNamespaceFrom(cwd = process.cwd()) {
220
261
  const file = await readConfigFile(cwd);
221
262
  return file.namespace ?? defaultNamespaceForCwd(cwd);
222
263
  }
264
+ var LEGACY_NAMESPACE = "default";
265
+ async function migrateLegacyNamespace(cwd = process.cwd()) {
266
+ const target = defaultNamespaceForCwd(cwd);
267
+ if (target === LEGACY_NAMESPACE) return null;
268
+ const path = configPath(cwd);
269
+ let raw;
270
+ try {
271
+ raw = JSON.parse(await readFile(path, "utf8"));
272
+ } catch {
273
+ return null;
274
+ }
275
+ if (raw.namespace !== LEGACY_NAMESPACE) return null;
276
+ if (await trackedByGit(cwd) || await hasMemoryInNamespace(cwd, LEGACY_NAMESPACE)) {
277
+ return null;
278
+ }
279
+ await writeAtomic(path, `${JSON.stringify({ ...raw, namespace: target }, null, 2)}
280
+ `);
281
+ return target;
282
+ }
283
+ async function hasMemoryInNamespace(cwd, namespace) {
284
+ const memoryDir = join2(cwd, ".codebuddy", "memory");
285
+ let entries;
286
+ try {
287
+ entries = await readdir(memoryDir, { recursive: true });
288
+ } catch {
289
+ return false;
290
+ }
291
+ const line = new RegExp(`^namespace: ['"]?${namespace}['"]?$`, "m");
292
+ for (const entry of entries) {
293
+ if (!entry.endsWith(".md")) continue;
294
+ const text = await readFile(join2(memoryDir, entry), "utf8").catch(() => "");
295
+ if (line.test(text)) return true;
296
+ }
297
+ return false;
298
+ }
223
299
  var MISSING_DATABASE_URL = "DATABASE_URL or .codebuddy/config.json postgresUrl is required.";
224
300
  async function loadRuntimeConfig(cwd = process.cwd(), options = {}) {
225
301
  const projectRoot = process.env.CODEBUDDY_PROJECT_ROOT ?? cwd;
@@ -249,7 +325,7 @@ async function checkConfigPermissions(cwd = process.cwd()) {
249
325
  const info = await stat(path);
250
326
  const modeNumber = info.mode & 511;
251
327
  const mode = modeNumber.toString(8).padStart(4, "0");
252
- const ok = modeNumber === 384;
328
+ const ok = process.platform === "win32" || modeNumber === 384;
253
329
  return {
254
330
  path,
255
331
  exists: true,
@@ -421,6 +497,7 @@ export {
421
497
  readConfigFile,
422
498
  defaultNamespaceForCwd,
423
499
  resolveNamespaceFrom,
500
+ migrateLegacyNamespace,
424
501
  MISSING_DATABASE_URL,
425
502
  loadRuntimeConfig,
426
503
  checkConfigPermissions,
@@ -432,4 +509,4 @@ export {
432
509
  setConfigBlock,
433
510
  redactSecrets
434
511
  };
435
- //# sourceMappingURL=chunk-776EQXDV.js.map
512
+ //# sourceMappingURL=chunk-5KZY6OTG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/markdown-store-fs.ts","../src/core/config-file.ts"],"sourcesContent":["import { lstat, open, rename } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\n\n/**\n * Filesystem helpers shared by the Markdown-backed stores\n * (`MemoryFileStore`, `PlanFileStore`). These enforce the security\n * invariants that keep `.codebuddy/` writes inside the repository root:\n * no path traversal, no symlinked path components, and crash-safe atomic\n * writes. Keep this module small and audited; both stores depend on it.\n */\n\n/**\n * Resolve `path` and assert it stays inside `root`. Returns the absolute\n * path. Throws if the resolved path escapes the root.\n */\nexport function resolveWithinRoot(root: string, path: string): string {\n const absoluteRoot = resolve(root);\n const absolute = resolve(path);\n const relativePath = relative(absoluteRoot, absolute);\n if (relativePath === \"..\" || relativePath.startsWith(`..${sep}`) || relativePath === \"\") {\n throw new Error(`Path escapes repository root: ${path}`);\n }\n return absolute;\n}\n\n/**\n * Walk each path component between `root` and `target` and refuse if any\n * component is a symbolic link. Prevents a planted symlink from\n * redirecting a write outside the repository. Missing components are\n * fine — the caller creates them.\n */\nexport async function assertNoSymlinkBetween(root: string, target: string): Promise<void> {\n const absoluteRoot = resolve(root);\n const absoluteTarget = resolve(target);\n const relativeTarget = relative(absoluteRoot, absoluteTarget);\n if (relativeTarget === \"..\" || relativeTarget.startsWith(`..${sep}`)) {\n throw new Error(`Path escapes repository root: ${target}`);\n }\n\n let current = absoluteRoot;\n const parts = relativeTarget.split(sep).filter(Boolean);\n for (const part of parts) {\n current = join(current, part);\n try {\n const info = await lstat(current);\n if (info.isSymbolicLink()) {\n throw new Error(`Refusing to use symlinked path: ${current}`);\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw error;\n }\n }\n}\n\n/**\n * Write `document` to `path` atomically: write to a unique temp file with\n * an exclusive (`wx`) create, fsync, then rename into place. A crash mid-\n * write leaves either the old file or nothing — never a partial file.\n *\n * `mode` sets permissions on NEWLY created files (an existing file keeps its\n * mode, matching rename semantics). Secrets-bearing configs should use 0o600.\n */\nexport async function writeAtomic(\n path: string,\n document: string,\n options?: { mode?: number },\n): Promise<void> {\n const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;\n const handle = await open(temporaryPath, \"wx\", options?.mode ?? 0o600);\n try {\n await handle.writeFile(document, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n await rename(temporaryPath, path);\n}\n\n/**\n * Split a Markdown document with leading YAML front matter into its\n * front-matter text and body. Throws on malformed or unterminated\n * front matter so hand-edited files fail loudly rather than silently\n * losing fields.\n */\nexport function parseFrontMatter(document: string): { frontMatter: string; content: string } {\n if (!document.startsWith(\"---\\n\")) {\n throw new Error(\"Markdown file must start with YAML front matter.\");\n }\n const closingIndex = document.indexOf(\"\\n---\\n\", 4);\n if (closingIndex === -1) {\n throw new Error(\"Markdown file has unterminated YAML front matter.\");\n }\n return {\n frontMatter: document.slice(4, closingIndex),\n content: document.slice(closingIndex + 5),\n };\n}\n\n/**\n * Compose a Markdown document from serialized front-matter YAML and a\n * body. Ensures exactly one trailing newline on the body so files are\n * stable across rewrites (minimal git diffs).\n */\nexport function composeMarkdown(frontMatterYaml: string, body: string): string {\n const trimmedFrontMatter = frontMatterYaml.trimEnd();\n const content = body.endsWith(\"\\n\") ? body : `${body}\\n`;\n return `---\\n${trimmedFrontMatter}\\n---\\n${content}`;\n}\n","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 })\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 /**\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 })\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 },\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":["join","resolve","join","resolve"]}
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  redactLlmKeys
3
- } from "./chunk-6MKEIDX5.js";
3
+ } from "./chunk-RAJJSCIP.js";
4
4
  import {
5
5
  writeAtomic
6
- } from "./chunk-776EQXDV.js";
6
+ } from "./chunk-5KZY6OTG.js";
7
7
 
8
8
  // src/llm/budget.ts
9
9
  import { mkdir, readFile } from "fs/promises";
@@ -196,4 +196,4 @@ export {
196
196
  coolingDown,
197
197
  chatJson
198
198
  };
199
- //# sourceMappingURL=chunk-UFZD3JAA.js.map
199
+ //# sourceMappingURL=chunk-5MQOE4J6.js.map
@@ -0,0 +1,32 @@
1
+ // src/cli/commands/docker-autostart.ts
2
+ import { spawn } from "child_process";
3
+ async function isDockerDaemonRunning() {
4
+ return new Promise((resolve) => {
5
+ const child = spawn("docker", ["info"], { stdio: "ignore" });
6
+ child.on("error", () => resolve(false));
7
+ child.on("close", (code) => resolve(code === 0));
8
+ });
9
+ }
10
+ async function tryStartDockerDesktop() {
11
+ if (process.platform !== "darwin") return { started: false };
12
+ return new Promise((resolve) => {
13
+ const child = spawn("open", ["-ga", "Docker"], { stdio: "ignore" });
14
+ child.on("error", () => resolve({ started: false }));
15
+ child.on("close", (code) => resolve({ started: code === 0 }));
16
+ });
17
+ }
18
+ async function waitForDockerDaemon(timeoutMs = 6e4) {
19
+ const deadline = Date.now() + timeoutMs;
20
+ while (Date.now() < deadline) {
21
+ if (await isDockerDaemonRunning()) return true;
22
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
23
+ }
24
+ return false;
25
+ }
26
+
27
+ export {
28
+ isDockerDaemonRunning,
29
+ tryStartDockerDesktop,
30
+ waitForDockerDaemon
31
+ };
32
+ //# sourceMappingURL=chunk-5UH3BEQR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/docker-autostart.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\n\n/**\n * `docker info` exits 0 when the daemon is reachable, non-zero otherwise.\n */\nexport async function isDockerDaemonRunning(): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(\"docker\", [\"info\"], { stdio: \"ignore\" });\n child.on(\"error\", () => resolve(false));\n child.on(\"close\", (code) => resolve(code === 0));\n });\n}\n\n/**\n * Try to start Docker Desktop on macOS. No-op on other platforms because\n * Linux Docker is typically a system service the user has to manage.\n */\nexport async function tryStartDockerDesktop(): Promise<{ started: boolean }> {\n if (process.platform !== \"darwin\") return { started: false };\n return new Promise((resolve) => {\n const child = spawn(\"open\", [\"-ga\", \"Docker\"], { stdio: \"ignore\" });\n child.on(\"error\", () => resolve({ started: false }));\n child.on(\"close\", (code) => resolve({ started: code === 0 }));\n });\n}\n\n/**\n * Wait up to `timeoutMs` for the Docker daemon to accept connections.\n * Polls `docker info` every second. Returns true if it came up in time.\n */\nexport async function waitForDockerDaemon(timeoutMs = 60_000): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await isDockerDaemonRunning()) return true;\n await new Promise((resolve) => setTimeout(resolve, 1_000));\n }\n return false;\n}\n"],"mappings":";AAAA,SAAS,aAAa;AAKtB,eAAsB,wBAA0C;AAC9D,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,UAAU,CAAC,MAAM,GAAG,EAAE,OAAO,SAAS,CAAC;AAC3D,UAAM,GAAG,SAAS,MAAM,QAAQ,KAAK,CAAC;AACtC,UAAM,GAAG,SAAS,CAAC,SAAS,QAAQ,SAAS,CAAC,CAAC;AAAA,EACjD,CAAC;AACH;AAMA,eAAsB,wBAAuD;AAC3E,MAAI,QAAQ,aAAa,SAAU,QAAO,EAAE,SAAS,MAAM;AAC3D,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,QAAQ,CAAC,OAAO,QAAQ,GAAG,EAAE,OAAO,SAAS,CAAC;AAClE,UAAM,GAAG,SAAS,MAAM,QAAQ,EAAE,SAAS,MAAM,CAAC,CAAC;AACnD,UAAM,GAAG,SAAS,CAAC,SAAS,QAAQ,EAAE,SAAS,SAAS,EAAE,CAAC,CAAC;AAAA,EAC9D,CAAC;AACH;AAMA,eAAsB,oBAAoB,YAAY,KAA0B;AAC9E,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI,MAAM,sBAAsB,EAAG,QAAO;AAC1C,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAK,CAAC;AAAA,EAC3D;AACA,SAAO;AACT;","names":[]}
@@ -2,13 +2,14 @@ import {
2
2
  failSoft,
3
3
  readHookInput,
4
4
  writeStdout
5
- } from "./chunk-PYLH4MH5.js";
5
+ } from "./chunk-FJOTP632.js";
6
6
  import {
7
7
  SessionCapsuleLedger,
8
8
  detectTestCommand,
9
9
  estimateTokens,
10
- findCoveringTest
11
- } from "./chunk-SYWJG5NA.js";
10
+ findCoveringTest,
11
+ hookRoot
12
+ } from "./chunk-J2EAZ4MA.js";
12
13
  import {
13
14
  NON_SOURCE_PATH,
14
15
  discoverTaskPaths,
@@ -16,12 +17,12 @@ import {
16
17
  loadProjectArchitectureMap,
17
18
  readBoundedSource,
18
19
  taskTerms
19
- } from "./chunk-L5AYZC3F.js";
20
+ } from "./chunk-NERKZSSE.js";
20
21
 
21
22
  // src/hooks/lean.ts
22
23
  import { createHash } from "crypto";
23
24
  import { appendFile, mkdir } from "fs/promises";
24
- import { extname, join, resolve } from "path";
25
+ import { extname, join } from "path";
25
26
  var LEAN_POINTER_PREFIX = "CodeBuddy likely files: ";
26
27
  var LEAN_LIMITS = {
27
28
  paths: 6,
@@ -33,7 +34,7 @@ var LEAN_HOOK_BUDGET_MS = 1500;
33
34
  var RENDER_RESERVE_MS = 300;
34
35
  async function runLeanHook(input, emit, budgetMs = LEAN_HOOK_BUDGET_MS) {
35
36
  const started = Date.now();
36
- const repositoryRoot = resolve(input.cwd ?? process.cwd());
37
+ const repositoryRoot = hookRoot(input.cwd);
37
38
  const prompt = input.prompt?.trim() ?? "";
38
39
  if (prompt === "" || prompt.startsWith("/")) return "";
39
40
  const sessionId = input.session_id ?? "default";
@@ -172,4 +173,4 @@ export {
172
173
  renderLeanBlock,
173
174
  runLeanHookCommand
174
175
  };
175
- //# sourceMappingURL=chunk-N34XHWBA.js.map
176
+ //# sourceMappingURL=chunk-B6CO63SQ.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 { 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 must answer while rendering still fits in the hook budget.\n { semantic: false, llmDeadlineAt: started + budgetMs - RENDER_RESERVE_MS },\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\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(spansOf)) {\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): 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 = pointer.lines.slice(start - 1, end).join(\"\\n\");\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":"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","names":[]}
@@ -0,0 +1,79 @@
1
+ // src/cli/commands/postgres-docker.ts
2
+ import { spawn } from "child_process";
3
+ import { existsSync } from "fs";
4
+ import { dirname, resolve } from "path";
5
+ import { fileURLToPath } from "url";
6
+ function resolveBundledComposeFile(from = dirname(fileURLToPath(import.meta.url))) {
7
+ const candidates = [];
8
+ for (let dir = from; ; dir = dirname(dir)) {
9
+ candidates.push(resolve(dir, "docker-compose.yml"));
10
+ if (dirname(dir) === dir) break;
11
+ }
12
+ candidates.push(resolve(process.cwd(), "docker-compose.yml"));
13
+ for (const candidate of candidates) {
14
+ if (existsSync(candidate)) return candidate;
15
+ }
16
+ throw new Error(`Could not locate docker-compose.yml. Looked in:
17
+ ${candidates.join("\n ")}`);
18
+ }
19
+ async function runDockerCompose(args, options = {}) {
20
+ const spawnImpl = options.spawnImpl ?? spawn;
21
+ const composeFile = resolveBundledComposeFile();
22
+ const project = ["-p", "codebuddy"];
23
+ const attempts = [
24
+ { kind: "v2", bin: "docker", args: ["compose", ...project, "-f", composeFile, ...args] },
25
+ { kind: "v1", bin: "docker-compose", args: [...project, "-f", composeFile, ...args] }
26
+ ];
27
+ let lastSpawnError = null;
28
+ for (const attempt of attempts) {
29
+ try {
30
+ return await spawnOnce(spawnImpl, attempt);
31
+ } catch (error) {
32
+ const err = error;
33
+ if (err.code !== "ENOENT") throw err;
34
+ lastSpawnError = err;
35
+ }
36
+ }
37
+ throw new Error(
38
+ `Could not find docker. Install Docker Desktop (or docker-compose v1) and ensure it is on your PATH. (${lastSpawnError?.message ?? "spawn ENOENT"})`
39
+ );
40
+ }
41
+ function spawnOnce(spawnImpl, attempt) {
42
+ return new Promise((resolvePromise, rejectPromise) => {
43
+ const child = spawnImpl(attempt.bin, attempt.args, {
44
+ stdio: ["ignore", "pipe", "pipe"]
45
+ });
46
+ let stdout = "";
47
+ let stderr = "";
48
+ child.stdout?.on("data", (chunk) => {
49
+ stdout += String(chunk);
50
+ });
51
+ child.stderr?.on("data", (chunk) => {
52
+ stderr += String(chunk);
53
+ });
54
+ child.on("error", (error) => {
55
+ rejectPromise(error);
56
+ });
57
+ child.on("close", (code) => {
58
+ resolvePromise({ code: code ?? 0, stdout, stderr });
59
+ });
60
+ });
61
+ }
62
+ async function postgresUp(options) {
63
+ return runDockerCompose(["up", "-d"], options ?? {});
64
+ }
65
+ async function postgresDown(options) {
66
+ return runDockerCompose(["down"], options ?? {});
67
+ }
68
+ async function postgresStatus(options) {
69
+ return runDockerCompose(["ps"], options ?? {});
70
+ }
71
+
72
+ export {
73
+ resolveBundledComposeFile,
74
+ runDockerCompose,
75
+ postgresUp,
76
+ postgresDown,
77
+ postgresStatus
78
+ };
79
+ //# sourceMappingURL=chunk-CSRD5OQA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/postgres-docker.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport type ComposeCommand =\n | { kind: \"v2\"; bin: \"docker\"; args: string[] }\n | { kind: \"v1\"; bin: \"docker-compose\"; args: string[] };\n\nexport function resolveBundledComposeFile(from = dirname(fileURLToPath(import.meta.url))): string {\n // tsup code-splits, so this module may run from dist/, dist/<chunk>.js or\n // src/cli/commands/; fixed `..` counts break on one of them. Walk up to the\n // nearest docker-compose.yml so the package's bundled file wins over a\n // stray one in the user's cwd.\n const candidates: string[] = [];\n for (let dir = from; ; dir = dirname(dir)) {\n candidates.push(resolve(dir, \"docker-compose.yml\"));\n if (dirname(dir) === dir) break;\n }\n candidates.push(resolve(process.cwd(), \"docker-compose.yml\"));\n for (const candidate of candidates) {\n if (existsSync(candidate)) return candidate;\n }\n throw new Error(`Could not locate docker-compose.yml. Looked in:\\n ${candidates.join(\"\\n \")}`);\n}\n\nexport type DockerComposeResult = {\n code: number;\n stdout: string;\n stderr: string;\n};\n\nexport type RunDockerComposeOptions = {\n /** Override spawn for tests. */\n spawnImpl?: typeof spawn;\n};\n\n/**\n * Try `docker compose` (v2 — bundled with Docker Desktop) first, then fall back\n * to the legacy standalone `docker-compose` (v1) for older installs.\n */\nexport async function runDockerCompose(\n args: string[],\n options: RunDockerComposeOptions = {},\n): Promise<DockerComposeResult> {\n const spawnImpl = options.spawnImpl ?? spawn;\n const composeFile = resolveBundledComposeFile();\n // A fixed project name: compose otherwise names the project (and its data\n // volume, `codebuddy_codebuddy-postgres-data`) after the folder holding the\n // compose file, so another install location would get an empty database.\n const project = [\"-p\", \"codebuddy\"];\n const attempts: ComposeCommand[] = [\n { kind: \"v2\", bin: \"docker\", args: [\"compose\", ...project, \"-f\", composeFile, ...args] },\n { kind: \"v1\", bin: \"docker-compose\", args: [...project, \"-f\", composeFile, ...args] },\n ];\n\n let lastSpawnError: NodeJS.ErrnoException | null = null;\n\n for (const attempt of attempts) {\n try {\n return await spawnOnce(spawnImpl, attempt);\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n // ENOENT means the binary isn't on PATH — try the next candidate.\n // Any other error is propagated immediately.\n if (err.code !== \"ENOENT\") throw err;\n lastSpawnError = err;\n }\n }\n\n throw new Error(\n `Could not find docker. Install Docker Desktop (or docker-compose v1) and ensure it is on your PATH. (${lastSpawnError?.message ?? \"spawn ENOENT\"})`,\n );\n}\n\nfunction spawnOnce(spawnImpl: typeof spawn, attempt: ComposeCommand): Promise<DockerComposeResult> {\n return new Promise((resolvePromise, rejectPromise) => {\n const child = spawnImpl(attempt.bin, attempt.args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout?.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n child.on(\"error\", (error) => {\n rejectPromise(error);\n });\n child.on(\"close\", (code) => {\n resolvePromise({ code: code ?? 0, stdout, stderr });\n });\n });\n}\n\nexport async function postgresUp(options?: RunDockerComposeOptions): Promise<DockerComposeResult> {\n return runDockerCompose([\"up\", \"-d\"], options ?? {});\n}\n\nexport async function postgresDown(\n options?: RunDockerComposeOptions,\n): Promise<DockerComposeResult> {\n return runDockerCompose([\"down\"], options ?? {});\n}\n\nexport async function postgresStatus(\n options?: RunDockerComposeOptions,\n): Promise<DockerComposeResult> {\n return runDockerCompose([\"ps\"], options ?? {});\n}\n"],"mappings":";AAAA,SAAS,aAAa;AACtB,SAAS,kBAAkB;AAC3B,SAAS,SAAS,eAAe;AACjC,SAAS,qBAAqB;AAMvB,SAAS,0BAA0B,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAW;AAKhG,QAAM,aAAuB,CAAC;AAC9B,WAAS,MAAM,QAAQ,MAAM,QAAQ,GAAG,GAAG;AACzC,eAAW,KAAK,QAAQ,KAAK,oBAAoB,CAAC;AAClD,QAAI,QAAQ,GAAG,MAAM,IAAK;AAAA,EAC5B;AACA,aAAW,KAAK,QAAQ,QAAQ,IAAI,GAAG,oBAAoB,CAAC;AAC5D,aAAW,aAAa,YAAY;AAClC,QAAI,WAAW,SAAS,EAAG,QAAO;AAAA,EACpC;AACA,QAAM,IAAI,MAAM;AAAA,IAAsD,WAAW,KAAK,MAAM,CAAC,EAAE;AACjG;AAiBA,eAAsB,iBACpB,MACA,UAAmC,CAAC,GACN;AAC9B,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,cAAc,0BAA0B;AAI9C,QAAM,UAAU,CAAC,MAAM,WAAW;AAClC,QAAM,WAA6B;AAAA,IACjC,EAAE,MAAM,MAAM,KAAK,UAAU,MAAM,CAAC,WAAW,GAAG,SAAS,MAAM,aAAa,GAAG,IAAI,EAAE;AAAA,IACvF,EAAE,MAAM,MAAM,KAAK,kBAAkB,MAAM,CAAC,GAAG,SAAS,MAAM,aAAa,GAAG,IAAI,EAAE;AAAA,EACtF;AAEA,MAAI,iBAA+C;AAEnD,aAAW,WAAW,UAAU;AAC9B,QAAI;AACF,aAAO,MAAM,UAAU,WAAW,OAAO;AAAA,IAC3C,SAAS,OAAO;AACd,YAAM,MAAM;AAGZ,UAAI,IAAI,SAAS,SAAU,OAAM;AACjC,uBAAiB;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR,wGAAwG,gBAAgB,WAAW,cAAc;AAAA,EACnJ;AACF;AAEA,SAAS,UAAU,WAAyB,SAAuD;AACjG,SAAO,IAAI,QAAQ,CAAC,gBAAgB,kBAAkB;AACpD,UAAM,QAAQ,UAAU,QAAQ,KAAK,QAAQ,MAAM;AAAA,MACjD,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AACD,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU;AAClC,gBAAU,OAAO,KAAK;AAAA,IACxB,CAAC;AACD,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU;AAClC,gBAAU,OAAO,KAAK;AAAA,IACxB,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,UAAU;AAC3B,oBAAc,KAAK;AAAA,IACrB,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,qBAAe,EAAE,MAAM,QAAQ,GAAG,QAAQ,OAAO,CAAC;AAAA,IACpD,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,WAAW,SAAiE;AAChG,SAAO,iBAAiB,CAAC,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC;AACrD;AAEA,eAAsB,aACpB,SAC8B;AAC9B,SAAO,iBAAiB,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;AACjD;AAEA,eAAsB,eACpB,SAC8B;AAC9B,SAAO,iBAAiB,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC;AAC/C;","names":[]}
@@ -31,4 +31,4 @@ export {
31
31
  writeStdout,
32
32
  readHookInput
33
33
  };
34
- //# sourceMappingURL=chunk-PYLH4MH5.js.map
34
+ //# sourceMappingURL=chunk-FJOTP632.js.map
@@ -11,8 +11,19 @@ var extractor = null;
11
11
  function modelCacheDir() {
12
12
  return process.env.CODEBUDDY_MODEL_CACHE ?? join(homedir(), ".cache", "codebuddy", "models");
13
13
  }
14
+ var MISSING_RUNTIME = "Local models need the optional package @huggingface/transformers, which is not installed; dense recall is off and keyword recall still works. Install it with: npm i -g @huggingface/transformers (or reinstall CodeBuddy without --omit=optional).";
15
+ function localRuntimeInstalled() {
16
+ try {
17
+ import.meta.resolve("@huggingface/transformers");
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
14
23
  function loadTransformers() {
15
- transformers ??= import("@huggingface/transformers").then((module) => {
24
+ transformers ??= import("@huggingface/transformers").catch((error) => {
25
+ throw error.code === "ERR_MODULE_NOT_FOUND" ? new Error(MISSING_RUNTIME) : error;
26
+ }).then((module) => {
16
27
  module.env.cacheDir = modelCacheDir();
17
28
  module.env.logLevel = module.LogLevel.ERROR;
18
29
  return module;
@@ -85,7 +96,9 @@ async function rerankLocally(model, query, documents) {
85
96
 
86
97
  export {
87
98
  modelCacheDir,
99
+ MISSING_RUNTIME,
100
+ localRuntimeInstalled,
88
101
  LocalModelProvider,
89
102
  rerankLocally
90
103
  };
91
- //# sourceMappingURL=chunk-U4HAJLIB.js.map
104
+ //# sourceMappingURL=chunk-IBLJOHJL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/local.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n EmbeddingRequest,\n EmbeddingResponse,\n ModelProvider,\n TextGenerationResponse,\n} from \"./adapter.js\";\nimport type { EmbeddingSpace } from \"./embedding-space.js\";\nimport { LOCAL_EMBEDDING_MODEL } from \"./models.js\";\n\n/**\n * Local Hugging Face models through transformers.js (ONNX Runtime, CPU). The\n * weights download once into `~/.cache/codebuddy/models` (override with\n * `CODEBUDDY_MODEL_CACHE`); afterwards nothing leaves the machine and no token\n * is needed. `@huggingface/transformers` is an optional dependency, loaded on\n * first use, so a missing install only disables semantic ranking.\n */\n\ntype Transformers = typeof import(\"@huggingface/transformers\");\n/** The slice of transformers.js' FeatureExtractionPipeline used here. */\ntype Extractor = (\n texts: string[],\n options: { pooling: \"cls\"; normalize: boolean },\n) => Promise<{ tolist(): number[][] }>;\n\n/** Hard cap on a single input; ModernBERT reads 8k tokens, memories are far shorter. */\nconst MAX_INPUT_CHARS = 8_000;\n\nlet transformers: Promise<Transformers> | null = null;\nlet extractor: Promise<Extractor> | null = null;\n\nexport function modelCacheDir(): string {\n return process.env.CODEBUDDY_MODEL_CACHE ?? join(homedir(), \".cache\", \"codebuddy\", \"models\");\n}\n\n/** Shown when the optional runtime was not installed (`npm i --omit=optional`, a failed native build). */\nexport const MISSING_RUNTIME =\n \"Local models need the optional package @huggingface/transformers, which is not installed; \" +\n \"dense recall is off and keyword recall still works. Install it with: npm i -g @huggingface/transformers \" +\n \"(or reinstall CodeBuddy without --omit=optional).\";\n\n/** Whether the optional local-model runtime can be loaded, without loading it. */\nexport function localRuntimeInstalled(): boolean {\n try {\n import.meta.resolve(\"@huggingface/transformers\");\n return true;\n } catch {\n return false;\n }\n}\n\nfunction loadTransformers(): Promise<Transformers> {\n transformers ??= import(\"@huggingface/transformers\")\n .catch((error: NodeJS.ErrnoException) => {\n throw error.code === \"ERR_MODULE_NOT_FOUND\" ? new Error(MISSING_RUNTIME) : error;\n })\n .then((module) => {\n module.env.cacheDir = modelCacheDir();\n // Info/debug logs go to console.log, i.e. stdout: the MCP JSON-RPC channel\n // under `codebuddy serve`. Errors only (they use stderr), here and in ONNX Runtime.\n module.env.logLevel = module.LogLevel.ERROR;\n return module;\n });\n // A failed import (optional dependency missing) is retried on the next call.\n transformers.catch(() => {\n transformers = null;\n });\n return transformers;\n}\n\nexport class LocalModelProvider implements ModelProvider {\n readonly space: EmbeddingSpace = {\n model: LOCAL_EMBEDDING_MODEL.id,\n revision: LOCAL_EMBEDDING_MODEL.revision,\n schema: LOCAL_EMBEDDING_MODEL.schema,\n dimensions: LOCAL_EMBEDDING_MODEL.dimensions,\n };\n\n /** `request.model` is ignored: one provider is one embedding space. */\n async embed(request: EmbeddingRequest): Promise<EmbeddingResponse> {\n const inputs = (typeof request.input === \"string\" ? [request.input] : request.input).map(\n (text) => text.slice(0, MAX_INPUT_CHARS),\n );\n extractor ??= loadTransformers().then(\n async (module) =>\n (await module.pipeline(\"feature-extraction\", this.space.model, {\n revision: this.space.revision,\n dtype: \"q8\",\n })) as unknown as Extractor,\n );\n // A failed load (no network on first use) is retried on the next call.\n extractor.catch(() => {\n extractor = null;\n });\n const extract = await extractor;\n // Granite r2 is trained with CLS pooling; L2-normalized so cosine = dot.\n const output = await extract(inputs, { pooling: \"cls\", normalize: true });\n return { ...this.space, vectors: output.tolist() };\n }\n\n async generateText(): Promise<TextGenerationResponse> {\n throw new Error(\"The local provider only embeds; text generation uses the BYOK LLM layer.\");\n }\n}\n\ntype Reranker = {\n tokenizer: (\n texts: string[],\n options: { text_pair: string[]; padding: boolean; truncation: boolean },\n ) => unknown;\n model: (inputs: unknown) => Promise<{ logits: { tolist(): number[][] } }>;\n};\nconst rerankers = new Map<string, Promise<Reranker>>();\n\n/**\n * Local cross-encoder logits for (query, document) pairs, in input order.\n * Higher is more relevant; values order documents for one query and are not\n * calibrated across queries, so callers order by them and never threshold.\n */\nexport async function rerankLocally(\n model: { id: string; revision?: string },\n query: string,\n documents: string[],\n): Promise<number[]> {\n if (documents.length === 0) return [];\n let pending = rerankers.get(model.id);\n if (!pending) {\n pending = loadTransformers().then(async (module) => {\n const options = model.revision ? { revision: model.revision } : {};\n const [tokenizer, classifier] = await Promise.all([\n module.AutoTokenizer.from_pretrained(model.id, options),\n module.AutoModelForSequenceClassification.from_pretrained(model.id, {\n ...options,\n dtype: \"q8\",\n }),\n ]);\n return { tokenizer, model: classifier } as unknown as Reranker;\n });\n rerankers.set(model.id, pending);\n pending.catch(() => rerankers.delete(model.id));\n }\n const { tokenizer, model: classifier } = await pending;\n const inputs = tokenizer(\n documents.map(() => query.slice(0, MAX_INPUT_CHARS)),\n {\n text_pair: documents.map((document) => document.slice(0, MAX_INPUT_CHARS)),\n padding: true,\n truncation: true,\n },\n );\n const { logits } = await classifier(inputs);\n return logits.tolist().map((row) => row[0] ?? Number.NEGATIVE_INFINITY);\n}\n"],"mappings":"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","names":["tokenizer","classifier"]}