@orquentra/codebuddy 2.15.1 → 2.15.3
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.
- package/ARCHITECTURE.md +11 -6
- package/dist/{chunk-VBDM7HC5.js → chunk-3L5IR6OW.js} +2 -2
- package/dist/{chunk-VBDM7HC5.js.map → chunk-3L5IR6OW.js.map} +1 -1
- package/dist/{chunk-AFXWCSQ4.js → chunk-3TZNQJTH.js} +3 -3
- package/dist/{chunk-RKF4HBBQ.js → chunk-4LMBZWIF.js} +47 -12
- package/dist/chunk-4LMBZWIF.js.map +1 -0
- package/dist/{chunk-RGIVHKBH.js → chunk-4TKKLQLH.js} +2 -2
- package/dist/{chunk-OO7Q7XZF.js → chunk-5ZLNHYJH.js} +30 -14
- package/dist/chunk-5ZLNHYJH.js.map +1 -0
- package/dist/{chunk-VF7DKRIU.js → chunk-6KSSJJZO.js} +8 -7
- package/dist/chunk-6KSSJJZO.js.map +1 -0
- package/dist/{chunk-MNSMW4MZ.js → chunk-7IXPARDM.js} +3 -3
- package/dist/{chunk-K4XNTYV7.js → chunk-FEFOZ2ZW.js} +14 -3
- package/dist/chunk-FEFOZ2ZW.js.map +1 -0
- package/dist/{chunk-7JPUMXFE.js → chunk-FGJ2KPPN.js} +53 -26
- package/dist/chunk-FGJ2KPPN.js.map +1 -0
- package/dist/{chunk-GNV4X67F.js → chunk-KGDRGA2L.js} +10 -10
- package/dist/{chunk-4DTO2E4Y.js → chunk-LZTYPJ5I.js} +35 -17
- package/dist/chunk-LZTYPJ5I.js.map +1 -0
- package/dist/{chunk-BNZDEU5K.js → chunk-R5E2YGLE.js} +13 -7
- package/dist/chunk-R5E2YGLE.js.map +1 -0
- package/dist/{chunk-6OQKL35B.js → chunk-R5JRKMEP.js} +1 -1
- package/dist/chunk-R5JRKMEP.js.map +1 -0
- package/dist/{chunk-PZ4EBFIA.js → chunk-SPQU2422.js} +4 -4
- package/dist/{chunk-NNJSAYHY.js → chunk-SXVH74NE.js} +36 -6
- package/dist/chunk-SXVH74NE.js.map +1 -0
- package/dist/{chunk-IM5RFEW4.js → chunk-TIMS3PB7.js} +2 -2
- package/dist/cli/index.js +4 -4
- package/dist/{config-S2BDMT4B.js → config-32X53LWY.js} +3 -3
- package/dist/{expand-TONFVT5D.js → expand-VFYOWNZS.js} +4 -4
- package/dist/{file-vectors-VREL75MA.js → file-vectors-UBOKGYXX.js} +3 -2
- package/dist/{index-facts-MBJKBYA3.js → index-facts-ZTUANWBF.js} +6 -6
- package/dist/{index-pass-RP23G2CR.js → index-pass-S6NC25P5.js} +5 -5
- package/dist/index.js +8 -7
- package/dist/{install-MCJIJMFF.js → install-ZTE3H7QM.js} +7 -6
- package/dist/{lean-CBHVW6JM.js → lean-5N6BOONS.js} +4 -3
- package/dist/{local-TH5XCLVJ.js → local-5EYLEBEP.js} +4 -2
- package/dist/{local-llm-GZREB5DF.js → local-llm-ESMVP5LE.js} +3 -3
- package/dist/{main-QF6Z552J.js → main-B3HZCGVE.js} +45 -31
- package/dist/main-B3HZCGVE.js.map +1 -0
- package/dist/{memory-LSZKSF53.js → memory-CI7HIGHN.js} +4 -4
- package/dist/{operations-BPI633VN.js → operations-MW52HIQ3.js} +3 -3
- package/dist/{rerank-EPCTSEVP.js → rerank-O4S5BC45.js} +4 -4
- package/dist/{runtime-W5ACTDFS.js → runtime-SZAWIXEB.js} +5 -4
- package/dist/{summaries-W2J5FCLG.js → summaries-GCHZGIIA.js} +10 -8
- package/dist/{zvec-LSA2IRAU.js → zvec-V34P24WF.js} +10 -5
- package/package.json +1 -1
- package/dist/chunk-4DTO2E4Y.js.map +0 -1
- package/dist/chunk-6OQKL35B.js.map +0 -1
- package/dist/chunk-7JPUMXFE.js.map +0 -1
- package/dist/chunk-BNZDEU5K.js.map +0 -1
- package/dist/chunk-K4XNTYV7.js.map +0 -1
- package/dist/chunk-NNJSAYHY.js.map +0 -1
- package/dist/chunk-OO7Q7XZF.js.map +0 -1
- package/dist/chunk-RKF4HBBQ.js.map +0 -1
- package/dist/chunk-VF7DKRIU.js.map +0 -1
- package/dist/main-QF6Z552J.js.map +0 -1
- /package/dist/{chunk-AFXWCSQ4.js.map → chunk-3TZNQJTH.js.map} +0 -0
- /package/dist/{chunk-RGIVHKBH.js.map → chunk-4TKKLQLH.js.map} +0 -0
- /package/dist/{chunk-MNSMW4MZ.js.map → chunk-7IXPARDM.js.map} +0 -0
- /package/dist/{chunk-GNV4X67F.js.map → chunk-KGDRGA2L.js.map} +0 -0
- /package/dist/{chunk-PZ4EBFIA.js.map → chunk-SPQU2422.js.map} +0 -0
- /package/dist/{chunk-IM5RFEW4.js.map → chunk-TIMS3PB7.js.map} +0 -0
- /package/dist/{config-S2BDMT4B.js.map → config-32X53LWY.js.map} +0 -0
- /package/dist/{expand-TONFVT5D.js.map → expand-VFYOWNZS.js.map} +0 -0
- /package/dist/{file-vectors-VREL75MA.js.map → file-vectors-UBOKGYXX.js.map} +0 -0
- /package/dist/{index-facts-MBJKBYA3.js.map → index-facts-ZTUANWBF.js.map} +0 -0
- /package/dist/{index-pass-RP23G2CR.js.map → index-pass-S6NC25P5.js.map} +0 -0
- /package/dist/{install-MCJIJMFF.js.map → install-ZTE3H7QM.js.map} +0 -0
- /package/dist/{lean-CBHVW6JM.js.map → lean-5N6BOONS.js.map} +0 -0
- /package/dist/{local-TH5XCLVJ.js.map → local-5EYLEBEP.js.map} +0 -0
- /package/dist/{local-llm-GZREB5DF.js.map → local-llm-ESMVP5LE.js.map} +0 -0
- /package/dist/{memory-LSZKSF53.js.map → memory-CI7HIGHN.js.map} +0 -0
- /package/dist/{operations-BPI633VN.js.map → operations-MW52HIQ3.js.map} +0 -0
- /package/dist/{rerank-EPCTSEVP.js.map → rerank-O4S5BC45.js.map} +0 -0
- /package/dist/{runtime-W5ACTDFS.js.map → runtime-SZAWIXEB.js.map} +0 -0
- /package/dist/{summaries-W2J5FCLG.js.map → summaries-GCHZGIIA.js.map} +0 -0
- /package/dist/{zvec-LSA2IRAU.js.map → zvec-V34P24WF.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
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"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/commands/local-llm.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\nimport { confirm, isCancel, spinner } from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { readConfigFile, setConfigBlock } from \"../../core/config-file.js\";\nimport {\n checkLocalModelResources,\n downloadLocalModel,\n formatBytes,\n freeMemory,\n installedLocalModel,\n LLAMA_RUNTIME,\n LOCAL_MODELS,\n type LocalModelSize,\n localModelPath,\n localRuntimeAvailable,\n removeLocalModels,\n} from \"../../llm/local.js\";\nimport type { cardProgress } from \"../../llm/summaries.js\";\n\nconst ok = (text: string) => `${pc.green(\"✓\")} ${text}`;\nconst warn = (text: string) => `${pc.yellow(\"!\")} ${text}`;\nconst row = (label: string, value: string) => ` ${label.padEnd(10)} ${value}`;\n\nexport const LOCAL_LLM_SIZES = Object.keys(LOCAL_MODELS) as LocalModelSize[];\n\n/**\n * `codebuddy models add local-llm`: the explicit opt-in for the local small\n * model. Shows the size, checks disk and RAM, installs the node-llama-cpp\n * runtime when missing (after confirmation), downloads the pinned GGUF with a\n * checksum check, and records the size in `.codebuddy/config.json`.\n */\nexport async function addLocalLlm(\n options: { size?: LocalModelSize; yes?: boolean; interactive?: boolean } = {},\n): Promise<boolean> {\n const spec = LOCAL_MODELS[options.size ?? \"default\"];\n const interactive = options.interactive ?? process.stdin.isTTY === true;\n console.log(\n `${pc.bold(\"Local LLM\")} ${spec.id} ${pc.dim(`(${formatBytes(spec.bytes)} download, ~${formatBytes(spec.ramBytes)} RAM while carding)`)}`,\n );\n const resources = await checkLocalModelResources(spec);\n if (!resources.ok) {\n console.log(warn(resources.reason));\n return false;\n }\n if (freeMemory() < spec.ramBytes) {\n console.log(warn(`only ${formatBytes(freeMemory())} RAM free right now; carding may be slow`));\n }\n if (!(await localRuntimeAvailable())) {\n const install = options.yes\n ? true\n : interactive\n ? await confirm({\n message: `Install the local runtime (${LLAMA_RUNTIME}, ~20–35 MB prebuilt) with npm -g?`,\n initialValue: true,\n })\n : false;\n if (isCancel(install) || !install) {\n console.log(warn(`Runtime missing. Install it with: npm install -g ${LLAMA_RUNTIME}`));\n return false;\n }\n const code = await new Promise<number>((resolve) => {\n const child = spawn(\"npm\", [\"install\", \"-g\", LLAMA_RUNTIME], { stdio: \"inherit\" });\n child.on(\"error\", () => resolve(1));\n child.on(\"close\", (exit) => resolve(exit ?? 1));\n });\n if (code !== 0 || !(await localRuntimeAvailable())) {\n console.log(warn(`Runtime install failed. Run: npm install -g ${LLAMA_RUNTIME}`));\n return false;\n }\n }\n if ((await installedLocalModel(spec.size))?.id !== spec.id) {\n const progress = spinner();\n progress.start(`Downloading ${spec.file}`);\n let shown = 0;\n try {\n await downloadLocalModel(spec, (received, total) => {\n const percent = Math.floor((received / total) * 100);\n if (percent !== shown) {\n shown = percent;\n progress.message(`Downloading ${spec.file} ${percent}%`);\n }\n });\n progress.stop(ok(`${spec.id} verified ${pc.dim(localModelPath(spec))}`));\n } catch (error) {\n progress.stop(warn((error as Error).message));\n return false;\n }\n } else {\n console.log(ok(`${spec.id} already installed`));\n }\n const llm = (await readConfigFile()).llm;\n // A BYOK block stays as it is (the local model is then unused until it is switched).\n if (llm === undefined || llm.provider === \"local\" || llm.enabled === false) {\n await setConfigBlock(\"llm\", { ...llm, enabled: true, provider: \"local\", size: spec.size });\n } else {\n console.log(\n pc.dim(\n ` .codebuddy/config.json uses ${llm.provider}; set llm.provider to \"local\" to use it.`,\n ),\n );\n }\n console.log(\n pc.dim(\n \" Cards are written in the background by `codebuddy serve` or `codebuddy index --cards`.\",\n ),\n );\n return true;\n}\n\nexport async function removeLocalLlm(): Promise<void> {\n const removed = await removeLocalModels();\n const llm = (await readConfigFile()).llm;\n if (llm?.provider === \"local\") await setConfigBlock(\"llm\", { ...llm, enabled: false });\n console.log(\n removed.length > 0\n ? ok(`removed ${removed.join(\", \")}; CodeBuddy falls back to 2.14 behaviour`)\n : pc.dim(\"no local LLM installed\"),\n );\n}\n\nexport async function printLocalLlmStatus(\n cards: Awaited<ReturnType<typeof cardProgress>> | null,\n): Promise<void> {\n const llm = (await readConfigFile()).llm;\n const installed = await installedLocalModel(llm?.size);\n console.log(pc.bold(\"Local LLM\"));\n if (!installed) {\n console.log(row(\"model\", pc.dim(\"not installed (optional: codebuddy models add local-llm)\")));\n return;\n }\n const runtime = await localRuntimeAvailable();\n console.log(row(\"model\", ok(`${installed.id} ${pc.dim(formatBytes(installed.bytes))}`)));\n console.log(\n row(\"runtime\", runtime ? ok(LLAMA_RUNTIME) : warn(`missing: npm install -g ${LLAMA_RUNTIME}`)),\n );\n if (llm?.enabled === false)\n console.log(row(\"status\", warn(\"disabled in .codebuddy/config.json\")));\n if (!cards) return;\n const pass = cards.lastPass;\n const detail = cards.writer\n ? `being written by pid ${cards.writer}`\n : pass &&\n `last pass: ${pass.summarized} written${pass.timedOut ? `, ${pass.timedOut} timed out` : \"\"}, ${pass.remaining} left (${pass.stoppedBy})`;\n console.log(\n row(\n \"cards\",\n `${cards.carded} / ${cards.eligible} source files${detail ? pc.dim(` (${detail})`) : \"\"}`,\n ),\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/llm/local.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { mkdir, rename, rm, stat, statfs, utimes, writeFile } from \"node:fs/promises\";\nimport { availableParallelism, freemem, totalmem } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { z } from \"zod\";\nimport { modelCacheDir } from \"../providers/local.js\";\nimport type { ChatJsonRequest, LlmResult } from \"./client.js\";\n\n/**\n * Local text generation (Qwen3 only: 0.6B default, 1.7B \"better\") for the small-model features (intent cards, index\n * facts, query expansion, and optionally rerank): a GGUF model run by\n * node-llama-cpp (llama.cpp bindings; prebuilt for macOS arm64/x64, Linux\n * x64/arm64 and Windows x64; Metal/CUDA/Vulkan when present, CPU otherwise).\n * See ARCHITECTURE.md for why node-llama-cpp and not Cactus.\n *\n * Installed by setup (`codebuddy`, skipped with `--no-local-llm` or\n * `CODEBUDDY_LOCAL_LLM=0`) or `codebuddy models add local-llm`: the runtime with\n * npm, the weights checksum-verified into the shared model cache. Without\n * either, every caller gets `disabled` and keeps 2.14.0 behaviour. Nothing\n * here makes a network call except `downloadLocalModel`.\n *\n * Generation runs on llama.cpp's own native threads (capped at cores − 1),\n * so the MCP event loop keeps answering while a card is written; background\n * passes also yield to foreground work through `waitForForeground`.\n */\n\nexport type LocalModelSize = \"default\" | \"better\";\n\nexport type LocalModelSpec = {\n size: LocalModelSize;\n id: string;\n repo: string;\n revision: string;\n file: string;\n bytes: number;\n sha256: string;\n /** Rough resident memory while loaded, for the free-RAM check. */\n ramBytes: number;\n /** Qwen3 thinks unless told not to; the JSON grammar alone does not stop it. */\n noThink: boolean;\n};\n\nconst GB = 1024 ** 3;\n\n/** Pinned revisions and checksums (Hugging Face LFS sha256). */\nexport const LOCAL_MODELS: Record<LocalModelSize, LocalModelSpec> = {\n default: {\n size: \"default\",\n id: \"qwen3-0.6b-q4_k_m\",\n repo: \"unsloth/Qwen3-0.6B-GGUF\",\n revision: \"50968a4468ef4233ed78cd7c3de230dd1d61a56b\",\n file: \"Qwen3-0.6B-Q4_K_M.gguf\",\n bytes: 396_705_472,\n sha256: \"ac2d97712095a558e31573f62f466a3f9d93990898b0ec79d7c974c1780d524a\",\n ramBytes: 1 * GB,\n noThink: true,\n },\n better: {\n size: \"better\",\n id: \"qwen3-1.7b-q4_k_m\",\n repo: \"unsloth/Qwen3-1.7B-GGUF\",\n revision: \"d7f544eead698dbd1f15126ef60b45a1e1933222\",\n file: \"Qwen3-1.7B-Q4_K_M.gguf\",\n bytes: 1_107_409_472,\n sha256: \"b139949c5bd74937ad8ed8c8cf3d9ffb1e99c866c823204dc42c0d91fa181897\",\n ramBytes: 2.2 * GB,\n noThink: true,\n },\n};\n\n/** Exact runtime version the adapter was tested against. */\nexport const LLAMA_RUNTIME = \"node-llama-cpp@3.22.3\";\n\nexport function localModelPath(spec: LocalModelSpec): string {\n return join(modelCacheDir(), \"gguf\", spec.file);\n}\n\n/** The installed model of `preferred` size, else any installed one (default first). */\nexport async function installedLocalModel(\n preferred?: LocalModelSize,\n): Promise<LocalModelSpec | null> {\n if (process.env.CODEBUDDY_LOCAL_LLM === \"0\") return null;\n const order: LocalModelSize[] = [...(preferred ? [preferred] : []), \"default\", \"better\"];\n for (const size of new Set(order)) {\n const spec = LOCAL_MODELS[size];\n const info = await stat(localModelPath(spec)).catch(() => null);\n if (info?.isFile() && info.size === spec.bytes) return spec;\n }\n return null;\n}\n\n/** Whether the node-llama-cpp runtime can be imported from this install. */\nexport async function localRuntimeAvailable(): Promise<boolean> {\n try {\n await importRuntime();\n return true;\n } catch {\n return false;\n }\n}\n\nexport type DownloadCheck = { ok: true } | { ok: false; reason: string };\n\n/**\n * Whether setup should install the model on its own. A low-end machine (under\n * 4 GB of RAM or a single core) would spend most of a session carding at a few\n * tokens per second; it keeps keyword + vector discovery, and\n * `codebuddy models add local-llm` still works there by hand.\n */\nexport function machineCanCard(): DownloadCheck {\n if (totalmem() < 4 * GB) {\n return { ok: false, reason: `${formatBytes(totalmem())} RAM (setup installs it from 4 GB)` };\n }\n if (availableParallelism() < 2) return { ok: false, reason: \"a single CPU core\" };\n return { ok: true };\n}\n\n/** Free disk (model cache volume) and RAM before a download. */\nexport async function checkLocalModelResources(spec: LocalModelSpec): Promise<DownloadCheck> {\n const directory = modelCacheDir();\n await mkdir(directory, { recursive: true });\n const fs = await statfs(directory).catch(() => null);\n if (fs && fs.bavail * fs.bsize < spec.bytes * 1.1) {\n return {\n ok: false,\n reason: `needs ${formatBytes(spec.bytes * 1.1)} free in ${directory}, ${formatBytes(fs.bavail * fs.bsize)} available`,\n };\n }\n if (totalmem() < spec.ramBytes * 2) {\n return {\n ok: false,\n reason: `needs about ${formatBytes(spec.ramBytes)} of RAM while loaded; this machine has ${formatBytes(totalmem())} in total`,\n };\n }\n return { ok: true };\n}\n\n/**\n * Download the pinned GGUF into the model cache, verifying size and sha256\n * before it is moved into place (a partial or corrupt file is never used).\n */\nexport async function downloadLocalModel(\n spec: LocalModelSpec,\n onProgress?: (received: number, total: number) => void,\n fetchImpl: typeof fetch = fetch,\n): Promise<string> {\n const target = localModelPath(spec);\n await mkdir(dirname(target), { recursive: true });\n const partial = `${target}.partial`;\n const url = `https://huggingface.co/${spec.repo}/resolve/${spec.revision}/${spec.file}`;\n const response = await fetchImpl(url);\n if (!response.ok || !response.body) {\n throw new Error(`download failed: HTTP ${response.status} for ${url}`);\n }\n const hash = createHash(\"sha256\");\n let received = 0;\n const source = Readable.fromWeb(response.body as import(\"node:stream/web\").ReadableStream);\n source.on(\"data\", (chunk: Buffer) => {\n hash.update(chunk);\n received += chunk.length;\n onProgress?.(received, spec.bytes);\n });\n try {\n await pipeline(source, createWriteStream(partial));\n const digest = hash.digest(\"hex\");\n if (received !== spec.bytes || digest !== spec.sha256) {\n throw new Error(\n `checksum mismatch for ${spec.file} (got ${received} bytes, sha256 ${digest.slice(0, 12)}…); not installed`,\n );\n }\n await rename(partial, target);\n } finally {\n await rm(partial, { force: true });\n }\n return target;\n}\n\nexport async function removeLocalModels(): Promise<string[]> {\n const removed: string[] = [];\n for (const spec of Object.values(LOCAL_MODELS)) {\n const path = localModelPath(spec);\n if (await stat(path).catch(() => null)) {\n await rm(path, { force: true });\n removed.push(spec.id);\n }\n }\n return removed;\n}\n\n// ── Generation ──────────────────────────────────────────────────────────\n\n/** Minimal structural types for the dynamically imported runtime. */\ntype Llama = {\n loadModel(options: { modelPath: string }): Promise<LlamaModel>;\n createGrammarForJsonSchema(schema: unknown): Promise<unknown>;\n getGrammarFor(type: \"json\"): Promise<unknown>;\n};\ntype LlamaModel = {\n createContext(options: { contextSize: number; threads: number }): Promise<LlamaContext>;\n dispose(): Promise<void>;\n};\ntype LlamaContext = { getSequence(): unknown; dispose(): Promise<void> };\ntype ChatSession = {\n prompt(\n text: string,\n options: {\n grammar?: unknown;\n maxTokens: number;\n temperature: number;\n budgets?: { thoughtTokens: number };\n signal?: AbortSignal;\n stopOnAbortSignal?: boolean;\n },\n ): Promise<string>;\n resetChatHistory(): void;\n setChatHistory?(history: unknown): void;\n dispose(options?: { disposeSequence?: boolean }): void;\n};\ntype Runtime = {\n getLlama(options?: { gpu?: \"auto\" | false }): Promise<Llama>;\n LlamaChatSession: new (options: {\n contextSequence: unknown;\n autoDisposeSequence?: boolean;\n systemPrompt?: string;\n }) => ChatSession;\n};\n\ntype Loaded = {\n spec: LocalModelSpec;\n llama: Llama;\n model: LlamaModel;\n context: LlamaContext;\n sequence: unknown;\n runtime: Runtime;\n};\n\nlet loading: Promise<Loaded> | null = null;\nlet loaded: Loaded | null = null;\n/** One generation at a time: a single context sequence is shared. */\nlet queue: Promise<unknown> = Promise.resolve();\nconst grammars = new Map<unknown, Promise<unknown>>();\n\nasync function importRuntime(): Promise<Runtime> {\n // A variable specifier: not a dependency, never bundled, resolved at run time.\n const specifier = process.env.CODEBUDDY_LLAMA_RUNTIME ?? \"node-llama-cpp\";\n return (await import(specifier)) as Runtime;\n}\n\n/** Context large enough for the bounded card/fact prompts (~1k tokens in, ≤ 400 out). */\nconst CONTEXT_TOKENS = 4096;\n\n/** Physical cores minus one (logical on SMT machines: a ceiling, not an exact count). */\nexport function generationThreads(): number {\n const configured = Number(process.env.CODEBUDDY_LLM_THREADS);\n if (Number.isInteger(configured) && configured > 0) return configured;\n return Math.max(1, availableParallelism() - 1);\n}\n\nasync function load(spec: LocalModelSpec): Promise<Loaded> {\n if (loaded?.spec.id === spec.id) return loaded;\n if (!loading) {\n loading = (async () => {\n const runtime = await importRuntime();\n const llama = await runtime.getLlama({ gpu: \"auto\" });\n const model = await llama.loadModel({ modelPath: localModelPath(spec) });\n const context = await model.createContext({\n contextSize: CONTEXT_TOKENS,\n threads: generationThreads(),\n });\n loaded = { spec, llama, model, context, sequence: context.getSequence(), runtime };\n return loaded;\n })().finally(() => {\n loading = null;\n });\n }\n return loading;\n}\n\n/**\n * The model leaves memory this long after its last generation, so a finished\n * card pass does not keep ~1 GB resident in `codebuddy serve` for the rest of\n * the session. The next pass (or a warm-up) loads it again in under a second.\n */\nexport const IDLE_UNLOAD_MS = 60_000;\nlet idleTimer: NodeJS.Timeout | null = null;\nlet running = 0;\n\nfunction scheduleUnload(): void {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = setTimeout(() => void unloadLocalModel(), IDLE_UNLOAD_MS);\n // Never keeps the process alive on its own.\n idleTimer.unref();\n}\n\n/** Free the model and its context now (idle timer, `serve` shutdown). Never throws. */\nexport async function unloadLocalModel(): Promise<void> {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = null;\n const current = loaded;\n if (!current || running > 0) return;\n loaded = null;\n grammars.clear();\n await current.context.dispose().catch(() => {});\n await current.model.dispose().catch(() => {});\n}\n\n/** True once the model is resident in this process (query expansion only runs then). */\nexport function isLocalModelWarm(): boolean {\n return loaded !== null;\n}\n\n/** Load the model now (background warm-up); resolves false when it cannot load. */\nexport async function warmLocalModel(spec: LocalModelSpec): Promise<boolean> {\n try {\n await load(spec);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * `chatJson` for the local backend: JSON constrained by a grammar built from\n * the request's zod schema (plain JSON grammar if the schema cannot be\n * converted), then validated with zod. Never throws.\n */\nexport async function localChatJson<T>(\n spec: LocalModelSpec,\n request: ChatJsonRequest<T>,\n): Promise<LlmResult<T>> {\n const started = Date.now();\n const fail = (reason: \"timeout\" | \"invalid\" | \"disabled\", detail: string): LlmResult<T> => ({\n ok: false,\n reason,\n detail: detail.slice(0, 300),\n latencyMs: Date.now() - started,\n });\n let model: Loaded;\n try {\n model = await withDeadline(load(spec), request.deadlineMs);\n } catch (error) {\n return (error as Error).name === \"TimeoutError\"\n ? fail(\"timeout\", \"model still loading\")\n : fail(\"disabled\", `local model unavailable: ${(error as Error).message}`);\n }\n const run = async (): Promise<LlmResult<T>> => {\n running++;\n try {\n return await generate();\n } finally {\n running--;\n scheduleUnload();\n }\n };\n const generate = async (): Promise<LlmResult<T>> => {\n // The idle unload may have run while this call waited in the queue.\n if (!loaded) model = await load(spec);\n const remaining = request.deadlineMs - (Date.now() - started);\n if (remaining <= 0) return fail(\"timeout\", `no answer within ${request.deadlineMs} ms`);\n const grammar = await grammarFor(model.llama, request.jsonSchema ?? request.schema);\n const session = new model.runtime.LlamaChatSession({\n contextSequence: model.sequence,\n autoDisposeSequence: false,\n systemPrompt: request.system,\n });\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), remaining);\n try {\n const text = await session.prompt(`${spec.noThink ? \"/no_think\\n\" : \"\"}${request.user}`, {\n grammar,\n maxTokens: request.maxTokens,\n temperature: 0,\n ...(spec.noThink ? { budgets: { thoughtTokens: 0 } } : {}),\n signal: controller.signal,\n stopOnAbortSignal: true,\n });\n if (controller.signal.aborted) {\n return fail(\"timeout\", `no answer within ${request.deadlineMs} ms`);\n }\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return fail(\"invalid\", `output is not JSON: ${text.slice(0, 120)}`);\n }\n const parsed = request.schema.safeParse(value);\n if (!parsed.success)\n return fail(\"invalid\", `output failed validation: ${text.slice(0, 120)}`);\n return {\n ok: true,\n value: parsed.data,\n model: spec.id,\n latencyMs: Date.now() - started,\n tokens: Math.ceil((request.system.length + request.user.length + text.length) / 4),\n cached: false,\n };\n } catch (error) {\n return controller.signal.aborted\n ? fail(\"timeout\", `no answer within ${request.deadlineMs} ms`)\n : fail(\"invalid\", String((error as Error)?.message ?? error));\n } finally {\n clearTimeout(timer);\n session.dispose({ disposeSequence: false });\n }\n };\n const result = queue.then(run, run);\n queue = result.catch(() => undefined);\n return result;\n}\n\nasync function grammarFor(llama: Llama, schema: z.ZodType | object): Promise<unknown> {\n let grammar = grammars.get(schema);\n if (!grammar) {\n grammar = (async () => {\n try {\n const json =\n schema instanceof z.ZodType\n ? z.toJSONSchema(schema, { io: \"input\", unrepresentable: \"any\" })\n : schema;\n return await llama.createGrammarForJsonSchema(stripSchemaMeta(json));\n } catch {\n return llama.getGrammarFor(\"json\");\n }\n })();\n grammars.set(schema, grammar);\n }\n return grammar;\n}\n\n/** llama.cpp's schema grammar wants plain structure: no `$schema`, no defaults. */\nfunction stripSchemaMeta(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(stripSchemaMeta);\n if (!value || typeof value !== \"object\") return value;\n const out: Record<string, unknown> = {};\n for (const [key, child] of Object.entries(value)) {\n if (key === \"$schema\" || key === \"default\" || key === \"additionalProperties\") continue;\n out[key] = stripSchemaMeta(child);\n }\n return out;\n}\n\nfunction withDeadline<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(\n () => reject(Object.assign(new Error(\"deadline\"), { name: \"TimeoutError\" })),\n ms,\n );\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\n// ── Foreground yield ────────────────────────────────────────────────────\n\n/** A prompt hook or MCP tool call touched this within the window: background work waits. */\nconst FOREGROUND_WINDOW_MS = 2_000;\nconst FOREGROUND_MAX_WAIT_MS = 15_000;\n\nfunction foregroundPath(root: string): string {\n return join(root, \".codebuddy\", \"cache\", \"foreground\");\n}\n\n/** Record that a hook or tool call is running (cheap: one utimes). Never throws. */\nexport async function markForeground(root: string): Promise<void> {\n const path = foregroundPath(root);\n const now = new Date();\n try {\n await utimes(path, now, now);\n } catch {\n await mkdir(dirname(path), { recursive: true }).catch(() => {});\n await writeFile(path, \"\").catch(() => {});\n }\n}\n\n/** Wait (bounded) until no foreground work was marked within the window. */\nexport async function waitForForeground(root: string, signal?: AbortSignal): Promise<void> {\n const until = Date.now() + FOREGROUND_MAX_WAIT_MS;\n while (Date.now() < until && !signal?.aborted) {\n const info = await stat(foregroundPath(root)).catch(() => null);\n if (!info || Date.now() - info.mtimeMs > FOREGROUND_WINDOW_MS) return;\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n}\n\n// ── Status ──────────────────────────────────────────────────────────────\n\nexport type LocalModelStatus = {\n runtime: boolean;\n installed: LocalModelSpec | null;\n path: string | null;\n};\n\nexport async function localModelStatus(preferred?: LocalModelSize): Promise<LocalModelStatus> {\n const installed = await installedLocalModel(preferred);\n return {\n runtime: await localRuntimeAvailable(),\n installed,\n path: installed ? localModelPath(installed) : null,\n };\n}\n\nexport function formatBytes(bytes: number): string {\n return bytes >= GB ? `${(bytes / GB).toFixed(1)} GB` : `${Math.round(bytes / 1024 ** 2)} MB`;\n}\n\n/** Free RAM now (for the `models add` warning; the hard check uses total RAM). */\nexport function freeMemory(): number {\n return freemem();\n}\n"],"mappings":"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","names":[]}
|