@orquentra/codebuddy 2.4.1 → 2.6.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 (61) hide show
  1. package/README.md +25 -2
  2. package/dist/{chunk-ITGRANYW.js → chunk-6MKEIDX5.js} +2 -2
  3. package/dist/{chunk-K63EOA4B.js → chunk-776EQXDV.js} +25 -6
  4. package/dist/chunk-776EQXDV.js.map +1 -0
  5. package/dist/chunk-JFDRBPYF.js +99 -0
  6. package/dist/chunk-JFDRBPYF.js.map +1 -0
  7. package/dist/chunk-JPHJBCZB.js +99 -0
  8. package/dist/chunk-JPHJBCZB.js.map +1 -0
  9. package/dist/{chunk-IBMJYIEP.js → chunk-L5AYZC3F.js} +7 -7
  10. package/dist/{chunk-DSN5CEHO.js → chunk-N34XHWBA.js} +3 -3
  11. package/dist/{chunk-3ZELDCR3.js → chunk-PCE4STAY.js} +7 -7
  12. package/dist/{chunk-TI5TQD6D.js → chunk-QDBDYCM2.js} +2 -2
  13. package/dist/{chunk-5OWZVZSO.js → chunk-SYWJG5NA.js} +8 -165
  14. package/dist/chunk-SYWJG5NA.js.map +1 -0
  15. package/dist/chunk-U4HAJLIB.js +91 -0
  16. package/dist/chunk-U4HAJLIB.js.map +1 -0
  17. package/dist/{chunk-IE2OUO54.js → chunk-UFZD3JAA.js} +3 -3
  18. package/dist/chunk-UN57FKQX.js +4033 -0
  19. package/dist/chunk-UN57FKQX.js.map +1 -0
  20. package/dist/chunk-VAKBJV26.js +5112 -0
  21. package/dist/chunk-VAKBJV26.js.map +1 -0
  22. package/dist/chunk-XBJQKCLU.js +103 -0
  23. package/dist/chunk-XBJQKCLU.js.map +1 -0
  24. package/dist/cli/index.js +2 -2
  25. package/dist/cloud-7WM3YLI4.js +17 -0
  26. package/dist/{codebuddy-D2xJ6hFk.d.ts → codebuddy-DM-i0Fu5.d.ts} +49 -18
  27. package/dist/{config-S2TULOMK.js → config-KFEHLV6W.js} +3 -3
  28. package/dist/index.d.ts +23 -3
  29. package/dist/index.js +34 -25
  30. package/dist/langgraph/index.d.ts +1 -1
  31. package/dist/lean-command-C5YPFKHP.js +15 -0
  32. package/dist/local-OTOY56B4.js +13 -0
  33. package/dist/{main-BFDVQHKO.js → main-HFHLGID4.js} +1410 -245
  34. package/dist/main-HFHLGID4.js.map +1 -0
  35. package/dist/{memory-NIBFAMY4.js → memory-4RGVLPKG.js} +6 -6
  36. package/dist/operations-LOBAQRLO.js +24 -0
  37. package/dist/operations-LOBAQRLO.js.map +1 -0
  38. package/dist/{rerank-Q7MIFNKA.js → rerank-3P3C2UHN.js} +4 -4
  39. package/dist/{summaries-TSE44KUQ.js → summaries-ZBV5IN7Y.js} +6 -6
  40. package/dist/summaries-ZBV5IN7Y.js.map +1 -0
  41. package/dist/{zvec-F27AR357.js → zvec-MAPSJQSR.js} +3 -3
  42. package/dist/zvec-MAPSJQSR.js.map +1 -0
  43. package/package.json +6 -2
  44. package/dist/chunk-5OWZVZSO.js.map +0 -1
  45. package/dist/chunk-K63EOA4B.js.map +0 -1
  46. package/dist/chunk-OLFCOR4U.js +0 -8580
  47. package/dist/chunk-OLFCOR4U.js.map +0 -1
  48. package/dist/lean-command-D6TZJEFX.js +0 -13
  49. package/dist/main-BFDVQHKO.js.map +0 -1
  50. /package/dist/{chunk-ITGRANYW.js.map → chunk-6MKEIDX5.js.map} +0 -0
  51. /package/dist/{chunk-IBMJYIEP.js.map → chunk-L5AYZC3F.js.map} +0 -0
  52. /package/dist/{chunk-DSN5CEHO.js.map → chunk-N34XHWBA.js.map} +0 -0
  53. /package/dist/{chunk-3ZELDCR3.js.map → chunk-PCE4STAY.js.map} +0 -0
  54. /package/dist/{chunk-TI5TQD6D.js.map → chunk-QDBDYCM2.js.map} +0 -0
  55. /package/dist/{chunk-IE2OUO54.js.map → chunk-UFZD3JAA.js.map} +0 -0
  56. /package/dist/{config-S2TULOMK.js.map → cloud-7WM3YLI4.js.map} +0 -0
  57. /package/dist/{lean-command-D6TZJEFX.js.map → config-KFEHLV6W.js.map} +0 -0
  58. /package/dist/{summaries-TSE44KUQ.js.map → lean-command-C5YPFKHP.js.map} +0 -0
  59. /package/dist/{zvec-F27AR357.js.map → local-OTOY56B4.js.map} +0 -0
  60. /package/dist/{memory-NIBFAMY4.js.map → memory-4RGVLPKG.js.map} +0 -0
  61. /package/dist/{rerank-Q7MIFNKA.js.map → rerank-3P3C2UHN.js.map} +0 -0
package/README.md CHANGED
@@ -18,17 +18,40 @@ codebuddy
18
18
 
19
19
  The setup wizard configures the project and agent integration. Project context
20
20
  works without a database; semantic memory recall optionally uses PostgreSQL
21
- with pgvector and a configured embedding provider.
21
+ with pgvector and a local Hugging Face embedding model (no token needed).
22
+
23
+ Run setup inside the project you want CodeBuddy to read. Claude Desktop
24
+ registration saves that folder's absolute path, so restarting the desktop app
25
+ does not change which project is indexed. `serve` requires an initialized
26
+ project (`.codebuddy/config.json`) and refuses filesystem roots and home folders.
27
+ Older desktop registrations without a project path can be repaired by running
28
+ `codebuddy claude install` from the initialized project folder, then restarting
29
+ Claude Desktop.
22
30
 
23
31
  ## What it does
24
32
 
25
33
  - **Task context:** relevant files, symbols, code excerpts, nearby dependencies,
26
34
  and covering tests within a token budget.
27
- - **Project memory:** decisions, rules, and handoffs stored as readable Markdown.
35
+ - **Project memory:** decisions, rules, and handoffs stored as readable Markdown,
36
+ recalled on every prompt by hybrid lexical + vector search
37
+ ([how it works](docs/semantic-memory.md)).
28
38
  - **Change guidance:** plans, policies, incident history, and risk signals.
29
39
  - **Optional retrieval:** zvec semantic search, Graphify architecture maps, and
30
40
  a bring-your-own LLM layer.
31
41
 
42
+ ## See what it does
43
+
44
+ ```bash
45
+ codebuddy dashboard
46
+ ```
47
+
48
+ This opens a live local page showing:
49
+ - every prompt and the memories recalled for it;
50
+ - MCP tool calls with their latency;
51
+ - the tokens delivered to your agent.
52
+
53
+ It updates as you work, and the traces never leave your machine.
54
+
32
55
  ## Try it without a database
33
56
 
34
57
  ```bash
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFile
3
- } from "./chunk-K63EOA4B.js";
3
+ } from "./chunk-776EQXDV.js";
4
4
 
5
5
  // src/llm/config.ts
6
6
  var BASE_URL = {
@@ -60,4 +60,4 @@ export {
60
60
  resolveLlmConfig,
61
61
  redactLlmKeys
62
62
  };
63
- //# sourceMappingURL=chunk-ITGRANYW.js.map
63
+ //# sourceMappingURL=chunk-6MKEIDX5.js.map
@@ -95,8 +95,15 @@ var fileSchema = z.object({
95
95
  namespace: z.string().min(1).optional(),
96
96
  tokenBudget: z.number().int().positive().optional(),
97
97
  provider: z.object({
98
- type: z.literal("huggingface").default("huggingface"),
99
- apiKey: z.string().min(1).optional()
98
+ type: z.enum(["local", "huggingface", "openrouter"]).default("local"),
99
+ apiKey: z.string().min(1).optional(),
100
+ model: z.string().min(1).optional(),
101
+ relevanceFloor: z.number().min(-1).max(1).optional()
102
+ }).optional(),
103
+ /** Optional cross-encoder that orders recalled facts (off by default). */
104
+ rerank: z.object({
105
+ provider: z.enum(["off", "local", "huggingface", "openrouter"]).default("off"),
106
+ model: z.string().min(1).optional()
100
107
  }).optional(),
101
108
  plan: z.object({
102
109
  policy: z.enum(PLAN_POLICIES).default("suggest")
@@ -175,7 +182,7 @@ async function initConfigFile(cwd = process.cwd()) {
175
182
  // Default to the folder name (sanitized) so a fresh scaffold agrees with
176
183
  // doctor's expected default instead of warning immediately after init.
177
184
  namespace: defaultNamespaceForCwd(cwd),
178
- provider: { type: "huggingface" },
185
+ provider: { type: "local" },
179
186
  tokenBudget: 4e3
180
187
  };
181
188
  await writeFile(path, `${JSON.stringify(template, null, 2)}
@@ -227,8 +234,10 @@ async function loadRuntimeConfig(cwd = process.cwd(), options = {}) {
227
234
  namespace: process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),
228
235
  projectRoot,
229
236
  provider: {
230
- type: "huggingface",
231
- ...hfToken ? { apiKey: hfToken } : {}
237
+ type: file.provider?.type ?? "local",
238
+ ...hfToken ? { apiKey: hfToken } : {},
239
+ ...file.provider?.model ? { model: file.provider.model } : {},
240
+ ...file.provider?.relevanceFloor !== void 0 ? { relevanceFloor: file.provider.relevanceFloor } : {}
232
241
  },
233
242
  ...file.tokenBudget ? { tokenBudget: file.tokenBudget } : {},
234
243
  ...file.graphify ? { graphify: file.graphify } : {}
@@ -355,6 +364,15 @@ async function setLlmEnabled(enabled, options = {}, cwd = process.cwd()) {
355
364
  await writeAtomic(path, `${JSON.stringify(raw, null, 2)}
356
365
  `, { mode: 384 });
357
366
  }
367
+ async function setConfigBlock(key, value, cwd = process.cwd()) {
368
+ await initConfigFile(cwd);
369
+ const path = configPath(cwd);
370
+ const raw = JSON.parse(await readFile(path, "utf8"));
371
+ raw[key] = value;
372
+ fileSchema.parse(raw);
373
+ await writeAtomic(path, `${JSON.stringify(raw, null, 2)}
374
+ `, { mode: 384 });
375
+ }
358
376
  function redactSecrets(value) {
359
377
  if (typeof value === "string") {
360
378
  let redacted = value;
@@ -411,6 +429,7 @@ export {
411
429
  setPlanPolicy,
412
430
  setZvecEnabled,
413
431
  setLlmEnabled,
432
+ setConfigBlock,
414
433
  redactSecrets
415
434
  };
416
- //# sourceMappingURL=chunk-K63EOA4B.js.map
435
+ //# sourceMappingURL=chunk-776EQXDV.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 { constants } from \"node:fs\";\nimport { access, chmod, mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join } 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 try {\n const raw = await readFile(path, \"utf8\");\n return fileSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\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\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 const ok = 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","join"]}
@@ -0,0 +1,99 @@
1
+ import {
2
+ EMBEDDING_DIMENSIONS
3
+ } from "./chunk-JPHJBCZB.js";
4
+
5
+ // src/providers/cloud.ts
6
+ var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
7
+ var HF_ROUTER_BASE_URL = "https://router.huggingface.co/hf-inference/models";
8
+ async function postJson(url, apiKey, body, timeoutMs) {
9
+ const response = await fetch(url, {
10
+ method: "POST",
11
+ headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
12
+ body: JSON.stringify(body),
13
+ signal: AbortSignal.timeout(timeoutMs)
14
+ });
15
+ const text = await response.text();
16
+ if (!response.ok) {
17
+ let message = text.slice(0, 200);
18
+ try {
19
+ const parsed = JSON.parse(text);
20
+ message = typeof parsed.error === "string" ? parsed.error : parsed.error?.message ?? message;
21
+ } catch {
22
+ }
23
+ throw new Error(
24
+ `${new URL(url).host} returned ${response.status}: ${message.split(apiKey).join("[REDACTED]")}`
25
+ );
26
+ }
27
+ return JSON.parse(text);
28
+ }
29
+ var OpenRouterEmbeddingProvider = class {
30
+ constructor(options) {
31
+ this.options = options;
32
+ this.space = {
33
+ model: options.model,
34
+ revision: "hosted",
35
+ schema: `openrouter-embeddings-d${EMBEDDING_DIMENSIONS}`,
36
+ dimensions: EMBEDDING_DIMENSIONS
37
+ };
38
+ }
39
+ options;
40
+ space;
41
+ async embed(request) {
42
+ const input = typeof request.input === "string" ? [request.input] : request.input;
43
+ const json = await postJson(
44
+ `${this.options.baseUrl ?? OPENROUTER_BASE_URL}/embeddings`,
45
+ this.options.apiKey,
46
+ { model: this.space.model, input, dimensions: EMBEDDING_DIMENSIONS },
47
+ this.options.timeoutMs ?? 1e4
48
+ );
49
+ const rows = [...json.data ?? []].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
50
+ const vectors = rows.map((row) => row.embedding ?? []);
51
+ const width = vectors[0]?.length ?? 0;
52
+ if (vectors.length !== input.length || width !== EMBEDDING_DIMENSIONS) {
53
+ throw new Error(
54
+ `OpenRouter model ${this.space.model} returned ${vectors.length} vector(s) of ${width} dimensions; CodeBuddy stores ${EMBEDDING_DIMENSIONS}. Pick a model that supports \`dimensions: ${EMBEDDING_DIMENSIONS}\` (for example openai/text-embedding-3-small).`
55
+ );
56
+ }
57
+ return { ...this.space, vectors };
58
+ }
59
+ async generateText() {
60
+ throw new Error("The OpenRouter embedding provider only embeds.");
61
+ }
62
+ };
63
+ async function rerankOpenRouter(options, query, documents) {
64
+ const json = await postJson(
65
+ `${options.baseUrl ?? OPENROUTER_BASE_URL}/rerank`,
66
+ options.apiKey,
67
+ { model: options.model, query, documents, top_n: documents.length },
68
+ options.timeoutMs ?? 2e3
69
+ );
70
+ const scores = documents.map(() => Number.NEGATIVE_INFINITY);
71
+ for (const result of json.results ?? []) {
72
+ if (result.index >= 0 && result.index < scores.length)
73
+ scores[result.index] = result.relevance_score;
74
+ }
75
+ return scores;
76
+ }
77
+ async function rerankHuggingFace(options, query, documents) {
78
+ const json = await postJson(
79
+ `${options.baseUrl ?? HF_ROUTER_BASE_URL}/${options.model}`,
80
+ options.apiKey,
81
+ { inputs: documents.map((document) => ({ text: query, text_pair: document })) },
82
+ options.timeoutMs ?? 2e3
83
+ );
84
+ if (!Array.isArray(json) || json.length !== documents.length) {
85
+ throw new Error(`Hugging Face reranker ${options.model} returned an unexpected shape.`);
86
+ }
87
+ return json.map(
88
+ (item) => (Array.isArray(item) ? item[0]?.score : item.score) ?? Number.NEGATIVE_INFINITY
89
+ );
90
+ }
91
+
92
+ export {
93
+ OPENROUTER_BASE_URL,
94
+ HF_ROUTER_BASE_URL,
95
+ OpenRouterEmbeddingProvider,
96
+ rerankOpenRouter,
97
+ rerankHuggingFace
98
+ };
99
+ //# sourceMappingURL=chunk-JFDRBPYF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/cloud.ts"],"sourcesContent":["import type {\n EmbeddingRequest,\n EmbeddingResponse,\n ModelProvider,\n TextGenerationResponse,\n} from \"./adapter.js\";\nimport type { EmbeddingSpace } from \"./embedding-space.js\";\nimport { EMBEDDING_DIMENSIONS } from \"./models.js\";\n\n/**\n * Hosted alternatives to the local models. Keys come from the environment\n * (OPENROUTER_API_KEY, HF_TOKEN) and never appear in errors.\n */\n\nexport const OPENROUTER_BASE_URL = \"https://openrouter.ai/api/v1\";\nexport const HF_ROUTER_BASE_URL = \"https://router.huggingface.co/hf-inference/models\";\n\n/** Fetch JSON with a timeout; an error names the status and the provider's message, never the key. */\nasync function postJson(\n url: string,\n apiKey: string,\n body: unknown,\n timeoutMs: number,\n): Promise<unknown> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { authorization: `Bearer ${apiKey}`, \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(timeoutMs),\n });\n const text = await response.text();\n if (!response.ok) {\n let message = text.slice(0, 200);\n try {\n const parsed = JSON.parse(text) as { error?: string | { message?: string } };\n message =\n typeof parsed.error === \"string\" ? parsed.error : (parsed.error?.message ?? message);\n } catch {\n // Not JSON: keep the raw excerpt.\n }\n throw new Error(\n `${new URL(url).host} returned ${response.status}: ${message.split(apiKey).join(\"[REDACTED]\")}`,\n );\n }\n return JSON.parse(text);\n}\n\n/**\n * OpenRouter's OpenAI-compatible embeddings endpoint, asking for 384\n * dimensions (Matryoshka models such as openai/text-embedding-3-small honour\n * it). A model that cannot return 384-d vectors fails with a clear message.\n */\nexport class OpenRouterEmbeddingProvider implements ModelProvider {\n readonly space: EmbeddingSpace;\n\n constructor(\n private readonly options: {\n apiKey: string;\n model: string;\n baseUrl?: string;\n timeoutMs?: number;\n },\n ) {\n this.space = {\n model: options.model,\n revision: \"hosted\",\n schema: `openrouter-embeddings-d${EMBEDDING_DIMENSIONS}`,\n dimensions: EMBEDDING_DIMENSIONS,\n };\n }\n\n async embed(request: EmbeddingRequest): Promise<EmbeddingResponse> {\n const input = typeof request.input === \"string\" ? [request.input] : request.input;\n const json = (await postJson(\n `${this.options.baseUrl ?? OPENROUTER_BASE_URL}/embeddings`,\n this.options.apiKey,\n { model: this.space.model, input, dimensions: EMBEDDING_DIMENSIONS },\n this.options.timeoutMs ?? 10_000,\n )) as { data?: Array<{ index?: number; embedding?: number[] }> };\n const rows = [...(json.data ?? [])].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));\n const vectors = rows.map((row) => row.embedding ?? []);\n const width = vectors[0]?.length ?? 0;\n if (vectors.length !== input.length || width !== EMBEDDING_DIMENSIONS) {\n throw new Error(\n `OpenRouter model ${this.space.model} returned ${vectors.length} vector(s) of ${width} dimensions; ` +\n `CodeBuddy stores ${EMBEDDING_DIMENSIONS}. Pick a model that supports \\`dimensions: ${EMBEDDING_DIMENSIONS}\\` (for example openai/text-embedding-3-small).`,\n );\n }\n return { ...this.space, vectors };\n }\n\n async generateText(): Promise<TextGenerationResponse> {\n throw new Error(\"The OpenRouter embedding provider only embeds.\");\n }\n}\n\n/** OpenRouter `/rerank` (Cohere format): relevance score per document, in input order. */\nexport async function rerankOpenRouter(\n options: { apiKey: string; model: string; baseUrl?: string; timeoutMs?: number },\n query: string,\n documents: string[],\n): Promise<number[]> {\n const json = (await postJson(\n `${options.baseUrl ?? OPENROUTER_BASE_URL}/rerank`,\n options.apiKey,\n { model: options.model, query, documents, top_n: documents.length },\n options.timeoutMs ?? 2_000,\n )) as { results?: Array<{ index: number; relevance_score: number }> };\n const scores = documents.map(() => Number.NEGATIVE_INFINITY);\n for (const result of json.results ?? []) {\n if (result.index >= 0 && result.index < scores.length)\n scores[result.index] = result.relevance_score;\n }\n return scores;\n}\n\n/**\n * Hugging Face Inference (text-classification over query/document pairs) for\n * cross-encoders such as BAAI/bge-reranker-v2-m3. Accepts either one\n * `{score}` per pair or a `[{label, score}]` list per pair.\n */\nexport async function rerankHuggingFace(\n options: { apiKey: string; model: string; baseUrl?: string; timeoutMs?: number },\n query: string,\n documents: string[],\n): Promise<number[]> {\n const json = (await postJson(\n `${options.baseUrl ?? HF_ROUTER_BASE_URL}/${options.model}`,\n options.apiKey,\n { inputs: documents.map((document) => ({ text: query, text_pair: document })) },\n options.timeoutMs ?? 2_000,\n )) as Array<{ score?: number } | Array<{ score?: number }>>;\n if (!Array.isArray(json) || json.length !== documents.length) {\n throw new Error(`Hugging Face reranker ${options.model} returned an unexpected shape.`);\n }\n return json.map(\n (item) => (Array.isArray(item) ? item[0]?.score : item.score) ?? Number.NEGATIVE_INFINITY,\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,99 @@
1
+ // src/providers/models.ts
2
+ var EMBEDDING_DIMENSIONS = 384;
3
+ var LOCAL_EMBEDDING_MODEL = {
4
+ id: "onnx-community/granite-embedding-small-english-r2-ONNX",
5
+ role: "primary",
6
+ kind: "embedding",
7
+ dimensions: EMBEDDING_DIMENSIONS,
8
+ revision: "1dc7835ba0cb9c76a3618d0bf0c427c97671b3c8",
9
+ schema: "transformers.js@4-q8-cls-l2",
10
+ supportsBatch: true,
11
+ relevanceFloor: 0.84
12
+ };
13
+ var DEFAULT_RERANK_MODELS = {
14
+ local: {
15
+ id: "mixedbread-ai/mxbai-rerank-xsmall-v1",
16
+ revision: "b5c6e9da73abc3711f593f705371cdbe9e0fe422"
17
+ },
18
+ huggingface: { id: "BAAI/bge-reranker-v2-m3" },
19
+ openrouter: { id: "cohere/rerank-v3.5" }
20
+ };
21
+ var DEFAULT_CLOUD_EMBEDDING_MODELS = {
22
+ huggingface: "sentence-transformers/all-MiniLM-L6-v2",
23
+ openrouter: "openai/text-embedding-3-small"
24
+ };
25
+ var DEFAULT_EMBEDDING_MODELS = [
26
+ {
27
+ id: "sentence-transformers/all-MiniLM-L6-v2",
28
+ role: "primary",
29
+ kind: "embedding",
30
+ dimensions: EMBEDDING_DIMENSIONS,
31
+ dailyCallBudget: 1e3,
32
+ supportsBatch: true,
33
+ relevanceFloor: 0.42
34
+ },
35
+ {
36
+ id: "BAAI/bge-small-en-v1.5",
37
+ role: "fallback",
38
+ kind: "embedding",
39
+ dimensions: EMBEDDING_DIMENSIONS,
40
+ dailyCallBudget: 1e3,
41
+ supportsBatch: true,
42
+ relevanceFloor: 0.69
43
+ }
44
+ ];
45
+ var DEFAULT_LLM_MODELS = [
46
+ {
47
+ id: "meta-llama/Llama-3.2-3B-Instruct",
48
+ role: "primary",
49
+ kind: "llm",
50
+ contextWindow: 131072,
51
+ dailyCallBudget: 300
52
+ },
53
+ {
54
+ id: "Qwen/Qwen2.5-7B-Instruct",
55
+ role: "fallback",
56
+ kind: "llm",
57
+ contextWindow: 32768,
58
+ dailyCallBudget: 300
59
+ },
60
+ {
61
+ id: "mistralai/Mistral-7B-Instruct-v0.3",
62
+ role: "last_resort",
63
+ kind: "llm",
64
+ contextWindow: 32768,
65
+ dailyCallBudget: 300
66
+ }
67
+ ];
68
+ var MODEL_REGISTRY = [
69
+ LOCAL_EMBEDDING_MODEL,
70
+ ...DEFAULT_EMBEDDING_MODELS,
71
+ ...DEFAULT_LLM_MODELS
72
+ ];
73
+ function getModelDefinition(modelId) {
74
+ return MODEL_REGISTRY.find((model) => model.id === modelId);
75
+ }
76
+ function getModelContextWindow(modelId) {
77
+ return getModelDefinition(modelId)?.contextWindow;
78
+ }
79
+ function assertCompatibleEmbeddingModels(models) {
80
+ const mismatchedModel = models.find((model) => model.dimensions !== EMBEDDING_DIMENSIONS);
81
+ if (mismatchedModel) {
82
+ throw new Error(
83
+ `Embedding model ${mismatchedModel.id} has ${mismatchedModel.dimensions ?? "unknown"} dimensions; expected ${EMBEDDING_DIMENSIONS}.`
84
+ );
85
+ }
86
+ }
87
+
88
+ export {
89
+ EMBEDDING_DIMENSIONS,
90
+ LOCAL_EMBEDDING_MODEL,
91
+ DEFAULT_RERANK_MODELS,
92
+ DEFAULT_CLOUD_EMBEDDING_MODELS,
93
+ DEFAULT_EMBEDDING_MODELS,
94
+ DEFAULT_LLM_MODELS,
95
+ getModelDefinition,
96
+ getModelContextWindow,
97
+ assertCompatibleEmbeddingModels
98
+ };
99
+ //# sourceMappingURL=chunk-JPHJBCZB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/models.ts"],"sourcesContent":["export type ModelRole = \"primary\" | \"fallback\" | \"last_resort\";\nexport type ModelKind = \"embedding\" | \"llm\";\n\nexport type ModelDefinition = {\n id: string;\n role: ModelRole;\n kind: ModelKind;\n dimensions?: number;\n /** Actual deployment revision metadata; this does not pin Hugging Face requests. */\n revision?: string;\n schema?: string;\n contextWindow?: number;\n dailyCallBudget?: number;\n supportsBatch?: boolean;\n /**\n * Cosine at or above which a memory counts as semantically relevant to a\n * task on its own (without a shared word or path). Calibrated per model by\n * `npm run eval:semantic`; cosines are not comparable across models.\n */\n relevanceFloor?: number;\n};\n\nexport const EMBEDDING_DIMENSIONS = 384;\n\n/**\n * Default local embedder (ONNX via transformers.js, no network after the first\n * download): ModernBERT-based, 384-d, 8k context, trained on code retrieval.\n * The revision is a pinned commit so the embedding space never drifts.\n */\nexport const LOCAL_EMBEDDING_MODEL = {\n id: \"onnx-community/granite-embedding-small-english-r2-ONNX\",\n role: \"primary\",\n kind: \"embedding\",\n dimensions: EMBEDDING_DIMENSIONS,\n revision: \"1dc7835ba0cb9c76a3618d0bf0c427c97671b3c8\",\n schema: \"transformers.js@4-q8-cls-l2\",\n supportsBatch: true,\n relevanceFloor: 0.84,\n} satisfies ModelDefinition;\n\n/**\n * Cross-encoder rerankers per `rerank.provider` (off by default: on\n * `npm run eval:semantic` none beat granite's dense order). The local default\n * was the best small one measured (13/20 first results, ~20 ms per prompt).\n */\nexport const DEFAULT_RERANK_MODELS = {\n local: {\n id: \"mixedbread-ai/mxbai-rerank-xsmall-v1\",\n revision: \"b5c6e9da73abc3711f593f705371cdbe9e0fe422\",\n },\n huggingface: { id: \"BAAI/bge-reranker-v2-m3\" },\n openrouter: { id: \"cohere/rerank-v3.5\" },\n} as const;\n\n/** Default hosted embedders; each must return 384-d vectors. */\nexport const DEFAULT_CLOUD_EMBEDDING_MODELS = {\n huggingface: \"sentence-transformers/all-MiniLM-L6-v2\",\n openrouter: \"openai/text-embedding-3-small\",\n} as const;\n\nexport const DEFAULT_EMBEDDING_MODELS = [\n {\n id: \"sentence-transformers/all-MiniLM-L6-v2\",\n role: \"primary\",\n kind: \"embedding\",\n dimensions: EMBEDDING_DIMENSIONS,\n dailyCallBudget: 1_000,\n supportsBatch: true,\n relevanceFloor: 0.42,\n },\n {\n id: \"BAAI/bge-small-en-v1.5\",\n role: \"fallback\",\n kind: \"embedding\",\n dimensions: EMBEDDING_DIMENSIONS,\n dailyCallBudget: 1_000,\n supportsBatch: true,\n relevanceFloor: 0.69,\n },\n] satisfies ModelDefinition[];\n\nexport const DEFAULT_LLM_MODELS = [\n {\n id: \"meta-llama/Llama-3.2-3B-Instruct\",\n role: \"primary\",\n kind: \"llm\",\n contextWindow: 131_072,\n dailyCallBudget: 300,\n },\n {\n id: \"Qwen/Qwen2.5-7B-Instruct\",\n role: \"fallback\",\n kind: \"llm\",\n contextWindow: 32_768,\n dailyCallBudget: 300,\n },\n {\n id: \"mistralai/Mistral-7B-Instruct-v0.3\",\n role: \"last_resort\",\n kind: \"llm\",\n contextWindow: 32_768,\n dailyCallBudget: 300,\n },\n] satisfies ModelDefinition[];\n\nexport const MODEL_REGISTRY: ModelDefinition[] = [\n LOCAL_EMBEDDING_MODEL,\n ...DEFAULT_EMBEDDING_MODELS,\n ...DEFAULT_LLM_MODELS,\n];\n\nexport function getModelDefinition(modelId: string): ModelDefinition | undefined {\n return MODEL_REGISTRY.find((model) => model.id === modelId);\n}\n\nexport function getModelContextWindow(modelId: string): number | undefined {\n return getModelDefinition(modelId)?.contextWindow;\n}\n\nexport function assertCompatibleEmbeddingModels(models: ModelDefinition[]): void {\n const mismatchedModel = models.find((model) => model.dimensions !== EMBEDDING_DIMENSIONS);\n\n if (mismatchedModel) {\n throw new Error(\n `Embedding model ${mismatchedModel.id} has ${mismatchedModel.dimensions ?? \"unknown\"} dimensions; expected ${EMBEDDING_DIMENSIONS}.`,\n );\n }\n}\n"],"mappings":";AAsBO,IAAM,uBAAuB;AAO7B,IAAM,wBAAwB;AAAA,EACnC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,eAAe;AAAA,EACf,gBAAgB;AAClB;AAOO,IAAM,wBAAwB;AAAA,EACnC,OAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU;AAAA,EACZ;AAAA,EACA,aAAa,EAAE,IAAI,0BAA0B;AAAA,EAC7C,YAAY,EAAE,IAAI,qBAAqB;AACzC;AAGO,IAAM,iCAAiC;AAAA,EAC5C,aAAa;AAAA,EACb,YAAY;AACd;AAEO,IAAM,2BAA2B;AAAA,EACtC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AACF;AAEO,IAAM,qBAAqB;AAAA,EAChC;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe;AAAA,IACf,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe;AAAA,IACf,iBAAiB;AAAA,EACnB;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,IACN,eAAe;AAAA,IACf,iBAAiB;AAAA,EACnB;AACF;AAEO,IAAM,iBAAoC;AAAA,EAC/C;AAAA,EACA,GAAG;AAAA,EACH,GAAG;AACL;AAEO,SAAS,mBAAmB,SAA8C;AAC/E,SAAO,eAAe,KAAK,CAAC,UAAU,MAAM,OAAO,OAAO;AAC5D;AAEO,SAAS,sBAAsB,SAAqC;AACzE,SAAO,mBAAmB,OAAO,GAAG;AACtC;AAEO,SAAS,gCAAgC,QAAiC;AAC/E,QAAM,kBAAkB,OAAO,KAAK,CAAC,UAAU,MAAM,eAAe,oBAAoB;AAExF,MAAI,iBAAiB;AACnB,UAAM,IAAI;AAAA,MACR,mBAAmB,gBAAgB,EAAE,QAAQ,gBAAgB,cAAc,SAAS,yBAAyB,oBAAoB;AAAA,IACnI;AAAA,EACF;AACF;","names":[]}
@@ -6,7 +6,7 @@ import {
6
6
  assertNoSymlinkBetween,
7
7
  readConfigFile,
8
8
  writeAtomic
9
- } from "./chunk-K63EOA4B.js";
9
+ } from "./chunk-776EQXDV.js";
10
10
 
11
11
  // src/map/indexer.ts
12
12
  import { readdir, readFile, stat } from "fs/promises";
@@ -1077,10 +1077,10 @@ async function discoverTaskPaths(repositoryRoot, task, manifest, mapSource, opti
1077
1077
  async function withLlmSummaries(repositoryRoot, manifest) {
1078
1078
  const entries = Object.values(manifest.entries);
1079
1079
  try {
1080
- const { resolveLlmConfig } = await import("./config-S2TULOMK.js");
1080
+ const { resolveLlmConfig } = await import("./config-KFEHLV6W.js");
1081
1081
  const config = await resolveLlmConfig(repositoryRoot);
1082
1082
  if (!config.enabled || !config.features.summaries) return entries;
1083
- const { loadLlmSummaries } = await import("./summaries-TSE44KUQ.js");
1083
+ const { loadLlmSummaries } = await import("./summaries-ZBV5IN7Y.js");
1084
1084
  const summaries = await loadLlmSummaries(repositoryRoot, manifest);
1085
1085
  if (summaries.size === 0) return entries;
1086
1086
  return entries.map((entry) => {
@@ -1132,7 +1132,7 @@ async function rankTaskFiles(repositoryRoot, task, manifest, mapSource, options)
1132
1132
  reason: "semantic retrieval off",
1133
1133
  stale: 0,
1134
1134
  changed: 0
1135
- } : await import("./zvec-F27AR357.js").then(({ zvecCandidates }) => zvecCandidates(repositoryRoot, task)).catch(
1135
+ } : await import("./zvec-MAPSJQSR.js").then(({ zvecCandidates }) => zvecCandidates(repositoryRoot, task)).catch(
1136
1136
  () => ({
1137
1137
  status: "failed",
1138
1138
  paths: [],
@@ -1232,7 +1232,7 @@ var MIN_RERANK_MS = 200;
1232
1232
  async function llmRerank(input) {
1233
1233
  let config;
1234
1234
  try {
1235
- config = await (await import("./config-S2TULOMK.js")).resolveLlmConfig(input.repositoryRoot);
1235
+ config = await (await import("./config-KFEHLV6W.js")).resolveLlmConfig(input.repositoryRoot);
1236
1236
  } catch {
1237
1237
  return null;
1238
1238
  }
@@ -1257,7 +1257,7 @@ async function llmRerank(input) {
1257
1257
  input.deadlineAt === void 0 ? config.timeoutMs : input.deadlineAt - Date.now()
1258
1258
  );
1259
1259
  if (deadlineMs < MIN_RERANK_MS) return skipped("no time left in the hook budget");
1260
- const { rerankCandidates } = await import("./rerank-Q7MIFNKA.js");
1260
+ const { rerankCandidates } = await import("./rerank-3P3C2UHN.js");
1261
1261
  const result = await rerankCandidates(
1262
1262
  input.repositoryRoot,
1263
1263
  config,
@@ -1575,4 +1575,4 @@ export {
1575
1575
  namedSegments,
1576
1576
  loadProjectArchitectureMap
1577
1577
  };
1578
- //# sourceMappingURL=chunk-IBMJYIEP.js.map
1578
+ //# sourceMappingURL=chunk-L5AYZC3F.js.map
@@ -8,7 +8,7 @@ import {
8
8
  detectTestCommand,
9
9
  estimateTokens,
10
10
  findCoveringTest
11
- } from "./chunk-5OWZVZSO.js";
11
+ } from "./chunk-SYWJG5NA.js";
12
12
  import {
13
13
  NON_SOURCE_PATH,
14
14
  discoverTaskPaths,
@@ -16,7 +16,7 @@ import {
16
16
  loadProjectArchitectureMap,
17
17
  readBoundedSource,
18
18
  taskTerms
19
- } from "./chunk-IBMJYIEP.js";
19
+ } from "./chunk-L5AYZC3F.js";
20
20
 
21
21
  // src/hooks/lean.ts
22
22
  import { createHash } from "crypto";
@@ -172,4 +172,4 @@ export {
172
172
  renderLeanBlock,
173
173
  runLeanHookCommand
174
174
  };
175
- //# sourceMappingURL=chunk-DSN5CEHO.js.map
175
+ //# sourceMappingURL=chunk-N34XHWBA.js.map
@@ -1,23 +1,23 @@
1
+ import {
2
+ chatJson
3
+ } from "./chunk-UFZD3JAA.js";
1
4
  import {
2
5
  NON_SOURCE_PATH,
3
6
  TEST_PATH,
4
7
  loadFreshIndex,
5
8
  loadProjectArchitectureMap,
6
9
  readBoundedSource
7
- } from "./chunk-IBMJYIEP.js";
8
- import {
9
- chatJson
10
- } from "./chunk-IE2OUO54.js";
10
+ } from "./chunk-L5AYZC3F.js";
11
11
  import {
12
12
  redactSensitive,
13
13
  scanSensitive
14
14
  } from "./chunk-GDJH7XJC.js";
15
15
  import {
16
16
  resolveLlmConfig
17
- } from "./chunk-ITGRANYW.js";
17
+ } from "./chunk-6MKEIDX5.js";
18
18
  import {
19
19
  writeAtomic
20
- } from "./chunk-K63EOA4B.js";
20
+ } from "./chunk-776EQXDV.js";
21
21
 
22
22
  // src/llm/summaries.ts
23
23
  import { mkdir, readFile } from "fs/promises";
@@ -201,4 +201,4 @@ export {
201
201
  loadLlmSummaries,
202
202
  summarizeRepository
203
203
  };
204
- //# sourceMappingURL=chunk-3ZELDCR3.js.map
204
+ //# sourceMappingURL=chunk-PCE4STAY.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  readConfigFile,
3
3
  writeAtomic
4
- } from "./chunk-K63EOA4B.js";
4
+ } from "./chunk-776EQXDV.js";
5
5
 
6
6
  // src/retrieval/zvec.ts
7
7
  import { execFile, spawn } from "child_process";
@@ -321,4 +321,4 @@ export {
321
321
  zvecStatus,
322
322
  buildZvecIndex
323
323
  };
324
- //# sourceMappingURL=chunk-TI5TQD6D.js.map
324
+ //# sourceMappingURL=chunk-QDBDYCM2.js.map