sensemaking 0.25.0 → 0.26.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 +14 -10
- package/dist/cjs/cli/index.d.cts +1 -1
- package/dist/cjs/cli/index.d.ts +1 -1
- package/dist/cjs/cli/index.js +1 -1
- 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/related.js +4 -3
- package/dist/cjs/cli/related.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 +16 -1
- package/dist/cjs/commands/peek.d.ts +16 -1
- package/dist/cjs/commands/peek.js +71 -33
- 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 +26 -3
- package/dist/cjs/commands/search.d.ts +26 -3
- package/dist/cjs/commands/search.js +211 -71
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.d.cts +4 -3
- package/dist/cjs/commands/signals.d.ts +4 -3
- package/dist/cjs/commands/signals.js +13 -4
- 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/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 +1 -1
- package/dist/esm/cli/index.js +1 -1
- 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/related.js +5 -3
- package/dist/esm/cli/related.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 +16 -1
- package/dist/esm/commands/peek.js +35 -9
- 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 +26 -3
- package/dist/esm/commands/search.js +85 -18
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.d.ts +4 -3
- package/dist/esm/commands/signals.js +11 -4
- 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/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/SKILL.md +4 -2
- package/skills/sense/references/stores/duckdb.md +1 -1
- 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/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":"AACA,SAASA,aAAa,EAAEC,eAAe,EAAEC,KAAK,QAAQ,oBAAoB;AAC1E,SAASC,WAAW,QAAQ,qBAAqB;AACjD,SAASC,cAAc,QAAQ,sBAAsB;AACrD,SAASC,aAAa,QAAQ,uBAAuB;AACrD,SAASC,UAAU,QAAQ,yBAAyB;AACpD,SAASC,UAAU,QAAQ,qBAAqB;AAQhD,+FAA+F;AAC/F,oFAAoF;AACpF,SAASC,SAASC,MAAe,EAAEC,IAAY,EAAEC,MAA2C,EAAEC,SAAS,CAAC,EAAEC,WAAoB;IAC5H,MAAMC,SAAS;QAACH,mBAAAA,6BAAAA,OAAQI,KAAK;QAAEJ,mBAAAA,6BAAAA,OAAQK,OAAO;KAAC,CAACC,MAAM,CAACC,SAASC,IAAI,CAAC;IACrE,+FAA+F;IAC/F,yDAAyD;IACzD,OAAOlB,gBAAgBQ,QAAQC,MAAMG,gBAAgBO,YAAY;QAAEC,cAAcR;IAAY,IAAIO,WAAWE,GAAG,CAAC,CAACC,IAAO,CAAA;YACtHC,WAAWD,EAAEC,SAAS,GAAGZ;YACzBa,SAASF,EAAEE,OAAO,GAAGb;YACrBc,MAAMZ,SAAS,GAAGA,OAAO,EAAE,EAAES,EAAEG,IAAI,EAAE,GAAGH,EAAEG,IAAI;QAChD,CAAA;AACF;AAEA,OAAO,MAAMC,QAAiB;IAC5BC,MAAM;IACN,MAAMC,QAAOC,EAAE;QACb,MAAMA,GAAGC,IAAI,CAAC,CAAC,8JAA8J,CAAC;IAChL;IACAC,SAAQC,GAAG,EAAEvB,IAAI,EAAEC,MAAM,EAAEuB,KAAK,EAAEC,GAAG,EAAE1B,MAAM;YAG8C0B;QAFzF,mFAAmF;QACnF,4FAA4F;QAC5F,OAAO3B,SAASC,mBAAAA,oBAAAA,SAAUP,MAAMQ,OAAOA,MAAMC,QAAQL,WAAW2B,OAAO3B,WAAWI,OAAOyB,gBAAAA,2BAAAA,aAAAA,IAAKR,KAAK,cAAVQ,iCAAAA,WAAYtB,WAAW;IAClH;IACA,MAAMuB,QAAON,EAAE,EAAEO,KAAK;QACpB,IAAIA,MAAMC,MAAM,KAAK,GAAG;QACxB,MAAMR,GAAGS,QAAQ,CACf,2CACAF,MAAMf,GAAG,CAAC,CAACkB,IAAM;gBAACA;aAAE;IAExB;IACA,8FAA8F;IAC9F,qFAAqF;IACrF,MAAMC,OAAMX,EAAE,EAAEY,IAAI;QAClB,MAAMC,OAAoB,EAAE;QAC5B,MAAMC,QAAQ,IAAIC;QAClB,KAAK,MAAM,EAAEC,IAAI,EAAEC,SAAS,EAAE,IAAIL,KAAM;YACtC,IAAI,CAACK,WAAW;YAChB,MAAMC,SAASD;YACfC,OAAOC,OAAO,CAAC,CAAC1B,GAAG2B,MAAQP,KAAKQ,IAAI,CAAC;oBAACL;oBAAMI;oBAAK3B,EAAEC,SAAS;oBAAED,EAAEE,OAAO;iBAAC;YACxEmB,MAAMQ,GAAG,CACPN,MACAE,OAAO1B,GAAG,CAAC,CAACC,IAAMA,EAAEG,IAAI;QAE5B;QACA,IAAIiB,KAAKL,MAAM,KAAK,GAAG;QACvB,+FAA+F;QAC/F,uDAAuD;QACvD,MAAM/B,WAAWuB,IAAI,cAAc;YAAC;YAAQ;YAAS;YAAc;SAAW,EAAE,+GAA+Ga;QAC/L,uFAAuF;QACvF,6EAA6E;QAC7EvC,eAAe0B,IAAIc;IACrB;IACAS,gBAAeC,IAAI,EAAEC,IAAI;QACvB,OAAOA,KAAK5B,KAAK;IACnB;IACA,+FAA+F;IAC/F,8DAA8D;IAC9D6B,WAAUrB,GAAG;QACX,MAAMsB,IAAItD,YAAYgC;QACtB,IAAI,CAACsB,GAAG,OAAO;QACf,uFAAuF;QACvF,+DAA+D;QAC/D,MAAMC,WAAWD,EAAEE,QAAQ,KAAK,WAAWtD,cAAcoD,EAAEG,KAAK,IAAIxC;QACpE,uFAAuF;QACvF,4FAA4F;QAC5F,MAAMyC,eAAeJ,EAAE5C,WAAW,KAAKO,YAAY,GAAGpB,cAAc,CAAC,EAAEyD,EAAE5C,WAAW,EAAE,GAAGb;QACzF,OAAO,CAAC,MAAM,EAAEyD,EAAEE,QAAQ,CAAC,CAAC,EAAEF,EAAEG,KAAK,CAAC,CAAC,EAAEC,eAAeH,aAAatC,YAAY,CAAC,CAAC,EAAEsC,UAAU,GAAG,IAAI;IACxG;AACF,EAAE"}
|
|
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, PeekSection, PresetCoverage, RelatedOptions, RelatedResult, SearchOptions, SearchResult, SearchResultVia, 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, PeekSection, PresetCoverage, RelatedOptions, RelatedResult, SearchOptions, SearchResult, SearchResultVia, 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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output/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 keeps them as-is.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\n// RFC 4180, deliberately not cell(): csv is the redirect-to-file format, so it keeps every\n// character, newlines included; what it cannot carry (NULL vs empty, types) is what json is for.\n// An array (e.g. snippets) joins with the one delimiter our content provably cannot contain,\n// inside the quoted field newlines already trigger (DESIGN.md \"Rendering a list of snippets\").\nfunction csvField(value: unknown): string {\n const text = value === null || value === undefined ? '' : Array.isArray(value) ? value.join('\\n') : 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// JSON.stringify throws on BigInt, and stores return int64 as BigInt (DuckDB does): a value\n// that fits a safe integer stays a number, a larger one becomes its decimal string.\nconst MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\nconst bigintSafe = (_key: string, value: unknown): unknown => (typeof value === 'bigint' ? (value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString()) : value);\n\nexport function stringifyJson(value: unknown, indent?: number): string {\n return JSON.stringify(value, bigintSafe, indent);\n}\n\nexport function printRows(rows: Row[], format: RowFormat, columns?: string[]): void {\n if (format === 'csv') {\n // Names from a row wherever there is one: the statement's column list keeps duplicates the\n // row collapsed. Nothing at all with no row and no columns -- a bare newline is an empty record.\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`: the result is never held as\n// rows and again as a rendered string. Table buffers (column width depends on the last row); json/csv write row by row.\nexport async function printRowStream(rows: AsyncIterable<Row>, format: RowFormat, columns: string[]): Promise<void> {\n const iterator = rows[Symbol.asyncIterator]();\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 = await iterator.next();\n const rest: AsyncIterable<Row> = { [Symbol.asyncIterator]: () => iterator };\n\n if (format === 'table') {\n const collected: Row[] = first.done ? [] : [first.value];\n for await (const row of rest) collected.push(row);\n printRows(collected, 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 const indent = (row: Row) =>\n stringifyJson(row, 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 await (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 await (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\n// An array cell (snippets) becomes one physical line per element; every other value stays the\n// single line cell() already produced. No cell in any other command holds one today, so a row\n// with no array-valued cell yields exactly one physical line, unchanged from before this existed.\nfunction cellLines(value: unknown): string[] {\n if (Array.isArray(value)) return value.length === 0 ? [''] : value.map((v) => cell(v));\n return [cell(value)];\n}\n\nfunction renderRows(rows: Row[], format: Format, width = process.stdout.columns): string {\n if (format === 'json') return stringifyJson(rows, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const linesByCell = rows.map((row) => columns.map((col) => cellLines(row[col])));\n const heights = linesByCell.map((cellsInRow) => Math.max(1, ...cellsInRow.map((lines) => lines.length)));\n const natural = columns.map((col, i) => Math.max(col.length, ...linesByCell.flatMap((cellsInRow) => cellsInRow[i]).map((line) => line.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 // A row becomes as many physical lines as its tallest cell; other columns blank-pad on\n // continuation lines (DESIGN.md \"Rendering a list of snippets\").\n const body: string[] = [];\n for (let r = 0; r < rows.length; r++) {\n for (let li = 0; li < heights[r]; li++) body.push(formatRow(columns.map((_, c) => linesByCell[r][c][li] ?? '')));\n }\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...body].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\n// name, or name:weight when the weight is not the default 1.\nfunction signalLabels(signals: Record<string, number>): string {\n return Object.entries(signals)\n .map(([name, weight]) => (weight === 1 ? name : `${name}:${weight}`))\n .join(', ');\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number; signals: Record<string, number> }>): string[] {\n // \"0 embedded\" is ambiguous between a scope that declined vectors and one that has not built\n // them yet, so a preset without the vectors signal says what it uses instead.\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.signals.vectors !== undefined ? '' : ` (signals: ${signalLabels(p.signals)})`}`)];\n}\n\nexport function renderMap(result: {\n docs: { count: number; bytes: number };\n fields: Row[];\n fieldsTotal: number;\n features: { on: string[]; off: string[] };\n presets: Array<{ name: string; files: number; embedded: number; signals: Record<string, number> }>;\n hubs: Row[];\n recent: Row[];\n recentCaveat: string | null;\n}): 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":["OVERSIZE_BYTES","cell","value","String","replace","csvField","text","undefined","Array","isArray","join","test","csvLine","values","map","warnOversize","bytes","rowCount","console","warn","Math","round","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","bigintSafe","_key","toString","stringifyJson","indent","JSON","stringify","printRows","rows","format","columns","names","length","Object","keys","Set","rendered","row","name","log","Buffer","byteLength","renderRows","printRowStream","iterator","Symbol","asyncIterator","first","next","rest","collected","done","push","write","process","stdout","split","line","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","cellLines","v","width","linesByCell","col","heights","cellsInRow","lines","natural","i","flatMap","clip","w","padEnd","slice","formatRow","trimEnd","body","r","li","_","c","repeat","featureOffHint","featuresLine","features","off","on","signalLabels","signals","entries","weight","presetsLines","presets","p","files","embedded","vectors","renderMap","result","parts","docs","count","fields","fieldsTotal","hubs","includes","recentCaveat","recent","renderPeek","path","tokens","parseError","key","frontmatter","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/output.ts"],"sourcesContent":["import type { TreeMap } from '../commands/map.ts';\nimport type { Peek } from '../commands/peek.ts';\n\n// 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 keeps them as-is.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\n// RFC 4180, deliberately not cell(): csv is the redirect-to-file format, so it keeps every\n// character, newlines included; what it cannot carry (NULL vs empty, types) is what json is for.\n// An array (e.g. snippets) joins with the one delimiter our content provably cannot contain,\n// inside the quoted field newlines already trigger (DESIGN.md \"Rendering a list of snippets\").\nfunction csvField(value: unknown): string {\n const text = value === null || value === undefined ? '' : Array.isArray(value) ? value.join('\\n') : 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// JSON.stringify throws on BigInt, and stores return int64 as BigInt (DuckDB does): a value\n// that fits a safe integer stays a number, a larger one becomes its decimal string.\nconst MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);\nconst bigintSafe = (_key: string, value: unknown): unknown => (typeof value === 'bigint' ? (value >= -MAX_SAFE && value <= MAX_SAFE ? Number(value) : value.toString()) : value);\n\nexport function stringifyJson(value: unknown, indent?: number): string {\n return JSON.stringify(value, bigintSafe, indent);\n}\n\nexport function printRows(rows: Row[], format: RowFormat, columns?: string[]): void {\n if (format === 'csv') {\n // Names from a row wherever there is one: the statement's column list keeps duplicates the\n // row collapsed. Nothing at all with no row and no columns -- a bare newline is an empty record.\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`: the result is never held as\n// rows and again as a rendered string. Table buffers (column width depends on the last row); json/csv write row by row.\nexport async function printRowStream(rows: AsyncIterable<Row>, format: RowFormat, columns: string[]): Promise<void> {\n const iterator = rows[Symbol.asyncIterator]();\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 = await iterator.next();\n const rest: AsyncIterable<Row> = { [Symbol.asyncIterator]: () => iterator };\n\n if (format === 'table') {\n const collected: Row[] = first.done ? [] : [first.value];\n for await (const row of rest) collected.push(row);\n printRows(collected, 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 const indent = (row: Row) =>\n stringifyJson(row, 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 await (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 await (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\n// An array cell (snippets) becomes one physical line per element; every other value stays the\n// single line cell() already produced. No cell in any other command holds one today, so a row\n// with no array-valued cell yields exactly one physical line, unchanged from before this existed.\nfunction cellLines(value: unknown): string[] {\n if (Array.isArray(value)) return value.length === 0 ? [''] : value.map((v) => cell(v));\n return [cell(value)];\n}\n\nfunction renderRows(rows: object[], format: Format, width = process.stdout.columns): string {\n if (format === 'json') return stringifyJson(rows, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const linesByCell = rows.map((row) => columns.map((col) => cellLines(Reflect.get(row, col))));\n const heights = linesByCell.map((cellsInRow) => Math.max(1, ...cellsInRow.map((lines) => lines.length)));\n const natural = columns.map((col, i) => Math.max(col.length, ...linesByCell.flatMap((cellsInRow) => cellsInRow[i]).map((line) => line.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 // A row becomes as many physical lines as its tallest cell; other columns blank-pad on\n // continuation lines (DESIGN.md \"Rendering a list of snippets\").\n const body: string[] = [];\n for (let r = 0; r < rows.length; r++) {\n for (let li = 0; li < heights[r]; li++) body.push(formatRow(columns.map((_, c) => linesByCell[r][c][li] ?? '')));\n }\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...body].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\n// name, or name:weight when the weight is not the default 1.\nfunction signalLabels(signals: Record<string, number>): string {\n return Object.entries(signals)\n .map(([name, weight]) => (weight === 1 ? name : `${name}:${weight}`))\n .join(', ');\n}\n\nexport function presetsLines(presets: Array<{ name: string; files: number; embedded: number; signals: Record<string, number> }>): string[] {\n // \"0 embedded\" is ambiguous between a scope that declined vectors and one that has not built\n // them yet, so a preset without the vectors signal says what it uses instead.\n return ['presets:', ...presets.map((p) => ` ${p.name}: ${p.files} file(s), ${p.embedded} embedded${p.signals.vectors !== undefined ? '' : ` (signals: ${signalLabels(p.signals)})`}`)];\n}\n\nexport function renderMap(result: TreeMap): 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: Peek): 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)} ${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":["OVERSIZE_BYTES","cell","value","String","replace","csvField","text","undefined","Array","isArray","join","test","csvLine","values","map","warnOversize","bytes","rowCount","console","warn","Math","round","MAX_SAFE","BigInt","Number","MAX_SAFE_INTEGER","bigintSafe","_key","toString","stringifyJson","indent","JSON","stringify","printRows","rows","format","columns","names","length","Object","keys","Set","rendered","row","name","log","Buffer","byteLength","renderRows","printRowStream","iterator","Symbol","asyncIterator","first","next","rest","collected","done","push","write","process","stdout","split","line","MIN_COLUMN","TRUNCATED","fitWidths","widths","limit","gaps","fitted","total","reduce","a","b","widest","indexOf","max","cellLines","v","width","linesByCell","col","Reflect","get","heights","cellsInRow","lines","natural","i","flatMap","clip","w","padEnd","slice","formatRow","trimEnd","body","r","li","_","c","repeat","featureOffHint","featuresLine","features","off","on","signalLabels","signals","entries","weight","presetsLines","presets","p","files","embedded","vectors","renderMap","result","parts","docs","count","fields","fieldsTotal","hubs","includes","recentCaveat","recent","renderPeek","path","tokens","parseError","key","frontmatter","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/scan/pool.d.ts
CHANGED
|
@@ -10,11 +10,12 @@ export type FileResult = {
|
|
|
10
10
|
export declare class ParsePool {
|
|
11
11
|
private pool;
|
|
12
12
|
private workerData;
|
|
13
|
+
private workerCount;
|
|
13
14
|
private operation;
|
|
14
15
|
poolsCreated: number;
|
|
15
16
|
private enqueue;
|
|
16
17
|
private ensure;
|
|
17
|
-
run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined,
|
|
18
|
+
run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined, requestedWorkers: number): Promise<FileResult[]>;
|
|
18
19
|
private dispatch;
|
|
19
20
|
private destroy;
|
|
20
21
|
close(): Promise<void>;
|
package/dist/esm/scan/pool.js
CHANGED
|
@@ -4,6 +4,9 @@ import { resolveWorkerFile } from '../lib/worker-file.js';
|
|
|
4
4
|
import { reviveError } from './worker-error.js';
|
|
5
5
|
// Tinypool is ESM-only; our floor (>=22.20) has native require(esm), so the tier-2 house deferral reaches it.
|
|
6
6
|
const _require = typeof require === 'undefined' ? createRequire(import.meta.url) : require;
|
|
7
|
+
// Candidate queue depth gives uneven files more than one scheduling turn per worker.
|
|
8
|
+
// The bounded parser comparison decides whether this value ships.
|
|
9
|
+
const BATCHES_PER_WORKER = 4;
|
|
7
10
|
// Created lazily and reused while dispatch context stays equivalent. A builder owns one instance
|
|
8
11
|
// for its lifetime instead of paying pool startup for unchanged reconciles.
|
|
9
12
|
export class ParsePool {
|
|
@@ -12,24 +15,26 @@ export class ParsePool {
|
|
|
12
15
|
this.operation = result.then(()=>undefined, ()=>undefined);
|
|
13
16
|
return result;
|
|
14
17
|
}
|
|
15
|
-
async ensure(workerData,
|
|
16
|
-
if (this.pool && !isDeepStrictEqual(this.workerData, workerData)) await this.destroy();
|
|
18
|
+
async ensure(workerData, workerCount) {
|
|
19
|
+
if (this.pool && (!isDeepStrictEqual(this.workerData, workerData) || this.workerCount !== workerCount)) await this.destroy();
|
|
17
20
|
if (!this.pool) {
|
|
18
21
|
const { Tinypool } = _require('tinypool');
|
|
19
22
|
this.pool = new Tinypool({
|
|
20
23
|
filename: resolveWorkerFile('parse'),
|
|
21
|
-
minThreads:
|
|
22
|
-
maxThreads:
|
|
24
|
+
minThreads: workerCount,
|
|
25
|
+
maxThreads: workerCount,
|
|
23
26
|
workerData
|
|
24
27
|
});
|
|
25
28
|
this.workerData = workerData;
|
|
29
|
+
this.workerCount = workerCount;
|
|
26
30
|
this.poolsCreated++;
|
|
27
31
|
}
|
|
28
32
|
return this.pool;
|
|
29
33
|
}
|
|
30
|
-
// Never the tree: a worker task carries
|
|
31
|
-
//
|
|
32
|
-
run(files, features, cfg, onParsed,
|
|
34
|
+
// Never the tree: a worker task carries a bounded FileStat batch and returns only what
|
|
35
|
+
// parseFile returns for each file, never the token tree.
|
|
36
|
+
run(files, features, cfg, onParsed, requestedWorkers) {
|
|
37
|
+
if (files.length === 0) return Promise.resolve([]);
|
|
33
38
|
// Snapshot at invocation so caller mutation cannot change queued work or its reuse baseline.
|
|
34
39
|
const workerData = {
|
|
35
40
|
cfg: structuredClone(cfg),
|
|
@@ -37,38 +42,66 @@ export class ParsePool {
|
|
|
37
42
|
...new Set(features.map((feature)=>feature.name))
|
|
38
43
|
].sort()
|
|
39
44
|
};
|
|
40
|
-
|
|
45
|
+
const workerCount = Math.max(1, Math.min(requestedWorkers, files.length));
|
|
46
|
+
return this.enqueue(()=>this.dispatch(files, workerData, onParsed, workerCount));
|
|
41
47
|
}
|
|
42
|
-
async dispatch(files, workerData, onParsed,
|
|
43
|
-
const pool = await this.ensure(workerData,
|
|
48
|
+
async dispatch(files, workerData, onParsed, workerCount) {
|
|
49
|
+
const pool = await this.ensure(workerData, workerCount);
|
|
50
|
+
const batches = batchFiles(files, Math.min(files.length, workerCount * BATCHES_PER_WORKER));
|
|
44
51
|
let done = 0;
|
|
45
|
-
const settled = await Promise.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
const settled = await Promise.all(batches.map(async (batch)=>{
|
|
53
|
+
let taskResults;
|
|
54
|
+
try {
|
|
55
|
+
taskResults = await pool.run(batch);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return {
|
|
58
|
+
results: [],
|
|
59
|
+
failures: [
|
|
60
|
+
err
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const results = [];
|
|
65
|
+
const failures = [];
|
|
66
|
+
for (const result of taskResults){
|
|
67
|
+
if (!result.ok) {
|
|
68
|
+
failures.push(reviveError(result.error));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
results.push({
|
|
72
|
+
doc: result.doc,
|
|
73
|
+
warnings: result.warnings,
|
|
74
|
+
parseMs: result.parseMs
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
onParsed === null || onParsed === void 0 ? void 0 : onParsed(++done);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
failures.push(err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
49
82
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
parseMs: result.parseMs
|
|
83
|
+
results,
|
|
84
|
+
failures
|
|
53
85
|
};
|
|
54
86
|
}));
|
|
55
87
|
const results = [];
|
|
56
88
|
const failures = [];
|
|
57
|
-
for (const
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
for (const batch of settled){
|
|
90
|
+
results.push(...batch.results);
|
|
91
|
+
failures.push(...batch.failures);
|
|
60
92
|
}
|
|
61
93
|
if (failures.length === 1) throw failures[0];
|
|
62
94
|
if (failures.length > 1) throw new AggregateError(failures, `${failures.length} parse tasks failed`);
|
|
63
|
-
//
|
|
95
|
+
// Promise.all and contiguous batches preserve the input order that first-seen columns use.
|
|
64
96
|
return results;
|
|
65
97
|
}
|
|
66
98
|
async destroy() {
|
|
67
99
|
if (!this.pool) return;
|
|
68
100
|
const pool = this.pool;
|
|
101
|
+
await pool.destroy();
|
|
69
102
|
this.pool = undefined;
|
|
70
103
|
this.workerData = undefined;
|
|
71
|
-
|
|
104
|
+
this.workerCount = undefined;
|
|
72
105
|
}
|
|
73
106
|
// A pool never created costs nothing to destroy. Queuing lets active dispatches drain first.
|
|
74
107
|
close() {
|
|
@@ -77,7 +110,31 @@ export class ParsePool {
|
|
|
77
110
|
constructor(){
|
|
78
111
|
this.operation = Promise.resolve();
|
|
79
112
|
// Tinypools this instance has constructed. Repeated equivalent dispatches leave this unchanged;
|
|
80
|
-
// a context change or close followed by run constructs another pool.
|
|
113
|
+
// a context/count change or close followed by run constructs another pool.
|
|
81
114
|
this.poolsCreated = 0;
|
|
82
115
|
}
|
|
83
116
|
}
|
|
117
|
+
function batchFiles(files, batchCount) {
|
|
118
|
+
const batches = [];
|
|
119
|
+
let start = 0;
|
|
120
|
+
let remainingBytes = files.reduce((sum, file)=>sum + file.size, 0);
|
|
121
|
+
for(let batchIndex = 0; batchIndex < batchCount; batchIndex++){
|
|
122
|
+
const remainingBatches = batchCount - batchIndex;
|
|
123
|
+
if (remainingBatches === 1) {
|
|
124
|
+
batches.push(files.slice(start));
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
const targetBytes = remainingBytes / remainingBatches;
|
|
128
|
+
const lastEnd = files.length - (remainingBatches - 1);
|
|
129
|
+
let end = start;
|
|
130
|
+
let bytes = 0;
|
|
131
|
+
while(end < lastEnd && (end === start || bytes < targetBytes)){
|
|
132
|
+
bytes += files[end].size;
|
|
133
|
+
end++;
|
|
134
|
+
}
|
|
135
|
+
batches.push(files.slice(start, end));
|
|
136
|
+
start = end;
|
|
137
|
+
remainingBytes -= bytes;
|
|
138
|
+
}
|
|
139
|
+
return batches;
|
|
140
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/pool.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { isDeepStrictEqual } from 'node:util';\n// Type-only: erased at build, but keeps depcheck's usage check satisfied for the tier-2\n// `_require` below (see coding-standards' deferral tiers).\nimport type * as TinypoolNS from 'tinypool';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport { resolveWorkerFile } from '../lib/worker-file.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport type { ParseTask, ParseTaskResult, ParseWorkerData } from '../workers/parse.ts';\nimport type { FileStat } from './list.ts';\nimport { reviveError } from './worker-error.ts';\n\n// Tinypool is ESM-only; our floor (>=22.20) has native require(esm), so the tier-2 house deferral reaches it.\nconst _require = typeof require === 'undefined' ? createRequire(import.meta.url) : require;\n\n// parseMs is worker-side only (see workers/parse.ts); absent on the serial path.\nexport type FileResult = { doc: ParsedDoc; warnings: string[]; parseMs?: number };\n\n// Created lazily and reused while dispatch context stays equivalent. A builder owns one instance\n// for its lifetime instead of paying pool startup for unchanged reconciles.\nexport class ParsePool {\n private pool: TinypoolNS.Tinypool | undefined;\n private workerData: ParseWorkerData | undefined;\n private operation: Promise<void> = Promise.resolve();\n // Tinypools this instance has constructed. Repeated equivalent dispatches leave this unchanged;\n // a context change or close followed by run constructs another pool.\n poolsCreated = 0;\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.operation.then(operation);\n this.operation = result.then(\n () => undefined,\n () => undefined\n );\n return result;\n }\n\n private async ensure(workerData: ParseWorkerData, maxWorkers: number): Promise<TinypoolNS.Tinypool> {\n if (this.pool && !isDeepStrictEqual(this.workerData, workerData)) await this.destroy();\n if (!this.pool) {\n const { Tinypool } = _require('tinypool') as typeof TinypoolNS;\n this.pool = new Tinypool({ filename: resolveWorkerFile('parse'), minThreads: maxWorkers, maxThreads: maxWorkers, workerData });\n this.workerData = workerData;\n this.poolsCreated++;\n }\n return this.pool;\n }\n\n // Never the tree: a worker task carries one FileStat and returns only what parseFile returns --\n // extracted text and feature values, never the token tree.\n run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined, maxWorkers: number): Promise<FileResult[]> {\n // Snapshot at invocation so caller mutation cannot change queued work or its reuse baseline.\n const workerData: ParseWorkerData = { cfg: structuredClone(cfg), featureNames: [...new Set(features.map((feature) => feature.name))].sort() };\n return this.enqueue(() => this.dispatch(files, workerData, onParsed, maxWorkers));\n }\n\n private async dispatch(files: FileStat[], workerData: ParseWorkerData, onParsed: ((done: number) => void) | undefined, maxWorkers: number): Promise<FileResult[]> {\n const pool = await this.ensure(workerData, maxWorkers);\n let done = 0;\n const settled = await Promise.allSettled(\n files.map(async (file): Promise<FileResult> => {\n const result = (await pool.run(file as ParseTask)) as ParseTaskResult;\n if (!result.ok) throw reviveError(result.error);\n onParsed?.(++done);\n return { doc: result.doc, warnings: result.warnings, parseMs: result.parseMs };\n })\n );\n const results: FileResult[] = [];\n const failures: unknown[] = [];\n for (const result of settled) {\n if (result.status === 'fulfilled') results.push(result.value);\n else failures.push(result.reason);\n }\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, `${failures.length} parse tasks failed`);\n // allSettled preserves input order, which downstream first-seen column order depends on.\n return results;\n }\n\n private async destroy(): Promise<void> {\n if (!this.pool) return;\n const pool = this.pool;\n this.pool = undefined;\n this.workerData = undefined;\n await pool.destroy();\n }\n\n // A pool never created costs nothing to destroy. Queuing lets active dispatches drain first.\n close(): Promise<void> {\n return this.enqueue(() => this.destroy());\n }\n}\n"],"names":["createRequire","isDeepStrictEqual","resolveWorkerFile","reviveError","_require","require","url","ParsePool","enqueue","operation","result","then","undefined","ensure","workerData","maxWorkers","pool","destroy","Tinypool","filename","minThreads","maxThreads","poolsCreated","run","files","features","cfg","onParsed","structuredClone","featureNames","Set","map","feature","name","sort","dispatch","done","settled","Promise","allSettled","file","ok","error","doc","warnings","parseMs","results","failures","status","push","value","reason","length","AggregateError","close","resolve"],"mappings":"AAAA,SAASA,aAAa,QAAQ,cAAc;AAC5C,SAASC,iBAAiB,QAAQ,YAAY;AAM9C,SAASC,iBAAiB,QAAQ,wBAAwB;AAI1D,SAASC,WAAW,QAAQ,oBAAoB;AAEhD,8GAA8G;AAC9G,MAAMC,WAAW,OAAOC,YAAY,cAAcL,cAAc,YAAYM,GAAG,IAAID;AAKnF,iGAAiG;AACjG,4EAA4E;AAC5E,OAAO,MAAME;IAQHC,QAAWC,SAA2B,EAAc;QAC1D,MAAMC,SAAS,IAAI,CAACD,SAAS,CAACE,IAAI,CAACF;QACnC,IAAI,CAACA,SAAS,GAAGC,OAAOC,IAAI,CAC1B,IAAMC,WACN,IAAMA;QAER,OAAOF;IACT;IAEA,MAAcG,OAAOC,UAA2B,EAAEC,UAAkB,EAAgC;QAClG,IAAI,IAAI,CAACC,IAAI,IAAI,CAACf,kBAAkB,IAAI,CAACa,UAAU,EAAEA,aAAa,MAAM,IAAI,CAACG,OAAO;QACpF,IAAI,CAAC,IAAI,CAACD,IAAI,EAAE;YACd,MAAM,EAAEE,QAAQ,EAAE,GAAGd,SAAS;YAC9B,IAAI,CAACY,IAAI,GAAG,IAAIE,SAAS;gBAAEC,UAAUjB,kBAAkB;gBAAUkB,YAAYL;gBAAYM,YAAYN;gBAAYD;YAAW;YAC5H,IAAI,CAACA,UAAU,GAAGA;YAClB,IAAI,CAACQ,YAAY;QACnB;QACA,OAAO,IAAI,CAACN,IAAI;IAClB;IAEA,gGAAgG;IAChG,2DAA2D;IAC3DO,IAAIC,KAAiB,EAAEC,QAAmB,EAAEC,GAAW,EAAEC,QAA8C,EAAEZ,UAAkB,EAAyB;QAClJ,6FAA6F;QAC7F,MAAMD,aAA8B;YAAEY,KAAKE,gBAAgBF;YAAMG,cAAc;mBAAI,IAAIC,IAAIL,SAASM,GAAG,CAAC,CAACC,UAAYA,QAAQC,IAAI;aAAG,CAACC,IAAI;QAAG;QAC5I,OAAO,IAAI,CAAC1B,OAAO,CAAC,IAAM,IAAI,CAAC2B,QAAQ,CAACX,OAAOV,YAAYa,UAAUZ;IACvE;IAEA,MAAcoB,SAASX,KAAiB,EAAEV,UAA2B,EAAEa,QAA8C,EAAEZ,UAAkB,EAAyB;QAChK,MAAMC,OAAO,MAAM,IAAI,CAACH,MAAM,CAACC,YAAYC;QAC3C,IAAIqB,OAAO;QACX,MAAMC,UAAU,MAAMC,QAAQC,UAAU,CACtCf,MAAMO,GAAG,CAAC,OAAOS;YACf,MAAM9B,SAAU,MAAMM,KAAKO,GAAG,CAACiB;YAC/B,IAAI,CAAC9B,OAAO+B,EAAE,EAAE,MAAMtC,YAAYO,OAAOgC,KAAK;YAC9Cf,qBAAAA,+BAAAA,SAAW,EAAES;YACb,OAAO;gBAAEO,KAAKjC,OAAOiC,GAAG;gBAAEC,UAAUlC,OAAOkC,QAAQ;gBAAEC,SAASnC,OAAOmC,OAAO;YAAC;QAC/E;QAEF,MAAMC,UAAwB,EAAE;QAChC,MAAMC,WAAsB,EAAE;QAC9B,KAAK,MAAMrC,UAAU2B,QAAS;YAC5B,IAAI3B,OAAOsC,MAAM,KAAK,aAAaF,QAAQG,IAAI,CAACvC,OAAOwC,KAAK;iBACvDH,SAASE,IAAI,CAACvC,OAAOyC,MAAM;QAClC;QACA,IAAIJ,SAASK,MAAM,KAAK,GAAG,MAAML,QAAQ,CAAC,EAAE;QAC5C,IAAIA,SAASK,MAAM,GAAG,GAAG,MAAM,IAAIC,eAAeN,UAAU,GAAGA,SAASK,MAAM,CAAC,mBAAmB,CAAC;QACnG,yFAAyF;QACzF,OAAON;IACT;IAEA,MAAc7B,UAAyB;QACrC,IAAI,CAAC,IAAI,CAACD,IAAI,EAAE;QAChB,MAAMA,OAAO,IAAI,CAACA,IAAI;QACtB,IAAI,CAACA,IAAI,GAAGJ;QACZ,IAAI,CAACE,UAAU,GAAGF;QAClB,MAAMI,KAAKC,OAAO;IACpB;IAEA,6FAA6F;IAC7FqC,QAAuB;QACrB,OAAO,IAAI,CAAC9C,OAAO,CAAC,IAAM,IAAI,CAACS,OAAO;IACxC;;aAnEQR,YAA2B6B,QAAQiB,OAAO;QAClD,gGAAgG;QAChG,qEAAqE;aACrEjC,eAAe;;AAiEjB"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan/pool.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { isDeepStrictEqual } from 'node:util';\n// Type-only: erased at build, but keeps depcheck's usage check satisfied for the tier-2\n// `_require` below (see coding-standards' deferral tiers).\nimport type * as TinypoolNS from 'tinypool';\nimport type { Config } from '../config/index.ts';\nimport type { Feature } from '../features/types.ts';\nimport { resolveWorkerFile } from '../lib/worker-file.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport type { ParseTaskResult, ParseWorkerData } from '../workers/parse.ts';\nimport type { FileStat } from './list.ts';\nimport { reviveError } from './worker-error.ts';\n\n// Tinypool is ESM-only; our floor (>=22.20) has native require(esm), so the tier-2 house deferral reaches it.\nconst _require = typeof require === 'undefined' ? createRequire(import.meta.url) : require;\n// Candidate queue depth gives uneven files more than one scheduling turn per worker.\n// The bounded parser comparison decides whether this value ships.\nconst BATCHES_PER_WORKER = 4;\n\n// parseMs is worker-side only (see workers/parse.ts); absent on the serial path.\nexport type FileResult = { doc: ParsedDoc; warnings: string[]; parseMs?: number };\n\n// Created lazily and reused while dispatch context stays equivalent. A builder owns one instance\n// for its lifetime instead of paying pool startup for unchanged reconciles.\nexport class ParsePool {\n private pool: TinypoolNS.Tinypool | undefined;\n private workerData: ParseWorkerData | undefined;\n private workerCount: number | undefined;\n private operation: Promise<void> = Promise.resolve();\n // Tinypools this instance has constructed. Repeated equivalent dispatches leave this unchanged;\n // a context/count change or close followed by run constructs another pool.\n poolsCreated = 0;\n\n private enqueue<T>(operation: () => Promise<T>): Promise<T> {\n const result = this.operation.then(operation);\n this.operation = result.then(\n () => undefined,\n () => undefined\n );\n return result;\n }\n\n private async ensure(workerData: ParseWorkerData, workerCount: number): Promise<TinypoolNS.Tinypool> {\n if (this.pool && (!isDeepStrictEqual(this.workerData, workerData) || this.workerCount !== workerCount)) await this.destroy();\n if (!this.pool) {\n const { Tinypool } = _require('tinypool') as typeof TinypoolNS;\n this.pool = new Tinypool({ filename: resolveWorkerFile('parse'), minThreads: workerCount, maxThreads: workerCount, workerData });\n this.workerData = workerData;\n this.workerCount = workerCount;\n this.poolsCreated++;\n }\n return this.pool;\n }\n\n // Never the tree: a worker task carries a bounded FileStat batch and returns only what\n // parseFile returns for each file, never the token tree.\n run(files: FileStat[], features: Feature[], cfg: Config, onParsed: ((done: number) => void) | undefined, requestedWorkers: number): Promise<FileResult[]> {\n if (files.length === 0) return Promise.resolve([]);\n // Snapshot at invocation so caller mutation cannot change queued work or its reuse baseline.\n const workerData: ParseWorkerData = { cfg: structuredClone(cfg), featureNames: [...new Set(features.map((feature) => feature.name))].sort() };\n const workerCount = Math.max(1, Math.min(requestedWorkers, files.length));\n return this.enqueue(() => this.dispatch(files, workerData, onParsed, workerCount));\n }\n\n private async dispatch(files: FileStat[], workerData: ParseWorkerData, onParsed: ((done: number) => void) | undefined, workerCount: number): Promise<FileResult[]> {\n const pool = await this.ensure(workerData, workerCount);\n const batches = batchFiles(files, Math.min(files.length, workerCount * BATCHES_PER_WORKER));\n let done = 0;\n const settled = await Promise.all(\n batches.map(async (batch): Promise<{ results: FileResult[]; failures: unknown[] }> => {\n let taskResults: ParseTaskResult;\n try {\n taskResults = (await pool.run(batch)) as ParseTaskResult;\n } catch (err) {\n return { results: [], failures: [err] };\n }\n\n const results: FileResult[] = [];\n const failures: unknown[] = [];\n for (const result of taskResults) {\n if (!result.ok) {\n failures.push(reviveError(result.error));\n continue;\n }\n results.push({ doc: result.doc, warnings: result.warnings, parseMs: result.parseMs });\n try {\n onParsed?.(++done);\n } catch (err) {\n failures.push(err);\n }\n }\n return { results, failures };\n })\n );\n const results: FileResult[] = [];\n const failures: unknown[] = [];\n for (const batch of settled) {\n results.push(...batch.results);\n failures.push(...batch.failures);\n }\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) throw new AggregateError(failures, `${failures.length} parse tasks failed`);\n // Promise.all and contiguous batches preserve the input order that first-seen columns use.\n return results;\n }\n\n private async destroy(): Promise<void> {\n if (!this.pool) return;\n const pool = this.pool;\n await pool.destroy();\n this.pool = undefined;\n this.workerData = undefined;\n this.workerCount = undefined;\n }\n\n // A pool never created costs nothing to destroy. Queuing lets active dispatches drain first.\n close(): Promise<void> {\n return this.enqueue(() => this.destroy());\n }\n}\n\nfunction batchFiles(files: FileStat[], batchCount: number): FileStat[][] {\n const batches: FileStat[][] = [];\n let start = 0;\n let remainingBytes = files.reduce((sum, file) => sum + file.size, 0);\n\n for (let batchIndex = 0; batchIndex < batchCount; batchIndex++) {\n const remainingBatches = batchCount - batchIndex;\n if (remainingBatches === 1) {\n batches.push(files.slice(start));\n break;\n }\n\n const targetBytes = remainingBytes / remainingBatches;\n const lastEnd = files.length - (remainingBatches - 1);\n let end = start;\n let bytes = 0;\n while (end < lastEnd && (end === start || bytes < targetBytes)) {\n bytes += files[end].size;\n end++;\n }\n batches.push(files.slice(start, end));\n start = end;\n remainingBytes -= bytes;\n }\n return batches;\n}\n"],"names":["createRequire","isDeepStrictEqual","resolveWorkerFile","reviveError","_require","require","url","BATCHES_PER_WORKER","ParsePool","enqueue","operation","result","then","undefined","ensure","workerData","workerCount","pool","destroy","Tinypool","filename","minThreads","maxThreads","poolsCreated","run","files","features","cfg","onParsed","requestedWorkers","length","Promise","resolve","structuredClone","featureNames","Set","map","feature","name","sort","Math","max","min","dispatch","batches","batchFiles","done","settled","all","batch","taskResults","err","results","failures","ok","push","error","doc","warnings","parseMs","AggregateError","close","batchCount","start","remainingBytes","reduce","sum","file","size","batchIndex","remainingBatches","slice","targetBytes","lastEnd","end","bytes"],"mappings":"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"}
|
|
@@ -14,5 +14,6 @@ export interface ReparseOptions {
|
|
|
14
14
|
maxWorkers?: number;
|
|
15
15
|
pool?: ParsePool;
|
|
16
16
|
}
|
|
17
|
+
export declare const PARSE_BYTES_PER_WORKER: number;
|
|
17
18
|
export declare function featuresForFile(features: Feature[], cfg: Config, file: FileStat): Feature[];
|
|
18
19
|
export declare function reparseFiles(files: FileStat[], features: Feature[], cfg: Config, knownColumns: ReadonlySet<string>, onParsed?: (done: number) => void, options?: ReparseOptions): Promise<ReparseResult>;
|