From 87b4113bcbd53eacffeb8a94167cd7333140e57d Mon Sep 17 00:00:00 2001 From: RicardoAGL Date: Wed, 13 May 2026 12:18:44 +0200 Subject: [PATCH 1/2] fix(lattice): normalize Windows path separators in section/ref file IDs On Windows, path.relative() returns backslash-separated paths. These backslashes were embedded raw into section IDs and ref file fields, causing wiki-link resolution to fail with false broken-link errors on Windows (e.g. 8 spurious errors in a clean project). Fix: chain .replace(/\/g, '/') after the .md strip in both parseSections() and extractRefs(). This is a no-op on Mac/Linux where relative() already returns forward slashes. Fixes broken `lat check` output on Windows. --- src/lattice.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lattice.ts b/src/lattice.ts index 857a345..d207530 100644 --- a/src/lattice.ts +++ b/src/lattice.ts @@ -99,7 +99,7 @@ export function parseSections( ): Section[] { const tree = parse(content); const file = projectRoot - ? relative(projectRoot, filePath).replace(/\.md$/, '') + ? relative(projectRoot, filePath).replace(/\.md$/, '').replace(/\\/g, '/') : basename(filePath, '.md'); const sectionFilePath = projectRoot ? relative(projectRoot, filePath) @@ -619,7 +619,7 @@ export function extractRefs( ): Ref[] { const tree = parse(content); const file = projectRoot - ? relative(projectRoot, filePath).replace(/\.md$/, '') + ? relative(projectRoot, filePath).replace(/\.md$/, '').replace(/\\/g, '/') : basename(filePath, '.md'); const refs: Ref[] = []; From 7d8a0af709c7c2ffc232f64c75a04864c29b8476 Mon Sep 17 00:00:00 2001 From: RicardoAGL Date: Wed, 13 May 2026 13:15:32 +0200 Subject: [PATCH 2/2] feat(search): add Ollama embedding provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `ollama:` key format to LAT_LLM_KEY, enabling local embedding generation via Ollama without an external API key. Usage: LAT_LLM_KEY=ollama:nomic-embed-text lat search "query" Environment variables: OLLAMA_HOST Ollama base URL (default: http://localhost:11434) LAT_OLLAMA_DIMENSIONS Override vector dimensions for unlisted models Known dimensions are pre-configured for: nomic-embed-text (768), mxbai-embed-large (1024), all-minilm (384), snowflake-arctic-embed (1024), bge-large (1024), bge-base (768), bge-small (384). For any other model, set LAT_OLLAMA_DIMENSIONS to the model's output size. The Ollama /v1/embeddings endpoint is OpenAI-compatible — no auth header is sent (Ollama runs locally and requires none). --- src/search/provider.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/search/provider.ts b/src/search/provider.ts index 16ee2b6..c838d72 100644 --- a/src/search/provider.ts +++ b/src/search/provider.ts @@ -6,6 +6,18 @@ export type EmbeddingProvider = { headers: (key: string) => Record; }; +// Known dimensions for common Ollama embedding models. +// Override with LAT_OLLAMA_DIMENSIONS for unlisted models. +const OLLAMA_KNOWN_DIMENSIONS: Record = { + 'nomic-embed-text': 768, + 'mxbai-embed-large': 1024, + 'all-minilm': 384, + 'snowflake-arctic-embed': 1024, + 'bge-large': 1024, + 'bge-base': 768, + 'bge-small': 384, +}; + const openai: EmbeddingProvider = { name: 'openai', apiBase: 'https://api.openai.com/v1', @@ -39,6 +51,25 @@ export function detectProvider(key: string): EmbeddingProvider { headers: () => ({ 'Content-Type': 'application/json' }), }; } + if (key.startsWith('ollama:')) { + const model = key.slice('ollama:'.length).trim(); + if (!model) + throw new Error( + 'ollama: key must include a model name, e.g. LAT_LLM_KEY=ollama:nomic-embed-text', + ); + const host = process.env.OLLAMA_HOST ?? 'http://localhost:11434'; + const dimOverride = process.env.LAT_OLLAMA_DIMENSIONS; + const dimensions = dimOverride + ? parseInt(dimOverride, 10) + : (OLLAMA_KNOWN_DIMENSIONS[model] ?? 768); + return { + name: 'ollama', + apiBase: `${host}/v1`, + model, + dimensions, + headers: () => ({ 'Content-Type': 'application/json' }), + }; + } if (key.startsWith('sk-ant-')) { throw new Error( "Anthropic doesn't offer an embedding model. Set LAT_LLM_KEY to an OpenAI (sk-...) or Vercel AI Gateway (vck_...) key.", @@ -47,6 +78,6 @@ export function detectProvider(key: string): EmbeddingProvider { if (key.startsWith('vck_')) return vercel; if (key.startsWith('sk-')) return openai; throw new Error( - `Unrecognized LAT_LLM_KEY prefix. Supported: OpenAI (sk-...), Vercel AI Gateway (vck_...).`, + `Unrecognized LAT_LLM_KEY prefix. Supported: OpenAI (sk-...), Vercel AI Gateway (vck_...), Ollama (ollama:).`, ); }