@orquentra/codebuddy 2.14.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/ARCHITECTURE.md +98 -0
  2. package/README.md +87 -6
  3. package/dist/{chunk-LW75SKZR.js → chunk-4BSO3HST.js} +126 -18
  4. package/dist/chunk-4BSO3HST.js.map +1 -0
  5. package/dist/{chunk-CAEPHLHC.js → chunk-57642NGU.js} +2 -44
  6. package/dist/chunk-57642NGU.js.map +1 -0
  7. package/dist/{chunk-ESZXCZIN.js → chunk-6YI2D5F5.js} +33 -5
  8. package/dist/chunk-6YI2D5F5.js.map +1 -0
  9. package/dist/{chunk-4DKYKDHI.js → chunk-7IPSDDO5.js} +5 -3
  10. package/dist/{chunk-4DKYKDHI.js.map → chunk-7IPSDDO5.js.map} +1 -1
  11. package/dist/chunk-BTTQWSDS.js +372 -0
  12. package/dist/chunk-BTTQWSDS.js.map +1 -0
  13. package/dist/{chunk-YJPCS2GP.js → chunk-D5E7SGI5.js} +57 -18
  14. package/dist/chunk-D5E7SGI5.js.map +1 -0
  15. package/dist/chunk-EMWGLKPR.js +45 -0
  16. package/dist/chunk-EMWGLKPR.js.map +1 -0
  17. package/dist/chunk-ITXE27SZ.js +358 -0
  18. package/dist/chunk-ITXE27SZ.js.map +1 -0
  19. package/dist/{chunk-LRQNPYCK.js → chunk-JJQDVGF7.js} +13 -7
  20. package/dist/chunk-JJQDVGF7.js.map +1 -0
  21. package/dist/{chunk-XSNW6Y3X.js → chunk-KQCP3ZYH.js} +28 -24
  22. package/dist/chunk-KQCP3ZYH.js.map +1 -0
  23. package/dist/{chunk-YWDH6DQ6.js → chunk-KYRM3HPP.js} +21 -19
  24. package/dist/{chunk-YWDH6DQ6.js.map → chunk-KYRM3HPP.js.map} +1 -1
  25. package/dist/{chunk-KNR2P2UY.js → chunk-M75SN4D4.js} +19 -4
  26. package/dist/chunk-M75SN4D4.js.map +1 -0
  27. package/dist/{chunk-L6BRYZ6G.js → chunk-RZ266NHE.js} +7 -7
  28. package/dist/chunk-U7KDM62P.js +43 -0
  29. package/dist/chunk-U7KDM62P.js.map +1 -0
  30. package/dist/chunk-UATJCD2T.js +368 -0
  31. package/dist/chunk-UATJCD2T.js.map +1 -0
  32. package/dist/{chunk-YNSTIMCF.js → chunk-UR3DF74I.js} +352 -157
  33. package/dist/chunk-UR3DF74I.js.map +1 -0
  34. package/dist/chunk-UTPC66MV.js +128 -0
  35. package/dist/chunk-UTPC66MV.js.map +1 -0
  36. package/dist/chunk-WHKXUXHE.js +156 -0
  37. package/dist/chunk-WHKXUXHE.js.map +1 -0
  38. package/dist/{chunk-7QMHIJBA.js → chunk-YMXFSHXT.js} +969 -968
  39. package/dist/chunk-YMXFSHXT.js.map +1 -0
  40. package/dist/cli/index.js +4 -4
  41. package/dist/config-TXQJCCVI.js +15 -0
  42. package/dist/expand-KWYGNCKZ.js +98 -0
  43. package/dist/expand-KWYGNCKZ.js.map +1 -0
  44. package/dist/file-vectors-MKXTSGGH.js +19 -0
  45. package/dist/index-facts-4MTEHKMN.js +38 -0
  46. package/dist/index-pass-TBDUEZCB.js +14 -0
  47. package/dist/index.js +24 -22
  48. package/dist/{install-EU35ERK5.js → install-E3JWINTN.js} +11 -10
  49. package/dist/{lean-43ADYTBW.js → lean-MLTAFS5L.js} +8 -7
  50. package/dist/local-75DJECVR.js +48 -0
  51. package/dist/local-llm-MECVJTVU.js +19 -0
  52. package/dist/local-llm-MECVJTVU.js.map +1 -0
  53. package/dist/{main-SFC5LV4E.js → main-RLMKQZW4.js} +332 -123
  54. package/dist/main-RLMKQZW4.js.map +1 -0
  55. package/dist/{memory-TGHVTBME.js → memory-PFFVMTCH.js} +9 -6
  56. package/dist/{memory-TGHVTBME.js.map → memory-PFFVMTCH.js.map} +1 -1
  57. package/dist/{operations-6Z6ZDVBP.js → operations-STR7S5UO.js} +8 -7
  58. package/dist/operations-STR7S5UO.js.map +1 -0
  59. package/dist/{rerank-IQAEWLQR.js → rerank-XXKWDI5Q.js} +7 -4
  60. package/dist/{rerank-IQAEWLQR.js.map → rerank-XXKWDI5Q.js.map} +1 -1
  61. package/dist/{runtime-W5AW32SD.js → runtime-SWFWGTQ4.js} +9 -8
  62. package/dist/runtime-SWFWGTQ4.js.map +1 -0
  63. package/dist/summaries-QXWFFDUZ.js +31 -0
  64. package/dist/summaries-QXWFFDUZ.js.map +1 -0
  65. package/dist/{zvec-24HLAFGN.js → zvec-F73IBWO5.js} +13 -5
  66. package/dist/zvec-F73IBWO5.js.map +1 -0
  67. package/package.json +3 -2
  68. package/dist/chunk-7QMHIJBA.js.map +0 -1
  69. package/dist/chunk-CAEPHLHC.js.map +0 -1
  70. package/dist/chunk-ESC4Y3L7.js +0 -204
  71. package/dist/chunk-ESC4Y3L7.js.map +0 -1
  72. package/dist/chunk-ESZXCZIN.js.map +0 -1
  73. package/dist/chunk-KNR2P2UY.js.map +0 -1
  74. package/dist/chunk-LRQNPYCK.js.map +0 -1
  75. package/dist/chunk-LW75SKZR.js.map +0 -1
  76. package/dist/chunk-SFMIMLXR.js +0 -63
  77. package/dist/chunk-SFMIMLXR.js.map +0 -1
  78. package/dist/chunk-XSNW6Y3X.js.map +0 -1
  79. package/dist/chunk-YJPCS2GP.js.map +0 -1
  80. package/dist/chunk-YNSTIMCF.js.map +0 -1
  81. package/dist/config-GEZFLXK6.js +0 -12
  82. package/dist/main-SFC5LV4E.js.map +0 -1
  83. package/dist/summaries-32PHOB6L.js +0 -20
  84. /package/dist/{chunk-L6BRYZ6G.js.map → chunk-RZ266NHE.js.map} +0 -0
  85. /package/dist/{config-GEZFLXK6.js.map → config-TXQJCCVI.js.map} +0 -0
  86. /package/dist/{install-EU35ERK5.js.map → file-vectors-MKXTSGGH.js.map} +0 -0
  87. /package/dist/{lean-43ADYTBW.js.map → index-facts-4MTEHKMN.js.map} +0 -0
  88. /package/dist/{operations-6Z6ZDVBP.js.map → index-pass-TBDUEZCB.js.map} +0 -0
  89. /package/dist/{runtime-W5AW32SD.js.map → install-E3JWINTN.js.map} +0 -0
  90. /package/dist/{summaries-32PHOB6L.js.map → lean-MLTAFS5L.js.map} +0 -0
  91. /package/dist/{zvec-24HLAFGN.js.map → local-75DJECVR.js.map} +0 -0
@@ -70,15 +70,28 @@ var fileSchema = z.object({
70
70
  */
71
71
  llm: z.object({
72
72
  enabled: z.boolean(),
73
- provider: z.enum(["groq", "openrouter"]).optional(),
73
+ provider: z.enum(["groq", "openrouter", "local"]).optional(),
74
74
  model: z.string().min(1).optional(),
75
+ /** Local model size (`codebuddy models add local-llm --size`); default "default". */
76
+ size: z.enum(["default", "better"]).optional(),
75
77
  fallback: z.object({ provider: z.enum(["groq", "openrouter"]), model: z.string().min(1) }).optional(),
76
78
  timeoutMs: z.number().int().min(100).max(5e3).optional(),
77
79
  dailyTokenBudget: z.number().int().positive().optional(),
78
80
  features: z.object({
79
81
  summaries: z.boolean().optional(),
80
82
  rerank: z.boolean().optional(),
81
- memory: z.boolean().optional()
83
+ memory: z.boolean().optional(),
84
+ facts: z.boolean().optional(),
85
+ expansion: z.boolean().optional()
86
+ }).optional()
87
+ }).optional(),
88
+ /** Task → file discovery tuning (`context/discovery.ts`). */
89
+ discovery: z.object({
90
+ /** The agreement rule: top k per dense signal and the cosine floors for a vote. */
91
+ agreement: z.object({
92
+ topK: z.number().int().min(1).max(20).optional(),
93
+ chunkFloor: z.number().min(-1).max(1).optional(),
94
+ cardFloor: z.number().min(-1).max(1).optional()
82
95
  }).optional()
83
96
  }).optional(),
84
97
  /** Optional local semantic retrieval via zvec-grep (`zg`). Off unless enabled. */
@@ -87,7 +100,9 @@ var fileSchema = z.object({
87
100
  binary: z.string().min(1).optional(),
88
101
  timeoutMs: z.number().int().positive().max(1e4).optional(),
89
102
  /** Task IDF coverage a vector-only file needs (0..1, default 0.3; docs/zvec-floor.md). */
90
- minScore: z.number().min(0).max(1).optional()
103
+ minScore: z.number().min(0).max(1).optional(),
104
+ /** zg embedding model for the intent-card workspace (default local/bge-small-en-v1.5). */
105
+ cardModel: z.string().min(1).optional()
91
106
  }).optional()
92
107
  });
93
108
  function configPath(cwd = process.cwd()) {
@@ -459,4 +474,4 @@ export {
459
474
  setConfigBlock,
460
475
  redactSecrets
461
476
  };
462
- //# sourceMappingURL=chunk-KNR2P2UY.js.map
477
+ //# sourceMappingURL=chunk-M75SN4D4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/config-file.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { constants } from \"node:fs\";\nimport { access, chmod, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join, resolve } from \"node:path\";\nimport { z } from \"zod\";\nimport { writeAtomic } from \"./markdown-store-fs.js\";\nimport type { CodeBuddyConfig } from \"./types.js\";\n\nexport const CONFIG_DIR = \".codebuddy\";\nexport const CONFIG_FILE = \"config.json\";\nexport const POLICY_FILE = \"policies.yaml\";\n\nconst PROJECT_GITIGNORE = [\n \"# CodeBuddy local-only files\",\n \"/config.json\",\n \"/cache/\",\n \"/memory/review/\",\n \"/memory/review-archive/\",\n \"/plans/.locks/\",\n \"/memory/.locks/\",\n \"\",\n \"# Keep these reviewable project artifacts shareable by default:\",\n \"# /memory/facts/*.md\",\n \"# /memory/summaries/*.md\",\n \"# /plans/*.md\",\n \"# If your project memory is private, ignore /memory/ in the repo root .gitignore.\",\n \"\",\n].join(\"\\n\");\n\nconst DEFAULT_POLICY_FILE = [\n \"rules:\",\n \" - id: auth-sensitive\",\n ' pattern: \"src/auth/**\"',\n ' message: \"Auth changes need careful review.\"',\n \" weight: 70\",\n \"\",\n].join(\"\\n\");\n\n/**\n * Plan policy controls how strongly CodeBuddy nudges agents toward writing\n * a plan before substantive work:\n * - off: plans are never suggested or required.\n * - suggest: agents are reminded to plan, but small tasks stay lightweight (default).\n * - required: agents should refuse substantive changes without an active plan.\n */\nexport const PLAN_POLICIES = [\"off\", \"suggest\", \"required\"] as const;\nexport type PlanPolicy = (typeof PLAN_POLICIES)[number];\n\nconst fileSchema = z.object({\n postgresUrl: z.string().min(1).optional(),\n namespace: z.string().min(1).optional(),\n tokenBudget: z.number().int().positive().optional(),\n provider: z\n .object({\n type: z.enum([\"local\", \"huggingface\", \"openrouter\"]).default(\"local\"),\n apiKey: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n relevanceFloor: z.number().min(-1).max(1).optional(),\n recallFloor: z.number().min(-1).max(1).optional(),\n })\n .optional(),\n /** Optional cross-encoder that orders recalled facts (off by default). */\n rerank: z\n .object({\n provider: z.enum([\"off\", \"local\", \"huggingface\", \"openrouter\"]).default(\"off\"),\n model: z.string().min(1).optional(),\n })\n .optional(),\n plan: z\n .object({\n policy: z.enum(PLAN_POLICIES).default(\"suggest\"),\n })\n .optional(),\n graphify: z\n .object({\n enabled: z.boolean(),\n graphPath: z.string().min(1).optional(),\n })\n .optional(),\n /** Git churn risk signal (`risk/signals/churn.ts`); defaults keep the 30-day window. */\n risk: z\n .object({\n churnWindowDays: z.number().int().positive().max(3650).optional(),\n churnMode: z.enum([\"window\", \"lifetime-size\"]).optional(),\n })\n .optional(),\n /**\n * Optional hosted small-model layer (bring your own key). Keys are read from\n * the environment only, never from this file (`src/llm/config.ts`).\n */\n llm: z\n .object({\n enabled: z.boolean(),\n provider: z.enum([\"groq\", \"openrouter\", \"local\"]).optional(),\n model: z.string().min(1).optional(),\n /** Local model size (`codebuddy models add local-llm --size`); default \"default\". */\n size: z.enum([\"default\", \"better\"]).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 facts: z.boolean().optional(),\n expansion: z.boolean().optional(),\n })\n .optional(),\n })\n .optional(),\n /** Task → file discovery tuning (`context/discovery.ts`). */\n discovery: z\n .object({\n /** The agreement rule: top k per dense signal and the cosine floors for a vote. */\n agreement: z\n .object({\n topK: z.number().int().min(1).max(20).optional(),\n chunkFloor: z.number().min(-1).max(1).optional(),\n cardFloor: z.number().min(-1).max(1).optional(),\n })\n .optional(),\n })\n .optional(),\n /** Optional local semantic retrieval via zvec-grep (`zg`). Off unless enabled. */\n zvec: z\n .object({\n enabled: z.boolean(),\n binary: z.string().min(1).optional(),\n timeoutMs: z.number().int().positive().max(10_000).optional(),\n /** Task IDF coverage a vector-only file needs (0..1, default 0.3; docs/zvec-floor.md). */\n minScore: z.number().min(0).max(1).optional(),\n /** zg embedding model for the intent-card workspace (default local/bge-small-en-v1.5). */\n cardModel: z.string().min(1).optional(),\n })\n .optional(),\n});\n\nexport type CodeBuddyFileConfig = z.infer<typeof fileSchema>;\n\nexport type ConfigPermissionReport = {\n path: string;\n exists: boolean;\n mode: string | null;\n ok: boolean;\n warning?: string;\n};\n\nexport type ProjectScaffoldReport = {\n codebuddyDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memoryFactsDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memorySummariesDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n plansDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n gitignore: { path: string; exists: boolean; ok: boolean; warning?: string };\n policies: { path: string; exists: boolean; ok: boolean; warning?: string };\n};\n\nexport function configPath(cwd = process.cwd()): string {\n return join(cwd, CONFIG_DIR, CONFIG_FILE);\n}\n\nexport async function initProjectScaffold(cwd = process.cwd()): Promise<{\n codebuddyDir: string;\n gitignorePath: string;\n policyPath: string;\n}> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n await mkdir(join(codebuddyDir, \"memory\", \"facts\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"memory\", \"summaries\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"plans\"), { recursive: true, mode: 0o700 });\n\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n try {\n await access(gitignorePath, constants.F_OK);\n let gitignore = await readFile(gitignorePath, \"utf8\");\n const lines = new Set(gitignore.split(/\\r?\\n/));\n const missing = [\"/memory/review/\", \"/memory/review-archive/\"].filter(\n (line) => !lines.has(line),\n );\n if (missing.length > 0) {\n gitignore = `${gitignore.trimEnd()}\\n${missing.join(\"\\n\")}\\n`;\n await writeFile(gitignorePath, gitignore, { mode: 0o644 });\n }\n } catch {\n await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 0o644 });\n }\n\n const policyPath = join(codebuddyDir, POLICY_FILE);\n try {\n await access(policyPath, constants.F_OK);\n } catch {\n await writeFile(policyPath, DEFAULT_POLICY_FILE, { mode: 0o644 });\n }\n\n return { codebuddyDir, gitignorePath, policyPath };\n}\n\nexport async function initConfigFile(\n cwd = process.cwd(),\n): Promise<{ path: string; created: boolean }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n try {\n await access(path, constants.F_OK);\n await chmod(path, 0o600);\n return { path, created: false };\n } catch {\n const template: CodeBuddyFileConfig = {\n postgresUrl: process.env.DATABASE_URL ?? \"postgres://localhost:5432/codebuddy\",\n // Default to the folder name (sanitized) so a fresh scaffold agrees with\n // doctor's expected default instead of warning immediately after init.\n namespace: defaultNamespaceForCwd(cwd),\n provider: { type: \"local\" },\n tokenBudget: 4000,\n };\n await writeFile(path, `${JSON.stringify(template, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path, created: true };\n }\n}\n\n/** Persist a complete project config without changing its private permissions. */\nexport async function writeConfigFile(\n config: CodeBuddyFileConfig,\n cwd = process.cwd(),\n): Promise<{ path: string }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path };\n}\n\nexport async function readConfigFile(cwd = process.cwd()): Promise<CodeBuddyFileConfig> {\n const path = configPath(cwd);\n let parsed: CodeBuddyFileConfig;\n try {\n const raw = await readFile(path, \"utf8\");\n parsed = fileSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n if ((parsed.zvec?.binary !== undefined || parsed.postgresUrl) && (await trackedByGit(cwd))) {\n return withoutRepositorySuppliedSecrets(parsed, path);\n }\n return parsed;\n}\n\n/**\n * `init` gitignores `.codebuddy/config.json`, so a tracked one came with the\n * repository, not from this user. A cloned repository must not choose a\n * program for CodeBuddy to run (`zvec.binary`) or a database to send project\n * memory to (`postgresUrl`); those two keys are ignored in that case and must\n * come from the environment (DATABASE_URL) or an untracked config.\n */\nfunction withoutRepositorySuppliedSecrets(\n config: CodeBuddyFileConfig,\n path: string,\n): CodeBuddyFileConfig {\n const { postgresUrl: _postgresUrl, ...rest } = config;\n const ignored = [\n ...(config.postgresUrl ? [\"postgresUrl\"] : []),\n ...(config.zvec?.binary !== undefined ? [\"zvec.binary\"] : []),\n ];\n if (!warnedTracked.has(path)) {\n warnedTracked.add(path);\n process.stderr.write(\n `codebuddy: ignoring ${ignored.join(\" and \")} in ${path}: the file is tracked by git, so it came with the repository. ` +\n \"Untrack it (git rm --cached) or set DATABASE_URL.\\n\",\n );\n }\n if (!rest.zvec) return rest;\n const { binary: _binary, ...zvec } = rest.zvec;\n return { ...rest, zvec };\n}\n\nconst warnedTracked = new Set<string>();\nconst trackedCache = new Map<string, Promise<boolean>>();\n\n/** Whether `.codebuddy/config.json` under `root` is tracked by git (cached per process). */\nfunction trackedByGit(root: string): Promise<boolean> {\n const key = resolve(root);\n let pending = trackedCache.get(key);\n if (!pending) {\n pending = new Promise((done) => {\n execFile(\n \"git\",\n [\"ls-files\", \"--error-unmatch\", \"--\", \".codebuddy/config.json\"],\n { cwd: key, timeout: 5_000 },\n (error) => done(error === null),\n );\n });\n trackedCache.set(key, pending);\n }\n return pending;\n}\n\n/**\n * The ONE namespace-resolution rule, shared by the DB/MCP runtime, every\n * file-based CLI command, and doctor. Precedence:\n * 1. CODEBUDDY_NAMESPACE env\n * 2. `.codebuddy/config.json` `namespace`\n * 3. the current folder name (sanitized) — matching what doctor reports as\n * the expected default.\n */\nexport function sanitizeNamespace(value: string): string {\n return value.replace(/[^a-zA-Z0-9_.:-]/g, \"-\").slice(0, 128);\n}\n\n/** Folder-name fallback used when neither env nor config specify a namespace. */\nexport function defaultNamespaceForCwd(cwd = process.cwd()): string {\n return sanitizeNamespace(basename(cwd)) || \"default\";\n}\n\nexport async function resolveNamespaceFrom(cwd = process.cwd()): Promise<string> {\n if (process.env.CODEBUDDY_NAMESPACE) return process.env.CODEBUDDY_NAMESPACE;\n const file = await readConfigFile(cwd);\n return file.namespace ?? defaultNamespaceForCwd(cwd);\n}\n\n/** Scaffolds before 2.0 wrote this literal, so every project shared one namespace. */\nconst LEGACY_NAMESPACE = \"default\";\n\n/**\n * Rewrites a legacy `\"namespace\": \"default\"` in `.codebuddy/config.json` to the\n * folder's default and returns the new namespace, or null when nothing changed.\n * Skipped when the file is tracked by git (it belongs to the repository) or\n * when Markdown memory was saved under \"default\" (it would drop out of recall).\n */\nexport async function migrateLegacyNamespace(cwd = process.cwd()): Promise<string | null> {\n const target = defaultNamespaceForCwd(cwd);\n if (target === LEGACY_NAMESPACE) return null;\n const path = configPath(cwd);\n let raw: Record<string, unknown>;\n try {\n raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n if (raw.namespace !== LEGACY_NAMESPACE) return null;\n if ((await trackedByGit(cwd)) || (await hasMemoryInNamespace(cwd, LEGACY_NAMESPACE))) {\n return null;\n }\n await writeAtomic(path, `${JSON.stringify({ ...raw, namespace: target }, null, 2)}\\n`);\n return target;\n}\n\nasync function hasMemoryInNamespace(cwd: string, namespace: string): Promise<boolean> {\n const memoryDir = join(cwd, \".codebuddy\", \"memory\");\n let entries: string[];\n try {\n entries = await readdir(memoryDir, { recursive: true });\n } catch {\n return false;\n }\n const line = new RegExp(`^namespace: ['\"]?${namespace}['\"]?$`, \"m\");\n for (const entry of entries) {\n if (!entry.endsWith(\".md\")) continue;\n const text = await readFile(join(memoryDir, entry), \"utf8\").catch(() => \"\");\n if (line.test(text)) return true;\n }\n return false;\n}\n\nexport const MISSING_DATABASE_URL =\n \"DATABASE_URL or .codebuddy/config.json postgresUrl is required.\";\n\n/**\n * `requireDatabase: false` (the MCP server) returns an empty `postgresUrl`\n * instead of throwing, so file-based tools still run without PostgreSQL;\n * `createRuntime` rejects the empty URL with the same message.\n */\nexport async function loadRuntimeConfig(\n cwd = process.cwd(),\n options: { requireDatabase?: boolean } = {},\n): Promise<CodeBuddyConfig> {\n const projectRoot = process.env.CODEBUDDY_PROJECT_ROOT ?? cwd;\n const file = await readConfigFile(projectRoot);\n const postgresUrl = process.env.DATABASE_URL ?? file.postgresUrl ?? \"\";\n if (!postgresUrl && options.requireDatabase !== false) {\n throw new Error(MISSING_DATABASE_URL);\n }\n const hfToken = process.env.HF_TOKEN ?? file.provider?.apiKey;\n return {\n postgresUrl,\n namespace:\n process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),\n projectRoot,\n provider: {\n type: file.provider?.type ?? \"local\",\n ...(hfToken ? { apiKey: hfToken } : {}),\n ...(file.provider?.model ? { model: file.provider.model } : {}),\n ...(file.provider?.relevanceFloor !== undefined\n ? { relevanceFloor: file.provider.relevanceFloor }\n : {}),\n ...(file.provider?.recallFloor !== undefined\n ? { recallFloor: file.provider.recallFloor }\n : {}),\n },\n ...(file.tokenBudget ? { tokenBudget: file.tokenBudget } : {}),\n ...(file.graphify ? { graphify: file.graphify } : {}),\n };\n}\n\nexport async function checkConfigPermissions(cwd = process.cwd()): Promise<ConfigPermissionReport> {\n const path = configPath(cwd);\n try {\n const info = await stat(path);\n const modeNumber = info.mode & 0o777;\n const mode = modeNumber.toString(8).padStart(4, \"0\");\n // Windows has no POSIX mode bits (stat reports 0o666/0o444 whatever chmod\n // did); the file is protected by the user profile's ACL instead.\n const ok = process.platform === \"win32\" || modeNumber === 0o600;\n return {\n path,\n exists: true,\n mode,\n ok,\n ...(ok ? {} : { warning: \".codebuddy/config.json should be chmod 0600.\" }),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { path, exists: false, mode: null, ok: true };\n }\n throw error;\n }\n}\n\nexport async function checkProjectScaffold(cwd = process.cwd()): Promise<ProjectScaffoldReport> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n const memoryFactsDir = join(codebuddyDir, \"memory\", \"facts\");\n const memorySummariesDir = join(codebuddyDir, \"memory\", \"summaries\");\n const plansDir = join(codebuddyDir, \"plans\");\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n const policyPath = join(codebuddyDir, POLICY_FILE);\n\n const [\n codebuddyDirExists,\n memoryFactsDirExists,\n memorySummariesDirExists,\n plansDirExists,\n gitignoreExists,\n policiesExist,\n ] = await Promise.all([\n pathExists(codebuddyDir),\n pathExists(memoryFactsDir),\n pathExists(memorySummariesDir),\n pathExists(plansDir),\n pathExists(gitignorePath),\n pathExists(policyPath),\n ]);\n\n return {\n codebuddyDir: {\n path: codebuddyDir,\n exists: codebuddyDirExists,\n ok: codebuddyDirExists,\n ...(codebuddyDirExists\n ? {}\n : { warning: \"Run `codebuddy use` or `codebuddy init` to create the project scaffold.\" }),\n },\n memoryFactsDir: {\n path: memoryFactsDir,\n exists: memoryFactsDirExists,\n ok: memoryFactsDirExists,\n ...(memoryFactsDirExists ? {} : { warning: \"Missing .codebuddy/memory/facts directory.\" }),\n },\n memorySummariesDir: {\n path: memorySummariesDir,\n exists: memorySummariesDirExists,\n ok: memorySummariesDirExists,\n ...(memorySummariesDirExists\n ? {}\n : { warning: \"Missing .codebuddy/memory/summaries directory.\" }),\n },\n plansDir: {\n path: plansDir,\n exists: plansDirExists,\n ok: plansDirExists,\n ...(plansDirExists ? {} : { warning: \"Missing .codebuddy/plans directory.\" }),\n },\n gitignore: {\n path: gitignorePath,\n exists: gitignoreExists,\n ok: gitignoreExists,\n ...(gitignoreExists ? {} : { warning: \"Missing .codebuddy/.gitignore.\" }),\n },\n policies: {\n path: policyPath,\n exists: policiesExist,\n ok: policiesExist,\n ...(policiesExist\n ? {}\n : {\n warning:\n \"Missing .codebuddy/policies.yaml. Run `codebuddy use` or `codebuddy init` to scaffold it.\",\n }),\n },\n };\n}\n\n/** Read the plan policy, honoring CODEBUDDY_PLAN_POLICY then the config file. */\nexport async function loadPlanPolicy(cwd = process.cwd()): Promise<PlanPolicy> {\n const fromEnv = process.env.CODEBUDDY_PLAN_POLICY;\n if (fromEnv && (PLAN_POLICIES as readonly string[]).includes(fromEnv)) {\n return fromEnv as PlanPolicy;\n }\n const file = await readConfigFile(cwd);\n return file.plan?.policy ?? \"suggest\";\n}\n\n/** Persist the plan policy into `.codebuddy/config.json`, creating it if needed. */\nexport async function setPlanPolicy(policy: PlanPolicy, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.plan = { ...(raw.plan as Record<string, unknown> | undefined), policy };\n // Atomic rewrite: this file holds the postgresUrl and provider apiKey.\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn optional zvec-grep retrieval on or off in `.codebuddy/config.json`. */\nexport async function setZvecEnabled(enabled: boolean, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.zvec = { ...(raw.zvec as Record<string, unknown> | undefined), enabled };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn the BYOK LLM layer on or off; never writes a key (keys live in the environment). */\nexport async function setLlmEnabled(\n enabled: boolean,\n options: { provider?: \"groq\" | \"openrouter\"; model?: string } = {},\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.llm = {\n ...(raw.llm as Record<string, unknown> | undefined),\n enabled,\n ...(options.provider ? { provider: options.provider } : {}),\n ...(options.model ? { model: options.model } : {}),\n };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/**\n * Replace one top-level block of `.codebuddy/config.json` (`provider`,\n * `rerank`), keeping every other key and the private file mode. The result\n * must still parse, so a bad value never reaches disk.\n */\nexport async function setConfigBlock(\n key: \"provider\" | \"rerank\" | \"llm\",\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":[]}
@@ -1,3 +1,6 @@
1
+ import {
2
+ runLeanHook
3
+ } from "./chunk-JJQDVGF7.js";
1
4
  import {
2
5
  CLIENT_ADAPTERS,
3
6
  CLIENT_IDS,
@@ -5,9 +8,6 @@ import {
5
8
  sharedRulesReaders,
6
9
  supportedModes
7
10
  } from "./chunk-V7PT6ER5.js";
8
- import {
9
- runLeanHook
10
- } from "./chunk-LRQNPYCK.js";
11
11
  import {
12
12
  appendHostEvents,
13
13
  extractEditedPaths,
@@ -21,16 +21,16 @@ import {
21
21
  runResetHook,
22
22
  runResumeHook,
23
23
  runStageHook
24
- } from "./chunk-XSNW6Y3X.js";
24
+ } from "./chunk-KQCP3ZYH.js";
25
25
  import {
26
26
  hookRoot
27
27
  } from "./chunk-TGFTKWOR.js";
28
28
  import {
29
29
  indexRepository
30
- } from "./chunk-YNSTIMCF.js";
30
+ } from "./chunk-UR3DF74I.js";
31
31
  import {
32
32
  resolveNamespaceFrom
33
- } from "./chunk-KNR2P2UY.js";
33
+ } from "./chunk-M75SN4D4.js";
34
34
  import {
35
35
  writeAtomic
36
36
  } from "./chunk-CG5B3LYG.js";
@@ -1662,4 +1662,4 @@ export {
1662
1662
  setHooks,
1663
1663
  detectClient
1664
1664
  };
1665
- //# sourceMappingURL=chunk-L6BRYZ6G.js.map
1665
+ //# sourceMappingURL=chunk-RZ266NHE.js.map
@@ -0,0 +1,43 @@
1
+ import {
2
+ resolveLlmConfig
3
+ } from "./chunk-WHKXUXHE.js";
4
+ import {
5
+ loadRuntimeConfig
6
+ } from "./chunk-M75SN4D4.js";
7
+
8
+ // src/llm/index-pass.ts
9
+ var VECTOR_BUDGET_MS = 5 * 6e4;
10
+ async function runIndexModelPass(root, options = {}) {
11
+ const config = await resolveLlmConfig(root);
12
+ if (!config.enabled) return { cards: null, facts: null, cardVectors: 0 };
13
+ const signal = options.signal ? { signal: options.signal } : {};
14
+ const { summarizeRepository } = await import("./summaries-QXWFFDUZ.js");
15
+ const cards = config.features.summaries ? await summarizeRepository(root, {
16
+ config,
17
+ pace: config.provider !== "local",
18
+ ...signal,
19
+ ...options.onProgress ? { onProgress: options.onProgress } : {}
20
+ }) : null;
21
+ const { extractIndexFacts } = await import("./index-facts-4MTEHKMN.js");
22
+ const facts = await extractIndexFacts(root, { config, ...signal });
23
+ let cardVectors = 0;
24
+ const local = (await loadRuntimeConfig(root, { requireDatabase: false }).catch(() => null))?.provider.type === "local";
25
+ if (local && cards && cards.summarized > 0 && !options.signal?.aborted) {
26
+ const { syncFileVectors } = await import("./file-vectors-MKXTSGGH.js");
27
+ cardVectors = (await syncFileVectors(root, VECTOR_BUDGET_MS))?.cards ?? 0;
28
+ }
29
+ return {
30
+ cards: cards && {
31
+ summarized: cards.summarized,
32
+ remaining: cards.remaining,
33
+ stoppedBy: cards.stoppedBy
34
+ },
35
+ facts: facts.stoppedBy === "disabled" ? null : { written: facts.written, removed: facts.removed },
36
+ cardVectors
37
+ };
38
+ }
39
+
40
+ export {
41
+ runIndexModelPass
42
+ };
43
+ //# sourceMappingURL=chunk-U7KDM62P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/llm/index-pass.ts"],"sourcesContent":["import { loadRuntimeConfig } from \"../core/config-file.js\";\nimport { resolveLlmConfig } from \"./config.js\";\n\nexport type IndexModelPass = {\n cards: { summarized: number; remaining: number; stoppedBy: string } | null;\n facts: { written: number; removed: number } | null;\n cardVectors: number;\n};\n\n/** One file vector budget per pass: card vectors are one short text per file. */\nconst VECTOR_BUDGET_MS = 5 * 60_000;\n\n/**\n * The model-backed part of indexing, always off the request path: intent\n * cards for new or changed source files, index facts for changed sources,\n * then the card vectors. Resumable (everything is cached by content hash) and\n * a no-op returning nulls when no model layer is enabled.\n */\nexport async function runIndexModelPass(\n root: string,\n options: { signal?: AbortSignal; onProgress?: (done: number, total: number) => void } = {},\n): Promise<IndexModelPass> {\n const config = await resolveLlmConfig(root);\n if (!config.enabled) return { cards: null, facts: null, cardVectors: 0 };\n const signal = options.signal ? { signal: options.signal } : {};\n const { summarizeRepository } = await import(\"./summaries.js\");\n const cards = config.features.summaries\n ? await summarizeRepository(root, {\n config,\n pace: config.provider !== \"local\",\n ...signal,\n ...(options.onProgress ? { onProgress: options.onProgress } : {}),\n })\n : null;\n const { extractIndexFacts } = await import(\"./index-facts.js\");\n const facts = await extractIndexFacts(root, { config, ...signal });\n let cardVectors = 0;\n // Only the local embedder: a hosted one would bill every card.\n const local =\n (await loadRuntimeConfig(root, { requireDatabase: false }).catch(() => null))?.provider.type ===\n \"local\";\n if (local && cards && cards.summarized > 0 && !options.signal?.aborted) {\n const { syncFileVectors } = await import(\"../context/file-vectors.js\");\n cardVectors = (await syncFileVectors(root, VECTOR_BUDGET_MS))?.cards ?? 0;\n }\n return {\n cards: cards && {\n summarized: cards.summarized,\n remaining: cards.remaining,\n stoppedBy: cards.stoppedBy,\n },\n facts:\n facts.stoppedBy === \"disabled\" ? null : { written: facts.written, removed: facts.removed },\n cardVectors,\n };\n}\n"],"mappings":";;;;;;;;AAUA,IAAM,mBAAmB,IAAI;AAQ7B,eAAsB,kBACpB,MACA,UAAwF,CAAC,GAChE;AACzB,QAAM,SAAS,MAAM,iBAAiB,IAAI;AAC1C,MAAI,CAAC,OAAO,QAAS,QAAO,EAAE,OAAO,MAAM,OAAO,MAAM,aAAa,EAAE;AACvE,QAAM,SAAS,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAC9D,QAAM,EAAE,oBAAoB,IAAI,MAAM,OAAO,yBAAgB;AAC7D,QAAM,QAAQ,OAAO,SAAS,YAC1B,MAAM,oBAAoB,MAAM;AAAA,IAC9B;AAAA,IACA,MAAM,OAAO,aAAa;AAAA,IAC1B,GAAG;AAAA,IACH,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;AAAA,EACjE,CAAC,IACD;AACJ,QAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,2BAAkB;AAC7D,QAAM,QAAQ,MAAM,kBAAkB,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;AACjE,MAAI,cAAc;AAElB,QAAM,SACH,MAAM,kBAAkB,MAAM,EAAE,iBAAiB,MAAM,CAAC,EAAE,MAAM,MAAM,IAAI,IAAI,SAAS,SACxF;AACF,MAAI,SAAS,SAAS,MAAM,aAAa,KAAK,CAAC,QAAQ,QAAQ,SAAS;AACtE,UAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,4BAA4B;AACrE,mBAAe,MAAM,gBAAgB,MAAM,gBAAgB,IAAI,SAAS;AAAA,EAC1E;AACA,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,MACd,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM;AAAA,IACnB;AAAA,IACA,OACE,MAAM,cAAc,aAAa,OAAO,EAAE,SAAS,MAAM,SAAS,SAAS,MAAM,QAAQ;AAAA,IAC3F;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,368 @@
1
+ import {
2
+ chatJson
3
+ } from "./chunk-6YI2D5F5.js";
4
+ import {
5
+ MemoryFileStore,
6
+ termOverlap
7
+ } from "./chunk-YMXFSHXT.js";
8
+ import {
9
+ generateEntityId
10
+ } from "./chunk-EMWGLKPR.js";
11
+ import {
12
+ redactSensitive,
13
+ scanSensitive
14
+ } from "./chunk-AL6VBTNA.js";
15
+ import {
16
+ resolveLlmConfig
17
+ } from "./chunk-WHKXUXHE.js";
18
+ import {
19
+ resolveNamespaceFrom
20
+ } from "./chunk-M75SN4D4.js";
21
+ import {
22
+ writeAtomic
23
+ } from "./chunk-CG5B3LYG.js";
24
+ import {
25
+ waitForForeground
26
+ } from "./chunk-BTTQWSDS.js";
27
+
28
+ // src/llm/index-facts.ts
29
+ import { createHash } from "crypto";
30
+ import { mkdir, readdir, readFile } from "fs/promises";
31
+ import { dirname, join } from "path";
32
+ import { parse as parseYaml } from "yaml";
33
+ import { z } from "zod";
34
+ var INDEX_FACT_AGENT = "codebuddy-index";
35
+ var MAX_FACTS = 40;
36
+ var PER_SOURCE = 8;
37
+ var SECTION_CHARS = 1600;
38
+ var DUPLICATE_OVERLAP = 0.8;
39
+ var TIMEOUT_MS = { local: 6e4, hosted: 2e4 };
40
+ var PROSE_SOURCES = /^(README|CONTRIBUTING|AGENTS|CLAUDE)(\.[a-z]+)?$/i;
41
+ var POLICY_DOC = /(deprecat|release[-_ ]?(policy|process)|versioning|support[-_ ]?policy|compatib)/i;
42
+ async function factSources(root) {
43
+ const top = await readdir(root).catch(() => []);
44
+ const docs = await readdir(join(root, "docs"), { recursive: true }).catch(() => []);
45
+ const workflows = await readdir(join(root, ".github", "workflows")).catch(() => []);
46
+ return [
47
+ ...["package.json", "pyproject.toml"].filter((name) => top.includes(name)),
48
+ ...top.filter((name) => PROSE_SOURCES.test(name)).sort(),
49
+ ...workflows.filter((name) => /\.ya?ml$/.test(name)).sort().map((name) => `.github/workflows/${name}`),
50
+ ...docs.map((name) => name.split("\\").join("/")).filter((name) => /\.(md|rst|txt)$/i.test(name) && POLICY_DOC.test(name)).sort().slice(0, 4).map((name) => `docs/${name}`)
51
+ ];
52
+ }
53
+ async function extractIndexFacts(root, options = {}) {
54
+ const resolved = options.config ?? await resolveLlmConfig(root);
55
+ if (!resolved.enabled || resolved.features.facts === false) {
56
+ return { written: 0, removed: 0, skippedSources: 0, stoppedBy: "disabled" };
57
+ }
58
+ const config = resolved;
59
+ const namespace = options.namespace ?? await resolveNamespaceFrom(root);
60
+ const store = new MemoryFileStore(root);
61
+ const ledger = await readLedger(root);
62
+ const sources = await factSources(root);
63
+ let facts = await store.listFacts();
64
+ let removed = 0;
65
+ const removeFrom = async (path) => {
66
+ for (const fact of facts) {
67
+ if (fact.createdByAgent === INDEX_FACT_AGENT && fact.paths[0] === path) {
68
+ await store.deleteFact(fact.id);
69
+ removed++;
70
+ }
71
+ }
72
+ facts = await store.listFacts();
73
+ };
74
+ for (const path of Object.keys(ledger.sources)) {
75
+ if (!sources.includes(path)) {
76
+ await removeFrom(path);
77
+ delete ledger.sources[path];
78
+ }
79
+ }
80
+ let written = 0;
81
+ let skippedSources = 0;
82
+ let stoppedBy = "done";
83
+ const indexFacts = () => facts.filter((fact) => fact.createdByAgent === INDEX_FACT_AGENT).length;
84
+ for (const path of sources) {
85
+ if (options.signal?.aborted) {
86
+ stoppedBy = "aborted";
87
+ break;
88
+ }
89
+ const text = await readFile(join(root, path), "utf8").catch(() => null);
90
+ if (text === null) continue;
91
+ const hash = sha(text);
92
+ if (ledger.sources[path] === hash) {
93
+ skippedSources++;
94
+ continue;
95
+ }
96
+ await removeFrom(path);
97
+ if (indexFacts() >= MAX_FACTS) {
98
+ stoppedBy = "cap";
99
+ break;
100
+ }
101
+ const candidates = await candidatesFor(root, path, text, config, options.signal);
102
+ let fromSource = 0;
103
+ for (const candidate of candidates) {
104
+ if (fromSource >= PER_SOURCE || indexFacts() >= MAX_FACTS) break;
105
+ if (scanSensitive(candidate.text).sensitive) continue;
106
+ if (facts.some(
107
+ (fact) => Math.max(
108
+ termOverlap(candidate.text, fact.object),
109
+ termOverlap(candidate.text, fact.content)
110
+ ) >= DUPLICATE_OVERLAP
111
+ )) {
112
+ continue;
113
+ }
114
+ const id = generateEntityId("fact");
115
+ const content = `${candidate.text} (source: ${path}:L${candidate.line})`;
116
+ await store.writeFact({
117
+ id,
118
+ namespace,
119
+ subject: path,
120
+ predicate: "project-fact",
121
+ object: candidate.text,
122
+ confidence: 0.8,
123
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
124
+ createdByAgent: INDEX_FACT_AGENT,
125
+ sourceInteractionId: null,
126
+ sourceDeleted: false,
127
+ content,
128
+ paths: [path],
129
+ verification: [`${path}:L${candidate.line}`],
130
+ idempotencyKey: `index:${path}:${sha(candidate.text).slice(0, 16)}`.slice(0, 200),
131
+ fingerprint: sha(`${namespace}
132
+ ${content}`)
133
+ });
134
+ facts = await store.listFacts();
135
+ written++;
136
+ fromSource++;
137
+ }
138
+ ledger.sources[path] = hash;
139
+ await writeLedger(root, ledger);
140
+ }
141
+ await writeLedger(root, ledger);
142
+ return { written, removed, skippedSources, stoppedBy };
143
+ }
144
+ async function listIndexFacts(root) {
145
+ return (await new MemoryFileStore(root).listFacts()).filter(
146
+ (fact) => fact.createdByAgent === INDEX_FACT_AGENT
147
+ );
148
+ }
149
+ async function pruneIndexFacts(root) {
150
+ const store = new MemoryFileStore(root);
151
+ const facts = await listIndexFacts(root);
152
+ for (const fact of facts) await store.deleteFact(fact.id);
153
+ await writeLedger(root, { version: 1, sources: {} });
154
+ return facts.length;
155
+ }
156
+ async function candidatesFor(root, path, text, config, signal) {
157
+ if (path === "package.json") return packageFacts(path, text);
158
+ if (path === "pyproject.toml") return pyprojectFacts(path, text);
159
+ if (path.startsWith(".github/workflows/")) return workflowFacts(path, text);
160
+ return proseFacts(root, path, stripManagedBlocks(text), config, signal);
161
+ }
162
+ function packageFacts(path, text) {
163
+ let manifest;
164
+ try {
165
+ manifest = JSON.parse(text);
166
+ } catch {
167
+ return [];
168
+ }
169
+ const out = [];
170
+ const node = manifest.engines?.node;
171
+ if (node)
172
+ out.push({ path, line: lineOf(text, '"node"'), text: `The project requires Node.js ${node}.` });
173
+ for (const name of ["test", "lint", "typecheck", "build", "format"]) {
174
+ const script = manifest.scripts?.[name];
175
+ if (script) {
176
+ out.push({
177
+ path,
178
+ line: lineOf(text, `"${name}"`),
179
+ text: `Run \`npm run ${name}\` to ${VERB[name]} (it runs \`${script.slice(0, 120)}\`).`
180
+ });
181
+ }
182
+ }
183
+ return out;
184
+ }
185
+ var VERB = {
186
+ test: "run the tests",
187
+ lint: "lint the code",
188
+ typecheck: "type-check the code",
189
+ build: "build the project",
190
+ format: "format the code"
191
+ };
192
+ function pyprojectFacts(path, text) {
193
+ const out = [];
194
+ const python = text.match(/^\s*requires-python\s*=\s*["']([^"']+)["']/m)?.[1];
195
+ if (python) {
196
+ out.push({
197
+ path,
198
+ line: lineOf(text, "requires-python"),
199
+ text: `The project supports Python ${python}.`
200
+ });
201
+ }
202
+ for (const [tool, what] of [
203
+ ["ruff", "Ruff is the linter/formatter"],
204
+ ["mypy", "mypy type-checks the code"],
205
+ ["pyright", "Pyright type-checks the code"],
206
+ ["pytest", "Tests run with pytest"]
207
+ ]) {
208
+ const header = `[tool.${tool}`;
209
+ if (text.includes(header)) {
210
+ out.push({
211
+ path,
212
+ line: lineOf(text, header),
213
+ text: `${what} (configured in pyproject.toml).`
214
+ });
215
+ }
216
+ }
217
+ return out;
218
+ }
219
+ var CHECK_COMMAND = /\b(test|tests|pytest|lint|ruff|mypy|pyright|tsc|typecheck|vitest|jest|biome|eslint|go vet|cargo (test|clippy)|coverage)\b/i;
220
+ function workflowFacts(path, text) {
221
+ let workflow;
222
+ try {
223
+ workflow = parseYaml(text) ?? {};
224
+ } catch {
225
+ return [];
226
+ }
227
+ const commands = /* @__PURE__ */ new Set();
228
+ for (const job of Object.values(workflow.jobs ?? {})) {
229
+ for (const step of job?.steps ?? []) {
230
+ const run = typeof step?.run === "string" ? step.run.split("\n")[0]?.trim() : void 0;
231
+ if (run && CHECK_COMMAND.test(run) && run.length <= 120) commands.add(run);
232
+ }
233
+ }
234
+ const triggers = JSON.stringify(workflow.on ?? workflow.true ?? "");
235
+ const gates = triggers.includes("pull_request");
236
+ return [...commands].slice(0, 4).map((command) => ({
237
+ path,
238
+ line: lineOf(text, command),
239
+ text: `CI (${workflow.name ?? path}) runs \`${command}\`${gates ? " on pull requests; it must pass before merging" : ""}.`
240
+ }));
241
+ }
242
+ var proseSchema = z.object({
243
+ facts: z.array(z.object({ line: z.number().int(), fact: z.string() })).default([])
244
+ });
245
+ var FACT_GRAMMAR = {
246
+ type: "object",
247
+ properties: {
248
+ facts: {
249
+ type: "array",
250
+ maxItems: 6,
251
+ items: {
252
+ type: "object",
253
+ properties: { line: { type: "integer" }, fact: { type: "string", maxLength: 240 } }
254
+ }
255
+ }
256
+ }
257
+ };
258
+ var SYSTEM = [
259
+ "You extract durable project facts from a repository document for a team memory.",
260
+ 'Answer with JSON only: {"facts":[{"line":12,"fact":"..."}]}.',
261
+ "Keep only lasting rules and facts a developer must know: conventions, required checks, supported versions, deprecation and release policy, how to run or test the project.",
262
+ "Each fact is one short sentence in the document's own words; line is the number of the line it comes from.",
263
+ "Skip marketing, installation steps for end users, links, badges and anything temporary. Give at most 6 facts; none is fine."
264
+ ].join(" ");
265
+ async function proseFacts(root, path, text, config, signal) {
266
+ const lines = text.split(/\r?\n/);
267
+ const out = [];
268
+ for (const section of sections(lines)) {
269
+ if (signal?.aborted || out.length >= PER_SOURCE) break;
270
+ if (config.local) await waitForForeground(root, signal);
271
+ const numbered = section.map(({ line, text: text2 }) => `${line}: ${redactSensitive(text2)}`).join("\n");
272
+ const result = await chatJson(config, {
273
+ system: SYSTEM,
274
+ user: `File: ${path}
275
+
276
+ ${numbered}`,
277
+ schema: proseSchema,
278
+ jsonSchema: FACT_GRAMMAR,
279
+ maxTokens: 500,
280
+ deadlineMs: config.local ? TIMEOUT_MS.local : TIMEOUT_MS.hosted,
281
+ purpose: "background",
282
+ repositoryRoot: root
283
+ });
284
+ if (!result.ok) continue;
285
+ for (const fact of result.value.facts) {
286
+ const sentence = fact.fact.trim().replace(/\s+/g, " ").slice(0, 240);
287
+ if (isStatement(sentence) && hasLineEvidence(lines, fact.line, sentence)) {
288
+ out.push({ path, line: fact.line, text: sentence });
289
+ }
290
+ }
291
+ }
292
+ return out;
293
+ }
294
+ function isStatement(text) {
295
+ if (/```|^\$\s|^(cd|npm|npx|pip|git|make|curl)\s/i.test(text)) return false;
296
+ return (text.match(new RegExp("\\p{L}{2,}", "gu")) ?? []).length >= 5;
297
+ }
298
+ function hasLineEvidence(lines, line, fact) {
299
+ if (line < 1 || line > lines.length) return false;
300
+ const nearby = new Set(
301
+ lines.slice(Math.max(0, line - 3), line + 2).join(" ").toLowerCase().match(/[\p{L}\p{N}_]{4,}/gu) ?? []
302
+ );
303
+ const words = [...new Set(fact.toLowerCase().match(/[\p{L}\p{N}_]{4,}/gu) ?? [])];
304
+ const shared = words.filter((word) => nearby.has(word)).length;
305
+ return shared >= Math.min(2, words.length) && shared > 0;
306
+ }
307
+ function sections(lines) {
308
+ const out = [];
309
+ let current = [];
310
+ let size = 0;
311
+ lines.forEach((text, index) => {
312
+ const heading = /^#{1,3}\s/.test(text);
313
+ if (current.length > 0 && (heading || size + text.length > SECTION_CHARS)) {
314
+ out.push(current);
315
+ current = [];
316
+ size = 0;
317
+ }
318
+ if (text.trim()) {
319
+ current.push({ line: index + 1, text: text.slice(0, 400) });
320
+ size += Math.min(text.length, 400) + 6;
321
+ }
322
+ });
323
+ if (current.length > 0) out.push(current);
324
+ return out.slice(0, 12);
325
+ }
326
+ function stripManagedBlocks(text) {
327
+ return text.replace(
328
+ /<!-- codebuddy:[\w-]+:start -->[\s\S]*?<!-- codebuddy:[\w-]+:end -->/g,
329
+ (block) => block.replace(/[^\n]/g, "")
330
+ );
331
+ }
332
+ function lineOf(text, needle) {
333
+ const index = text.indexOf(needle);
334
+ return index < 0 ? 1 : text.slice(0, index).split("\n").length;
335
+ }
336
+ function sha(text) {
337
+ return createHash("sha256").update(text).digest("hex");
338
+ }
339
+ function ledgerPath(root) {
340
+ return join(root, ".codebuddy", "cache", "index-facts.json");
341
+ }
342
+ async function readLedger(root) {
343
+ try {
344
+ const parsed = JSON.parse(await readFile(ledgerPath(root), "utf8"));
345
+ if (parsed.version === 1 && parsed.sources) return parsed;
346
+ } catch {
347
+ }
348
+ return { version: 1, sources: {} };
349
+ }
350
+ async function writeLedger(root, ledger) {
351
+ await mkdir(dirname(ledgerPath(root)), { recursive: true });
352
+ await writeAtomic(ledgerPath(root), JSON.stringify(ledger));
353
+ }
354
+
355
+ export {
356
+ INDEX_FACT_AGENT,
357
+ MAX_FACTS,
358
+ factSources,
359
+ extractIndexFacts,
360
+ listIndexFacts,
361
+ pruneIndexFacts,
362
+ packageFacts,
363
+ pyprojectFacts,
364
+ workflowFacts,
365
+ isStatement,
366
+ hasLineEvidence
367
+ };
368
+ //# sourceMappingURL=chunk-UATJCD2T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/llm/index-facts.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, readdir, readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport { resolveNamespaceFrom } from \"../core/config-file.js\";\nimport { generateEntityId } from \"../core/ids.js\";\nimport { writeAtomic } from \"../core/markdown-store-fs.js\";\nimport { MemoryFileStore } from \"../core/memory-file-store.js\";\nimport { termOverlap } from \"../core/memory-search.js\";\nimport { redactSensitive, scanSensitive } from \"../core/sensitive.js\";\nimport { chatJson } from \"./client.js\";\nimport { type EnabledLlmConfig, resolveLlmConfig } from \"./config.js\";\nimport { waitForForeground } from \"./local.js\";\n\n/**\n * Project facts drawn from the repository at index time, so a fresh project\n * does not start with empty memory: supported versions and the test/lint\n * commands from manifests (read deterministically), required CI checks from\n * workflows, and rules from README / CONTRIBUTING / AGENTS.md / CLAUDE.md and\n * deprecation or release policies (read by the small model, local or BYOK).\n *\n * Every fact keeps its source: `paths: [file]`, `verification: [\"file:Lline\"]`,\n * and a line the fact's words must actually appear near. They are ordinary\n * Markdown facts (the durable store `remember` writes too), marked\n * `createdByAgent: \"codebuddy-index\"`; re-extraction only ever replaces facts\n * with that origin whose source file changed, never a human or agent fact.\n * Sensitive text is dropped, not redacted. At most `MAX_FACTS` per repository.\n */\nexport const INDEX_FACT_AGENT = \"codebuddy-index\";\nexport const MAX_FACTS = 40;\nconst PER_SOURCE = 8;\nconst SECTION_CHARS = 1_600;\n/** A fact this close to an existing one (Jaccard of content words) is a duplicate. */\nconst DUPLICATE_OVERLAP = 0.8;\nconst TIMEOUT_MS = { local: 60_000, hosted: 20_000 } as const;\n\nexport type IndexFactsResult = {\n written: number;\n removed: number;\n skippedSources: number;\n stoppedBy: \"done\" | \"disabled\" | \"cap\" | \"aborted\";\n};\n\ntype Candidate = { path: string; line: number; text: string };\ntype Ledger = { version: 1; sources: Record<string, string> };\n\nconst PROSE_SOURCES = /^(README|CONTRIBUTING|AGENTS|CLAUDE)(\\.[a-z]+)?$/i;\nconst POLICY_DOC =\n /(deprecat|release[-_ ]?(policy|process)|versioning|support[-_ ]?policy|compatib)/i;\n\n/** Files facts are read from, relative to the root, in priority order. */\nexport async function factSources(root: string): Promise<string[]> {\n const top = await readdir(root).catch(() => [] as string[]);\n const docs = await readdir(join(root, \"docs\"), { recursive: true }).catch(() => [] as string[]);\n const workflows = await readdir(join(root, \".github\", \"workflows\")).catch(() => [] as string[]);\n return [\n ...[\"package.json\", \"pyproject.toml\"].filter((name) => top.includes(name)),\n ...top.filter((name) => PROSE_SOURCES.test(name)).sort(),\n ...workflows\n .filter((name) => /\\.ya?ml$/.test(name))\n .sort()\n .map((name) => `.github/workflows/${name}`),\n ...docs\n .map((name) => name.split(\"\\\\\").join(\"/\"))\n .filter((name) => /\\.(md|rst|txt)$/i.test(name) && POLICY_DOC.test(name))\n .sort()\n .slice(0, 4)\n .map((name) => `docs/${name}`),\n ];\n}\n\n/**\n * Extract (or refresh) index facts. Sources whose content hash is unchanged\n * are skipped; a changed or deleted source has its old index facts removed\n * first. Without an enabled model layer nothing happens (2.14 behaviour).\n */\nexport async function extractIndexFacts(\n root: string,\n options: { config?: EnabledLlmConfig; signal?: AbortSignal; namespace?: string } = {},\n): Promise<IndexFactsResult> {\n const resolved = options.config ?? (await resolveLlmConfig(root));\n if (!resolved.enabled || resolved.features.facts === false) {\n return { written: 0, removed: 0, skippedSources: 0, stoppedBy: \"disabled\" };\n }\n const config = resolved;\n const namespace = options.namespace ?? (await resolveNamespaceFrom(root));\n const store = new MemoryFileStore(root);\n const ledger = await readLedger(root);\n const sources = await factSources(root);\n let facts = await store.listFacts();\n let removed = 0;\n const removeFrom = async (path: string) => {\n for (const fact of facts) {\n if (fact.createdByAgent === INDEX_FACT_AGENT && fact.paths[0] === path) {\n await store.deleteFact(fact.id);\n removed++;\n }\n }\n facts = await store.listFacts();\n };\n for (const path of Object.keys(ledger.sources)) {\n if (!sources.includes(path)) {\n await removeFrom(path);\n delete ledger.sources[path];\n }\n }\n\n let written = 0;\n let skippedSources = 0;\n let stoppedBy: IndexFactsResult[\"stoppedBy\"] = \"done\";\n const indexFacts = () => facts.filter((fact) => fact.createdByAgent === INDEX_FACT_AGENT).length;\n for (const path of sources) {\n if (options.signal?.aborted) {\n stoppedBy = \"aborted\";\n break;\n }\n const text = await readFile(join(root, path), \"utf8\").catch(() => null);\n if (text === null) continue;\n const hash = sha(text);\n if (ledger.sources[path] === hash) {\n skippedSources++;\n continue;\n }\n await removeFrom(path);\n if (indexFacts() >= MAX_FACTS) {\n stoppedBy = \"cap\";\n break;\n }\n const candidates = await candidatesFor(root, path, text, config, options.signal);\n let fromSource = 0;\n for (const candidate of candidates) {\n if (fromSource >= PER_SOURCE || indexFacts() >= MAX_FACTS) break;\n if (scanSensitive(candidate.text).sensitive) continue;\n // Never a second copy of what a human, agent or earlier source already said.\n if (\n facts.some(\n (fact) =>\n Math.max(\n termOverlap(candidate.text, fact.object),\n termOverlap(candidate.text, fact.content),\n ) >= DUPLICATE_OVERLAP,\n )\n ) {\n continue;\n }\n const id = generateEntityId(\"fact\");\n const content = `${candidate.text} (source: ${path}:L${candidate.line})`;\n await store.writeFact({\n id,\n namespace,\n subject: path,\n predicate: \"project-fact\",\n object: candidate.text,\n confidence: 0.8,\n createdAt: new Date().toISOString(),\n createdByAgent: INDEX_FACT_AGENT,\n sourceInteractionId: null,\n sourceDeleted: false,\n content,\n paths: [path],\n verification: [`${path}:L${candidate.line}`],\n idempotencyKey: `index:${path}:${sha(candidate.text).slice(0, 16)}`.slice(0, 200),\n fingerprint: sha(`${namespace}\\n${content}`),\n });\n facts = await store.listFacts();\n written++;\n fromSource++;\n }\n ledger.sources[path] = hash;\n await writeLedger(root, ledger);\n }\n await writeLedger(root, ledger);\n return { written, removed, skippedSources, stoppedBy };\n}\n\n/** Index facts currently stored (for `memory list|prune --origin index`). */\nexport async function listIndexFacts(root: string) {\n return (await new MemoryFileStore(root).listFacts()).filter(\n (fact) => fact.createdByAgent === INDEX_FACT_AGENT,\n );\n}\n\n/** Delete every index fact and forget the source hashes, so the next pass re-extracts. */\nexport async function pruneIndexFacts(root: string): Promise<number> {\n const store = new MemoryFileStore(root);\n const facts = await listIndexFacts(root);\n for (const fact of facts) await store.deleteFact(fact.id);\n await writeLedger(root, { version: 1, sources: {} });\n return facts.length;\n}\n\nasync function candidatesFor(\n root: string,\n path: string,\n text: string,\n config: EnabledLlmConfig,\n signal: AbortSignal | undefined,\n): Promise<Candidate[]> {\n if (path === \"package.json\") return packageFacts(path, text);\n if (path === \"pyproject.toml\") return pyprojectFacts(path, text);\n if (path.startsWith(\".github/workflows/\")) return workflowFacts(path, text);\n return proseFacts(root, path, stripManagedBlocks(text), config, signal);\n}\n\n/** Supported Node version and the project's own check commands. */\nexport function packageFacts(path: string, text: string): Candidate[] {\n let manifest: { engines?: Record<string, string>; scripts?: Record<string, string> };\n try {\n manifest = JSON.parse(text);\n } catch {\n return [];\n }\n const out: Candidate[] = [];\n const node = manifest.engines?.node;\n if (node)\n out.push({ path, line: lineOf(text, '\"node\"'), text: `The project requires Node.js ${node}.` });\n for (const name of [\"test\", \"lint\", \"typecheck\", \"build\", \"format\"]) {\n const script = manifest.scripts?.[name];\n if (script) {\n out.push({\n path,\n line: lineOf(text, `\"${name}\"`),\n text: `Run \\`npm run ${name}\\` to ${VERB[name]} (it runs \\`${script.slice(0, 120)}\\`).`,\n });\n }\n }\n return out;\n}\nconst VERB: Record<string, string> = {\n test: \"run the tests\",\n lint: \"lint the code\",\n typecheck: \"type-check the code\",\n build: \"build the project\",\n format: \"format the code\",\n};\n\n/** Supported Python versions and the configured tools. */\nexport function pyprojectFacts(path: string, text: string): Candidate[] {\n const out: Candidate[] = [];\n const python = text.match(/^\\s*requires-python\\s*=\\s*[\"']([^\"']+)[\"']/m)?.[1];\n if (python) {\n out.push({\n path,\n line: lineOf(text, \"requires-python\"),\n text: `The project supports Python ${python}.`,\n });\n }\n for (const [tool, what] of [\n [\"ruff\", \"Ruff is the linter/formatter\"],\n [\"mypy\", \"mypy type-checks the code\"],\n [\"pyright\", \"Pyright type-checks the code\"],\n [\"pytest\", \"Tests run with pytest\"],\n ] as const) {\n const header = `[tool.${tool}`;\n if (text.includes(header)) {\n out.push({\n path,\n line: lineOf(text, header),\n text: `${what} (configured in pyproject.toml).`,\n });\n }\n }\n return out;\n}\n\nconst CHECK_COMMAND =\n /\\b(test|tests|pytest|lint|ruff|mypy|pyright|tsc|typecheck|vitest|jest|biome|eslint|go vet|cargo (test|clippy)|coverage)\\b/i;\n\n/** CI's required checks: the run steps that test, lint or type-check. */\nexport function workflowFacts(path: string, text: string): Candidate[] {\n let workflow: {\n name?: string;\n on?: unknown;\n jobs?: Record<string, { steps?: Array<{ run?: string }> }>;\n };\n try {\n workflow = parseYaml(text) ?? {};\n } catch {\n return [];\n }\n const commands = new Set<string>();\n for (const job of Object.values(workflow.jobs ?? {})) {\n for (const step of job?.steps ?? []) {\n const run = typeof step?.run === \"string\" ? step.run.split(\"\\n\")[0]?.trim() : undefined;\n if (run && CHECK_COMMAND.test(run) && run.length <= 120) commands.add(run);\n }\n }\n // Only a workflow that runs on pull requests gates merging (a release job does not).\n // YAML 1.1 readers may parse the `on:` key as boolean true.\n const triggers = JSON.stringify(workflow.on ?? (workflow as Record<string, unknown>).true ?? \"\");\n const gates = triggers.includes(\"pull_request\");\n return [...commands].slice(0, 4).map((command) => ({\n path,\n line: lineOf(text, command),\n text: `CI (${workflow.name ?? path}) runs \\`${command}\\`${gates ? \" on pull requests; it must pass before merging\" : \"\"}.`,\n }));\n}\n\nconst proseSchema = z.object({\n facts: z.array(z.object({ line: z.number().int(), fact: z.string() })).default([]),\n});\n\n/** Grammar bounds for the local model (see summaries.ts CARD_GRAMMAR). */\nconst FACT_GRAMMAR = {\n type: \"object\",\n properties: {\n facts: {\n type: \"array\",\n maxItems: 6,\n items: {\n type: \"object\",\n properties: { line: { type: \"integer\" }, fact: { type: \"string\", maxLength: 240 } },\n },\n },\n },\n};\n\nconst SYSTEM = [\n \"You extract durable project facts from a repository document for a team memory.\",\n 'Answer with JSON only: {\"facts\":[{\"line\":12,\"fact\":\"...\"}]}.',\n \"Keep only lasting rules and facts a developer must know: conventions, required checks, supported versions, deprecation and release policy, how to run or test the project.\",\n \"Each fact is one short sentence in the document's own words; line is the number of the line it comes from.\",\n \"Skip marketing, installation steps for end users, links, badges and anything temporary. Give at most 6 facts; none is fine.\",\n].join(\" \");\n\nasync function proseFacts(\n root: string,\n path: string,\n text: string,\n config: EnabledLlmConfig,\n signal: AbortSignal | undefined,\n): Promise<Candidate[]> {\n const lines = text.split(/\\r?\\n/);\n const out: Candidate[] = [];\n for (const section of sections(lines)) {\n if (signal?.aborted || out.length >= PER_SOURCE) break;\n if (config.local) await waitForForeground(root, signal);\n const numbered = section\n .map(({ line, text }) => `${line}: ${redactSensitive(text)}`)\n .join(\"\\n\");\n const result = await chatJson(config, {\n system: SYSTEM,\n user: `File: ${path}\\n\\n${numbered}`,\n schema: proseSchema,\n jsonSchema: FACT_GRAMMAR,\n maxTokens: 500,\n deadlineMs: config.local ? TIMEOUT_MS.local : TIMEOUT_MS.hosted,\n purpose: \"background\",\n repositoryRoot: root,\n });\n if (!result.ok) continue;\n for (const fact of result.value.facts) {\n const sentence = fact.fact.trim().replace(/\\s+/g, \" \").slice(0, 240);\n if (isStatement(sentence) && hasLineEvidence(lines, fact.line, sentence)) {\n out.push({ path, line: fact.line, text: sentence });\n }\n }\n }\n return out;\n}\n\n/**\n * A fact is a statement: at least five words of prose, not a shell line, a\n * code fence or a bare heading the small model copied out of a README.\n */\nexport function isStatement(text: string): boolean {\n if (/```|^\\$\\s|^(cd|npm|npx|pip|git|make|curl)\\s/i.test(text)) return false;\n return (text.match(/\\p{L}{2,}/gu) ?? []).length >= 5;\n}\n\n/**\n * The model's fact must be supported by its cited line (± 2): at least two of\n * its content words (≥ 4 letters) appear there, or all of them for a short fact.\n */\nexport function hasLineEvidence(lines: string[], line: number, fact: string): boolean {\n if (line < 1 || line > lines.length) return false;\n const nearby = new Set(\n lines\n .slice(Math.max(0, line - 3), line + 2)\n .join(\" \")\n .toLowerCase()\n .match(/[\\p{L}\\p{N}_]{4,}/gu) ?? [],\n );\n const words = [...new Set(fact.toLowerCase().match(/[\\p{L}\\p{N}_]{4,}/gu) ?? [])];\n const shared = words.filter((word) => nearby.has(word)).length;\n return shared >= Math.min(2, words.length) && shared > 0;\n}\n\n/** Heading-delimited chunks of at most `SECTION_CHARS`, with 1-based line numbers. */\nfunction sections(lines: string[]): Array<Array<{ line: number; text: string }>> {\n const out: Array<Array<{ line: number; text: string }>> = [];\n let current: Array<{ line: number; text: string }> = [];\n let size = 0;\n lines.forEach((text, index) => {\n const heading = /^#{1,3}\\s/.test(text);\n if (current.length > 0 && (heading || size + text.length > SECTION_CHARS)) {\n out.push(current);\n current = [];\n size = 0;\n }\n if (text.trim()) {\n current.push({ line: index + 1, text: text.slice(0, 400) });\n size += Math.min(text.length, 400) + 6;\n }\n });\n if (current.length > 0) out.push(current);\n // A long README: the first sections carry the rules that matter.\n return out.slice(0, 12);\n}\n\n/** CodeBuddy's own managed blocks in CLAUDE.md / AGENTS.md are not project facts. */\nfunction stripManagedBlocks(text: string): string {\n // Blank the block (keeping its line breaks) so cited line numbers stay true.\n return text.replace(\n /<!-- codebuddy:[\\w-]+:start -->[\\s\\S]*?<!-- codebuddy:[\\w-]+:end -->/g,\n (block) => block.replace(/[^\\n]/g, \"\"),\n );\n}\n\nfunction lineOf(text: string, needle: string): number {\n const index = text.indexOf(needle);\n return index < 0 ? 1 : text.slice(0, index).split(\"\\n\").length;\n}\n\nfunction sha(text: string): string {\n return createHash(\"sha256\").update(text).digest(\"hex\");\n}\n\nfunction ledgerPath(root: string): string {\n return join(root, \".codebuddy\", \"cache\", \"index-facts.json\");\n}\n\nasync function readLedger(root: string): Promise<Ledger> {\n try {\n const parsed = JSON.parse(await readFile(ledgerPath(root), \"utf8\")) as Ledger;\n if (parsed.version === 1 && parsed.sources) return parsed;\n } catch {\n // Missing: every source is new.\n }\n return { version: 1, sources: {} };\n}\n\nasync function writeLedger(root: string, ledger: Ledger): Promise<void> {\n await mkdir(dirname(ledgerPath(root)), { recursive: true });\n await writeAtomic(ledgerPath(root), JSON.stringify(ledger));\n}\n"],"mappings":"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","names":["text"]}