sensemaking 0.12.0 → 0.12.1
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/dist/cjs/commands.d.cts +1 -0
- package/dist/cjs/commands.d.ts +1 -0
- package/dist/cjs/commands.js +55 -43
- package/dist/cjs/commands.js.map +1 -1
- package/dist/cjs/features/embed.d.cts +1 -1
- package/dist/cjs/features/embed.d.ts +1 -1
- package/dist/cjs/features/embed.js +2 -1
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output.d.cts +1 -0
- package/dist/cjs/output.d.ts +1 -0
- package/dist/cjs/output.js +3 -1
- package/dist/cjs/output.js.map +1 -1
- package/dist/esm/commands.d.ts +1 -0
- package/dist/esm/commands.js +57 -34
- package/dist/esm/commands.js.map +1 -1
- package/dist/esm/features/embed.d.ts +1 -1
- package/dist/esm/features/embed.js +2 -1
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output.d.ts +1 -0
- package/dist/esm/output.js +3 -1
- package/dist/esm/output.js.map +1 -1
- package/package.json +1 -1
- package/skills/sense/SKILL.md +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config.ts';\nimport { embedConfig } from '../config.ts';\nimport { SenseError } from '../errors.ts';\nimport { progress } from '../progress.ts';\nimport { parseFile } from '../scan.ts';\nimport type { Feature } from './types.ts';\n\n// Heading-based chunks, int8 vectors with a per-vector scale, NULL vector = not yet embedded.\n// Reconcile writes dirty rows; embedding tops up on the next search, so staleness costs recall.\n\n// Storage lever fixed by the bake-off (BENCHMARKING.md): int8 at 256 dims is\n// quality-free vs f32-512 when fused. Queries stay f32 at the same dims.\nconst STORE_DIMS = 256;\n// Seed chunks that participate in a `related` scan; see similarNotes for the measurement.\nconst TARGET_CHUNK_CAP = 16;\nconst BATCH = 64;\n\nexport interface EmbedProvider {\n id: string; // model identity; participates in the cache key, change -> re-embed\n dims: number;\n embed(texts: string[]): Promise<Float32Array[]>;\n}\n\ninterface Chunk {\n startLine: number;\n endLine: number;\n text: string;\n}\n\n// Deterministic, so embed time can re-derive text from stored line ranges. Heading-delimited,\n// preamble kept, whole body when no headings; the title/summary prefix mirrors bm25 weighting.\n//\n// Chunks the body, not the raw file, with `offset` shifting line numbers back onto the raw\n// file so a range stays a direct Read range (sections is 1-indexed over raw too). Chunking raw\n// put the frontmatter block in its own leading chunk on every note with a heading, which made\n// `lines` point at YAML and made frontmatter-only notes near-identical to each other. It also\n// disagreed with FTS, which indexes the body alone.\nfunction chunksOf(body: string, search?: { title: string; summary: string }, offset = 0): Chunk[] {\n const lines = body.split('\\n');\n const starts: number[] = [];\n let inFence = false;\n for (let i = 0; i < lines.length; i++) {\n if (/^(```|~~~)/.test(lines[i])) {\n inFence = !inFence;\n continue;\n }\n if (!inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);\n }\n const bounds = starts.length === 0 ? [1] : starts[0] > 1 ? [1, ...starts] : starts;\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n const chunks: Chunk[] = [];\n bounds.forEach((start, i) => {\n const end = i + 1 < bounds.length ? bounds[i + 1] - 1 : lines.length;\n const text = lines\n .slice(start - 1, end)\n .join('\\n')\n .trim();\n // A body with nothing in it yields no chunks at all, so a frontmatter-only note has no\n // vectors rather than a vector of its own YAML.\n if (text.length > 0) chunks.push({ startLine: start + offset, endLine: end + offset, text: prefix ? `${prefix}\\n${text}` : text });\n });\n return chunks;\n}\n\nexport const embed: Feature = {\n name: 'embed',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, body, search) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n return chunksOf(body, search, raw.split('\\n').length - body.split('\\n').length);\n },\n remove(db, path) {\n db.prepare('DELETE FROM embeddings WHERE \"path\" = ?').run(path);\n },\n // A tree with no embedding model never had extract() run for the doc (db.ts's per-file\n // filter skips it), so extracted is undefined here -- store nothing, i.e. no rows.\n store(db, path, extracted) {\n if (!extracted) return;\n const insert = db.prepare('INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)');\n (extracted as Chunk[]).forEach((c, idx) => insert.run(path, idx, c.startLine, c.endLine));\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n};\n\n// --- static type: Model2Vec safetensors + pure-JS tokenizer, model cached in ~/.cache ---\n// Encode convention from model2vec/model.py: no special tokens, drop unk ids, mean-pool,\n// L2-normalize.\n\nconst MODEL_FILES = ['model.safetensors', 'tokenizer.json'];\n// The pair, for messages that name what a local model directory must contain.\nexport const MODEL_FILENAMES = MODEL_FILES.join(' and ');\n\n// A Hugging Face repo id: one slash, HF's charset. Anything else is a path, used as one rather\n// than flattened into a cache key Windows would reject.\nconst HF_MODEL_ID = /^[A-Za-z0-9][\\w.-]*\\/[A-Za-z0-9][\\w.-]*$/;\n\n// Downloadable iff it names a Hugging Face repo; a local path is the caller's to populate.\nexport function isDownloadable(model: string): boolean {\n return HF_MODEL_ID.test(model);\n}\n\n// Machine-wide, not per-tree: `.sense/` is the index a rebuild throws away, while a 124 MB\n// model is shared by every tree on the machine. Honors XDG_CACHE_HOME where it is set;\n// elsewhere ~/.cache, which is also where Hugging Face's own libraries keep theirs.\nfunction cacheRoot(): string {\n const xdg = process.env.XDG_CACHE_HOME;\n return join(xdg && xdg.length > 0 ? xdg : join(homedir(), '.cache'), 'sensemaking', 'models');\n}\n\n// A model is either a local directory the caller pointed at, or the cache `sense download`\n// fills. Nothing here fetches: an absent model degrades search to its other signals rather\n// than pulling 124 MB out of a command that reads like a query.\nexport function modelDir(model: string): string {\n if (!HF_MODEL_ID.test(model)) return model;\n return join(cacheRoot(), model.replace(/\\//g, '--'));\n}\n\n// Both files, so an interrupted download reads as absent and the next one resumes it.\nexport function hasModelFiles(model: string): boolean {\n const dir = modelDir(model);\n return MODEL_FILES.every((file) => existsSync(join(dir, file)));\n}\n\n// api providers have nothing to download, so they are never \"missing\" here; an unreachable\n// endpoint surfaces as an EMBED_MODEL error at call time instead.\nexport function modelPresent(cfg: Config): boolean {\n const e = embedConfig(cfg);\n if (!e) return false;\n return e.type === 'api' || hasModelFiles(e.model);\n}\n\nfunction fetchToFile(url: string, dest: string): Promise<void> {\n return fetch(url).then(async (res) => {\n if (!res.ok) throw new SenseError('EMBED_MODEL', `model download failed: ${url} -> HTTP ${res.status}`);\n writeFileSync(`${dest}.part`, Buffer.from(await res.arrayBuffer()));\n renameSync(`${dest}.part`, dest);\n });\n}\n\n// The only code path that touches the network for weights. `sense download` calls it; no\n// query ever does. Idempotent: a file already on disk is left alone.\nexport async function downloadModel(model: string, onFile?: (file: string, dir: string) => void): Promise<string> {\n if (!isDownloadable(model)) {\n throw new SenseError('EMBED_MODEL', `embed.model \"${model}\" is a local path, not a Hugging Face model id, so there is nothing to download; put model.safetensors and tokenizer.json in that directory, or name a model id like \"minishlab/potion-retrieval-32M\"`);\n }\n const dir = modelDir(model);\n mkdirSync(dir, { recursive: true });\n for (const file of MODEL_FILES) {\n if (existsSync(join(dir, file))) continue;\n onFile?.(file, dir);\n await fetchToFile(`https://huggingface.co/${model}/resolve/main/${file}`, join(dir, file));\n }\n return dir;\n}\n\nasync function staticProvider(model: string): Promise<EmbedProvider> {\n const dir = modelDir(model);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n // A local path the caller controls gets told what is missing where; only a repo id can\n // be fixed by downloading.\n const fix = isDownloadable(model) ? 'run `sense download`' : `expected ${MODEL_FILENAMES} in that directory`;\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); ${fix}`);\n }\n }\n\n const raw = readFileSync(join(dir, 'model.safetensors'));\n const headerLen = Number(raw.readBigUInt64LE(0));\n const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8')) as Record<string, { dtype: string; shape: number[]; data_offsets: number[] }>;\n const entry = Object.entries(header).find(([k]) => k !== '__metadata__');\n if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);\n const spec = entry[1];\n const dims = spec.shape[1];\n const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];\n const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));\n\n const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));\n // Lazy import: the tokenizer loads only on the semantic path, never at CLI startup.\n const { Tokenizer } = await import('@huggingface/tokenizers');\n const tok = new Tokenizer(tokenizerJson, {});\n const unkId = tokenizerJson.model?.vocab?.[tokenizerJson.model?.unk_token] ?? -1;\n\n function one(text: string): Float32Array {\n // The tokenizer yields undefined (not the unk id) for tokens outside the vocab; an\n // undefined id would index the matrix at NaN and poison the whole mean-pool -- and the\n // int8 conversion then stores the NaN vector as all zeros, silently. Keep integers only.\n const ids = (tok.encode(text, { add_special_tokens: false }).ids as number[]).filter((id) => Number.isInteger(id) && id !== unkId);\n const v = new Float32Array(dims);\n if (ids.length === 0) return v;\n for (const id of ids) {\n const off = id * dims;\n for (let d = 0; d < dims; d++) v[d] += matrix[off + d];\n }\n let norm = 0;\n for (let d = 0; d < dims; d++) {\n v[d] /= ids.length;\n norm += v[d] * v[d];\n }\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] /= norm;\n return v;\n }\n\n return { id: `static:${model}`, dims, embed: async (texts) => texts.map(one) };\n}\n\n// --- api type: one POST against any OpenAI-compatible /embeddings endpoint ---\n\nasync function apiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n if (!url) throw new SenseError('EMBED_MODEL', 'features.embed.type \"api\" requires a url');\n const base = url.replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[]): Promise<Float32Array[]> {\n const res = await fetch(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);\n const body = (await res.json()) as { data: Array<{ embedding: number[] }> };\n return body.data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n return { id: `api:${base}:${model}`, dims, embed: post };\n}\n\nconst providers = new Map<string, Promise<EmbedProvider>>();\n\nfunction getProvider(cfg: Config): Promise<EmbedProvider> {\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'this tree has no embedding model: add an \"embed\" block naming one to sense.config.json, then run `sense download`');\n const sig = `${e.type}:${e.model}:${e.url ?? ''}`;\n let p = providers.get(sig);\n if (!p) {\n p = e.type === 'api' ? apiProvider(e.model, e.url, e.key) : staticProvider(e.model);\n providers.set(sig, p);\n }\n return p;\n}\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage.\nfunction toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL, re-deriving chunk text from the files through the\n// same parse + chunker that stored the rows.\nexport async function embedPending(db: DatabaseSync, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = db.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk').all() as Array<{ path: string; chunk: number }>;\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n let chunks: Chunk[];\n try {\n // presets/embed are irrelevant here -- re-deriving chunk text for a doc that already\n // has embeddings rows means the tree had an embedding model at reconcile time.\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, size: 0, presets: [], embed: true }, [embed]).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE \"path\" = ? AND chunk = ?');\n // The lazy build is the one long silence a first search hits (measured 23s at\n // 26k notes); progress makes it distinguishable from a hang.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += BATCH) {\n const batch = jobs.slice(i, i + BATCH);\n const vectors = await provider.embed(batch.map((j) => j.text));\n db.exec('BEGIN');\n try {\n batch.forEach((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);\n });\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n report.tick(Math.min(i + BATCH, jobs.length));\n }\n report.finish();\n}\n\n// Dequantised int8 dot products land a little either side of a true cosine, so an identical\n// pair prints 1.001 and undermines a column whose whole job is being a bounded number.\nfunction asCosine(score: number): number {\n return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality,\n// and nearest-neighbour search always returns a neighbour however far away.\nexport async function semanticCandidates(db: DatabaseSync, cfg: Config, terms: string, fetch: number): Promise<Array<{ path: string; lines: string; similarity: number }>> {\n const baseDir = (cfg as Partial<ResolvedConfig>).baseDir;\n if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');\n await embedPending(db, cfg, baseDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore((await provider.embed([text]))[0], storeDims, false);\n\n const rows = db.prepare('SELECT \"path\", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{\n path: string;\n start_line: number;\n end_line: number;\n scale: number;\n vector: Uint8Array;\n }>;\n\n const best = new Map<string, { score: number; lines: string }>();\n for (const row of rows) {\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));\n let dot = 0;\n for (let d = 0; d < q.length; d++) dot += q[d] * qv[d];\n const score = dot * row.scale;\n const existing = best.get(row.path);\n if (!existing || score > existing.score) best.set(row.path, { score, lines: `L${row.start_line}-${row.end_line}` });\n }\n return [...best.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .slice(0, fetch)\n .map(([path, b]) => ({ path, lines: b.lines, similarity: asCosine(b.score) }));\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport function hasEmbedding(db: DatabaseSync, path: string): boolean {\n const row = db.prepare('SELECT 1 AS ok FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL LIMIT 1').get(path) as { ok: number } | undefined;\n return row !== undefined;\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors. Reads only what is stored, so it stays sync.\nexport function similarNotes(db: DatabaseSync, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Array<{ path: string; similarity: number }> {\n // Cost is target_chunks x stored_chunks, so a heading-dense seed multiplies a full-corpus\n // scan (12.7s at 201 chunks/note). Sample evenly, so late sections still get a vote.\n const targetRows = db.prepare('SELECT scale, vector FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL ORDER BY chunk').all(path) as Array<{ scale: number; vector: Uint8Array }>;\n if (targetRows.length === 0) return [];\n const step = Math.max(1, Math.ceil(targetRows.length / TARGET_CHUNK_CAP));\n const target = targetRows.filter((_, i) => i % step === 0).map((row) => ({ v: new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength), scale: row.scale }));\n\n const rows = db.prepare('SELECT \"path\", scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{ path: string; scale: number; vector: Uint8Array }>;\n const best = new Map<string, number>();\n for (const row of rows) {\n if (row.path === path || opts.exclude.has(row.path) || (opts.allowed && !opts.allowed.has(row.path))) continue;\n const other = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);\n for (const t of target) {\n let dot = 0;\n const len = Math.min(t.v.length, other.length);\n for (let d = 0; d < len; d++) dot += t.v[d] * other[d];\n const score = dot * t.scale * row.scale;\n const existing = best.get(row.path);\n if (existing === undefined || score > existing) best.set(row.path, score);\n }\n }\n\n return [...best.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, opts.k)\n .map(([p, score]) => ({ path: p, similarity: asCosine(score) }));\n}\n"],"names":["MODEL_FILENAMES","downloadModel","embed","embedPending","hasEmbedding","hasModelFiles","isDownloadable","modelDir","modelPresent","semanticCandidates","similarNotes","STORE_DIMS","TARGET_CHUNK_CAP","BATCH","chunksOf","body","search","offset","lines","split","starts","inFence","i","length","test","push","bounds","prefix","title","summary","filter","Boolean","join","chunks","forEach","start","end","text","slice","trim","startLine","endLine","name","schema","db","exec","extract","raw","remove","path","prepare","run","store","extracted","insert","c","idx","enabledForFile","_cfg","file","MODEL_FILES","HF_MODEL_ID","model","cacheRoot","xdg","process","env","XDG_CACHE_HOME","homedir","replace","dir","every","existsSync","cfg","e","embedConfig","type","fetchToFile","url","dest","fetch","then","res","ok","SenseError","status","Buffer","from","arrayBuffer","writeFileSync","renameSync","onFile","mkdirSync","recursive","staticProvider","tokenizerJson","fix","headerLen","header","entry","spec","dims","dataStart","matrix","Tokenizer","tok","unkId","one","ids","encode","add_special_tokens","id","Number","isInteger","v","Float32Array","off","d","norm","Math","sqrt","readFileSync","readBigUInt64LE","JSON","parse","subarray","toString","Object","entries","find","k","dtype","shape","byteOffset","data_offsets","buffer","vocab","unk_token","texts","map","apiProvider","keyEnv","base","headers","key","post","method","stringify","input","json","data","embedding","undefined","authorization","providers","Map","getProvider","sig","p","get","set","toStore","full","int8","scale","max","abs","baseDir","provider","dirty","storeDims","byPath","row","list","jobs","chunkIdxs","update","report","batch","vectors","j","job","q","Int8Array","round","chunk","err","tick","min","all","parseFile","relPath","absPath","mtimeMs","size","presets","doc","progress","finish","asCosine","score","terms","qv","rows","best","dot","existing","match","t","includes","vector","byteLength","start_line","end_line","sort","a","b","similarity","opts","targetRows","step","ceil","target","_","exclude","has","allowed","other","len"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config.ts';\nimport { embedConfig } from '../config.ts';\nimport { SenseError } from '../errors.ts';\nimport { progress } from '../progress.ts';\nimport { parseFile } from '../scan.ts';\nimport type { Feature } from './types.ts';\n\n// Heading-based chunks, int8 vectors with a per-vector scale, NULL vector = not yet embedded.\n// Reconcile writes dirty rows; embedding tops up on the next search, so staleness costs recall.\n\n// Storage lever fixed by the bake-off (BENCHMARKING.md): int8 at 256 dims is\n// quality-free vs f32-512 when fused. Queries stay f32 at the same dims.\nconst STORE_DIMS = 256;\n// Seed chunks that participate in a `related` scan; see similarNotes for the measurement.\nconst TARGET_CHUNK_CAP = 16;\nconst BATCH = 64;\n\nexport interface EmbedProvider {\n id: string; // model identity; participates in the cache key, change -> re-embed\n dims: number;\n embed(texts: string[]): Promise<Float32Array[]>;\n}\n\ninterface Chunk {\n startLine: number;\n endLine: number;\n text: string;\n}\n\n// Deterministic, so embed time can re-derive text from stored line ranges. Heading-delimited,\n// preamble kept, whole body when no headings; the title/summary prefix mirrors bm25 weighting.\n//\n// Chunks the body, not the raw file, with `offset` shifting line numbers back onto the raw\n// file so a range stays a direct Read range (sections is 1-indexed over raw too). Chunking raw\n// put the frontmatter block in its own leading chunk on every note with a heading, which made\n// `lines` point at YAML and made frontmatter-only notes near-identical to each other. It also\n// disagreed with FTS, which indexes the body alone.\nfunction chunksOf(body: string, search?: { title: string; summary: string }, offset = 0): Chunk[] {\n const lines = body.split('\\n');\n const starts: number[] = [];\n let inFence = false;\n for (let i = 0; i < lines.length; i++) {\n if (/^(```|~~~)/.test(lines[i])) {\n inFence = !inFence;\n continue;\n }\n if (!inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);\n }\n const bounds = starts.length === 0 ? [1] : starts[0] > 1 ? [1, ...starts] : starts;\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n const chunks: Chunk[] = [];\n bounds.forEach((start, i) => {\n const end = i + 1 < bounds.length ? bounds[i + 1] - 1 : lines.length;\n const text = lines\n .slice(start - 1, end)\n .join('\\n')\n .trim();\n // A body with nothing in it yields no chunks at all, so a frontmatter-only note has no\n // vectors rather than a vector of its own YAML.\n if (text.length > 0) chunks.push({ startLine: start + offset, endLine: end + offset, text: prefix ? `${prefix}\\n${text}` : text });\n });\n return chunks;\n}\n\nexport const embed: Feature = {\n name: 'embed',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, body, search) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n return chunksOf(body, search, raw.split('\\n').length - body.split('\\n').length);\n },\n remove(db, path) {\n db.prepare('DELETE FROM embeddings WHERE \"path\" = ?').run(path);\n },\n // A tree with no embedding model never had extract() run for the doc (db.ts's per-file\n // filter skips it), so extracted is undefined here -- store nothing, i.e. no rows.\n store(db, path, extracted) {\n if (!extracted) return;\n const insert = db.prepare('INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)');\n (extracted as Chunk[]).forEach((c, idx) => insert.run(path, idx, c.startLine, c.endLine));\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n};\n\n// --- static type: Model2Vec safetensors + pure-JS tokenizer, model cached in ~/.cache ---\n// Encode convention from model2vec/model.py: no special tokens, drop unk ids, mean-pool,\n// L2-normalize.\n\nconst MODEL_FILES = ['model.safetensors', 'tokenizer.json'];\n// The pair, for messages that name what a local model directory must contain.\nexport const MODEL_FILENAMES = MODEL_FILES.join(' and ');\n\n// A Hugging Face repo id: one slash, HF's charset. Anything else is a path, used as one rather\n// than flattened into a cache key Windows would reject.\nconst HF_MODEL_ID = /^[A-Za-z0-9][\\w.-]*\\/[A-Za-z0-9][\\w.-]*$/;\n\n// Downloadable iff it names a Hugging Face repo; a local path is the caller's to populate.\nexport function isDownloadable(model: string): boolean {\n return HF_MODEL_ID.test(model);\n}\n\n// Machine-wide, not per-tree: `.sense/` is the index a rebuild throws away, while a 124 MB\n// model is shared by every tree on the machine. Honors XDG_CACHE_HOME where it is set;\n// elsewhere ~/.cache, which is also where Hugging Face's own libraries keep theirs.\nfunction cacheRoot(): string {\n const xdg = process.env.XDG_CACHE_HOME;\n return join(xdg && xdg.length > 0 ? xdg : join(homedir(), '.cache'), 'sensemaking', 'models');\n}\n\n// A model is either a local directory the caller pointed at, or the cache `sense download`\n// fills. Nothing here fetches: an absent model degrades search to its other signals rather\n// than pulling 124 MB out of a command that reads like a query.\nexport function modelDir(model: string): string {\n if (!HF_MODEL_ID.test(model)) return model;\n return join(cacheRoot(), model.replace(/\\//g, '--'));\n}\n\n// Both files, so an interrupted download reads as absent and the next one resumes it.\nexport function hasModelFiles(model: string): boolean {\n const dir = modelDir(model);\n return MODEL_FILES.every((file) => existsSync(join(dir, file)));\n}\n\n// api providers have nothing to download, so they are never \"missing\" here; an unreachable\n// endpoint surfaces as an EMBED_MODEL error at call time instead.\nexport function modelPresent(cfg: Config): boolean {\n const e = embedConfig(cfg);\n if (!e) return false;\n return e.type === 'api' || hasModelFiles(e.model);\n}\n\nfunction fetchToFile(url: string, dest: string): Promise<void> {\n return fetch(url).then(async (res) => {\n if (!res.ok) throw new SenseError('EMBED_MODEL', `model download failed: ${url} -> HTTP ${res.status}`);\n writeFileSync(`${dest}.part`, Buffer.from(await res.arrayBuffer()));\n renameSync(`${dest}.part`, dest);\n });\n}\n\n// The only code path that touches the network for weights. `sense download` calls it; no\n// query ever does. Idempotent: a file already on disk is left alone.\nexport async function downloadModel(model: string, onFile?: (file: string, dir: string) => void): Promise<string> {\n if (!isDownloadable(model)) {\n throw new SenseError('EMBED_MODEL', `embed.model \"${model}\" is a local path, not a Hugging Face model id, so there is nothing to download; put model.safetensors and tokenizer.json in that directory, or name a model id like \"minishlab/potion-retrieval-32M\"`);\n }\n const dir = modelDir(model);\n mkdirSync(dir, { recursive: true });\n for (const file of MODEL_FILES) {\n if (existsSync(join(dir, file))) continue;\n onFile?.(file, dir);\n await fetchToFile(`https://huggingface.co/${model}/resolve/main/${file}`, join(dir, file));\n }\n return dir;\n}\n\nasync function staticProvider(model: string): Promise<EmbedProvider> {\n const dir = modelDir(model);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n // A local path the caller controls gets told what is missing where; only a repo id can\n // be fixed by downloading.\n const fix = isDownloadable(model) ? 'run `sense download`' : `expected ${MODEL_FILENAMES} in that directory`;\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); ${fix}`);\n }\n }\n\n const raw = readFileSync(join(dir, 'model.safetensors'));\n const headerLen = Number(raw.readBigUInt64LE(0));\n const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8')) as Record<string, { dtype: string; shape: number[]; data_offsets: number[] }>;\n const entry = Object.entries(header).find(([k]) => k !== '__metadata__');\n if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);\n const spec = entry[1];\n const dims = spec.shape[1];\n const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];\n const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));\n\n const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));\n // Lazy import: the tokenizer loads only on the semantic path, never at CLI startup.\n const { Tokenizer } = await import('@huggingface/tokenizers');\n const tok = new Tokenizer(tokenizerJson, {});\n const unkId = tokenizerJson.model?.vocab?.[tokenizerJson.model?.unk_token] ?? -1;\n\n function one(text: string): Float32Array {\n // The tokenizer yields undefined (not the unk id) for tokens outside the vocab; an\n // undefined id would index the matrix at NaN and poison the whole mean-pool -- and the\n // int8 conversion then stores the NaN vector as all zeros, silently. Keep integers only.\n const ids = (tok.encode(text, { add_special_tokens: false }).ids as number[]).filter((id) => Number.isInteger(id) && id !== unkId);\n const v = new Float32Array(dims);\n if (ids.length === 0) return v;\n for (const id of ids) {\n const off = id * dims;\n for (let d = 0; d < dims; d++) v[d] += matrix[off + d];\n }\n let norm = 0;\n for (let d = 0; d < dims; d++) {\n v[d] /= ids.length;\n norm += v[d] * v[d];\n }\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] /= norm;\n return v;\n }\n\n return { id: `static:${model}`, dims, embed: async (texts) => texts.map(one) };\n}\n\n// --- api type: one POST against any OpenAI-compatible /embeddings endpoint ---\n\nasync function apiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n if (!url) throw new SenseError('EMBED_MODEL', 'features.embed.type \"api\" requires a url');\n const base = url.replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[]): Promise<Float32Array[]> {\n const res = await fetch(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);\n const body = (await res.json()) as { data: Array<{ embedding: number[] }> };\n return body.data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n return { id: `api:${base}:${model}`, dims, embed: post };\n}\n\nconst providers = new Map<string, Promise<EmbedProvider>>();\n\nfunction getProvider(cfg: Config): Promise<EmbedProvider> {\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'this tree has no embedding model: add an \"embed\" block naming one to sense.config.json, then run `sense download`');\n const sig = `${e.type}:${e.model}:${e.url ?? ''}`;\n let p = providers.get(sig);\n if (!p) {\n p = e.type === 'api' ? apiProvider(e.model, e.url, e.key) : staticProvider(e.model);\n providers.set(sig, p);\n }\n return p;\n}\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage.\nfunction toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL, re-deriving chunk text from the files through the\n// same parse + chunker that stored the rows.\nexport async function embedPending(db: DatabaseSync, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = db.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk').all() as Array<{ path: string; chunk: number }>;\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n let chunks: Chunk[];\n try {\n // presets/embed are irrelevant here -- re-deriving chunk text for a doc that already\n // has embeddings rows means the tree had an embedding model at reconcile time.\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, size: 0, presets: [], embed: true }, [embed]).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE \"path\" = ? AND chunk = ?');\n // The lazy build is the one long silence a first search hits (measured 23s at\n // 26k notes); progress makes it distinguishable from a hang.\n const report = progress('embedding chunks', jobs.length);\n for (let i = 0; i < jobs.length; i += BATCH) {\n const batch = jobs.slice(i, i + BATCH);\n const vectors = await provider.embed(batch.map((j) => j.text));\n db.exec('BEGIN');\n try {\n batch.forEach((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);\n });\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n report.tick(Math.min(i + BATCH, jobs.length));\n }\n report.finish();\n}\n\n// Dequantised int8 dot products land a little either side of a true cosine, so an identical\n// pair prints 1.001 and undermines a column whose whole job is being a bounded number.\nfunction asCosine(score: number): number {\n return Math.round(Math.min(1, Math.max(-1, score)) * 1000) / 1000;\n}\n\n// Best chunk per file by cosine, its line range riding along; FTS5 operators are stripped as\n// lexical syntax. Similarity comes back because the fused score cannot express match quality,\n// and nearest-neighbour search always returns a neighbour however far away.\nexport async function semanticCandidates(db: DatabaseSync, cfg: Config, terms: string, fetch: number, allowed?: Set<string>): Promise<Array<{ path: string; lines: string; similarity: number }>> {\n const baseDir = (cfg as Partial<ResolvedConfig>).baseDir;\n if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');\n await embedPending(db, cfg, baseDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore((await provider.embed([text]))[0], storeDims, false);\n\n const rows = db.prepare('SELECT \"path\", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{\n path: string;\n start_line: number;\n end_line: number;\n scale: number;\n vector: Uint8Array;\n }>;\n\n const best = new Map<string, { score: number; lines: string }>();\n for (const row of rows) {\n if (allowed && !allowed.has(row.path)) continue;\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));\n let dot = 0;\n for (let d = 0; d < q.length; d++) dot += q[d] * qv[d];\n const score = dot * row.scale;\n const existing = best.get(row.path);\n if (!existing || score > existing.score) best.set(row.path, { score, lines: `L${row.start_line}-${row.end_line}` });\n }\n return [...best.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .slice(0, fetch)\n .map(([path, b]) => ({ path, lines: b.lines, similarity: asCosine(b.score) }));\n}\n\n// Whether a note has any chunk with a vector. Distinguishes \"nothing is near this note\" from\n// \"this note has no text\", which look the same in an empty result.\nexport function hasEmbedding(db: DatabaseSync, path: string): boolean {\n const row = db.prepare('SELECT 1 AS ok FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL LIMIT 1').get(path) as { ok: number } | undefined;\n return row !== undefined;\n}\n\n// Note-to-note similarity is the max cosine over (target chunk, other chunk) pairs, one linear\n// scan of stored vectors. Reads only what is stored, so it stays sync.\nexport function similarNotes(db: DatabaseSync, _cfg: Config, path: string, opts: { exclude: Set<string>; allowed?: Set<string>; k: number }): Array<{ path: string; similarity: number }> {\n // Cost is target_chunks x stored_chunks, so a heading-dense seed multiplies a full-corpus\n // scan (12.7s at 201 chunks/note). Sample evenly, so late sections still get a vote.\n const targetRows = db.prepare('SELECT scale, vector FROM embeddings WHERE \"path\" = ? AND vector IS NOT NULL ORDER BY chunk').all(path) as Array<{ scale: number; vector: Uint8Array }>;\n if (targetRows.length === 0) return [];\n const step = Math.max(1, Math.ceil(targetRows.length / TARGET_CHUNK_CAP));\n const target = targetRows.filter((_, i) => i % step === 0).map((row) => ({ v: new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength), scale: row.scale }));\n\n const rows = db.prepare('SELECT \"path\", scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{ path: string; scale: number; vector: Uint8Array }>;\n const best = new Map<string, number>();\n for (const row of rows) {\n if (row.path === path || opts.exclude.has(row.path) || (opts.allowed && !opts.allowed.has(row.path))) continue;\n const other = new Int8Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength);\n for (const t of target) {\n let dot = 0;\n const len = Math.min(t.v.length, other.length);\n for (let d = 0; d < len; d++) dot += t.v[d] * other[d];\n const score = dot * t.scale * row.scale;\n const existing = best.get(row.path);\n if (existing === undefined || score > existing) best.set(row.path, score);\n }\n }\n\n return [...best.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, opts.k)\n .map(([p, score]) => ({ path: p, similarity: asCosine(score) }));\n}\n"],"names":["MODEL_FILENAMES","downloadModel","embed","embedPending","hasEmbedding","hasModelFiles","isDownloadable","modelDir","modelPresent","semanticCandidates","similarNotes","STORE_DIMS","TARGET_CHUNK_CAP","BATCH","chunksOf","body","search","offset","lines","split","starts","inFence","i","length","test","push","bounds","prefix","title","summary","filter","Boolean","join","chunks","forEach","start","end","text","slice","trim","startLine","endLine","name","schema","db","exec","extract","raw","remove","path","prepare","run","store","extracted","insert","c","idx","enabledForFile","_cfg","file","MODEL_FILES","HF_MODEL_ID","model","cacheRoot","xdg","process","env","XDG_CACHE_HOME","homedir","replace","dir","every","existsSync","cfg","e","embedConfig","type","fetchToFile","url","dest","fetch","then","res","ok","SenseError","status","Buffer","from","arrayBuffer","writeFileSync","renameSync","onFile","mkdirSync","recursive","staticProvider","tokenizerJson","fix","headerLen","header","entry","spec","dims","dataStart","matrix","Tokenizer","tok","unkId","one","ids","encode","add_special_tokens","id","Number","isInteger","v","Float32Array","off","d","norm","Math","sqrt","readFileSync","readBigUInt64LE","JSON","parse","subarray","toString","Object","entries","find","k","dtype","shape","byteOffset","data_offsets","buffer","vocab","unk_token","texts","map","apiProvider","keyEnv","base","headers","key","post","method","stringify","input","json","data","embedding","undefined","authorization","providers","Map","getProvider","sig","p","get","set","toStore","full","int8","scale","max","abs","baseDir","provider","dirty","storeDims","byPath","row","list","jobs","chunkIdxs","update","report","batch","vectors","j","job","q","Int8Array","round","chunk","err","tick","min","all","parseFile","relPath","absPath","mtimeMs","size","presets","doc","progress","finish","asCosine","score","terms","allowed","qv","rows","best","dot","existing","match","t","includes","has","vector","byteLength","start_line","end_line","sort","a","b","similarity","opts","targetRows","step","ceil","target","_","exclude","other","len"],"mappings":"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"}
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -6,7 +6,7 @@ export type { OpenResult } from './db.js';
|
|
|
6
6
|
export { clearCache, open } from './db.js';
|
|
7
7
|
export type { SenseErrorCode } from './errors.js';
|
|
8
8
|
export { SenseError } from './errors.js';
|
|
9
|
-
export type { Row } from './output.js';
|
|
9
|
+
export type { Format, Row, RowFormat } from './output.js';
|
|
10
10
|
export { printRows } from './output.js';
|
|
11
11
|
export type { WatchEvent, WatchOptions } from './watch.js';
|
|
12
12
|
export { runWatch } from './watch.js';
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type { OpenResult } from './db.js';
|
|
|
6
6
|
export { clearCache, open } from './db.js';
|
|
7
7
|
export type { SenseErrorCode } from './errors.js';
|
|
8
8
|
export { SenseError } from './errors.js';
|
|
9
|
-
export type { Row } from './output.js';
|
|
9
|
+
export type { Format, Row, RowFormat } from './output.js';
|
|
10
10
|
export { printRows } from './output.js';
|
|
11
11
|
export type { WatchEvent, WatchOptions } from './watch.js';
|
|
12
12
|
export { runWatch } from './watch.js';
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands.ts';\nexport { mapTree, peek, presetCoverage, search } from './commands.ts';\nexport type { Config, EmbedConfig, FeatureName, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\nexport type { OpenResult } from './db.ts';\nexport { clearCache, open } from './db.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","SenseError","clearCache","initConfig","loadConfig","mapTree","migrateConfig","open","peek","presetCoverage","printRows","runWatch","search"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;;;;;;;;;;;;QAK9DA;eAAAA,yBAAe;;QAAyCC;eAAAA,mBAAS;;QAAEC;eAAAA,kCAAwB;;QAI3FC;eAAAA,oBAAU;;QAFVC;eAAAA,gBAAU;;QAFOC;eAAAA,oBAAU;;QAAEC;eAAAA,oBAAU;;QAFvCC;eAAAA,mBAAO;;QAEkCC;eAAAA,uBAAa;;QAE1CC;eAAAA,UAAI;;QAJPC;eAAAA,gBAAI;;QAAEC;eAAAA,0BAAc;;QAQ7BC;eAAAA,mBAAS;;QAGTC;eAAAA,iBAAQ;;QAXuBC;eAAAA,kBAAM;;;0BAAQ;wBAEsD;oBAE3E;wBAEN;wBAED;uBAGD"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands.ts';\nexport { mapTree, peek, presetCoverage, search } from './commands.ts';\nexport type { Config, EmbedConfig, FeatureName, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\nexport type { OpenResult } from './db.ts';\nexport { clearCache, open } from './db.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Format, Row, RowFormat } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","SenseError","clearCache","initConfig","loadConfig","mapTree","migrateConfig","open","peek","presetCoverage","printRows","runWatch","search"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;;;;;;;;;;;;QAK9DA;eAAAA,yBAAe;;QAAyCC;eAAAA,mBAAS;;QAAEC;eAAAA,kCAAwB;;QAI3FC;eAAAA,oBAAU;;QAFVC;eAAAA,gBAAU;;QAFOC;eAAAA,oBAAU;;QAAEC;eAAAA,oBAAU;;QAFvCC;eAAAA,mBAAO;;QAEkCC;eAAAA,uBAAa;;QAE1CC;eAAAA,UAAI;;QAJPC;eAAAA,gBAAI;;QAAEC;eAAAA,0BAAc;;QAQ7BC;eAAAA,mBAAS;;QAGTC;eAAAA,iBAAQ;;QAXuBC;eAAAA,kBAAM;;;0BAAQ;wBAEsD;oBAE3E;wBAEN;wBAED;uBAGD"}
|
package/dist/cjs/output.d.cts
CHANGED
package/dist/cjs/output.d.ts
CHANGED
package/dist/cjs/output.js
CHANGED
|
@@ -334,7 +334,9 @@ function renderMap(result) {
|
|
|
334
334
|
if (result.fieldsTotal > result.fields.length) parts.push("(+".concat(result.fieldsTotal - result.fields.length, " more fields)"));
|
|
335
335
|
if (result.hubs.length > 0) parts.push('\nhubs (by link rank):', renderRows(result.hubs, 'table'));
|
|
336
336
|
else if (result.features.off.includes('rank')) parts.push("\nhubs: off (".concat(featureOffHint('rank'), ")"));
|
|
337
|
-
parts.push('\nrecent:'
|
|
337
|
+
parts.push('\nrecent:');
|
|
338
|
+
if (result.recentCaveat) parts.push(result.recentCaveat);
|
|
339
|
+
parts.push(renderRows(result.recent, 'table'));
|
|
338
340
|
return parts.join('\n');
|
|
339
341
|
}
|
|
340
342
|
function renderPeek(result) {
|
package/dist/cjs/output.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json | csv\n\nexport type Row = Record<string, unknown>;\n\n// map/peek/status/path render structures rather than a row set, so csv has nothing to mean\n// there; the commands that emit rows take RowFormat.\nexport type Format = 'table' | 'json';\nexport type RowFormat = Format | 'csv';\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\n// RFC 4180, and deliberately not cell(): csv is the format for redirecting a result to a\n// file, so it keeps every character, embedded newlines included. What it cannot carry is\n// NULL vs empty string, or a value's type -- that is what json is for.\nfunction csvField(value: unknown): string {\n const text = value === null || value === undefined ? '' : String(value);\n return /[\"\\r\\n,]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\nfunction csvLine(values: unknown[]): string {\n return values.map(csvField).join(',');\n}\n\nfunction warnOversize(bytes: number, rowCount: number): void {\n if (bytes <= OVERSIZE_BYTES) return;\n console.warn(`warning: result is ${Math.round(bytes / 1000)} KB across ${rowCount} row(s); consider a LIMIT, fewer columns, snippet() instead of whole values, or --format csv redirected to a file`);\n}\n\n// Header names come from a row wherever there is one: a statement's column list keeps\n// duplicates that the row object has already collapsed, so `SELECT a AS x, b AS x` would print\n// b's value under both headers. `columns` is the fallback that labels a 0-row result, and with\n// no rows and no statement (a bounded command that found nothing) csv writes nothing at all --\n// a bare newline would read to a csv parser as one empty record.\nexport function printRows(rows: Row[], format: RowFormat, columns?: string[]): void {\n if (format === 'csv') {\n // Collapsed the same way a row object collapses them, so a statement naming a column\n // twice describes itself identically whether it matched anything or not.\n const names = rows.length > 0 ? Object.keys(rows[0]) : [...new Set(columns ?? [])];\n if (names.length === 0) return;\n const rendered = [csvLine(names), ...rows.map((row) => csvLine(names.map((name) => row[name])))].join('\\n');\n console.log(rendered);\n // Bytes, not code units -- CJK is 3 bytes per character.\n warnOversize(Buffer.byteLength(rendered), rows.length);\n return;\n }\n const rendered = renderRows(rows, format);\n console.log(rendered);\n warnOversize(Buffer.byteLength(rendered), rows.length);\n}\n\n// Streaming counterpart for the one unbounded caller, `sense sql`: rows arrive from an\n// iterator, so a large result is never held here as rows and again as a rendered string.\n// Table buffers by necessity (a column is as wide as its widest cell, which the last row can\n// change); json and csv write row by row. The writes are synchronous, so when stdout is a pipe\n// Node buffers what the reader has not taken yet -- the saving is this module's copies, not\n// the operating system's.\nexport function printRowStream(rows: Iterable<Row>, format: RowFormat, columns: string[]): void {\n const iterator = rows[Symbol.iterator]();\n // One step before any write. FTS5 parses a MATCH expression at step time rather than at\n // prepare time, so a bad search string throws here, with stdout still clean for the error.\n const first = iterator.next();\n const rest: Iterable<Row> = { [Symbol.iterator]: () => iterator };\n\n if (format === 'table') {\n printRows(first.done ? [] : [first.value, ...rest], format, columns);\n return;\n }\n\n let bytes = 0;\n let rowCount = 0;\n const write = (text: string): void => {\n // Bytes, not code units -- CJK is 3 bytes per character.\n bytes += Buffer.byteLength(text);\n process.stdout.write(text);\n };\n\n if (format === 'json') {\n // Byte-identical to JSON.stringify(rows, null, 2) + newline: each row re-indented one\n // level, comma-joined, inside the array brackets written here.\n // JSON.stringify throws on BigInt, so a row with an int64 past 2^53 (setReadBigInts) is\n // converted via the replacer: a value that fits a safe integer stays a number, a larger one\n // becomes its decimal string, since a JSON number cannot carry it losslessly.\n const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\n const bigintSafe = (_key: string, value: unknown) => (typeof value === 'bigint' ? (value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString()) : value);\n const indent = (row: Row) =>\n JSON.stringify(row, bigintSafe, 2)\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n');\n if (first.done) {\n write('[]\\n');\n } else {\n write('[\\n');\n write(indent(first.value));\n rowCount = 1;\n for (const row of rest) {\n write(',\\n');\n write(indent(row));\n rowCount++;\n }\n write('\\n]\\n');\n }\n } else if (first.done) {\n const names = [...new Set(columns)];\n if (names.length > 0) write(`${csvLine(names)}\\n`);\n } else {\n const names = Object.keys(first.value);\n write(`${csvLine(names)}\\n`);\n write(`${csvLine(names.map((name) => first.value[name]))}\\n`);\n rowCount = 1;\n for (const row of rest) {\n write(`${csvLine(names.map((name) => row[name]))}\\n`);\n rowCount++;\n }\n }\n warnOversize(bytes, rowCount);\n}\n\n// Tables are for reading, and a row wider than the terminal wraps unreadably: widest columns\n// give up space first, down to a floor. --format json is never truncated.\nconst MIN_COLUMN = 12;\nconst TRUNCATED = '…';\n\nfunction fitWidths(widths: number[], limit: number): number[] {\n const gaps = (widths.length - 1) * 2;\n const fitted = [...widths];\n for (;;) {\n const total = fitted.reduce((a, b) => a + b, 0) + gaps;\n if (total <= limit) return fitted;\n const widest = fitted.indexOf(Math.max(...fitted));\n if (fitted[widest] <= MIN_COLUMN) return fitted;\n fitted[widest] = Math.max(MIN_COLUMN, fitted[widest] - Math.max(1, total - limit));\n }\n}\n\nfunction renderRows(rows: Row[], format: Format, width = process.stdout.columns): string {\n if (format === 'json') return JSON.stringify(rows, null, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const cells = rows.map((row) => columns.map((col) => cell(row[col])));\n const natural = columns.map((col, i) => Math.max(col.length, ...cells.map((row) => row[i].length)));\n const widths = width && width > MIN_COLUMN ? fitWidths(natural, width) : natural;\n\n const clip = (value: string, w: number) => (value.length <= w ? value.padEnd(w) : `${value.slice(0, w - 1)}${TRUNCATED}`);\n const formatRow = (values: string[]) =>\n values\n .map((value, i) => clip(value, widths[i]))\n .join(' ')\n .trimEnd();\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...cells.map(formatRow)].join('\\n');\n}\n\n// Text renderers for the top-level commands; cli.ts prints what these return.\n\n// Names the config key that turns a feature back on. Only the three features-block toggles\n// reach here; `embed` has its own status line.\nexport function featureOffHint(name: string): string {\n return `features.${name}`;\n}\n\nexport function featuresLine(features: { on: string[]; off: string[] }): string {\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (${featureOffHint(name)})`).join(', ')}` : '';\n return `features: ${features.on.join(', ')}${off}`;\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number; semantic?: boolean }>): string[] {\n // \"0 embedded\" is ambiguous between a scope that declined vectors and one that has not built\n // them yet, so a semantic-off preset says which.\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.semantic === false ? ' (semantic: false)' : ''}`)];\n}\n\nexport function renderMap(result: { docs: { count: number; bytes: number }; fields: Row[]; fieldsTotal: number; features: { on: string[]; off: string[] }; presets: Array<{ name: string; files: number; embedded: number; semantic?: boolean }>; hubs: Row[]; recent: Row[] }): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`, featuresLine(result.features), '', ...presetsLines(result.presets), '', renderRows(result.fields, 'table')];\n if (result.fieldsTotal > result.fields.length) parts.push(`(+${result.fieldsTotal - result.fields.length} more fields)`);\n if (result.hubs.length > 0) parts.push('\\nhubs (by link rank):', renderRows(result.hubs, 'table'));\n else if (result.features.off.includes('rank')) parts.push(`\\nhubs: off (${featureOffHint('rank')})`);\n parts.push('\\nrecent:', renderRows(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: { path: string; tokens: number; frontmatter: Row; parseError?: string | null; sections: Row[]; outbound: string[]; backlinks: string[]; unresolved: string[]; sectionsTotal: number; outboundTotal: number; backlinksTotal: number; unresolvedTotal: number; off: string[] }): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\n // Otherwise a refused parse is indistinguishable from a note that has no frontmatter, which\n // is the confusion the whole quarantine design exists to remove.\n if (result.parseError) lines.push(` frontmatter: did not parse, so none of it is indexed (${result.parseError})`);\n for (const [key, value] of Object.entries(result.frontmatter)) lines.push(` ${key}: ${value}`);\n if (result.sections.length > 0) {\n lines.push('', 'sections:');\n for (const s of result.sections) lines.push(` ${'#'.repeat(s.level as number)} ${s.heading} [L${s.start_line}-${s.end_line}, ~${s.tokens}t]`);\n if (result.sectionsTotal > result.sections.length) lines.push(` (+${result.sectionsTotal - result.sections.length} more sections -- sections table has all of them)`);\n } else if (result.off.includes('sections')) lines.push('', 'sections: off (features.sections)');\n if (result.off.includes('links')) {\n lines.push('', 'links: off (features.links)');\n } else {\n const linkLine = (label: string, shown: string[], total: number) => {\n if (total > 0) lines.push(`${label} (${total}): ${shown.join(', ')}${total > shown.length ? `, +${total - shown.length} more` : ''}`);\n };\n if (result.outboundTotal + result.unresolvedTotal + result.backlinksTotal > 0) lines.push('');\n linkLine('links out', result.outbound, result.outboundTotal);\n linkLine('unresolved', result.unresolved, result.unresolvedTotal);\n linkLine('backlinks', result.backlinks, result.backlinksTotal);\n }\n return lines.join('\\n');\n}\n"],"names":["featureOffHint","featuresLine","presetsLines","printRowStream","printRows","renderMap","renderPeek","OVERSIZE_BYTES","cell","value","String","replace","csvField","text","undefined","test","csvLine","values","map","join","warnOversize","bytes","rowCount","console","warn","Math","round","rows","format","columns","names","length","Object","keys","Set","rendered","row","name","log","Buffer","byteLength","renderRows","iterator","Symbol","first","next","rest","done","write","process","stdout","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","bigintSafe","_key","toString","indent","JSON","stringify","split","line","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","width","cells","col","natural","i","clip","w","padEnd","slice","formatRow","trimEnd","repeat","features","off","on","presets","p","files","embedded","semantic","result","parts","docs","count","fields","fieldsTotal","push","hubs","includes","recent","lines","path","tokens","parseError","entries","frontmatter","key","sections","s","level","heading","start_line","end_line","sectionsTotal","linkLine","label","shown","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json | csv\n\nexport type Row = Record<string, unknown>;\n\n// map/peek/status/path render structures rather than a row set, so csv has nothing to mean\n// there; the commands that emit rows take RowFormat.\nexport type Format = 'table' | 'json';\nexport type RowFormat = Format | 'csv';\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\n// RFC 4180, and deliberately not cell(): csv is the format for redirecting a result to a\n// file, so it keeps every character, embedded newlines included. What it cannot carry is\n// NULL vs empty string, or a value's type -- that is what json is for.\nfunction csvField(value: unknown): string {\n const text = value === null || value === undefined ? '' : String(value);\n return /[\"\\r\\n,]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text;\n}\n\nfunction csvLine(values: unknown[]): string {\n return values.map(csvField).join(',');\n}\n\nfunction warnOversize(bytes: number, rowCount: number): void {\n if (bytes <= OVERSIZE_BYTES) return;\n console.warn(`warning: result is ${Math.round(bytes / 1000)} KB across ${rowCount} row(s); consider a LIMIT, fewer columns, snippet() instead of whole values, or --format csv redirected to a file`);\n}\n\n// Header names come from a row wherever there is one: a statement's column list keeps\n// duplicates that the row object has already collapsed, so `SELECT a AS x, b AS x` would print\n// b's value under both headers. `columns` is the fallback that labels a 0-row result, and with\n// no rows and no statement (a bounded command that found nothing) csv writes nothing at all --\n// a bare newline would read to a csv parser as one empty record.\nexport function printRows(rows: Row[], format: RowFormat, columns?: string[]): void {\n if (format === 'csv') {\n // Collapsed the same way a row object collapses them, so a statement naming a column\n // twice describes itself identically whether it matched anything or not.\n const names = rows.length > 0 ? Object.keys(rows[0]) : [...new Set(columns ?? [])];\n if (names.length === 0) return;\n const rendered = [csvLine(names), ...rows.map((row) => csvLine(names.map((name) => row[name])))].join('\\n');\n console.log(rendered);\n // Bytes, not code units -- CJK is 3 bytes per character.\n warnOversize(Buffer.byteLength(rendered), rows.length);\n return;\n }\n const rendered = renderRows(rows, format);\n console.log(rendered);\n warnOversize(Buffer.byteLength(rendered), rows.length);\n}\n\n// Streaming counterpart for the one unbounded caller, `sense sql`: rows arrive from an\n// iterator, so a large result is never held here as rows and again as a rendered string.\n// Table buffers by necessity (a column is as wide as its widest cell, which the last row can\n// change); json and csv write row by row. The writes are synchronous, so when stdout is a pipe\n// Node buffers what the reader has not taken yet -- the saving is this module's copies, not\n// the operating system's.\nexport function printRowStream(rows: Iterable<Row>, format: RowFormat, columns: string[]): void {\n const iterator = rows[Symbol.iterator]();\n // One step before any write. FTS5 parses a MATCH expression at step time rather than at\n // prepare time, so a bad search string throws here, with stdout still clean for the error.\n const first = iterator.next();\n const rest: Iterable<Row> = { [Symbol.iterator]: () => iterator };\n\n if (format === 'table') {\n printRows(first.done ? [] : [first.value, ...rest], format, columns);\n return;\n }\n\n let bytes = 0;\n let rowCount = 0;\n const write = (text: string): void => {\n // Bytes, not code units -- CJK is 3 bytes per character.\n bytes += Buffer.byteLength(text);\n process.stdout.write(text);\n };\n\n if (format === 'json') {\n // Byte-identical to JSON.stringify(rows, null, 2) + newline: each row re-indented one\n // level, comma-joined, inside the array brackets written here.\n // JSON.stringify throws on BigInt, so a row with an int64 past 2^53 (setReadBigInts) is\n // converted via the replacer: a value that fits a safe integer stays a number, a larger one\n // becomes its decimal string, since a JSON number cannot carry it losslessly.\n const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\n const bigintSafe = (_key: string, value: unknown) => (typeof value === 'bigint' ? (value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString()) : value);\n const indent = (row: Row) =>\n JSON.stringify(row, bigintSafe, 2)\n .split('\\n')\n .map((line) => ` ${line}`)\n .join('\\n');\n if (first.done) {\n write('[]\\n');\n } else {\n write('[\\n');\n write(indent(first.value));\n rowCount = 1;\n for (const row of rest) {\n write(',\\n');\n write(indent(row));\n rowCount++;\n }\n write('\\n]\\n');\n }\n } else if (first.done) {\n const names = [...new Set(columns)];\n if (names.length > 0) write(`${csvLine(names)}\\n`);\n } else {\n const names = Object.keys(first.value);\n write(`${csvLine(names)}\\n`);\n write(`${csvLine(names.map((name) => first.value[name]))}\\n`);\n rowCount = 1;\n for (const row of rest) {\n write(`${csvLine(names.map((name) => row[name]))}\\n`);\n rowCount++;\n }\n }\n warnOversize(bytes, rowCount);\n}\n\n// Tables are for reading, and a row wider than the terminal wraps unreadably: widest columns\n// give up space first, down to a floor. --format json is never truncated.\nconst MIN_COLUMN = 12;\nconst TRUNCATED = '…';\n\nfunction fitWidths(widths: number[], limit: number): number[] {\n const gaps = (widths.length - 1) * 2;\n const fitted = [...widths];\n for (;;) {\n const total = fitted.reduce((a, b) => a + b, 0) + gaps;\n if (total <= limit) return fitted;\n const widest = fitted.indexOf(Math.max(...fitted));\n if (fitted[widest] <= MIN_COLUMN) return fitted;\n fitted[widest] = Math.max(MIN_COLUMN, fitted[widest] - Math.max(1, total - limit));\n }\n}\n\nfunction renderRows(rows: Row[], format: Format, width = process.stdout.columns): string {\n if (format === 'json') return JSON.stringify(rows, null, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const cells = rows.map((row) => columns.map((col) => cell(row[col])));\n const natural = columns.map((col, i) => Math.max(col.length, ...cells.map((row) => row[i].length)));\n const widths = width && width > MIN_COLUMN ? fitWidths(natural, width) : natural;\n\n const clip = (value: string, w: number) => (value.length <= w ? value.padEnd(w) : `${value.slice(0, w - 1)}${TRUNCATED}`);\n const formatRow = (values: string[]) =>\n values\n .map((value, i) => clip(value, widths[i]))\n .join(' ')\n .trimEnd();\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...cells.map(formatRow)].join('\\n');\n}\n\n// Text renderers for the top-level commands; cli.ts prints what these return.\n\n// Names the config key that turns a feature back on. Only the three features-block toggles\n// reach here; `embed` has its own status line.\nexport function featureOffHint(name: string): string {\n return `features.${name}`;\n}\n\nexport function featuresLine(features: { on: string[]; off: string[] }): string {\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (${featureOffHint(name)})`).join(', ')}` : '';\n return `features: ${features.on.join(', ')}${off}`;\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number; semantic?: boolean }>): string[] {\n // \"0 embedded\" is ambiguous between a scope that declined vectors and one that has not built\n // them yet, so a semantic-off preset says which.\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.semantic === false ? ' (semantic: false)' : ''}`)];\n}\n\nexport function renderMap(result: { docs: { count: number; bytes: number }; fields: Row[]; fieldsTotal: number; features: { on: string[]; off: string[] }; presets: Array<{ name: string; files: number; embedded: number; semantic?: boolean }>; hubs: Row[]; recent: Row[]; recentCaveat: string | null }): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`, featuresLine(result.features), '', ...presetsLines(result.presets), '', renderRows(result.fields, 'table')];\n if (result.fieldsTotal > result.fields.length) parts.push(`(+${result.fieldsTotal - result.fields.length} more fields)`);\n if (result.hubs.length > 0) parts.push('\\nhubs (by link rank):', renderRows(result.hubs, 'table'));\n else if (result.features.off.includes('rank')) parts.push(`\\nhubs: off (${featureOffHint('rank')})`);\n parts.push('\\nrecent:');\n if (result.recentCaveat) parts.push(result.recentCaveat);\n parts.push(renderRows(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: { path: string; tokens: number; frontmatter: Row; parseError?: string | null; sections: Row[]; outbound: string[]; backlinks: string[]; unresolved: string[]; sectionsTotal: number; outboundTotal: number; backlinksTotal: number; unresolvedTotal: number; off: string[] }): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\n // Otherwise a refused parse is indistinguishable from a note that has no frontmatter, which\n // is the confusion the whole quarantine design exists to remove.\n if (result.parseError) lines.push(` frontmatter: did not parse, so none of it is indexed (${result.parseError})`);\n for (const [key, value] of Object.entries(result.frontmatter)) lines.push(` ${key}: ${value}`);\n if (result.sections.length > 0) {\n lines.push('', 'sections:');\n for (const s of result.sections) lines.push(` ${'#'.repeat(s.level as number)} ${s.heading} [L${s.start_line}-${s.end_line}, ~${s.tokens}t]`);\n if (result.sectionsTotal > result.sections.length) lines.push(` (+${result.sectionsTotal - result.sections.length} more sections -- sections table has all of them)`);\n } else if (result.off.includes('sections')) lines.push('', 'sections: off (features.sections)');\n if (result.off.includes('links')) {\n lines.push('', 'links: off (features.links)');\n } else {\n const linkLine = (label: string, shown: string[], total: number) => {\n if (total > 0) lines.push(`${label} (${total}): ${shown.join(', ')}${total > shown.length ? `, +${total - shown.length} more` : ''}`);\n };\n if (result.outboundTotal + result.unresolvedTotal + result.backlinksTotal > 0) lines.push('');\n linkLine('links out', result.outbound, result.outboundTotal);\n linkLine('unresolved', result.unresolved, result.unresolvedTotal);\n linkLine('backlinks', result.backlinks, result.backlinksTotal);\n }\n return lines.join('\\n');\n}\n"],"names":["featureOffHint","featuresLine","presetsLines","printRowStream","printRows","renderMap","renderPeek","OVERSIZE_BYTES","cell","value","String","replace","csvField","text","undefined","test","csvLine","values","map","join","warnOversize","bytes","rowCount","console","warn","Math","round","rows","format","columns","names","length","Object","keys","Set","rendered","row","name","log","Buffer","byteLength","renderRows","iterator","Symbol","first","next","rest","done","write","process","stdout","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","bigintSafe","_key","toString","indent","JSON","stringify","split","line","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","width","cells","col","natural","i","clip","w","padEnd","slice","formatRow","trimEnd","repeat","features","off","on","presets","p","files","embedded","semantic","result","parts","docs","count","fields","fieldsTotal","push","hubs","includes","recentCaveat","recent","lines","path","tokens","parseError","entries","frontmatter","key","sections","s","level","heading","start_line","end_line","sectionsTotal","linkLine","label","shown","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"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"}
|
package/dist/esm/commands.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface TreeMap {
|
|
|
32
32
|
presets: PresetCoverage[];
|
|
33
33
|
hubs: Row[];
|
|
34
34
|
recent: Row[];
|
|
35
|
+
recentCaveat: string | null;
|
|
35
36
|
}
|
|
36
37
|
export declare function mapTree(db: DatabaseSync, cfg: ResolvedConfig, overrides?: SearchOverrides): TreeMap;
|
|
37
38
|
export declare function resolveNote(paths: string[], arg: string): string;
|
package/dist/esm/commands.js
CHANGED
|
@@ -127,24 +127,27 @@ function scopeHasEmbeddings(db, cfg, scopedPaths) {
|
|
|
127
127
|
export async function search(db, cfg, terms, opts = {}) {
|
|
128
128
|
var _hits_get, _chunkLines_get, _chunkSimilarity_get;
|
|
129
129
|
const effective = resolveSearch(cfg, opts);
|
|
130
|
-
const { k
|
|
130
|
+
const { k } = effective;
|
|
131
131
|
const allPaths = db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
const scopePaths = rawScope(db, cfg, opts, allPaths);
|
|
133
|
+
const scopeActive = scopePaths.size < allPaths.length;
|
|
134
|
+
// The set every candidate pool must be filtered to before truncation: scope narrowed by
|
|
135
|
+
// --where, the same composition scopedPaths() gives the other commands. Runs the same where
|
|
136
|
+
// fragment matchSql does, so a bad column gets the same attributed error either way.
|
|
137
|
+
let allowedPaths;
|
|
138
|
+
try {
|
|
139
|
+
allowedPaths = narrowByWhere(db, scopePaths, effective.where);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
throw searchError(err, terms, effective.where);
|
|
142
|
+
}
|
|
143
|
+
const fetch = Math.max(k * 3, 30);
|
|
141
144
|
// Asking for vectors without a model is a misconfiguration, not a mode: degrading would make
|
|
142
145
|
// the same search answer differently before and after a download. semantic:false never asks.
|
|
143
146
|
const wantsVectors = effective.semantic && anyPresetEmbeds(cfg);
|
|
144
147
|
if (wantsVectors && !modelPresent(cfg)) {
|
|
145
148
|
throw new SenseError('EMBED_MODEL_MISSING', `preset "${effective.presetName}" searches with vectors, but the embedding model is not available; run \`sense download\`, or set "semantic": false on that preset to search on words and links`);
|
|
146
149
|
}
|
|
147
|
-
const semanticEnabled = wantsVectors && scopeHasEmbeddings(db, cfg,
|
|
150
|
+
const semanticEnabled = wantsVectors && scopeHasEmbeddings(db, cfg, allowedPaths);
|
|
148
151
|
// Terms pass to MATCH as written, with one transform: a run of text whose language marks no
|
|
149
152
|
// word boundaries becomes a quoted grapheme phrase (and a title:/summary:/text: qualifier
|
|
150
153
|
// ahead of it retargets its _seg column), matching how it was indexed. Text that already
|
|
@@ -158,12 +161,18 @@ export async function search(db, cfg, terms, opts = {}) {
|
|
|
158
161
|
const scope = effective.where;
|
|
159
162
|
const whereJoin = scope ? `JOIN frontmatter f ON f."path" = content.path` : '';
|
|
160
163
|
const whereCond = scope ? `AND (${scope})` : '';
|
|
164
|
+
// A scope narrower than the whole index must filter the candidate pool before LIMIT, not
|
|
165
|
+
// after -- otherwise scoped notes ranking below the global top-`fetch` never reach the
|
|
166
|
+
// filter. A join against a temp table, not a bound parameter list: real scopes run to
|
|
167
|
+
// thousands of paths, past SQLITE_MAX_VARIABLE_NUMBER on older builds.
|
|
168
|
+
if (scopeActive) materializeScope(db, '_search_scope', scopePaths);
|
|
169
|
+
const scopeCond = scopeActive ? `AND content.path IN (SELECT "path" FROM _search_scope)` : '';
|
|
161
170
|
// Docs past SNIPPET_BOUND skip snippet() entirely -- SQLite
|
|
162
171
|
// short-circuits the untaken CASE branch, so it's never invoked on them.
|
|
163
172
|
// Column 2 (text), not -1 (best column): -1 could surface a _seg sidecar as the excerpt,
|
|
164
173
|
// which is machine-spaced and not what the author wrote. A row that matches only in a
|
|
165
174
|
// sidecar gets an unhighlighted text excerpt below instead -- a stated cost.
|
|
166
|
-
const matchSql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, 2, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;
|
|
175
|
+
const matchSql = `SELECT content.path AS path, CASE WHEN length(content.text) <= ${SNIPPET_BOUND} THEN snippet(content, 2, '«', '»', '…', 10) ELSE NULL END AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ${scopeCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;
|
|
167
176
|
const query = contentTokenize(cfg) === undefined ? segmentMatch(terms) : terms;
|
|
168
177
|
let matchRows;
|
|
169
178
|
try {
|
|
@@ -171,10 +180,6 @@ export async function search(db, cfg, terms, opts = {}) {
|
|
|
171
180
|
} catch (err) {
|
|
172
181
|
throw searchError(err, terms, scope);
|
|
173
182
|
}
|
|
174
|
-
// Scope filtering happens in JS, on each candidate list, rather than in SQL: FTS5 match,
|
|
175
|
-
// link expansion, and vector search all run unscoped above (cheap to over-fetch), then get
|
|
176
|
-
// filtered here against the effective include/exclude before ranking is finalized.
|
|
177
|
-
matchRows = matchRows.filter((r)=>scopedPaths.has(r.path));
|
|
178
183
|
const hits = new Map(matchRows.map((r)=>[
|
|
179
184
|
r.path,
|
|
180
185
|
r.hit
|
|
@@ -200,7 +205,7 @@ export async function search(db, cfg, terms, opts = {}) {
|
|
|
200
205
|
]));
|
|
201
206
|
const ranked = [
|
|
202
207
|
...personalizedRank(allPaths, edges, seeds)
|
|
203
|
-
].filter(([path, score])=>score > 1e-9 &&
|
|
208
|
+
].filter(([path, score])=>score > 1e-9 && allowedPaths.has(path)).sort((a, b)=>b[1] - a[1]).slice(0, fetch);
|
|
204
209
|
ranked.forEach(([path], i)=>{
|
|
205
210
|
const existing = candidates.get(path);
|
|
206
211
|
if (existing) {
|
|
@@ -219,7 +224,7 @@ export async function search(db, cfg, terms, opts = {}) {
|
|
|
219
224
|
const chunkLines = new Map();
|
|
220
225
|
const chunkSimilarity = new Map();
|
|
221
226
|
if (semanticEnabled) {
|
|
222
|
-
const vec =
|
|
227
|
+
const vec = await semanticCandidates(db, cfg, terms, fetch, allowedPaths);
|
|
223
228
|
vec.forEach(({ path, lines, similarity }, i)=>{
|
|
224
229
|
chunkLines.set(path, lines);
|
|
225
230
|
chunkSimilarity.set(path, similarity);
|
|
@@ -239,8 +244,9 @@ export async function search(db, cfg, terms, opts = {}) {
|
|
|
239
244
|
db.exec('CREATE TEMP TABLE _search ("path" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT, lines TEXT, similarity REAL)');
|
|
240
245
|
const insert = db.prepare('INSERT INTO _search ("path", score, via, hit, lines, similarity) VALUES (?, ?, ?, ?, ?, ?)');
|
|
241
246
|
for (const [path, c] of candidates)insert.run(path, c.score, c.via, (_hits_get = hits.get(path)) !== null && _hits_get !== void 0 ? _hits_get : null, (_chunkLines_get = chunkLines.get(path)) !== null && _chunkLines_get !== void 0 ? _chunkLines_get : null, (_chunkSimilarity_get = chunkSimilarity.get(path)) !== null && _chunkSimilarity_get !== void 0 ? _chunkSimilarity_get : null);
|
|
242
|
-
//
|
|
243
|
-
//
|
|
247
|
+
// All three candidate paths (match, link, vector) already filtered to scope+where before
|
|
248
|
+
// reaching _search; this reapplies --where anyway since the join to frontmatter is already
|
|
249
|
+
// needed for the path column.
|
|
244
250
|
const where = scope ? `WHERE (${scope})` : '';
|
|
245
251
|
// lines is always present now: semantic rows carry their chunk's range, oversized-doc
|
|
246
252
|
// lexical rows gain one below, everything else stays null. similarity stays semantic-only.
|
|
@@ -265,15 +271,18 @@ export async function search(db, cfg, terms, opts = {}) {
|
|
|
265
271
|
}
|
|
266
272
|
return rows;
|
|
267
273
|
}
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
|
|
274
|
+
// Scope only (no --where): preset_files for a named preset, JS glob matching for an ad hoc
|
|
275
|
+
// include/exclude override. Shared by scopedPaths() and search(), which also needs the
|
|
276
|
+
// pre-where set to size the candidate-pool filter.
|
|
277
|
+
function rawScope(db, cfg, overrides, allPaths) {
|
|
272
278
|
const effective = resolveSearch(cfg, overrides);
|
|
273
|
-
const { include, exclude
|
|
274
|
-
const allPaths = db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
|
|
279
|
+
const { include, exclude } = effective;
|
|
275
280
|
const adHocScope = overrides.include !== undefined || overrides.exclude !== undefined || overrides.noExclude === true;
|
|
276
|
-
|
|
281
|
+
if (!adHocScope) return new Set(db.prepare('SELECT "path" FROM preset_files WHERE preset = ?').all(effective.presetName).map((r)=>r.path));
|
|
282
|
+
const paths = allPaths !== null && allPaths !== void 0 ? allPaths : db.prepare('SELECT "path" FROM frontmatter').all().map((r)=>r.path);
|
|
283
|
+
return new Set(paths.filter((p)=>inScope(p, include, exclude)));
|
|
284
|
+
}
|
|
285
|
+
function narrowByWhere(db, paths, where) {
|
|
277
286
|
if (!where) return paths;
|
|
278
287
|
const whereRows = db.prepare(`SELECT "path" FROM frontmatter f WHERE (${where})`).all();
|
|
279
288
|
const wherePaths = new Set(whereRows.map((r)=>r.path));
|
|
@@ -281,6 +290,12 @@ export function scopedPaths(db, cfg, overrides) {
|
|
|
281
290
|
...paths
|
|
282
291
|
].filter((p)=>wherePaths.has(p)));
|
|
283
292
|
}
|
|
293
|
+
// The scope resolver for non-search commands (path, peek, map): same coverage rule search()
|
|
294
|
+
// applies, then narrowed by the resolved `where`.
|
|
295
|
+
export function scopedPaths(db, cfg, overrides) {
|
|
296
|
+
const effective = resolveSearch(cfg, overrides);
|
|
297
|
+
return narrowByWhere(db, rawScope(db, cfg, overrides), effective.where);
|
|
298
|
+
}
|
|
284
299
|
// Indexing derives from presets, so the derivation stays visible. Read from preset_files, not
|
|
285
300
|
// recomputed from globs, so it reflects the cache rather than the config.
|
|
286
301
|
export function presetCoverage(db, cfg) {
|
|
@@ -311,15 +326,18 @@ function chunk(items, size) {
|
|
|
311
326
|
for(let i = 0; i < items.length; i += size)out.push(items.slice(i, i + size));
|
|
312
327
|
return out;
|
|
313
328
|
}
|
|
314
|
-
// Materializes
|
|
315
|
-
//
|
|
316
|
-
function
|
|
317
|
-
db.exec(
|
|
318
|
-
db.exec(
|
|
319
|
-
db.prepare(
|
|
329
|
+
// Materializes a path set into a named temp table (same shape as traverse.ts's allowed_nodes)
|
|
330
|
+
// so a query can join/filter against it cheaply instead of binding a parameter per path.
|
|
331
|
+
function materializeScope(db, table, paths) {
|
|
332
|
+
db.exec(`DROP TABLE IF EXISTS ${table}`);
|
|
333
|
+
db.exec(`CREATE TEMP TABLE ${table} ("path" TEXT PRIMARY KEY)`);
|
|
334
|
+
db.prepare(`INSERT INTO ${table} SELECT DISTINCT value FROM json_each(?1)`).run(JSON.stringify([
|
|
320
335
|
...paths
|
|
321
336
|
]));
|
|
322
337
|
}
|
|
338
|
+
function setupMapScope(db, paths) {
|
|
339
|
+
materializeScope(db, '_map_scope', paths);
|
|
340
|
+
}
|
|
323
341
|
// What is this scope: fixed-size output regardless of tree size. Coverage and features stay
|
|
324
342
|
// global -- they describe the tree, not the current question.
|
|
325
343
|
export function mapTree(db, cfg, overrides = {}) {
|
|
@@ -350,6 +368,10 @@ export function mapTree(db, cfg, overrides = {}) {
|
|
|
350
368
|
const fields = allFields.slice(0, 20);
|
|
351
369
|
const hubs = featureEnabled(cfg, 'rank') ? db.prepare(`SELECT f."path" AS path, round(f."_rank" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f."path" WHERE f."_rank" IS NOT NULL ${scopeAnd} ORDER BY f."_rank" DESC LIMIT 8`).all() : [];
|
|
352
370
|
const recent = db.prepare(`SELECT "path", datetime("_mtime" / 1000, 'unixepoch') AS modified FROM frontmatter ${scopeWhere} ORDER BY "_mtime" DESC LIMIT 5`).all();
|
|
371
|
+
// A fresh clone/copy stamps files with checkout time, not edit history; second granularity
|
|
372
|
+
// matches the `recent` table above and is coarse enough to catch that without an exact-ms match.
|
|
373
|
+
const topSecond = db.prepare(`SELECT COUNT(*) AS n FROM frontmatter ${scopeWhere} GROUP BY CAST("_mtime" / 1000 AS INTEGER) ORDER BY n DESC LIMIT 1`).get();
|
|
374
|
+
const recentCaveat = topSecond && docs.count > 1 && topSecond.n > docs.count / 2 ? `${topSecond.n} of ${docs.count} files share one modified second, so recency likely reflects a checkout or copy, not edit history` : null;
|
|
353
375
|
return {
|
|
354
376
|
docs,
|
|
355
377
|
fields,
|
|
@@ -357,7 +379,8 @@ export function mapTree(db, cfg, overrides = {}) {
|
|
|
357
379
|
features: featureStates(cfg),
|
|
358
380
|
presets: presetCoverage(db, cfg),
|
|
359
381
|
hubs,
|
|
360
|
-
recent
|
|
382
|
+
recent,
|
|
383
|
+
recentCaveat
|
|
361
384
|
};
|
|
362
385
|
}
|
|
363
386
|
// Note resolution shared by peek and path: an exact path, or a unique basename (case
|