sensemaking 0.25.1 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -12
- package/dist/cjs/cli/index.d.cts +3 -3
- package/dist/cjs/cli/index.d.ts +3 -3
- package/dist/cjs/cli/index.js +3 -3
- package/dist/cjs/cli/index.js.map +1 -1
- package/dist/cjs/cli/path.js +5 -29
- package/dist/cjs/cli/path.js.map +1 -1
- package/dist/cjs/cli/peek.js +43 -2
- package/dist/cjs/cli/peek.js.map +1 -1
- package/dist/cjs/cli/related.js +4 -3
- package/dist/cjs/cli/related.js.map +1 -1
- package/dist/cjs/cli/search.js +38 -6
- package/dist/cjs/cli/search.js.map +1 -1
- package/dist/cjs/commands/index.d.cts +6 -3
- package/dist/cjs/commands/index.d.ts +6 -3
- package/dist/cjs/commands/index.js +4 -0
- package/dist/cjs/commands/index.js.map +1 -1
- package/dist/cjs/commands/map.d.cts +14 -5
- package/dist/cjs/commands/map.d.ts +14 -5
- package/dist/cjs/commands/map.js +42 -35
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/path.d.cts +14 -0
- package/dist/cjs/commands/path.d.ts +14 -0
- package/dist/cjs/commands/path.js +209 -0
- package/dist/cjs/commands/path.js.map +1 -0
- package/dist/cjs/commands/peek.d.cts +25 -2
- package/dist/cjs/commands/peek.d.ts +25 -2
- package/dist/cjs/commands/peek.js +131 -50
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/related.d.cts +16 -3
- package/dist/cjs/commands/related.d.ts +16 -3
- package/dist/cjs/commands/related.js +131 -98
- package/dist/cjs/commands/related.js.map +1 -1
- package/dist/cjs/commands/scope.d.cts +1 -0
- package/dist/cjs/commands/scope.d.ts +1 -0
- package/dist/cjs/commands/scope.js +149 -0
- package/dist/cjs/commands/scope.js.map +1 -1
- package/dist/cjs/commands/search.d.cts +30 -3
- package/dist/cjs/commands/search.d.ts +30 -3
- package/dist/cjs/commands/search.js +240 -73
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.d.cts +22 -7
- package/dist/cjs/commands/signals.d.ts +22 -7
- package/dist/cjs/commands/signals.js +77 -15
- package/dist/cjs/commands/signals.js.map +1 -1
- package/dist/cjs/commands/snapshot.d.cts +3 -0
- package/dist/cjs/commands/snapshot.d.ts +3 -0
- package/dist/cjs/commands/snapshot.js +180 -0
- package/dist/cjs/commands/snapshot.js.map +1 -0
- package/dist/cjs/config/load.js +6 -0
- package/dist/cjs/config/load.js.map +1 -1
- package/dist/cjs/embed/cohere.js +41 -16
- package/dist/cjs/embed/cohere.js.map +1 -1
- package/dist/cjs/embed/endpoint.d.cts +2 -0
- package/dist/cjs/embed/endpoint.d.ts +2 -0
- package/dist/cjs/embed/endpoint.js +30 -0
- package/dist/cjs/embed/endpoint.js.map +1 -0
- package/dist/cjs/embed/handoff.d.cts +7 -2
- package/dist/cjs/embed/handoff.d.ts +7 -2
- package/dist/cjs/embed/handoff.js +10 -0
- package/dist/cjs/embed/handoff.js.map +1 -1
- package/dist/cjs/embed/http.d.cts +18 -1
- package/dist/cjs/embed/http.d.ts +18 -1
- package/dist/cjs/embed/http.js +239 -40
- package/dist/cjs/embed/http.js.map +1 -1
- package/dist/cjs/embed/openai.js +39 -17
- package/dist/cjs/embed/openai.js.map +1 -1
- package/dist/cjs/embed/query.d.cts +9 -3
- package/dist/cjs/embed/query.d.ts +9 -3
- package/dist/cjs/embed/query.js +257 -90
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/embed/registry.d.cts +2 -2
- package/dist/cjs/embed/registry.d.ts +2 -2
- package/dist/cjs/embed/registry.js +36 -4
- package/dist/cjs/embed/registry.js.map +1 -1
- package/dist/cjs/embed/static.js +6 -2
- package/dist/cjs/embed/static.js.map +1 -1
- package/dist/cjs/embed/store.js +42 -77
- package/dist/cjs/embed/store.js.map +1 -1
- package/dist/cjs/embed/types.d.cts +5 -2
- package/dist/cjs/embed/types.d.ts +5 -2
- package/dist/cjs/embed/types.js.map +1 -1
- package/dist/cjs/features/embed.js +290 -43
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/graph/traverse.d.cts +2 -2
- package/dist/cjs/graph/traverse.d.ts +2 -2
- package/dist/cjs/graph/traverse.js +15 -5
- package/dist/cjs/graph/traverse.js.map +1 -1
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output/output.d.cts +4 -36
- package/dist/cjs/output/output.d.ts +4 -36
- package/dist/cjs/output/output.js +1 -2
- package/dist/cjs/output/output.js.map +1 -1
- package/dist/cjs/scan/pool.d.cts +2 -1
- package/dist/cjs/scan/pool.d.ts +2 -1
- package/dist/cjs/scan/pool.js +116 -27
- package/dist/cjs/scan/pool.js.map +1 -1
- package/dist/cjs/scan/reparse.d.cts +1 -0
- package/dist/cjs/scan/reparse.d.ts +1 -0
- package/dist/cjs/scan/reparse.js +75 -21
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/store/builder.d.cts +11 -21
- package/dist/cjs/store/builder.d.ts +11 -21
- package/dist/cjs/store/builder.js +1077 -438
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js +16 -2
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/open.d.cts +1 -1
- package/dist/cjs/store/duckdb/open.d.ts +1 -1
- package/dist/cjs/store/duckdb/open.js +73 -65
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js +533 -15
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/embed-scope.js +19 -9
- package/dist/cjs/store/embed-scope.js.map +1 -1
- package/dist/cjs/store/index.d.cts +6 -0
- package/dist/cjs/store/index.d.ts +6 -0
- package/dist/cjs/store/index.js +8 -4
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/open.d.cts +6 -1
- package/dist/cjs/store/open.d.ts +6 -1
- package/dist/cjs/store/open.js +230 -307
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/readiness.d.cts +5 -0
- package/dist/cjs/store/readiness.d.ts +5 -0
- package/dist/cjs/store/readiness.js +39 -0
- package/dist/cjs/store/readiness.js.map +1 -0
- package/dist/cjs/store/reconcile.d.cts +26 -1
- package/dist/cjs/store/reconcile.d.ts +26 -1
- package/dist/cjs/store/reconcile.js +670 -576
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/signature.d.cts +1 -0
- package/dist/cjs/store/signature.d.ts +1 -0
- package/dist/cjs/store/signature.js +9 -0
- package/dist/cjs/store/signature.js.map +1 -1
- package/dist/cjs/store/sqlite/open.d.cts +1 -1
- package/dist/cjs/store/sqlite/open.d.ts +1 -1
- package/dist/cjs/store/sqlite/open.js +84 -90
- package/dist/cjs/store/sqlite/open.js.map +1 -1
- package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js +63 -60
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/sqlite/vectors.d.cts +2 -2
- package/dist/cjs/store/sqlite/vectors.d.ts +2 -2
- package/dist/cjs/store/sqlite/vectors.js +8 -4
- package/dist/cjs/store/sqlite/vectors.js.map +1 -1
- package/dist/cjs/store/stages.js.map +1 -1
- package/dist/cjs/store/turso/connection.d.cts +1 -0
- package/dist/cjs/store/turso/connection.d.ts +1 -0
- package/dist/cjs/store/turso/connection.js +319 -94
- package/dist/cjs/store/turso/connection.js.map +1 -1
- package/dist/cjs/store/turso/open.d.cts +1 -1
- package/dist/cjs/store/turso/open.d.ts +1 -1
- package/dist/cjs/store/turso/open.js +70 -74
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/store.js +25 -23
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/turso/vectors.d.cts +2 -2
- package/dist/cjs/store/turso/vectors.d.ts +2 -2
- package/dist/cjs/store/turso/vectors.js +26 -16
- package/dist/cjs/store/turso/vectors.js.map +1 -1
- package/dist/cjs/store/types.d.cts +4 -1
- package/dist/cjs/store/types.d.ts +4 -1
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/store/vectors.d.cts +14 -1
- package/dist/cjs/store/vectors.d.ts +14 -1
- package/dist/cjs/store/vectors.js +61 -0
- package/dist/cjs/store/vectors.js.map +1 -1
- package/dist/cjs/watch.d.cts +6 -0
- package/dist/cjs/watch.d.ts +6 -0
- package/dist/cjs/watch.js +42 -10
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.d.cts +4 -3
- package/dist/cjs/workers/parse.d.ts +4 -3
- package/dist/cjs/workers/parse.js +19 -17
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/cli/index.d.ts +3 -3
- package/dist/esm/cli/index.js +3 -3
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/cli/path.js +4 -10
- package/dist/esm/cli/path.js.map +1 -1
- package/dist/esm/cli/peek.js +20 -2
- package/dist/esm/cli/peek.js.map +1 -1
- package/dist/esm/cli/related.js +5 -3
- package/dist/esm/cli/related.js.map +1 -1
- package/dist/esm/cli/search.js +12 -2
- package/dist/esm/cli/search.js.map +1 -1
- package/dist/esm/commands/index.d.ts +6 -3
- package/dist/esm/commands/index.js +1 -0
- package/dist/esm/commands/index.js.map +1 -1
- package/dist/esm/commands/map.d.ts +14 -5
- package/dist/esm/commands/map.js +2 -0
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/path.d.ts +14 -0
- package/dist/esm/commands/path.js +33 -0
- package/dist/esm/commands/path.js.map +1 -0
- package/dist/esm/commands/peek.d.ts +25 -2
- package/dist/esm/commands/peek.js +59 -19
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/related.d.ts +16 -3
- package/dist/esm/commands/related.js +45 -28
- package/dist/esm/commands/related.js.map +1 -1
- package/dist/esm/commands/scope.d.ts +1 -0
- package/dist/esm/commands/scope.js +58 -0
- package/dist/esm/commands/scope.js.map +1 -1
- package/dist/esm/commands/search.d.ts +30 -3
- package/dist/esm/commands/search.js +95 -20
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.d.ts +22 -7
- package/dist/esm/commands/signals.js +74 -17
- package/dist/esm/commands/signals.js.map +1 -1
- package/dist/esm/commands/snapshot.d.ts +3 -0
- package/dist/esm/commands/snapshot.js +25 -0
- package/dist/esm/commands/snapshot.js.map +1 -0
- package/dist/esm/config/load.js +6 -0
- package/dist/esm/config/load.js.map +1 -1
- package/dist/esm/embed/cohere.js +35 -10
- package/dist/esm/embed/cohere.js.map +1 -1
- package/dist/esm/embed/endpoint.d.ts +2 -0
- package/dist/esm/embed/endpoint.js +11 -0
- package/dist/esm/embed/endpoint.js.map +1 -0
- package/dist/esm/embed/handoff.d.ts +7 -2
- package/dist/esm/embed/handoff.js +7 -0
- package/dist/esm/embed/handoff.js.map +1 -1
- package/dist/esm/embed/http.d.ts +18 -1
- package/dist/esm/embed/http.js +137 -17
- package/dist/esm/embed/http.js.map +1 -1
- package/dist/esm/embed/openai.js +34 -12
- package/dist/esm/embed/openai.js.map +1 -1
- package/dist/esm/embed/query.d.ts +9 -3
- package/dist/esm/embed/query.js +98 -26
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/embed/registry.d.ts +2 -2
- package/dist/esm/embed/registry.js +34 -5
- package/dist/esm/embed/registry.js.map +1 -1
- package/dist/esm/embed/static.js +10 -2
- package/dist/esm/embed/static.js.map +1 -1
- package/dist/esm/embed/store.js +30 -21
- package/dist/esm/embed/store.js.map +1 -1
- package/dist/esm/embed/types.d.ts +5 -2
- package/dist/esm/embed/types.js.map +1 -1
- package/dist/esm/features/embed.js +82 -21
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/graph/traverse.d.ts +2 -2
- package/dist/esm/graph/traverse.js +4 -2
- package/dist/esm/graph/traverse.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output/output.d.ts +4 -36
- package/dist/esm/output/output.js +1 -2
- package/dist/esm/output/output.js.map +1 -1
- package/dist/esm/scan/pool.d.ts +2 -1
- package/dist/esm/scan/pool.js +80 -23
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/scan/reparse.d.ts +1 -0
- package/dist/esm/scan/reparse.js +40 -12
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/store/builder.d.ts +11 -21
- package/dist/esm/store/builder.js +337 -133
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js +9 -1
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/open.d.ts +1 -1
- package/dist/esm/store/duckdb/open.js +13 -9
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js +145 -10
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/embed-scope.js +16 -6
- package/dist/esm/store/embed-scope.js.map +1 -1
- package/dist/esm/store/index.d.ts +6 -0
- package/dist/esm/store/index.js +10 -4
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/open.d.ts +6 -1
- package/dist/esm/store/open.js +77 -87
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/readiness.d.ts +5 -0
- package/dist/esm/store/readiness.js +11 -0
- package/dist/esm/store/readiness.js.map +1 -0
- package/dist/esm/store/reconcile.d.ts +26 -1
- package/dist/esm/store/reconcile.js +142 -120
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/signature.d.ts +1 -0
- package/dist/esm/store/signature.js +8 -0
- package/dist/esm/store/signature.js.map +1 -1
- package/dist/esm/store/sqlite/open.d.ts +1 -1
- package/dist/esm/store/sqlite/open.js +12 -12
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/sqlite/reconcile.js +2 -2
- package/dist/esm/store/sqlite/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +21 -18
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/sqlite/vectors.d.ts +2 -2
- package/dist/esm/store/sqlite/vectors.js +7 -5
- package/dist/esm/store/sqlite/vectors.js.map +1 -1
- package/dist/esm/store/stages.js.map +1 -1
- package/dist/esm/store/turso/connection.d.ts +1 -0
- package/dist/esm/store/turso/connection.js +97 -28
- package/dist/esm/store/turso/connection.js.map +1 -1
- package/dist/esm/store/turso/open.d.ts +1 -1
- package/dist/esm/store/turso/open.js +9 -8
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/store.js +16 -14
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/turso/vectors.d.ts +2 -2
- package/dist/esm/store/turso/vectors.js +27 -17
- package/dist/esm/store/turso/vectors.js.map +1 -1
- package/dist/esm/store/types.d.ts +4 -1
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/store/vectors.d.ts +14 -1
- package/dist/esm/store/vectors.js +55 -0
- package/dist/esm/store/vectors.js.map +1 -1
- package/dist/esm/watch.d.ts +6 -0
- package/dist/esm/watch.js +40 -14
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.d.ts +4 -3
- package/dist/esm/workers/parse.js +19 -17
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +1 -1
- package/skills/sense/EXAMPLES.md +3 -3
- package/skills/sense/SKILL.md +11 -3
- package/skills/sense/references/search.md +17 -1
- package/skills/sense/references/sql.md +1 -1
- package/skills/sense/references/stores/duckdb.md +1 -1
- package/skills/sense/references/terminology.md +32 -0
- package/skills/sense-setup/SKILL.md +2 -2
- package/skills/sense-setup/references/embeddings.md +1 -1
- package/skills/sense-setup/references/store-benchmarks.md +5 -5
- package/skills/sense-setup/references/stores/duckdb.md +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/static.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport Module from 'node:module';\nimport { join } from 'node:path';\nimport { SenseError } from '../errors.ts';\nimport { downloadModel, isDownloadable, MODEL_FILENAMES, MODEL_FILES, modelDir, readLanguages } from './store.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\n// @huggingface/tokenizers is a 540K static import; a top-level import would put it on every\n// command's module graph, so it's deferred to a synchronous require inside staticProvider.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n// Model2Vec safetensors + pure-JS tokenizer. Encode convention from model2vec/model.py: no\n// special tokens, drop unk ids, mean-pool, L2-normalize.\nexport async function staticProvider(model: string, root?: string): Promise<EmbedProvider> {\n let dir: string;\n let languages: string[] | undefined;\n if (isDownloadable(model)) {\n // Naming a HF id is consent: fetch whatever is missing now, announcing it on stderr.\n dir = await downloadModel(model, (file, into) => console.error(`sense: fetching ${model}/${file} into ${into}`), root);\n // Cached beside the repo; a local model directory has no card to read, so it has none.\n const cached = readLanguages(model, root);\n languages = cached && cached.length > 0 ? cached : undefined;\n } else {\n dir = modelDir(model, root);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); expected ${MODEL_FILENAMES} in that directory`);\n }\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 const { Tokenizer } = _require('@huggingface/tokenizers') as typeof 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 out-of-vocab tokens; unfiltered, it\n // would index the matrix at NaN, and int8 conversion stores that NaN vector as all zeros.\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 // Symmetric model: document and query embedding are the same call.\n return {
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/static.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport Module from 'node:module';\nimport { join } from 'node:path';\nimport { SenseError } from '../errors.ts';\nimport { downloadModel, isDownloadable, MODEL_FILENAMES, MODEL_FILES, modelDir, readLanguages } from './store.ts';\nimport type { EmbedCallOptions, EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 64;\n\n// @huggingface/tokenizers is a 540K static import; a top-level import would put it on every\n// command's module graph, so it's deferred to a synchronous require inside staticProvider.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\n// Model2Vec safetensors + pure-JS tokenizer. Encode convention from model2vec/model.py: no\n// special tokens, drop unk ids, mean-pool, L2-normalize.\nexport async function staticProvider(model: string, root?: string): Promise<EmbedProvider> {\n let dir: string;\n let languages: string[] | undefined;\n if (isDownloadable(model)) {\n // Naming a HF id is consent: fetch whatever is missing now, announcing it on stderr.\n dir = await downloadModel(model, (file, into) => console.error(`sense: fetching ${model}/${file} into ${into}`), root);\n // Cached beside the repo; a local model directory has no card to read, so it has none.\n const cached = readLanguages(model, root);\n languages = cached && cached.length > 0 ? cached : undefined;\n } else {\n dir = modelDir(model, root);\n for (const file of MODEL_FILES) {\n if (!existsSync(join(dir, file))) {\n throw new SenseError('EMBED_MODEL_MISSING', `embed model ${model} is not available (looked in ${dir}); expected ${MODEL_FILENAMES} in that directory`);\n }\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 const { Tokenizer } = _require('@huggingface/tokenizers') as typeof 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 out-of-vocab tokens; unfiltered, it\n // would index the matrix at NaN, and int8 conversion stores that NaN vector as all zeros.\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 // Symmetric model: document and query embedding are the same call.\n return {\n id: `static:${model}`,\n dims,\n batchCap: BATCH_CAP,\n languages,\n embedDocuments: async (texts: string[], options?: EmbedCallOptions) => {\n options?.signal?.throwIfAborted();\n return texts.map(one);\n },\n embedQuery: async (text: string, options?: EmbedCallOptions) => {\n options?.signal?.throwIfAborted();\n return one(text);\n },\n };\n}\n"],"names":["existsSync","readFileSync","Module","join","SenseError","downloadModel","isDownloadable","MODEL_FILENAMES","MODEL_FILES","modelDir","readLanguages","BATCH_CAP","_require","require","createRequire","url","staticProvider","model","root","tokenizerJson","dir","languages","file","into","console","error","cached","length","undefined","raw","headerLen","Number","readBigUInt64LE","header","JSON","parse","subarray","toString","entry","Object","entries","find","k","dtype","spec","dims","shape","dataStart","byteOffset","data_offsets","matrix","Float32Array","buffer","slice","Tokenizer","tok","unkId","vocab","unk_token","one","text","ids","encode","add_special_tokens","filter","id","isInteger","v","off","d","norm","Math","sqrt","batchCap","embedDocuments","texts","options","signal","throwIfAborted","map","embedQuery"],"mappings":"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"}
|
package/dist/esm/embed/store.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { embedConfig } from '../config/index.js';
|
|
4
4
|
import { SenseError } from '../errors.js';
|
|
5
5
|
import { writeFileAtomic } from '../lib/atomic-write.js';
|
|
6
|
-
import { fetchWithRetry } from './http.js';
|
|
6
|
+
import { fetchWithRetry, HTTP_ATTEMPT_TIMEOUT_MS, MODEL_FILE_ATTEMPT_TIMEOUT_MS } from './http.js';
|
|
7
7
|
import { DEFAULT_MODELS_ROOT, hasModelFiles, isDownloadable, languagesPath, MODEL_FILES, readRef, snapshotDir, writeLanguages, writeRef } from './identity.js';
|
|
8
8
|
import { isKnownLanguageTag, MEASURED_MODEL_LANGUAGES } from './languages.js';
|
|
9
9
|
// identity.ts holds paths and cache identity, no network; store.ts holds the network fetches
|
|
@@ -29,20 +29,28 @@ async function resolveSha(model, root = DEFAULT_MODELS_ROOT) {
|
|
|
29
29
|
const url = `https://huggingface.co/${model}/resolve/main/model.safetensors`;
|
|
30
30
|
// redirect: manual -- the header rides the Hub's own response; following the LFS/CDN
|
|
31
31
|
// redirect would read the CDN's headers instead, which do not carry it.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} catch (err) {
|
|
39
|
-
throw new SenseError('EMBED_MODEL', `could not reach ${url}: ${err.message}`);
|
|
40
|
-
}
|
|
32
|
+
const res = await fetchWithRetry(url, {
|
|
33
|
+
method: 'HEAD',
|
|
34
|
+
redirect: 'manual'
|
|
35
|
+
}, {
|
|
36
|
+
attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS
|
|
37
|
+
});
|
|
41
38
|
const sha = res.headers.get('x-repo-commit');
|
|
42
|
-
if (!sha) throw new SenseError('EMBED_MODEL', `${url}: response carried no x-repo-commit header`);
|
|
39
|
+
if (!sha) throw new SenseError('EMBED_MODEL', `${res.url}: response carried no x-repo-commit header`);
|
|
43
40
|
writeRef(model, sha, root);
|
|
44
41
|
return sha;
|
|
45
42
|
}
|
|
43
|
+
function isStringArray(value) {
|
|
44
|
+
return Array.isArray(value) && value.every((part)=>typeof part === 'string');
|
|
45
|
+
}
|
|
46
|
+
function isHfModelInfo(value) {
|
|
47
|
+
if (value === null || typeof value !== 'object') return false;
|
|
48
|
+
if ('tags' in value && value.tags !== undefined && !isStringArray(value.tags)) return false;
|
|
49
|
+
if (!('cardData' in value) || value.cardData === undefined) return true;
|
|
50
|
+
if (value.cardData === null || typeof value.cardData !== 'object') return false;
|
|
51
|
+
if (!('language' in value.cardData) || value.cardData.language === undefined) return true;
|
|
52
|
+
return typeof value.cardData.language === 'string' || isStringArray(value.cardData.language);
|
|
53
|
+
}
|
|
46
54
|
// cardData.language is the structured field; tags are a fallback for cards that only tag
|
|
47
55
|
// languages, filtered to recognized codes so an unrelated short tag is never mistaken for one.
|
|
48
56
|
function languagesFromCard(info) {
|
|
@@ -72,7 +80,9 @@ async function ensureLanguages(model, root = DEFAULT_MODELS_ROOT) {
|
|
|
72
80
|
}
|
|
73
81
|
let res;
|
|
74
82
|
try {
|
|
75
|
-
res = await fetchWithRetry(`https://huggingface.co/api/models/${model}`, {}
|
|
83
|
+
res = await fetchWithRetry(`https://huggingface.co/api/models/${model}`, {}, {
|
|
84
|
+
attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS
|
|
85
|
+
});
|
|
76
86
|
} catch {
|
|
77
87
|
cardUnreadable(model);
|
|
78
88
|
return;
|
|
@@ -83,7 +93,9 @@ async function ensureLanguages(model, root = DEFAULT_MODELS_ROOT) {
|
|
|
83
93
|
}
|
|
84
94
|
let info;
|
|
85
95
|
try {
|
|
86
|
-
|
|
96
|
+
const decoded = JSON.parse(new TextDecoder().decode(res.body));
|
|
97
|
+
if (!isHfModelInfo(decoded)) throw new Error('invalid model-card response');
|
|
98
|
+
info = decoded;
|
|
87
99
|
} catch {
|
|
88
100
|
cardUnreadable(model);
|
|
89
101
|
return;
|
|
@@ -91,14 +103,11 @@ async function ensureLanguages(model, root = DEFAULT_MODELS_ROOT) {
|
|
|
91
103
|
writeLanguages(model, (_languagesFromCard = languagesFromCard(info)) !== null && _languagesFromCard !== void 0 ? _languagesFromCard : [], root);
|
|
92
104
|
}
|
|
93
105
|
async function fetchToFile(url, dest) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
if (!res.ok) throw new SenseError('EMBED_MODEL', `${url} -> HTTP ${res.status}`);
|
|
101
|
-
writeFileAtomic(dest, Buffer.from(await res.arrayBuffer()));
|
|
106
|
+
const res = await fetchWithRetry(url, {}, {
|
|
107
|
+
attemptTimeoutMs: MODEL_FILE_ATTEMPT_TIMEOUT_MS
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok) throw new SenseError('EMBED_MODEL', `${res.url} -> HTTP ${res.status}`);
|
|
110
|
+
writeFileAtomic(dest, Buffer.from(res.body));
|
|
102
111
|
}
|
|
103
112
|
// Fetches an already-known sha straight into its snapshot dir, without touching refs/main --
|
|
104
113
|
// for downloadModel below, and for callers pinning an exact revision (the parity suite).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/store.ts"],"sourcesContent":["import { existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Config } from '../config/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { writeFileAtomic } from '../lib/atomic-write.ts';\nimport { fetchWithRetry } from './http.ts';\nimport { DEFAULT_MODELS_ROOT, hasModelFiles, isDownloadable, languagesPath, MODEL_FILES, readRef, snapshotDir, writeLanguages, writeRef } from './identity.ts';\nimport { isKnownLanguageTag, MEASURED_MODEL_LANGUAGES } from './languages.ts';\n\n// identity.ts holds paths and cache identity, no network; store.ts holds the network fetches\n// (downloads, sha resolution, language metadata), re-exported here for its network surface.\nexport { hasModelFiles, isDownloadable, MODEL_FILENAMES, MODEL_FILES, modelDir, readLanguages, refsMainPath, snapshotDir, writeLanguages, writeRef } from './identity.ts';\n\n// A non-static provider has nothing to download here; an unreachable endpoint surfaces as an\n// 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.provider !== 'static' || hasModelFiles(e.model);\n}\n\ntype ResolvedEmbedConfig = NonNullable<ReturnType<typeof embedConfig>>;\n\n// True when embed.model names a local path (not a Hugging Face id) that is missing its model\n// files -- nothing will ever fetch it for itself, unlike a downloadable id.\nexport function localModelMissing(e: ResolvedEmbedConfig | null): e is ResolvedEmbedConfig {\n return e !== null && e.provider === 'static' && !isDownloadable(e.model) && !hasModelFiles(e.model);\n}\n\n// `main` resolves to a sha once per model: a recorded ref pins until its snapshot is\n// removed, so only the first need for an id ever costs this HEAD request.\nasync function resolveSha(model: string, root: string = DEFAULT_MODELS_ROOT): Promise<string> {\n const existing = readRef(model, root);\n if (existing) return existing;\n const url = `https://huggingface.co/${model}/resolve/main/model.safetensors`;\n // redirect: manual -- the header rides the Hub's own response; following the LFS/CDN\n // redirect would read the CDN's headers instead, which do not carry it.\n let res: Response;\n try {\n res = await fetchWithRetry(url, { method: 'HEAD', redirect: 'manual' });\n } catch (err) {\n throw new SenseError('EMBED_MODEL', `could not reach ${url}: ${(err as Error).message}`);\n }\n const sha = res.headers.get('x-repo-commit');\n if (!sha) throw new SenseError('EMBED_MODEL', `${url}: response carried no x-repo-commit header`);\n writeRef(model, sha, root);\n return sha;\n}\n\ninterface HfModelInfo {\n cardData?: { language?: string | string[] };\n tags?: string[];\n}\n\n// cardData.language is the structured field; tags are a fallback for cards that only tag\n// languages, filtered to recognized codes so an unrelated short tag is never mistaken for one.\nfunction languagesFromCard(info: HfModelInfo): string[] | undefined {\n const lang = info.cardData?.language;\n if (typeof lang === 'string') return [lang];\n if (Array.isArray(lang) && lang.length > 0) return lang;\n const tagLangs = (info.tags ?? []).filter(isKnownLanguageTag);\n return tagLangs.length > 0 ? tagLangs : undefined;\n}\n\n// Unreachable metadata is not an error, only the language-fit check staying off, but it says so:\n// the check that keeps a misfit model loud cannot itself go quiet (PRINCIPLES: no-silent-modes).\nfunction cardUnreadable(model: string): void {\n console.error(`sense: could not read the model card for ${model}; the language-fit check is off until it resolves (retries on next run, or run \\`sense download\\`)`);\n}\n\nasync function ensureLanguages(model: string, root: string = DEFAULT_MODELS_ROOT): Promise<void> {\n if (existsSync(languagesPath(model, root))) return;\n // A measured entry needs no card and no network, and stays deterministic offline.\n const measured = MEASURED_MODEL_LANGUAGES[model];\n if (measured) {\n writeLanguages(model, measured, root);\n return;\n }\n let res: Response;\n try {\n res = await fetchWithRetry(`https://huggingface.co/api/models/${model}`, {});\n } catch {\n cardUnreadable(model);\n return;\n }\n if (!res.ok) {\n cardUnreadable(model);\n return;\n }\n let info: HfModelInfo;\n try {\n info = (await res.json()) as HfModelInfo;\n } catch {\n cardUnreadable(model);\n return;\n }\n writeLanguages(model, languagesFromCard(info) ?? [], root);\n}\n\nasync function fetchToFile(url: string, dest: string): Promise<void> {\n let res: Response;\n try {\n res = await fetchWithRetry(url, {});\n } catch (err) {\n throw new SenseError('EMBED_MODEL', `could not reach ${url}: ${(err as Error).message}`);\n }\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${url} -> HTTP ${res.status}`);\n writeFileAtomic(dest, Buffer.from(await res.arrayBuffer()));\n}\n\n// Fetches an already-known sha straight into its snapshot dir, without touching refs/main --\n// for downloadModel below, and for callers pinning an exact revision (the parity suite).\nexport async function downloadModelRevision(model: string, sha: string, onFile?: (file: string, dir: string) => void, root: string = DEFAULT_MODELS_ROOT): Promise<string> {\n const dir = snapshotDir(model, sha, root);\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/${sha}/${file}`, join(dir, file));\n }\n return dir;\n}\n\n// The only code path that resolves `main`: a static provider's first construction calls it\n// lazily, `sense download` as an explicit prefetch. Idempotent either way.\nexport async function downloadModel(model: string, onFile?: (file: string, dir: string) => void, root: string = DEFAULT_MODELS_ROOT): 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 sha = await resolveSha(model, root);\n // Independent of the weights above: a fresh HEAD/GET pair either way, so a lazy first\n // construction and an explicit prefetch both end up with a cached language verdict.\n await ensureLanguages(model, root);\n return downloadModelRevision(model, sha, onFile, root);\n}\n"],"names":["existsSync","mkdirSync","join","embedConfig","SenseError","writeFileAtomic","fetchWithRetry","DEFAULT_MODELS_ROOT","hasModelFiles","isDownloadable","languagesPath","MODEL_FILES","readRef","snapshotDir","writeLanguages","writeRef","isKnownLanguageTag","MEASURED_MODEL_LANGUAGES","MODEL_FILENAMES","modelDir","readLanguages","refsMainPath","modelPresent","cfg","e","provider","model","localModelMissing","resolveSha","root","existing","url","res","method","redirect","err","message","sha","headers","get","languagesFromCard","info","lang","cardData","language","Array","isArray","length","tagLangs","tags","filter","undefined","cardUnreadable","console","error","ensureLanguages","measured","ok","json","fetchToFile","dest","status","Buffer","from","arrayBuffer","downloadModelRevision","onFile","dir","recursive","file","downloadModel"],"mappings":"AAAA,SAASA,UAAU,EAAEC,SAAS,QAAQ,UAAU;AAChD,SAASC,IAAI,QAAQ,YAAY;AAEjC,SAASC,WAAW,QAAQ,qBAAqB;AACjD,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,eAAe,QAAQ,yBAAyB;AACzD,SAASC,cAAc,QAAQ,YAAY;AAC3C,SAASC,mBAAmB,EAAEC,aAAa,EAAEC,cAAc,EAAEC,aAAa,EAAEC,WAAW,EAAEC,OAAO,EAAEC,WAAW,EAAEC,cAAc,EAAEC,QAAQ,QAAQ,gBAAgB;AAC/J,SAASC,kBAAkB,EAAEC,wBAAwB,QAAQ,iBAAiB;AAE9E,6FAA6F;AAC7F,4FAA4F;AAC5F,SAAST,aAAa,EAAEC,cAAc,EAAES,eAAe,EAAEP,WAAW,EAAEQ,QAAQ,EAAEC,aAAa,EAAEC,YAAY,EAAER,WAAW,EAAEC,cAAc,EAAEC,QAAQ,QAAQ,gBAAgB;AAE1K,6FAA6F;AAC7F,0CAA0C;AAC1C,OAAO,SAASO,aAAaC,GAAW;IACtC,MAAMC,IAAIrB,YAAYoB;IACtB,IAAI,CAACC,GAAG,OAAO;IACf,OAAOA,EAAEC,QAAQ,KAAK,YAAYjB,cAAcgB,EAAEE,KAAK;AACzD;AAIA,6FAA6F;AAC7F,4EAA4E;AAC5E,OAAO,SAASC,kBAAkBH,CAA6B;IAC7D,OAAOA,MAAM,QAAQA,EAAEC,QAAQ,KAAK,YAAY,CAAChB,eAAee,EAAEE,KAAK,KAAK,CAAClB,cAAcgB,EAAEE,KAAK;AACpG;AAEA,qFAAqF;AACrF,0EAA0E;AAC1E,eAAeE,WAAWF,KAAa,EAAEG,OAAetB,mBAAmB;IACzE,MAAMuB,WAAWlB,QAAQc,OAAOG;IAChC,IAAIC,UAAU,OAAOA;IACrB,MAAMC,MAAM,CAAC,uBAAuB,EAAEL,MAAM,+BAA+B,CAAC;IAC5E,qFAAqF;IACrF,wEAAwE;IACxE,IAAIM;IACJ,IAAI;QACFA,MAAM,MAAM1B,eAAeyB,KAAK;YAAEE,QAAQ;YAAQC,UAAU;QAAS;IACvE,EAAE,OAAOC,KAAK;QACZ,MAAM,IAAI/B,WAAW,eAAe,CAAC,gBAAgB,EAAE2B,IAAI,EAAE,EAAE,AAACI,IAAcC,OAAO,EAAE;IACzF;IACA,MAAMC,MAAML,IAAIM,OAAO,CAACC,GAAG,CAAC;IAC5B,IAAI,CAACF,KAAK,MAAM,IAAIjC,WAAW,eAAe,GAAG2B,IAAI,0CAA0C,CAAC;IAChGhB,SAASW,OAAOW,KAAKR;IACrB,OAAOQ;AACT;AAOA,yFAAyF;AACzF,+FAA+F;AAC/F,SAASG,kBAAkBC,IAAiB;QAIxBA;QAHLA;IAAb,MAAMC,QAAOD,iBAAAA,KAAKE,QAAQ,cAAbF,qCAAAA,eAAeG,QAAQ;IACpC,IAAI,OAAOF,SAAS,UAAU,OAAO;QAACA;KAAK;IAC3C,IAAIG,MAAMC,OAAO,CAACJ,SAASA,KAAKK,MAAM,GAAG,GAAG,OAAOL;IACnD,MAAMM,WAAW,EAACP,aAAAA,KAAKQ,IAAI,cAATR,wBAAAA,aAAa,EAAE,EAAES,MAAM,CAAClC;IAC1C,OAAOgC,SAASD,MAAM,GAAG,IAAIC,WAAWG;AAC1C;AAEA,iGAAiG;AACjG,iGAAiG;AACjG,SAASC,eAAe1B,KAAa;IACnC2B,QAAQC,KAAK,CAAC,CAAC,yCAAyC,EAAE5B,MAAM,kGAAkG,CAAC;AACrK;AAEA,eAAe6B,gBAAgB7B,KAAa,EAAEG,OAAetB,mBAAmB;QA0BxDiC;IAzBtB,IAAIxC,WAAWU,cAAcgB,OAAOG,QAAQ;IAC5C,kFAAkF;IAClF,MAAM2B,WAAWvC,wBAAwB,CAACS,MAAM;IAChD,IAAI8B,UAAU;QACZ1C,eAAeY,OAAO8B,UAAU3B;QAChC;IACF;IACA,IAAIG;IACJ,IAAI;QACFA,MAAM,MAAM1B,eAAe,CAAC,kCAAkC,EAAEoB,OAAO,EAAE,CAAC;IAC5E,EAAE,OAAM;QACN0B,eAAe1B;QACf;IACF;IACA,IAAI,CAACM,IAAIyB,EAAE,EAAE;QACXL,eAAe1B;QACf;IACF;IACA,IAAIe;IACJ,IAAI;QACFA,OAAQ,MAAMT,IAAI0B,IAAI;IACxB,EAAE,OAAM;QACNN,eAAe1B;QACf;IACF;IACAZ,eAAeY,QAAOc,qBAAAA,kBAAkBC,mBAAlBD,gCAAAA,qBAA2B,EAAE,EAAEX;AACvD;AAEA,eAAe8B,YAAY5B,GAAW,EAAE6B,IAAY;IAClD,IAAI5B;IACJ,IAAI;QACFA,MAAM,MAAM1B,eAAeyB,KAAK,CAAC;IACnC,EAAE,OAAOI,KAAK;QACZ,MAAM,IAAI/B,WAAW,eAAe,CAAC,gBAAgB,EAAE2B,IAAI,EAAE,EAAE,AAACI,IAAcC,OAAO,EAAE;IACzF;IACA,IAAI,CAACJ,IAAIyB,EAAE,EAAE,MAAM,IAAIrD,WAAW,eAAe,GAAG2B,IAAI,SAAS,EAAEC,IAAI6B,MAAM,EAAE;IAC/ExD,gBAAgBuD,MAAME,OAAOC,IAAI,CAAC,MAAM/B,IAAIgC,WAAW;AACzD;AAEA,6FAA6F;AAC7F,yFAAyF;AACzF,OAAO,eAAeC,sBAAsBvC,KAAa,EAAEW,GAAW,EAAE6B,MAA4C,EAAErC,OAAetB,mBAAmB;IACtJ,MAAM4D,MAAMtD,YAAYa,OAAOW,KAAKR;IACpC5B,UAAUkE,KAAK;QAAEC,WAAW;IAAK;IACjC,KAAK,MAAMC,QAAQ1D,YAAa;QAC9B,IAAIX,WAAWE,KAAKiE,KAAKE,QAAQ;QACjCH,mBAAAA,6BAAAA,OAASG,MAAMF;QACf,MAAMR,YAAY,CAAC,uBAAuB,EAAEjC,MAAM,SAAS,EAAEW,IAAI,CAAC,EAAEgC,MAAM,EAAEnE,KAAKiE,KAAKE;IACxF;IACA,OAAOF;AACT;AAEA,2FAA2F;AAC3F,2EAA2E;AAC3E,OAAO,eAAeG,cAAc5C,KAAa,EAAEwC,MAA4C,EAAErC,OAAetB,mBAAmB;IACjI,IAAI,CAACE,eAAeiB,QAAQ;QAC1B,MAAM,IAAItB,WAAW,eAAe,CAAC,aAAa,EAAEsB,MAAM,qMAAqM,CAAC;IAClQ;IACA,MAAMW,MAAM,MAAMT,WAAWF,OAAOG;IACpC,sFAAsF;IACtF,oFAAoF;IACpF,MAAM0B,gBAAgB7B,OAAOG;IAC7B,OAAOoC,sBAAsBvC,OAAOW,KAAK6B,QAAQrC;AACnD"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/store.ts"],"sourcesContent":["import { existsSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Config } from '../config/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { writeFileAtomic } from '../lib/atomic-write.ts';\nimport { type BufferedResponse, fetchWithRetry, HTTP_ATTEMPT_TIMEOUT_MS, MODEL_FILE_ATTEMPT_TIMEOUT_MS } from './http.ts';\nimport { DEFAULT_MODELS_ROOT, hasModelFiles, isDownloadable, languagesPath, MODEL_FILES, readRef, snapshotDir, writeLanguages, writeRef } from './identity.ts';\nimport { isKnownLanguageTag, MEASURED_MODEL_LANGUAGES } from './languages.ts';\n\n// identity.ts holds paths and cache identity, no network; store.ts holds the network fetches\n// (downloads, sha resolution, language metadata), re-exported here for its network surface.\nexport { hasModelFiles, isDownloadable, MODEL_FILENAMES, MODEL_FILES, modelDir, readLanguages, refsMainPath, snapshotDir, writeLanguages, writeRef } from './identity.ts';\n\n// A non-static provider has nothing to download here; an unreachable endpoint surfaces as an\n// 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.provider !== 'static' || hasModelFiles(e.model);\n}\n\ntype ResolvedEmbedConfig = NonNullable<ReturnType<typeof embedConfig>>;\n\n// True when embed.model names a local path (not a Hugging Face id) that is missing its model\n// files -- nothing will ever fetch it for itself, unlike a downloadable id.\nexport function localModelMissing(e: ResolvedEmbedConfig | null): e is ResolvedEmbedConfig {\n return e !== null && e.provider === 'static' && !isDownloadable(e.model) && !hasModelFiles(e.model);\n}\n\n// `main` resolves to a sha once per model: a recorded ref pins until its snapshot is\n// removed, so only the first need for an id ever costs this HEAD request.\nasync function resolveSha(model: string, root: string = DEFAULT_MODELS_ROOT): Promise<string> {\n const existing = readRef(model, root);\n if (existing) return existing;\n const url = `https://huggingface.co/${model}/resolve/main/model.safetensors`;\n // redirect: manual -- the header rides the Hub's own response; following the LFS/CDN\n // redirect would read the CDN's headers instead, which do not carry it.\n const res = await fetchWithRetry(url, { method: 'HEAD', redirect: 'manual' }, { attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS });\n const sha = res.headers.get('x-repo-commit');\n if (!sha) throw new SenseError('EMBED_MODEL', `${res.url}: response carried no x-repo-commit header`);\n writeRef(model, sha, root);\n return sha;\n}\n\ninterface HfModelInfo {\n cardData?: { language?: string | string[] };\n tags?: string[];\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((part) => typeof part === 'string');\n}\n\nfunction isHfModelInfo(value: unknown): value is HfModelInfo {\n if (value === null || typeof value !== 'object') return false;\n if ('tags' in value && value.tags !== undefined && !isStringArray(value.tags)) return false;\n if (!('cardData' in value) || value.cardData === undefined) return true;\n if (value.cardData === null || typeof value.cardData !== 'object') return false;\n if (!('language' in value.cardData) || value.cardData.language === undefined) return true;\n return typeof value.cardData.language === 'string' || isStringArray(value.cardData.language);\n}\n\n// cardData.language is the structured field; tags are a fallback for cards that only tag\n// languages, filtered to recognized codes so an unrelated short tag is never mistaken for one.\nfunction languagesFromCard(info: HfModelInfo): string[] | undefined {\n const lang = info.cardData?.language;\n if (typeof lang === 'string') return [lang];\n if (Array.isArray(lang) && lang.length > 0) return lang;\n const tagLangs = (info.tags ?? []).filter(isKnownLanguageTag);\n return tagLangs.length > 0 ? tagLangs : undefined;\n}\n\n// Unreachable metadata is not an error, only the language-fit check staying off, but it says so:\n// the check that keeps a misfit model loud cannot itself go quiet (PRINCIPLES: no-silent-modes).\nfunction cardUnreadable(model: string): void {\n console.error(`sense: could not read the model card for ${model}; the language-fit check is off until it resolves (retries on next run, or run \\`sense download\\`)`);\n}\n\nasync function ensureLanguages(model: string, root: string = DEFAULT_MODELS_ROOT): Promise<void> {\n if (existsSync(languagesPath(model, root))) return;\n // A measured entry needs no card and no network, and stays deterministic offline.\n const measured = MEASURED_MODEL_LANGUAGES[model];\n if (measured) {\n writeLanguages(model, measured, root);\n return;\n }\n let res: BufferedResponse;\n try {\n res = await fetchWithRetry(`https://huggingface.co/api/models/${model}`, {}, { attemptTimeoutMs: HTTP_ATTEMPT_TIMEOUT_MS });\n } catch {\n cardUnreadable(model);\n return;\n }\n if (!res.ok) {\n cardUnreadable(model);\n return;\n }\n let info: HfModelInfo;\n try {\n const decoded: unknown = JSON.parse(new TextDecoder().decode(res.body));\n if (!isHfModelInfo(decoded)) throw new Error('invalid model-card response');\n info = decoded;\n } catch {\n cardUnreadable(model);\n return;\n }\n writeLanguages(model, languagesFromCard(info) ?? [], root);\n}\n\nasync function fetchToFile(url: string, dest: string): Promise<void> {\n const res = await fetchWithRetry(url, {}, { attemptTimeoutMs: MODEL_FILE_ATTEMPT_TIMEOUT_MS });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${res.url} -> HTTP ${res.status}`);\n writeFileAtomic(dest, Buffer.from(res.body));\n}\n\n// Fetches an already-known sha straight into its snapshot dir, without touching refs/main --\n// for downloadModel below, and for callers pinning an exact revision (the parity suite).\nexport async function downloadModelRevision(model: string, sha: string, onFile?: (file: string, dir: string) => void, root: string = DEFAULT_MODELS_ROOT): Promise<string> {\n const dir = snapshotDir(model, sha, root);\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/${sha}/${file}`, join(dir, file));\n }\n return dir;\n}\n\n// The only code path that resolves `main`: a static provider's first construction calls it\n// lazily, `sense download` as an explicit prefetch. Idempotent either way.\nexport async function downloadModel(model: string, onFile?: (file: string, dir: string) => void, root: string = DEFAULT_MODELS_ROOT): 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 sha = await resolveSha(model, root);\n // Independent of the weights above: a fresh HEAD/GET pair either way, so a lazy first\n // construction and an explicit prefetch both end up with a cached language verdict.\n await ensureLanguages(model, root);\n return downloadModelRevision(model, sha, onFile, root);\n}\n"],"names":["existsSync","mkdirSync","join","embedConfig","SenseError","writeFileAtomic","fetchWithRetry","HTTP_ATTEMPT_TIMEOUT_MS","MODEL_FILE_ATTEMPT_TIMEOUT_MS","DEFAULT_MODELS_ROOT","hasModelFiles","isDownloadable","languagesPath","MODEL_FILES","readRef","snapshotDir","writeLanguages","writeRef","isKnownLanguageTag","MEASURED_MODEL_LANGUAGES","MODEL_FILENAMES","modelDir","readLanguages","refsMainPath","modelPresent","cfg","e","provider","model","localModelMissing","resolveSha","root","existing","url","res","method","redirect","attemptTimeoutMs","sha","headers","get","isStringArray","value","Array","isArray","every","part","isHfModelInfo","tags","undefined","cardData","language","languagesFromCard","info","lang","length","tagLangs","filter","cardUnreadable","console","error","ensureLanguages","measured","ok","decoded","JSON","parse","TextDecoder","decode","body","Error","fetchToFile","dest","status","Buffer","from","downloadModelRevision","onFile","dir","recursive","file","downloadModel"],"mappings":"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"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export declare const STORE_DIMS = 256;
|
|
2
|
+
export interface EmbedCallOptions {
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
}
|
|
2
5
|
export interface EmbedProvider {
|
|
3
6
|
id: string;
|
|
4
7
|
dims: number;
|
|
5
8
|
batchCap: number;
|
|
6
9
|
languages?: string[];
|
|
7
|
-
embedDocuments(texts: string[]): Promise<Float32Array[]>;
|
|
8
|
-
embedQuery(text: string): Promise<Float32Array>;
|
|
10
|
+
embedDocuments(texts: string[], options?: EmbedCallOptions): Promise<Float32Array[]>;
|
|
11
|
+
embedQuery(text: string, options?: EmbedCallOptions): Promise<Float32Array>;
|
|
9
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/types.ts"],"sourcesContent":["// Storage lever fixed by the bake-off (benchmark/reports/2026-08-13-static-model-bakeoff.md):\n// int8 at 256 dims is quality-free vs f32-512 when fused; also the duckdb store's FLOAT[N] width.\nexport const STORE_DIMS = 256;\n\n// The provider contract every wire protocol implements: a Liskov-substitutable\n// surface so query/search code never branches on provider identity.\nexport interface EmbedProvider {\n id: string; // model identity; participates in the cache key, change -> re-embed\n dims: number;\n batchCap: number; // max texts per embedDocuments call\n languages?: string[]; // declared model languages, for the language-fit check\n embedDocuments(texts: string[]): Promise<Float32Array[]>;\n embedQuery(text: string): Promise<Float32Array>;\n}\n"],"names":["STORE_DIMS"],"mappings":"AAAA,8FAA8F;AAC9F,kGAAkG;AAClG,OAAO,MAAMA,aAAa,IAAI"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/types.ts"],"sourcesContent":["// Storage lever fixed by the bake-off (benchmark/reports/2026-08-13-static-model-bakeoff.md):\n// int8 at 256 dims is quality-free vs f32-512 when fused; also the duckdb store's FLOAT[N] width.\nexport const STORE_DIMS = 256;\n\nexport interface EmbedCallOptions {\n signal?: AbortSignal;\n}\n\n// The provider contract every wire protocol implements: a Liskov-substitutable\n// surface so query/search code never branches on provider identity.\nexport interface EmbedProvider {\n id: string; // model identity; participates in the cache key, change -> re-embed\n dims: number;\n batchCap: number; // max texts per embedDocuments call\n languages?: string[]; // declared model languages, for the language-fit check\n embedDocuments(texts: string[], options?: EmbedCallOptions): Promise<Float32Array[]>;\n embedQuery(text: string, options?: EmbedCallOptions): Promise<Float32Array>;\n}\n"],"names":["STORE_DIMS"],"mappings":"AAAA,8FAA8F;AAC9F,kGAAkG;AAClG,OAAO,MAAMA,aAAa,IAAI"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { CHUNK_VERSION, chunkFromBlocks, parse } from '../chunk/index.js';
|
|
2
2
|
import { embedConfig } from '../config/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { embedEndpointIdentity } from '../embed/endpoint.js';
|
|
4
|
+
import { identifyChunkText, stashChunkText } from '../embed/handoff.js';
|
|
4
5
|
import { modelIdentity } from '../embed/identity.js';
|
|
5
6
|
import { countLines } from '../scan/frontmatter.js';
|
|
6
7
|
import { appendRows } from '../store/shared.js';
|
|
8
|
+
const CHUNK_LOOKUP_PAGE = 512;
|
|
7
9
|
// The title/summary prefix mirrors bm25 field weighting. Chunks the body, not the raw file, so
|
|
8
10
|
// `offset` shifts line numbers back onto raw and a range stays a direct Read range.
|
|
9
11
|
function chunksOf(blocks, body, search, offset = 0, chunkTokens) {
|
|
@@ -24,7 +26,7 @@ function chunksOf(blocks, body, search, offset = 0, chunkTokens) {
|
|
|
24
26
|
export const embed = {
|
|
25
27
|
name: 'embed',
|
|
26
28
|
async schema (db) {
|
|
27
|
-
await 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))`);
|
|
29
|
+
await db.exec(`CREATE TABLE IF NOT EXISTS embeddings ("path" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, content_identity TEXT NOT NULL, scale REAL, vector BLOB, PRIMARY KEY ("path", chunk))`);
|
|
28
30
|
},
|
|
29
31
|
extract (raw, body, search, _data, cfg, blocks) {
|
|
30
32
|
var _cfg_embed;
|
|
@@ -32,39 +34,96 @@ export const embed = {
|
|
|
32
34
|
// blocks comes from parseFile's shared parse; falls back to parsing body for a direct call.
|
|
33
35
|
return chunksOf(blocks !== null && blocks !== void 0 ? blocks : parse(body), body, search, countLines(raw) - countLines(body), cfg === null || cfg === void 0 ? void 0 : (_cfg_embed = cfg.embed) === null || _cfg_embed === void 0 ? void 0 : _cfg_embed.chunkTokens);
|
|
34
36
|
},
|
|
35
|
-
async remove (db, paths) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
async remove (db, paths, delta) {
|
|
38
|
+
const vanishedSet = new Set(delta.vanished);
|
|
39
|
+
const vanished = paths.filter((path)=>vanishedSet.has(path));
|
|
40
|
+
if (vanished.length === 0) return;
|
|
41
|
+
await db.runBatch('DELETE FROM embeddings WHERE "path" = ?', vanished.map((path)=>[
|
|
42
|
+
path
|
|
39
43
|
]));
|
|
40
44
|
},
|
|
41
45
|
// A tree with no embedding model never had extract() run for the doc (reconcile.ts's per-file
|
|
42
46
|
// filter skips it), so extracted is undefined here -- those docs contribute no rows.
|
|
43
|
-
async store (db, docs) {
|
|
47
|
+
async store (db, docs, delta) {
|
|
44
48
|
const rows = [];
|
|
45
49
|
const texts = new Map();
|
|
50
|
+
const added = new Set(delta.added);
|
|
51
|
+
// Keyset pages bound comparison metadata without reading native vector payloads.
|
|
52
|
+
let firstPage;
|
|
53
|
+
let nextPage;
|
|
46
54
|
for (const { path, extracted } of docs){
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
const chunks = extracted !== null && extracted !== void 0 ? extracted : [];
|
|
56
|
+
if (chunks.length === 0) {
|
|
57
|
+
if (!added.has(path)) await db.runBatch('DELETE FROM embeddings WHERE "path" = ?', [
|
|
58
|
+
[
|
|
59
|
+
path
|
|
60
|
+
]
|
|
61
|
+
]);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const identified = chunks.map((chunk)=>identifyChunkText(chunk.text));
|
|
65
|
+
const kept = new Set();
|
|
66
|
+
if (!added.has(path)) {
|
|
67
|
+
let cursor;
|
|
68
|
+
for(;;){
|
|
69
|
+
let page;
|
|
70
|
+
if (cursor === undefined) {
|
|
71
|
+
firstPage !== null && firstPage !== void 0 ? firstPage : firstPage = await db.prepare(`SELECT chunk, start_line, end_line, content_identity FROM embeddings WHERE "path" = ? ORDER BY chunk LIMIT ${CHUNK_LOOKUP_PAGE}`);
|
|
72
|
+
page = await firstPage.all(path);
|
|
73
|
+
} else {
|
|
74
|
+
nextPage !== null && nextPage !== void 0 ? nextPage : nextPage = await db.prepare(`SELECT chunk, start_line, end_line, content_identity FROM embeddings WHERE "path" = ? AND chunk > ? ORDER BY chunk LIMIT ${CHUNK_LOOKUP_PAGE}`);
|
|
75
|
+
page = await nextPage.all(path, cursor);
|
|
76
|
+
}
|
|
77
|
+
const removed = [];
|
|
78
|
+
const moved = [];
|
|
79
|
+
for (const old of page){
|
|
80
|
+
const index = Number(old.chunk);
|
|
81
|
+
const chunk = chunks[index];
|
|
82
|
+
if (!chunk || old.content_identity !== identified[index].identity) {
|
|
83
|
+
removed.push([
|
|
84
|
+
path,
|
|
85
|
+
index
|
|
86
|
+
]);
|
|
87
|
+
} else {
|
|
88
|
+
kept.add(index);
|
|
89
|
+
if (Number(old.start_line) !== chunk.startLine || Number(old.end_line) !== chunk.endLine) moved.push([
|
|
90
|
+
chunk.startLine,
|
|
91
|
+
chunk.endLine,
|
|
92
|
+
path,
|
|
93
|
+
index
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (removed.length > 0) await db.runBatch('DELETE FROM embeddings WHERE "path" = ? AND chunk = ?', removed);
|
|
98
|
+
if (moved.length > 0) await db.runBatch('UPDATE embeddings SET start_line = ?, end_line = ? WHERE "path" = ? AND chunk = ?', moved);
|
|
99
|
+
if (page.length < CHUNK_LOOKUP_PAGE) break;
|
|
100
|
+
cursor = Number(page[page.length - 1].chunk);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const before = rows.length;
|
|
104
|
+
chunks.forEach((chunk, index)=>{
|
|
105
|
+
if (!kept.has(index)) rows.push([
|
|
50
106
|
path,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
107
|
+
index,
|
|
108
|
+
chunk.startLine,
|
|
109
|
+
chunk.endLine,
|
|
110
|
+
identified[index].identity
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
// No new pending rows means no retained handoff. Matching pending rows can use
|
|
114
|
+
// embedPending's exact indexed-source fallback, including after repeated store calls.
|
|
115
|
+
if (rows.length > before) texts.set(path, identified);
|
|
56
116
|
}
|
|
57
|
-
if (rows.length === 0) return;
|
|
58
117
|
// scale and vector are written by the embed pass, not here, so they are not in the column list
|
|
59
118
|
// and take the table's own default on the append path.
|
|
60
119
|
await appendRows(db, 'embeddings', [
|
|
61
120
|
'path',
|
|
62
121
|
'chunk',
|
|
63
122
|
'start_line',
|
|
64
|
-
'end_line'
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
123
|
+
'end_line',
|
|
124
|
+
'content_identity'
|
|
125
|
+
], 'INSERT INTO embeddings ("path", chunk, start_line, end_line, content_identity, scale, vector) VALUES (?, ?, ?, ?, ?, NULL, NULL)', rows);
|
|
126
|
+
// Hand off only paths with new pending rows; missing entries use the indexed source.
|
|
68
127
|
stashChunkText(db, texts);
|
|
69
128
|
},
|
|
70
129
|
enabledForFile (_cfg, file) {
|
|
@@ -81,6 +140,8 @@ export const embed = {
|
|
|
81
140
|
// W3b: chunkTokens rides the version token, since it's the one owner lever the chunker
|
|
82
141
|
// itself takes -- changing or clearing it must rebuild exactly like a chunker version bump.
|
|
83
142
|
const chunkVersion = e.chunkTokens !== undefined ? `${CHUNK_VERSION}:${e.chunkTokens}` : CHUNK_VERSION;
|
|
84
|
-
|
|
143
|
+
const endpoint = embedEndpointIdentity(e.provider, e.url);
|
|
144
|
+
const endpointPart = endpoint === undefined ? '' : `:endpoint=${endpoint}`;
|
|
145
|
+
return `embed:${e.provider}:${e.model}${endpointPart}:${chunkVersion}${identity !== undefined ? `@${identity}` : ''}`;
|
|
85
146
|
}
|
|
86
147
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import type { Block, Chunk } from '../chunk/index.ts';\nimport { CHUNK_VERSION, chunkFromBlocks, parse } from '../chunk/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { stashChunkText } from '../embed/handoff.ts';\nimport { modelIdentity } from '../embed/identity.ts';\nimport { countLines } from '../scan/frontmatter.ts';\nimport { appendRows } from '../store/shared.ts';\nimport type { Feature } from './types.ts';\n\n// int8 vectors with a per-vector scale, NULL vector = not yet embedded: reconcile writes dirty\n// rows and build (or an authorised scoped CLI query) prepares them before semantic retrieval.\n\nexport type { Chunk };\n\n// The title/summary prefix mirrors bm25 field weighting. Chunks the body, not the raw file, so\n// `offset` shifts line numbers back onto raw and a range stays a direct Read range.\nfunction chunksOf(blocks: Block[], body: string, search?: { title: string; summary: string }, offset = 0, chunkTokens?: number): Chunk[] {\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n // A hardcoded option change here must bump src/chunk/version.ts's CHUNK_VERSION, the signature\n // for every chunker input not already carried in config.\n return chunkFromBlocks(blocks, body, chunkTokens !== undefined ? { targetTokens: chunkTokens } : undefined).map((c) => ({\n startLine: c.startLine + offset,\n endLine: c.endLine + offset,\n text: prefix ? `${prefix}\\n${c.text}` : c.text,\n }));\n}\n\nexport const embed: Feature = {\n name: 'embed',\n async schema(db) {\n await 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, _data, cfg, blocks) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n // blocks comes from parseFile's shared parse; falls back to parsing body for a direct call.\n return chunksOf(blocks ?? parse(body), body, search, countLines(raw) - countLines(body), cfg?.embed?.chunkTokens);\n },\n async remove(db, paths) {\n if (paths.length === 0) return;\n await db.runBatch(\n 'DELETE FROM embeddings WHERE \"path\" = ?',\n paths.map((p) => [p])\n );\n },\n // A tree with no embedding model never had extract() run for the doc (reconcile.ts's per-file\n // filter skips it), so extracted is undefined here -- those docs contribute no rows.\n async store(db, docs) {\n const rows: unknown[][] = [];\n const texts = new Map<string, string[]>();\n for (const { path, extracted } of docs) {\n if (!extracted) continue;\n const chunks = extracted as Chunk[];\n chunks.forEach((c, idx) => rows.push([path, idx, c.startLine, c.endLine]));\n texts.set(\n path,\n chunks.map((c) => c.text)\n );\n }\n if (rows.length === 0) return;\n // scale and vector are written by the embed pass, not here, so they are not in the column list\n // and take the table's own default on the append path.\n await appendRows(db, 'embeddings', ['path', 'chunk', 'start_line', 'end_line'], 'INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)', rows);\n // The text extract() just computed, handed to the embed pass that follows in this same\n // process rather than re-derived from the file there (src/embed/handoff.ts).\n stashChunkText(db, texts);\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n // Keyed so a rebuild notice can name it \"embed settings\". Driven by embedConfig(cfg) directly:\n // a model can be configured with no preset yet using vectors.\n signature(cfg) {\n const e = embedConfig(cfg);\n if (!e) return 'embed:off';\n // Weight identity from identity.ts, no network: a static model's resolved sha or local\n // size+mtime, appended once known so changed weights re-embed.\n const identity = e.provider === 'static' ? modelIdentity(e.model) : undefined;\n // W3b: chunkTokens rides the version token, since it's the one owner lever the chunker\n // itself takes -- changing or clearing it must rebuild exactly like a chunker version bump.\n const chunkVersion = e.chunkTokens !== undefined ? `${CHUNK_VERSION}:${e.chunkTokens}` : CHUNK_VERSION;\n return `embed:${e.provider}:${e.model}:${chunkVersion}${identity !== undefined ? `@${identity}` : ''}`;\n },\n};\n"],"names":["CHUNK_VERSION","chunkFromBlocks","parse","embedConfig","stashChunkText","modelIdentity","countLines","appendRows","chunksOf","blocks","body","search","offset","chunkTokens","prefix","title","summary","filter","Boolean","join","undefined","targetTokens","map","c","startLine","endLine","text","embed","name","schema","db","exec","extract","raw","_data","cfg","remove","paths","length","runBatch","p","store","docs","rows","texts","Map","path","extracted","chunks","forEach","idx","push","set","enabledForFile","_cfg","file","signature","e","identity","provider","model","chunkVersion"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import type { Block, Chunk } from '../chunk/index.ts';\nimport { CHUNK_VERSION, chunkFromBlocks, parse } from '../chunk/index.ts';\nimport { embedConfig } from '../config/index.ts';\nimport { embedEndpointIdentity } from '../embed/endpoint.ts';\nimport { identifyChunkText, stashChunkText } from '../embed/handoff.ts';\nimport { modelIdentity } from '../embed/identity.ts';\nimport { countLines } from '../scan/frontmatter.ts';\nimport { appendRows } from '../store/shared.ts';\nimport type { Feature } from './types.ts';\n\n// int8 vectors with a per-vector scale, NULL vector = not yet embedded: reconcile writes dirty\n// rows and build (or an authorised scoped CLI query) prepares them before semantic retrieval.\n\nexport type { Chunk };\n\nconst CHUNK_LOOKUP_PAGE = 512;\ninterface ChunkRow {\n chunk: number;\n start_line: number;\n end_line: number;\n content_identity: string;\n}\n\n// The title/summary prefix mirrors bm25 field weighting. Chunks the body, not the raw file, so\n// `offset` shifts line numbers back onto raw and a range stays a direct Read range.\nfunction chunksOf(blocks: Block[], body: string, search?: { title: string; summary: string }, offset = 0, chunkTokens?: number): Chunk[] {\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n // A hardcoded option change here must bump src/chunk/version.ts's CHUNK_VERSION, the signature\n // for every chunker input not already carried in config.\n return chunkFromBlocks(blocks, body, chunkTokens !== undefined ? { targetTokens: chunkTokens } : undefined).map((c) => ({\n startLine: c.startLine + offset,\n endLine: c.endLine + offset,\n text: prefix ? `${prefix}\\n${c.text}` : c.text,\n }));\n}\n\nexport const embed: Feature = {\n name: 'embed',\n async schema(db) {\n await db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, content_identity TEXT NOT NULL, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, body, search, _data, cfg, blocks) {\n // Lines the frontmatter occupies, so body line 1 maps back to its raw line number.\n // blocks comes from parseFile's shared parse; falls back to parsing body for a direct call.\n return chunksOf(blocks ?? parse(body), body, search, countLines(raw) - countLines(body), cfg?.embed?.chunkTokens);\n },\n async remove(db, paths, delta) {\n const vanishedSet = new Set(delta.vanished);\n const vanished = paths.filter((path) => vanishedSet.has(path));\n if (vanished.length === 0) return;\n await db.runBatch(\n 'DELETE FROM embeddings WHERE \"path\" = ?',\n vanished.map((path) => [path])\n );\n },\n // A tree with no embedding model never had extract() run for the doc (reconcile.ts's per-file\n // filter skips it), so extracted is undefined here -- those docs contribute no rows.\n async store(db, docs, delta) {\n const rows: unknown[][] = [];\n const texts = new Map<string, ReturnType<typeof identifyChunkText>[]>();\n const added = new Set(delta.added);\n // Keyset pages bound comparison metadata without reading native vector payloads.\n let firstPage: Awaited<ReturnType<typeof db.prepare>> | undefined;\n let nextPage: Awaited<ReturnType<typeof db.prepare>> | undefined;\n for (const { path, extracted } of docs) {\n const chunks = (extracted as Chunk[] | undefined) ?? [];\n if (chunks.length === 0) {\n if (!added.has(path)) await db.runBatch('DELETE FROM embeddings WHERE \"path\" = ?', [[path]]);\n continue;\n }\n const identified = chunks.map((chunk) => identifyChunkText(chunk.text));\n const kept = new Set<number>();\n if (!added.has(path)) {\n let cursor: number | undefined;\n for (;;) {\n let page: ChunkRow[];\n if (cursor === undefined) {\n firstPage ??= await db.prepare(`SELECT chunk, start_line, end_line, content_identity FROM embeddings WHERE \"path\" = ? ORDER BY chunk LIMIT ${CHUNK_LOOKUP_PAGE}`);\n page = (await firstPage.all(path)) as ChunkRow[];\n } else {\n nextPage ??= await db.prepare(`SELECT chunk, start_line, end_line, content_identity FROM embeddings WHERE \"path\" = ? AND chunk > ? ORDER BY chunk LIMIT ${CHUNK_LOOKUP_PAGE}`);\n page = (await nextPage.all(path, cursor)) as ChunkRow[];\n }\n const removed: unknown[][] = [];\n const moved: unknown[][] = [];\n for (const old of page) {\n const index = Number(old.chunk);\n const chunk = chunks[index];\n if (!chunk || old.content_identity !== identified[index].identity) {\n removed.push([path, index]);\n } else {\n kept.add(index);\n if (Number(old.start_line) !== chunk.startLine || Number(old.end_line) !== chunk.endLine) moved.push([chunk.startLine, chunk.endLine, path, index]);\n }\n }\n if (removed.length > 0) await db.runBatch('DELETE FROM embeddings WHERE \"path\" = ? AND chunk = ?', removed);\n if (moved.length > 0) await db.runBatch('UPDATE embeddings SET start_line = ?, end_line = ? WHERE \"path\" = ? AND chunk = ?', moved);\n if (page.length < CHUNK_LOOKUP_PAGE) break;\n cursor = Number(page[page.length - 1].chunk);\n }\n }\n const before = rows.length;\n chunks.forEach((chunk, index) => {\n if (!kept.has(index)) rows.push([path, index, chunk.startLine, chunk.endLine, identified[index].identity]);\n });\n // No new pending rows means no retained handoff. Matching pending rows can use\n // embedPending's exact indexed-source fallback, including after repeated store calls.\n if (rows.length > before) texts.set(path, identified);\n }\n // scale and vector are written by the embed pass, not here, so they are not in the column list\n // and take the table's own default on the append path.\n await appendRows(db, 'embeddings', ['path', 'chunk', 'start_line', 'end_line', 'content_identity'], 'INSERT INTO embeddings (\"path\", chunk, start_line, end_line, content_identity, scale, vector) VALUES (?, ?, ?, ?, ?, NULL, NULL)', rows);\n // Hand off only paths with new pending rows; missing entries use the indexed source.\n stashChunkText(db, texts);\n },\n enabledForFile(_cfg, file) {\n return file.embed;\n },\n // Keyed so a rebuild notice can name it \"embed settings\". Driven by embedConfig(cfg) directly:\n // a model can be configured with no preset yet using vectors.\n signature(cfg) {\n const e = embedConfig(cfg);\n if (!e) return 'embed:off';\n // Weight identity from identity.ts, no network: a static model's resolved sha or local\n // size+mtime, appended once known so changed weights re-embed.\n const identity = e.provider === 'static' ? modelIdentity(e.model) : undefined;\n // W3b: chunkTokens rides the version token, since it's the one owner lever the chunker\n // itself takes -- changing or clearing it must rebuild exactly like a chunker version bump.\n const chunkVersion = e.chunkTokens !== undefined ? `${CHUNK_VERSION}:${e.chunkTokens}` : CHUNK_VERSION;\n const endpoint = embedEndpointIdentity(e.provider, e.url);\n const endpointPart = endpoint === undefined ? '' : `:endpoint=${endpoint}`;\n return `embed:${e.provider}:${e.model}${endpointPart}:${chunkVersion}${identity !== undefined ? `@${identity}` : ''}`;\n },\n};\n"],"names":["CHUNK_VERSION","chunkFromBlocks","parse","embedConfig","embedEndpointIdentity","identifyChunkText","stashChunkText","modelIdentity","countLines","appendRows","CHUNK_LOOKUP_PAGE","chunksOf","blocks","body","search","offset","chunkTokens","prefix","title","summary","filter","Boolean","join","undefined","targetTokens","map","c","startLine","endLine","text","embed","name","schema","db","exec","extract","raw","_data","cfg","remove","paths","delta","vanishedSet","Set","vanished","path","has","length","runBatch","store","docs","rows","texts","Map","added","firstPage","nextPage","extracted","chunks","identified","chunk","kept","cursor","page","prepare","all","removed","moved","old","index","Number","content_identity","identity","push","add","start_line","end_line","before","forEach","set","enabledForFile","_cfg","file","signature","e","provider","model","chunkVersion","endpoint","url","endpointPart"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Store } from '../store/types.js';
|
|
2
|
-
interface FindPathOptions {
|
|
2
|
+
export interface FindPathOptions {
|
|
3
3
|
directed?: boolean;
|
|
4
4
|
maxDepth?: number;
|
|
5
5
|
allowed?: Set<string>;
|
|
6
6
|
}
|
|
7
7
|
export declare function findPath(store: Store, from: string, to: string, opts?: FindPathOptions): Promise<string[] | null>;
|
|
8
|
-
export
|
|
8
|
+
export declare function findPathInSnapshot(store: Store, from: string, to: string, opts: FindPathOptions): Promise<string[] | null>;
|
|
@@ -53,9 +53,11 @@ async function reconstructPath(store, to) {
|
|
|
53
53
|
// One BFS from `from`, tracking a predecessor per node. Undirected by default, matching
|
|
54
54
|
// personalizedRank; null when `to` is unreached within `maxDepth`.
|
|
55
55
|
export function findPath(store, from, to, opts = {}) {
|
|
56
|
-
return serialQuery(store, ()=>
|
|
56
|
+
return serialQuery(store, ()=>store.transaction(()=>findPathInSnapshot(store, from, to, opts)));
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
// Internal entry for the scoped command wrapper, whose transaction also owns endpoint resolution
|
|
59
|
+
// and scope reads. Direct graph callers use findPath() above and receive the same snapshot rule.
|
|
60
|
+
export async function findPathInSnapshot(store, from, to, opts) {
|
|
59
61
|
var _opts_maxDepth;
|
|
60
62
|
if (from === to) return [
|
|
61
63
|
from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/graph/traverse.ts"],"sourcesContent":["// Frontier-by-frontier BFS through a temp visited table, one indexed anti-join per ring: a\n// local question touches a neighborhood, never the whole edge list.\n\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Store } from '../store/types.ts';\n\ntype Direction = 'forward' | 'reverse' | 'both';\n\
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/graph/traverse.ts"],"sourcesContent":["// Frontier-by-frontier BFS through a temp visited table, one indexed anti-join per ring: a\n// local question touches a neighborhood, never the whole edge list.\n\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Store } from '../store/types.ts';\n\ntype Direction = 'forward' | 'reverse' | 'both';\n\nexport interface FindPathOptions {\n directed?: boolean;\n maxDepth?: number;\n allowed?: Set<string>;\n}\n\nasync function resetVisited(store: Store): Promise<void> {\n await store.exec('DROP TABLE IF EXISTS temp.visited');\n await store.exec('CREATE TEMP TABLE visited (path TEXT PRIMARY KEY, depth INTEGER NOT NULL, pred TEXT)');\n}\n\n// Materializes the caller-resolved scope Set into a temp table so it joins like any other\n// indexed column. `extra`, when given, is always in scope -- findPath's `to` endpoint.\nasync function setupAllowed(store: Store, allowed: Set<string>, extra?: string): Promise<void> {\n const all = new Set(allowed);\n if (extra !== undefined) all.add(extra);\n await store.exec('DROP TABLE IF EXISTS temp.allowed_nodes');\n await store.exec('CREATE TEMP TABLE allowed_nodes (path TEXT PRIMARY KEY)');\n await store.runBatch(\n 'INSERT INTO allowed_nodes (path) VALUES (?)',\n [...all].map((p) => [p])\n );\n}\n\n// Only resolved links (dst IS NOT NULL) are followed, in either direction.\nfunction candidateSelect(direction: Direction): string {\n const forward = 'SELECT l.dst AS path, l.src AS pred FROM links l JOIN visited v ON v.path = l.src AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n const reverse = 'SELECT l.src AS path, l.dst AS pred FROM links l JOIN visited v ON v.path = l.dst AND v.depth = ?1 WHERE l.dst IS NOT NULL';\n if (direction === 'forward') return forward;\n if (direction === 'reverse') return reverse;\n return `${forward} UNION ALL ${reverse}`;\n}\n\n// One ring: candidates adjacent to the previous ring's frontier (?1), anti-joined against\n// everything already visited, optionally filtered to the allowed set, inserted at the new depth (?2); RETURNING gives what this ring newly reached.\nfunction ringSql(direction: Direction, hasAllowed: boolean): string {\n return `\nINSERT INTO visited (path, depth, pred)\nSELECT cand.path, ?2, MIN(cand.pred)\nFROM (${candidateSelect(direction)}) cand\nWHERE NOT EXISTS (SELECT 1 FROM visited v2 WHERE v2.path = cand.path)\n${hasAllowed ? 'AND EXISTS (SELECT 1 FROM allowed_nodes a WHERE a.path = cand.path)' : ''}\nGROUP BY cand.path\nORDER BY cand.path\nRETURNING path`;\n}\n\nasync function reconstructPath(store: Store, to: string): Promise<string[]> {\n const lookup = await store.prepare('SELECT pred FROM visited WHERE path = ?');\n const path: string[] = [];\n let cur: string | null = to;\n while (cur !== null) {\n path.push(cur);\n const row = (await lookup.get(cur)) as { pred: string | null } | undefined;\n cur = row ? row.pred : null;\n }\n return path.reverse();\n}\n\n// One BFS from `from`, tracking a predecessor per node. Undirected by default, matching\n// personalizedRank; null when `to` is unreached within `maxDepth`.\nexport function findPath(store: Store, from: string, to: string, opts: FindPathOptions = {}): Promise<string[] | null> {\n return serialQuery(store, () => store.transaction(() => findPathInSnapshot(store, from, to, opts)));\n}\n\n// Internal entry for the scoped command wrapper, whose transaction also owns endpoint resolution\n// and scope reads. Direct graph callers use findPath() above and receive the same snapshot rule.\nexport async function findPathInSnapshot(store: Store, from: string, to: string, opts: FindPathOptions): Promise<string[] | null> {\n if (from === to) return [from];\n\n const direction: Direction = opts.directed ? 'forward' : 'both';\n const maxDepth = opts.maxDepth ?? Infinity;\n if (maxDepth <= 0) return null;\n\n await resetVisited(store);\n const insertStart = await store.prepare('INSERT INTO visited (path, depth, pred) VALUES (?, 0, NULL)');\n await insertStart.run(from);\n if (opts.allowed) await setupAllowed(store, opts.allowed, to);\n\n const stmt = await store.prepare(ringSql(direction, !!opts.allowed));\n for (let ring = 1; ring <= maxDepth; ring++) {\n const rows = (await stmt.all(ring - 1, ring)) as Array<{ path: string }>;\n if (rows.length === 0) break;\n if (rows.some((row) => row.path === to)) return reconstructPath(store, to);\n }\n return null;\n}\n"],"names":["serialQuery","resetVisited","store","exec","setupAllowed","allowed","extra","all","Set","undefined","add","runBatch","map","p","candidateSelect","direction","forward","reverse","ringSql","hasAllowed","reconstructPath","to","lookup","prepare","path","cur","push","row","get","pred","findPath","from","opts","transaction","findPathInSnapshot","directed","maxDepth","Infinity","insertStart","run","stmt","ring","rows","length","some"],"mappings":"AAAA,2FAA2F;AAC3F,oEAAoE;AAEpE,SAASA,WAAW,QAAQ,yBAAyB;AAWrD,eAAeC,aAAaC,KAAY;IACtC,MAAMA,MAAMC,IAAI,CAAC;IACjB,MAAMD,MAAMC,IAAI,CAAC;AACnB;AAEA,0FAA0F;AAC1F,uFAAuF;AACvF,eAAeC,aAAaF,KAAY,EAAEG,OAAoB,EAAEC,KAAc;IAC5E,MAAMC,MAAM,IAAIC,IAAIH;IACpB,IAAIC,UAAUG,WAAWF,IAAIG,GAAG,CAACJ;IACjC,MAAMJ,MAAMC,IAAI,CAAC;IACjB,MAAMD,MAAMC,IAAI,CAAC;IACjB,MAAMD,MAAMS,QAAQ,CAClB,+CACA;WAAIJ;KAAI,CAACK,GAAG,CAAC,CAACC,IAAM;YAACA;SAAE;AAE3B;AAEA,2EAA2E;AAC3E,SAASC,gBAAgBC,SAAoB;IAC3C,MAAMC,UAAU;IAChB,MAAMC,UAAU;IAChB,IAAIF,cAAc,WAAW,OAAOC;IACpC,IAAID,cAAc,WAAW,OAAOE;IACpC,OAAO,GAAGD,QAAQ,WAAW,EAAEC,SAAS;AAC1C;AAEA,0FAA0F;AAC1F,oJAAoJ;AACpJ,SAASC,QAAQH,SAAoB,EAAEI,UAAmB;IACxD,OAAO,CAAC;;;MAGJ,EAAEL,gBAAgBC,WAAW;;AAEnC,EAAEI,aAAa,wEAAwE,GAAG;;;cAG5E,CAAC;AACf;AAEA,eAAeC,gBAAgBlB,KAAY,EAAEmB,EAAU;IACrD,MAAMC,SAAS,MAAMpB,MAAMqB,OAAO,CAAC;IACnC,MAAMC,OAAiB,EAAE;IACzB,IAAIC,MAAqBJ;IACzB,MAAOI,QAAQ,KAAM;QACnBD,KAAKE,IAAI,CAACD;QACV,MAAME,MAAO,MAAML,OAAOM,GAAG,CAACH;QAC9BA,MAAME,MAAMA,IAAIE,IAAI,GAAG;IACzB;IACA,OAAOL,KAAKP,OAAO;AACrB;AAEA,wFAAwF;AACxF,mEAAmE;AACnE,OAAO,SAASa,SAAS5B,KAAY,EAAE6B,IAAY,EAAEV,EAAU,EAAEW,OAAwB,CAAC,CAAC;IACzF,OAAOhC,YAAYE,OAAO,IAAMA,MAAM+B,WAAW,CAAC,IAAMC,mBAAmBhC,OAAO6B,MAAMV,IAAIW;AAC9F;AAEA,iGAAiG;AACjG,iGAAiG;AACjG,OAAO,eAAeE,mBAAmBhC,KAAY,EAAE6B,IAAY,EAAEV,EAAU,EAAEW,IAAqB;QAInFA;IAHjB,IAAID,SAASV,IAAI,OAAO;QAACU;KAAK;IAE9B,MAAMhB,YAAuBiB,KAAKG,QAAQ,GAAG,YAAY;IACzD,MAAMC,YAAWJ,iBAAAA,KAAKI,QAAQ,cAAbJ,4BAAAA,iBAAiBK;IAClC,IAAID,YAAY,GAAG,OAAO;IAE1B,MAAMnC,aAAaC;IACnB,MAAMoC,cAAc,MAAMpC,MAAMqB,OAAO,CAAC;IACxC,MAAMe,YAAYC,GAAG,CAACR;IACtB,IAAIC,KAAK3B,OAAO,EAAE,MAAMD,aAAaF,OAAO8B,KAAK3B,OAAO,EAAEgB;IAE1D,MAAMmB,OAAO,MAAMtC,MAAMqB,OAAO,CAACL,QAAQH,WAAW,CAAC,CAACiB,KAAK3B,OAAO;IAClE,IAAK,IAAIoC,OAAO,GAAGA,QAAQL,UAAUK,OAAQ;QAC3C,MAAMC,OAAQ,MAAMF,KAAKjC,GAAG,CAACkC,OAAO,GAAGA;QACvC,IAAIC,KAAKC,MAAM,KAAK,GAAG;QACvB,IAAID,KAAKE,IAAI,CAAC,CAACjB,MAAQA,IAAIH,IAAI,KAAKH,KAAK,OAAOD,gBAAgBlB,OAAOmB;IACzE;IACA,OAAO;AACT"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { Peek, PresetCoverage, SearchOptions, TreeMap } from './commands/index.js';
|
|
2
|
-
export { mapTree, peek, presetCoverage, search } from './commands/index.js';
|
|
1
|
+
export type { PathOptions, Peek, PeekOptions, PeekSection, PresetCoverage, RelatedOptions, RelatedResult, SearchOptions, SearchResult, SearchResultVia, SearchSignalContribution, TreeMap, TreeMapField, TreeMapHub, TreeMapRecent } from './commands/index.js';
|
|
2
|
+
export { findPath, mapTree, peek, presetCoverage, relatedNotes, search } from './commands/index.js';
|
|
3
3
|
export type { Config, EmbedConfig, FeatureName, InitOverrides, LoadConfigOptions, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights, StoreName } from './config/index.js';
|
|
4
4
|
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, SUPPORTED_CONFIG_VERSION } from './config/index.js';
|
|
5
5
|
export type { SenseErrorCode } from './errors.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Public library API. Deliberately small: every export is a stability promise;
|
|
2
2
|
// internals (feature registry, graph, scan, meta) stay module-private.
|
|
3
|
-
export { mapTree, peek, presetCoverage, search } from './commands/index.js';
|
|
3
|
+
export { findPath, mapTree, peek, presetCoverage, relatedNotes, search } from './commands/index.js';
|
|
4
4
|
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, SUPPORTED_CONFIG_VERSION } from './config/index.js';
|
|
5
5
|
export { SenseError } from './errors.js';
|
|
6
6
|
export { printRows } from './output/output.js';
|
package/dist/esm/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/index.ts';\nexport { mapTree, peek, presetCoverage, search } from './commands/index.ts';\nexport type { Config, EmbedConfig, FeatureName, InitOverrides, LoadConfigOptions, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights, StoreName } from './config/index.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, SUPPORTED_CONFIG_VERSION } from './config/index.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Format, Row, RowFormat } from './output/output.ts';\nexport { printRows } from './output/output.ts';\nexport type { BuildOptions, BuildResult, OpenOptions, OpenResult, Stages } from './store/index.ts';\nexport { buildIndex as build, CAPABILITY_NAMES, clearCache, openStore as open } from './store/index.ts';\nexport type { Capability, DocumentStore, FieldStat, LexicalHit, LexicalIndex, LexicalQueryOptions, RawStatement, RunResult, SqlSession, Statement, Store, VectorCandidate, VectorSimilar, VectorStore, VectorWriteRow } from './store/types.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["mapTree","peek","presetCoverage","search","CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","SIGNAL_NAMES","STATE_DIR","STORE_NAMES","SUPPORTED_CONFIG_VERSION","SenseError","printRows","buildIndex","build","CAPABILITY_NAMES","clearCache","openStore","open","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,OAAO,EAAEC,IAAI,EAAEC,cAAc,EAAEC,MAAM,QAAQ,sBAAsB;
|
|
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 { PathOptions, Peek, PeekOptions, PeekSection, PresetCoverage, RelatedOptions, RelatedResult, SearchOptions, SearchResult, SearchResultVia, SearchSignalContribution, TreeMap, TreeMapField, TreeMapHub, TreeMapRecent } from './commands/index.ts';\nexport { findPath, mapTree, peek, presetCoverage, relatedNotes, search } from './commands/index.ts';\nexport type { Config, EmbedConfig, FeatureName, InitOverrides, LoadConfigOptions, Preset, ResolvedConfig, SavedQuery, SavedSearch, SearchOverrides, SignalName, SignalWeights, StoreName } from './config/index.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, SIGNAL_NAMES, STATE_DIR, STORE_NAMES, SUPPORTED_CONFIG_VERSION } from './config/index.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport type { Format, Row, RowFormat } from './output/output.ts';\nexport { printRows } from './output/output.ts';\nexport type { BuildOptions, BuildResult, OpenOptions, OpenResult, Stages } from './store/index.ts';\nexport { buildIndex as build, CAPABILITY_NAMES, clearCache, openStore as open } from './store/index.ts';\nexport type { Capability, DocumentStore, FieldStat, LexicalHit, LexicalIndex, LexicalQueryOptions, RawStatement, RunResult, SqlSession, Statement, Store, VectorCandidate, VectorSimilar, VectorStore, VectorWriteRow } from './store/types.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["findPath","mapTree","peek","presetCoverage","relatedNotes","search","CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","SIGNAL_NAMES","STATE_DIR","STORE_NAMES","SUPPORTED_CONFIG_VERSION","SenseError","printRows","buildIndex","build","CAPABILITY_NAMES","clearCache","openStore","open","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,QAAQ,EAAEC,OAAO,EAAEC,IAAI,EAAEC,cAAc,EAAEC,YAAY,EAAEC,MAAM,QAAQ,sBAAsB;AAEpG,SAASC,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,YAAY,EAAEC,SAAS,EAAEC,WAAW,EAAEC,wBAAwB,QAAQ,oBAAoB;AAE3J,SAASC,UAAU,QAAQ,cAAc;AAEzC,SAASC,SAAS,QAAQ,qBAAqB;AAE/C,SAASC,cAAcC,KAAK,EAAEC,gBAAgB,EAAEC,UAAU,EAAEC,aAAaC,IAAI,QAAQ,mBAAmB;AAIxG,SAASC,QAAQ,QAAQ,aAAa"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { TreeMap } from '../commands/map.js';
|
|
2
|
+
import type { Peek } from '../commands/peek.js';
|
|
1
3
|
export type Row = Record<string, unknown>;
|
|
2
4
|
export type Format = 'table' | 'json';
|
|
3
5
|
export type RowFormat = Format | 'csv';
|
|
@@ -15,39 +17,5 @@ export declare function presetsLines(presets: Array<{
|
|
|
15
17
|
embedded: number;
|
|
16
18
|
signals: Record<string, number>;
|
|
17
19
|
}>): string[];
|
|
18
|
-
export declare function renderMap(result:
|
|
19
|
-
|
|
20
|
-
count: number;
|
|
21
|
-
bytes: number;
|
|
22
|
-
};
|
|
23
|
-
fields: Row[];
|
|
24
|
-
fieldsTotal: number;
|
|
25
|
-
features: {
|
|
26
|
-
on: string[];
|
|
27
|
-
off: string[];
|
|
28
|
-
};
|
|
29
|
-
presets: Array<{
|
|
30
|
-
name: string;
|
|
31
|
-
files: number;
|
|
32
|
-
embedded: number;
|
|
33
|
-
signals: Record<string, number>;
|
|
34
|
-
}>;
|
|
35
|
-
hubs: Row[];
|
|
36
|
-
recent: Row[];
|
|
37
|
-
recentCaveat: string | null;
|
|
38
|
-
}): string;
|
|
39
|
-
export declare function renderPeek(result: {
|
|
40
|
-
path: string;
|
|
41
|
-
tokens: number;
|
|
42
|
-
frontmatter: Row;
|
|
43
|
-
parseError?: string | null;
|
|
44
|
-
sections: Row[];
|
|
45
|
-
outbound: string[];
|
|
46
|
-
backlinks: string[];
|
|
47
|
-
unresolved: string[];
|
|
48
|
-
sectionsTotal: number;
|
|
49
|
-
outboundTotal: number;
|
|
50
|
-
backlinksTotal: number;
|
|
51
|
-
unresolvedTotal: number;
|
|
52
|
-
off: string[];
|
|
53
|
-
}): string;
|
|
20
|
+
export declare function renderMap(result: TreeMap): string;
|
|
21
|
+
export declare function renderPeek(result: Peek): string;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// rows -> table (default) | json | csv
|
|
2
1
|
// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.
|
|
3
2
|
const OVERSIZE_BYTES = 50000;
|
|
4
3
|
// Flatten embedded newlines so they can't break table column alignment; JSON output keeps them as-is.
|
|
@@ -139,7 +138,7 @@ function renderRows(rows, format, width = process.stdout.columns) {
|
|
|
139
138
|
if (format === 'json') return stringifyJson(rows, 2);
|
|
140
139
|
if (rows.length === 0) return '(0 rows)';
|
|
141
140
|
const columns = Object.keys(rows[0]);
|
|
142
|
-
const linesByCell = rows.map((row)=>columns.map((col)=>cellLines(row
|
|
141
|
+
const linesByCell = rows.map((row)=>columns.map((col)=>cellLines(Reflect.get(row, col))));
|
|
143
142
|
const heights = linesByCell.map((cellsInRow)=>Math.max(1, ...cellsInRow.map((lines)=>lines.length)));
|
|
144
143
|
const natural = columns.map((col, i)=>Math.max(col.length, ...linesByCell.flatMap((cellsInRow)=>cellsInRow[i]).map((line)=>line.length)));
|
|
145
144
|
const widths = width && width > MIN_COLUMN ? fitWidths(natural, width) : natural;
|