sensemaking 0.4.0 → 0.6.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.
Files changed (93) hide show
  1. package/README.md +16 -7
  2. package/dist/cjs/cli.js +5 -1
  3. package/dist/cjs/cli.js.map +1 -1
  4. package/dist/cjs/commands/find.js +173 -10
  5. package/dist/cjs/commands/find.js.map +1 -1
  6. package/dist/cjs/commands/map.js +1 -1
  7. package/dist/cjs/commands/map.js.map +1 -1
  8. package/dist/cjs/commands/peek.js +1 -1
  9. package/dist/cjs/commands/peek.js.map +1 -1
  10. package/dist/cjs/commands/shared.d.cts +1 -1
  11. package/dist/cjs/commands/shared.d.ts +1 -1
  12. package/dist/cjs/commands/shared.js +166 -8
  13. package/dist/cjs/commands/shared.js.map +1 -1
  14. package/dist/cjs/commands/status.js +10 -1
  15. package/dist/cjs/commands/status.js.map +1 -1
  16. package/dist/cjs/commands/types.d.cts +1 -0
  17. package/dist/cjs/commands/types.d.ts +1 -0
  18. package/dist/cjs/config.d.cts +26 -2
  19. package/dist/cjs/config.d.ts +26 -2
  20. package/dist/cjs/config.js +171 -10
  21. package/dist/cjs/config.js.map +1 -1
  22. package/dist/cjs/db.js +46 -16
  23. package/dist/cjs/db.js.map +1 -1
  24. package/dist/cjs/errors.d.cts +1 -1
  25. package/dist/cjs/errors.d.ts +1 -1
  26. package/dist/cjs/errors.js.map +1 -1
  27. package/dist/cjs/features/embed.d.cts +14 -0
  28. package/dist/cjs/features/embed.d.ts +14 -0
  29. package/dist/cjs/features/embed.js +840 -0
  30. package/dist/cjs/features/embed.js.map +1 -0
  31. package/dist/cjs/features/index.js +3 -1
  32. package/dist/cjs/features/index.js.map +1 -1
  33. package/dist/cjs/features/types.d.cts +4 -1
  34. package/dist/cjs/features/types.d.ts +4 -1
  35. package/dist/cjs/index.d.cts +3 -10
  36. package/dist/cjs/index.d.ts +3 -10
  37. package/dist/cjs/index.js +2 -52
  38. package/dist/cjs/index.js.map +1 -1
  39. package/dist/cjs/output.d.cts +5 -0
  40. package/dist/cjs/output.d.ts +5 -0
  41. package/dist/cjs/output.js +14 -1
  42. package/dist/cjs/output.js.map +1 -1
  43. package/dist/cjs/scan.js +9 -8
  44. package/dist/cjs/scan.js.map +1 -1
  45. package/dist/cjs/verbs.d.cts +8 -2
  46. package/dist/cjs/verbs.d.ts +8 -2
  47. package/dist/cjs/verbs.js +263 -76
  48. package/dist/cjs/verbs.js.map +1 -1
  49. package/dist/esm/cli.js +5 -1
  50. package/dist/esm/cli.js.map +1 -1
  51. package/dist/esm/commands/find.js +5 -4
  52. package/dist/esm/commands/find.js.map +1 -1
  53. package/dist/esm/commands/map.js +1 -3
  54. package/dist/esm/commands/map.js.map +1 -1
  55. package/dist/esm/commands/peek.js +1 -1
  56. package/dist/esm/commands/peek.js.map +1 -1
  57. package/dist/esm/commands/shared.d.ts +1 -1
  58. package/dist/esm/commands/shared.js +2 -2
  59. package/dist/esm/commands/shared.js.map +1 -1
  60. package/dist/esm/commands/status.js +8 -1
  61. package/dist/esm/commands/status.js.map +1 -1
  62. package/dist/esm/commands/types.d.ts +1 -0
  63. package/dist/esm/commands/types.js.map +1 -1
  64. package/dist/esm/config.d.ts +26 -2
  65. package/dist/esm/config.js +88 -10
  66. package/dist/esm/config.js.map +1 -1
  67. package/dist/esm/db.js +19 -10
  68. package/dist/esm/db.js.map +1 -1
  69. package/dist/esm/errors.d.ts +1 -1
  70. package/dist/esm/errors.js.map +1 -1
  71. package/dist/esm/features/embed.d.ts +14 -0
  72. package/dist/esm/features/embed.js +290 -0
  73. package/dist/esm/features/embed.js.map +1 -0
  74. package/dist/esm/features/index.js +3 -1
  75. package/dist/esm/features/index.js.map +1 -1
  76. package/dist/esm/features/types.d.ts +4 -1
  77. package/dist/esm/features/types.js.map +1 -1
  78. package/dist/esm/index.d.ts +3 -10
  79. package/dist/esm/index.js +4 -6
  80. package/dist/esm/index.js.map +1 -1
  81. package/dist/esm/output.d.ts +5 -0
  82. package/dist/esm/output.js +11 -1
  83. package/dist/esm/output.js.map +1 -1
  84. package/dist/esm/scan.js +9 -8
  85. package/dist/esm/scan.js.map +1 -1
  86. package/dist/esm/verbs.d.ts +8 -2
  87. package/dist/esm/verbs.js +51 -13
  88. package/dist/esm/verbs.js.map +1 -1
  89. package/package.json +2 -1
  90. package/schema.json +17 -1
  91. package/skills/sense/EXAMPLES.md +16 -0
  92. package/skills/sense/SKILL.md +41 -19
  93. package/skills/sense-setup/SKILL.md +73 -0
@@ -0,0 +1,290 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { embedConfig } from '../config.js';
5
+ import { SenseError } from '../errors.js';
6
+ import { parseFile } from '../scan.js';
7
+ // embeddings(path, chunk, start_line, end_line, scale, vector): heading-based chunks,
8
+ // int8 vectors with a per-vector dequantization scale, vector NULL = not yet embedded.
9
+ // Reconcile stores dirty rows inside its transaction; embedding tops up on the next
10
+ // semantic query (embedPending), so staleness costs recall, never correctness.
11
+ // Storage lever fixed by the bake-off (BENCHMARKING.md): int8 at 256 dims is
12
+ // quality-free vs f32-512 when fused. Queries stay f32 at the same dims.
13
+ const STORE_DIMS = 256;
14
+ const BATCH = 64;
15
+ // Deterministic chunker used at reconcile (line ranges stored) and at embed time (text
16
+ // re-derived from the file -- chunk text is never stored). Heading-delimited with the
17
+ // preamble kept; whole file when no headings. The title/summary prefix mirrors the
18
+ // bm25 column weighting.
19
+ function chunksOf(raw, search) {
20
+ const lines = raw.split('\n');
21
+ const starts = [];
22
+ let inFence = false;
23
+ for(let i = 0; i < lines.length; i++){
24
+ if (/^(```|~~~)/.test(lines[i])) {
25
+ inFence = !inFence;
26
+ continue;
27
+ }
28
+ if (!inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);
29
+ }
30
+ const bounds = starts.length === 0 ? [
31
+ 1
32
+ ] : starts[0] > 1 ? [
33
+ 1,
34
+ ...starts
35
+ ] : starts;
36
+ const prefix = [
37
+ search === null || search === void 0 ? void 0 : search.title,
38
+ search === null || search === void 0 ? void 0 : search.summary
39
+ ].filter(Boolean).join('\n');
40
+ const chunks = [];
41
+ bounds.forEach((start, i)=>{
42
+ const end = i + 1 < bounds.length ? bounds[i + 1] - 1 : lines.length;
43
+ const text = lines.slice(start - 1, end).join('\n').trim();
44
+ if (text.length > 0) chunks.push({
45
+ startLine: start,
46
+ endLine: end,
47
+ text: prefix ? `${prefix}\n${text}` : text
48
+ });
49
+ });
50
+ return chunks;
51
+ }
52
+ export const embed = {
53
+ name: 'embed',
54
+ schema (db) {
55
+ 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))`);
56
+ },
57
+ extract (raw, _body, search) {
58
+ return chunksOf(raw, search);
59
+ },
60
+ remove (db, path) {
61
+ db.prepare('DELETE FROM embeddings WHERE "path" = ?').run(path);
62
+ },
63
+ store (db, path, extracted) {
64
+ const insert = db.prepare('INSERT INTO embeddings ("path", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)');
65
+ extracted.forEach((c, idx)=>insert.run(path, idx, c.startLine, c.endLine));
66
+ }
67
+ };
68
+ // --- static type: Model2Vec safetensors + pure-JS tokenizer, model cached in ~/.cache ---
69
+ // Encode convention from model2vec/model.py: no special tokens, drop unk ids, mean-pool,
70
+ // L2-normalize.
71
+ function fetchToFile(url, dest) {
72
+ return fetch(url).then(async (res)=>{
73
+ if (!res.ok) throw new SenseError('EMBED_MODEL', `model download failed: ${url} -> HTTP ${res.status}`);
74
+ writeFileSync(`${dest}.part`, Buffer.from(await res.arrayBuffer()));
75
+ renameSync(`${dest}.part`, dest);
76
+ });
77
+ }
78
+ async function staticProvider(model) {
79
+ var _ref;
80
+ var _tokenizerJson_model, _tokenizerJson_model_vocab, _tokenizerJson_model1;
81
+ let dir = model;
82
+ if (!existsSync(join(dir, 'model.safetensors'))) {
83
+ dir = join(homedir(), '.cache', 'sensemaking', 'models', model.replace(/\//g, '--'));
84
+ mkdirSync(dir, {
85
+ recursive: true
86
+ });
87
+ for (const file of [
88
+ 'model.safetensors',
89
+ 'tokenizer.json'
90
+ ]){
91
+ if (!existsSync(join(dir, file))) {
92
+ console.error(`fetching ${model}/${file} into ${dir} (once; delete to refetch)`);
93
+ await fetchToFile(`https://huggingface.co/${model}/resolve/main/${file}`, join(dir, file));
94
+ }
95
+ }
96
+ }
97
+ const raw = readFileSync(join(dir, 'model.safetensors'));
98
+ const headerLen = Number(raw.readBigUInt64LE(0));
99
+ const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8'));
100
+ const entry = Object.entries(header).find(([k])=>k !== '__metadata__');
101
+ if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);
102
+ const spec = entry[1];
103
+ const dims = spec.shape[1];
104
+ const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];
105
+ const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));
106
+ const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));
107
+ // Lazy import: the tokenizer loads only on the semantic path, never at CLI startup.
108
+ const { Tokenizer } = await import('@huggingface/tokenizers');
109
+ const tok = new Tokenizer(tokenizerJson, {});
110
+ const unkId = (_ref = (_tokenizerJson_model1 = tokenizerJson.model) === null || _tokenizerJson_model1 === void 0 ? void 0 : (_tokenizerJson_model_vocab = _tokenizerJson_model1.vocab) === null || _tokenizerJson_model_vocab === void 0 ? void 0 : _tokenizerJson_model_vocab[(_tokenizerJson_model = tokenizerJson.model) === null || _tokenizerJson_model === void 0 ? void 0 : _tokenizerJson_model.unk_token]) !== null && _ref !== void 0 ? _ref : -1;
111
+ function one(text) {
112
+ const ids = tok.encode(text, {
113
+ add_special_tokens: false
114
+ }).ids.filter((id)=>id !== unkId);
115
+ const v = new Float32Array(dims);
116
+ if (ids.length === 0) return v;
117
+ for (const id of ids){
118
+ const off = id * dims;
119
+ for(let d = 0; d < dims; d++)v[d] += matrix[off + d];
120
+ }
121
+ let norm = 0;
122
+ for(let d = 0; d < dims; d++){
123
+ v[d] /= ids.length;
124
+ norm += v[d] * v[d];
125
+ }
126
+ norm = Math.sqrt(norm) + 1e-32;
127
+ for(let d = 0; d < dims; d++)v[d] /= norm;
128
+ return v;
129
+ }
130
+ return {
131
+ id: `static:${model}`,
132
+ dims,
133
+ embed: async (texts)=>texts.map(one)
134
+ };
135
+ }
136
+ // --- api type: one POST against any OpenAI-compatible /embeddings endpoint ---
137
+ async function apiProvider(model, url, keyEnv) {
138
+ if (!url) throw new SenseError('EMBED_MODEL', 'features.embed.type "api" requires a url');
139
+ const base = url.replace(/\/+$/, '');
140
+ const headers = {
141
+ 'content-type': 'application/json'
142
+ };
143
+ const key = keyEnv ? process.env[keyEnv] : undefined;
144
+ if (key) headers.authorization = `Bearer ${key}`;
145
+ async function post(texts) {
146
+ const res = await fetch(`${base}/embeddings`, {
147
+ method: 'POST',
148
+ headers,
149
+ body: JSON.stringify({
150
+ model,
151
+ input: texts
152
+ })
153
+ });
154
+ if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);
155
+ const body = await res.json();
156
+ return body.data.map((d)=>Float32Array.from(d.embedding));
157
+ }
158
+ const dims = (await post([
159
+ 'dimension probe'
160
+ ]))[0].length;
161
+ return {
162
+ id: `api:${base}:${model}`,
163
+ dims,
164
+ embed: post
165
+ };
166
+ }
167
+ const providers = new Map();
168
+ function getProvider(cfg) {
169
+ var _e_url;
170
+ const e = embedConfig(cfg);
171
+ if (!e) throw new SenseError('EMBED_DISABLED', 'semantic expansion needs features.embed in sense.config.json (e.g. "features": { "embed": true })');
172
+ const sig = `${e.type}:${e.model}:${(_e_url = e.url) !== null && _e_url !== void 0 ? _e_url : ''}`;
173
+ let p = providers.get(sig);
174
+ if (!p) {
175
+ p = e.type === 'api' ? apiProvider(e.model, e.url, e.key) : staticProvider(e.model);
176
+ providers.set(sig, p);
177
+ }
178
+ return p;
179
+ }
180
+ // Slice + re-normalize (Matryoshka); optionally round through int8 storage.
181
+ function toStore(full, dims, int8) {
182
+ const v = new Float32Array(dims);
183
+ let norm = 0;
184
+ for(let d = 0; d < dims; d++)norm += full[d] * full[d];
185
+ norm = Math.sqrt(norm) + 1e-32;
186
+ for(let d = 0; d < dims; d++)v[d] = full[d] / norm;
187
+ if (!int8) return {
188
+ v,
189
+ scale: 1
190
+ };
191
+ let max = 0;
192
+ for(let d = 0; d < dims; d++)max = Math.max(max, Math.abs(v[d]));
193
+ return {
194
+ v,
195
+ scale: max / 127 || 1
196
+ };
197
+ }
198
+ // Embed rows whose vector is NULL, re-deriving chunk text from the files through the
199
+ // same parse + chunker that stored the rows.
200
+ export async function embedPending(db, cfg, baseDir) {
201
+ const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table
202
+ const dirty = db.prepare('SELECT "path", chunk FROM embeddings WHERE vector IS NULL ORDER BY "path", chunk').all();
203
+ if (dirty.length === 0) return;
204
+ const storeDims = Math.min(STORE_DIMS, provider.dims);
205
+ const byPath = new Map();
206
+ for (const row of dirty){
207
+ var _byPath_get;
208
+ const list = (_byPath_get = byPath.get(row.path)) !== null && _byPath_get !== void 0 ? _byPath_get : [];
209
+ list.push(row.chunk);
210
+ byPath.set(row.path, list);
211
+ }
212
+ const jobs = [];
213
+ for (const [path, chunkIdxs] of byPath){
214
+ let chunks;
215
+ try {
216
+ chunks = parseFile({
217
+ relPath: path,
218
+ absPath: join(baseDir, path),
219
+ mtimeMs: 0,
220
+ size: 0
221
+ }, [
222
+ embed
223
+ ]).doc.extracted.embed;
224
+ } catch {
225
+ continue; // vanished since reconcile; the next reconcile removes its rows
226
+ }
227
+ for (const idx of chunkIdxs)if (chunks[idx]) jobs.push({
228
+ path,
229
+ chunk: idx,
230
+ text: chunks[idx].text
231
+ });
232
+ }
233
+ const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE "path" = ? AND chunk = ?');
234
+ for(let i = 0; i < jobs.length; i += BATCH){
235
+ const batch = jobs.slice(i, i + BATCH);
236
+ const vectors = await provider.embed(batch.map((j)=>j.text));
237
+ db.exec('BEGIN');
238
+ try {
239
+ batch.forEach((job, j)=>{
240
+ const { v, scale } = toStore(vectors[j], storeDims, true);
241
+ const q = new Int8Array(storeDims);
242
+ for(let d = 0; d < storeDims; d++)q[d] = Math.round(v[d] / scale);
243
+ update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);
244
+ });
245
+ db.exec('COMMIT');
246
+ } catch (err) {
247
+ db.exec('ROLLBACK');
248
+ throw err;
249
+ }
250
+ }
251
+ }
252
+ // Top candidates by cosine for a semantic find: best chunk per file, its line range
253
+ // riding along. FTS5 operators in the terms are lexical syntax, not meaning -- stripped
254
+ // before embedding.
255
+ export async function semanticCandidates(db, cfg, terms, fetch1) {
256
+ var _terms_match;
257
+ const baseDir = cfg.baseDir;
258
+ if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');
259
+ await embedPending(db, cfg, baseDir);
260
+ const provider = await getProvider(cfg);
261
+ const storeDims = Math.min(STORE_DIMS, provider.dims);
262
+ const text = ((_terms_match = terms.match(/[\p{L}\p{N}]+/gu)) !== null && _terms_match !== void 0 ? _terms_match : []).filter((t)=>![
263
+ 'AND',
264
+ 'OR',
265
+ 'NOT',
266
+ 'NEAR'
267
+ ].includes(t)).join(' ');
268
+ const { v: qv } = toStore((await provider.embed([
269
+ text
270
+ ]))[0], storeDims, false);
271
+ const rows = db.prepare('SELECT "path", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all();
272
+ const best = new Map();
273
+ for (const row of rows){
274
+ const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));
275
+ let dot = 0;
276
+ for(let d = 0; d < q.length; d++)dot += q[d] * qv[d];
277
+ const score = dot * row.scale;
278
+ const existing = best.get(row.path);
279
+ if (!existing || score > existing.score) best.set(row.path, {
280
+ score,
281
+ lines: `L${row.start_line}-${row.end_line}`
282
+ });
283
+ }
284
+ return [
285
+ ...best.entries()
286
+ ].sort((a, b)=>b[1].score - a[1].score).slice(0, fetch1).map(([path, b])=>({
287
+ path,
288
+ lines: b.lines
289
+ }));
290
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/embed.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from '../config.ts';\nimport { embedConfig } from '../config.ts';\nimport { SenseError } from '../errors.ts';\nimport { parseFile } from '../scan.ts';\nimport type { Feature } from './types.ts';\n\n// embeddings(path, chunk, start_line, end_line, scale, vector): heading-based chunks,\n// int8 vectors with a per-vector dequantization scale, vector NULL = not yet embedded.\n// Reconcile stores dirty rows inside its transaction; embedding tops up on the next\n// semantic query (embedPending), so staleness costs recall, never correctness.\n\n// Storage lever fixed by the bake-off (BENCHMARKING.md): int8 at 256 dims is\n// quality-free vs f32-512 when fused. Queries stay f32 at the same dims.\nconst STORE_DIMS = 256;\nconst BATCH = 64;\n\nexport interface EmbedProvider {\n id: string; // model identity; participates in the cache key, change -> re-embed\n dims: number;\n embed(texts: string[]): Promise<Float32Array[]>;\n}\n\ninterface Chunk {\n startLine: number;\n endLine: number;\n text: string;\n}\n\n// Deterministic chunker used at reconcile (line ranges stored) and at embed time (text\n// re-derived from the file -- chunk text is never stored). Heading-delimited with the\n// preamble kept; whole file when no headings. The title/summary prefix mirrors the\n// bm25 column weighting.\nfunction chunksOf(raw: string, search?: { title: string; summary: string }): Chunk[] {\n const lines = raw.split('\\n');\n const starts: number[] = [];\n let inFence = false;\n for (let i = 0; i < lines.length; i++) {\n if (/^(```|~~~)/.test(lines[i])) {\n inFence = !inFence;\n continue;\n }\n if (!inFence && /^#{1,6} +/.test(lines[i])) starts.push(i + 1);\n }\n const bounds = starts.length === 0 ? [1] : starts[0] > 1 ? [1, ...starts] : starts;\n const prefix = [search?.title, search?.summary].filter(Boolean).join('\\n');\n const chunks: Chunk[] = [];\n bounds.forEach((start, i) => {\n const end = i + 1 < bounds.length ? bounds[i + 1] - 1 : lines.length;\n const text = lines\n .slice(start - 1, end)\n .join('\\n')\n .trim();\n if (text.length > 0) chunks.push({ startLine: start, endLine: end, text: prefix ? `${prefix}\\n${text}` : text });\n });\n return chunks;\n}\n\nexport const embed: Feature = {\n name: 'embed',\n schema(db) {\n db.exec(`CREATE TABLE IF NOT EXISTS embeddings (\"path\" TEXT, chunk INTEGER, start_line INTEGER, end_line INTEGER, scale REAL, vector BLOB, PRIMARY KEY (\"path\", chunk))`);\n },\n extract(raw, _body, search) {\n return chunksOf(raw, search);\n },\n remove(db, path) {\n db.prepare('DELETE FROM embeddings WHERE \"path\" = ?').run(path);\n },\n store(db, path, extracted) {\n const insert = db.prepare('INSERT INTO embeddings (\"path\", chunk, start_line, end_line, scale, vector) VALUES (?, ?, ?, ?, NULL, NULL)');\n (extracted as Chunk[]).forEach((c, idx) => insert.run(path, idx, c.startLine, c.endLine));\n },\n};\n\n// --- static type: Model2Vec safetensors + pure-JS tokenizer, model cached in ~/.cache ---\n// Encode convention from model2vec/model.py: no special tokens, drop unk ids, mean-pool,\n// L2-normalize.\n\nfunction fetchToFile(url: string, dest: string): Promise<void> {\n return fetch(url).then(async (res) => {\n if (!res.ok) throw new SenseError('EMBED_MODEL', `model download failed: ${url} -> HTTP ${res.status}`);\n writeFileSync(`${dest}.part`, Buffer.from(await res.arrayBuffer()));\n renameSync(`${dest}.part`, dest);\n });\n}\n\nasync function staticProvider(model: string): Promise<EmbedProvider> {\n let dir = model;\n if (!existsSync(join(dir, 'model.safetensors'))) {\n dir = join(homedir(), '.cache', 'sensemaking', 'models', model.replace(/\\//g, '--'));\n mkdirSync(dir, { recursive: true });\n for (const file of ['model.safetensors', 'tokenizer.json']) {\n if (!existsSync(join(dir, file))) {\n console.error(`fetching ${model}/${file} into ${dir} (once; delete to refetch)`);\n await fetchToFile(`https://huggingface.co/${model}/resolve/main/${file}`, join(dir, file));\n }\n }\n }\n\n const raw = readFileSync(join(dir, 'model.safetensors'));\n const headerLen = Number(raw.readBigUInt64LE(0));\n const header = JSON.parse(raw.subarray(8, 8 + headerLen).toString('utf8')) as Record<string, { dtype: string; shape: number[]; data_offsets: number[] }>;\n const entry = Object.entries(header).find(([k]) => k !== '__metadata__');\n if (!entry || entry[1].dtype !== 'F32') throw new SenseError('EMBED_MODEL', `${model}: expected an F32 safetensors matrix`);\n const spec = entry[1];\n const dims = spec.shape[1];\n const dataStart = raw.byteOffset + 8 + headerLen + spec.data_offsets[0];\n const matrix = dataStart % 4 === 0 ? new Float32Array(raw.buffer, dataStart, spec.shape[0] * dims) : new Float32Array(raw.buffer.slice(dataStart, dataStart + spec.shape[0] * dims * 4));\n\n const tokenizerJson = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));\n // Lazy import: the tokenizer loads only on the semantic path, never at CLI startup.\n const { Tokenizer } = await import('@huggingface/tokenizers');\n const tok = new Tokenizer(tokenizerJson, {});\n const unkId = tokenizerJson.model?.vocab?.[tokenizerJson.model?.unk_token] ?? -1;\n\n function one(text: string): Float32Array {\n const ids = (tok.encode(text, { add_special_tokens: false }).ids as number[]).filter((id) => id !== unkId);\n const v = new Float32Array(dims);\n if (ids.length === 0) return v;\n for (const id of ids) {\n const off = id * dims;\n for (let d = 0; d < dims; d++) v[d] += matrix[off + d];\n }\n let norm = 0;\n for (let d = 0; d < dims; d++) {\n v[d] /= ids.length;\n norm += v[d] * v[d];\n }\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] /= norm;\n return v;\n }\n\n return { id: `static:${model}`, dims, embed: async (texts) => texts.map(one) };\n}\n\n// --- api type: one POST against any OpenAI-compatible /embeddings endpoint ---\n\nasync function apiProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n if (!url) throw new SenseError('EMBED_MODEL', 'features.embed.type \"api\" requires a url');\n const base = url.replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[]): Promise<Float32Array[]> {\n const res = await fetch(`${base}/embeddings`, { method: 'POST', headers, body: JSON.stringify({ model, input: texts }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/embeddings -> HTTP ${res.status}`);\n const body = (await res.json()) as { data: Array<{ embedding: number[] }> };\n return body.data.map((d) => Float32Array.from(d.embedding));\n }\n\n const dims = (await post(['dimension probe']))[0].length;\n return { id: `api:${base}:${model}`, dims, embed: post };\n}\n\nconst providers = new Map<string, Promise<EmbedProvider>>();\n\nfunction getProvider(cfg: Config): Promise<EmbedProvider> {\n const e = embedConfig(cfg);\n if (!e) throw new SenseError('EMBED_DISABLED', 'semantic expansion needs features.embed in sense.config.json (e.g. \"features\": { \"embed\": true })');\n const sig = `${e.type}:${e.model}:${e.url ?? ''}`;\n let p = providers.get(sig);\n if (!p) {\n p = e.type === 'api' ? apiProvider(e.model, e.url, e.key) : staticProvider(e.model);\n providers.set(sig, p);\n }\n return p;\n}\n\n// Slice + re-normalize (Matryoshka); optionally round through int8 storage.\nfunction toStore(full: Float32Array, dims: number, int8: boolean): { v: Float32Array; scale: number } {\n const v = new Float32Array(dims);\n let norm = 0;\n for (let d = 0; d < dims; d++) norm += full[d] * full[d];\n norm = Math.sqrt(norm) + 1e-32;\n for (let d = 0; d < dims; d++) v[d] = full[d] / norm;\n if (!int8) return { v, scale: 1 };\n let max = 0;\n for (let d = 0; d < dims; d++) max = Math.max(max, Math.abs(v[d]));\n return { v, scale: max / 127 || 1 };\n}\n\n// Embed rows whose vector is NULL, re-deriving chunk text from the files through the\n// same parse + chunker that stored the rows.\nexport async function embedPending(db: DatabaseSync, cfg: Config, baseDir: string): Promise<void> {\n const provider = await getProvider(cfg); // throws EMBED_DISABLED before touching the table\n const dirty = db.prepare('SELECT \"path\", chunk FROM embeddings WHERE vector IS NULL ORDER BY \"path\", chunk').all() as Array<{ path: string; chunk: number }>;\n if (dirty.length === 0) return;\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n\n const byPath = new Map<string, number[]>();\n for (const row of dirty) {\n const list = byPath.get(row.path) ?? [];\n list.push(row.chunk);\n byPath.set(row.path, list);\n }\n\n const jobs: Array<{ path: string; chunk: number; text: string }> = [];\n for (const [path, chunkIdxs] of byPath) {\n let chunks: Chunk[];\n try {\n chunks = parseFile({ relPath: path, absPath: join(baseDir, path), mtimeMs: 0, size: 0 }, [embed]).doc.extracted.embed as Chunk[];\n } catch {\n continue; // vanished since reconcile; the next reconcile removes its rows\n }\n for (const idx of chunkIdxs) if (chunks[idx]) jobs.push({ path, chunk: idx, text: chunks[idx].text });\n }\n\n const update = db.prepare('UPDATE embeddings SET scale = ?, vector = ? WHERE \"path\" = ? AND chunk = ?');\n for (let i = 0; i < jobs.length; i += BATCH) {\n const batch = jobs.slice(i, i + BATCH);\n const vectors = await provider.embed(batch.map((j) => j.text));\n db.exec('BEGIN');\n try {\n batch.forEach((job, j) => {\n const { v, scale } = toStore(vectors[j], storeDims, true);\n const q = new Int8Array(storeDims);\n for (let d = 0; d < storeDims; d++) q[d] = Math.round(v[d] / scale);\n update.run(scale, Buffer.from(q.buffer), job.path, job.chunk);\n });\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n }\n}\n\n// Top candidates by cosine for a semantic find: best chunk per file, its line range\n// riding along. FTS5 operators in the terms are lexical syntax, not meaning -- stripped\n// before embedding.\nexport async function semanticCandidates(db: DatabaseSync, cfg: Config, terms: string, fetch: number): Promise<Array<{ path: string; lines: string }>> {\n const baseDir = (cfg as Partial<ResolvedConfig>).baseDir;\n if (!baseDir) throw new SenseError('EMBED_MODEL', 'semantic expansion needs a config with baseDir (use loadConfig/open)');\n await embedPending(db, cfg, baseDir);\n\n const provider = await getProvider(cfg);\n const storeDims = Math.min(STORE_DIMS, provider.dims);\n const text = (terms.match(/[\\p{L}\\p{N}]+/gu) ?? []).filter((t) => !['AND', 'OR', 'NOT', 'NEAR'].includes(t)).join(' ');\n const { v: qv } = toStore((await provider.embed([text]))[0], storeDims, false);\n\n const rows = db.prepare('SELECT \"path\", start_line, end_line, scale, vector FROM embeddings WHERE vector IS NOT NULL').all() as Array<{\n path: string;\n start_line: number;\n end_line: number;\n scale: number;\n vector: Uint8Array;\n }>;\n\n const best = new Map<string, { score: number; lines: string }>();\n for (const row of rows) {\n const q = new Int8Array(row.vector.buffer, row.vector.byteOffset, Math.min(storeDims, row.vector.byteLength));\n let dot = 0;\n for (let d = 0; d < q.length; d++) dot += q[d] * qv[d];\n const score = dot * row.scale;\n const existing = best.get(row.path);\n if (!existing || score > existing.score) best.set(row.path, { score, lines: `L${row.start_line}-${row.end_line}` });\n }\n return [...best.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .slice(0, fetch)\n .map(([path, b]) => ({ path, lines: b.lines }));\n}\n"],"names":["existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","homedir","join","embedConfig","SenseError","parseFile","STORE_DIMS","BATCH","chunksOf","raw","search","lines","split","starts","inFence","i","length","test","push","bounds","prefix","title","summary","filter","Boolean","chunks","forEach","start","end","text","slice","trim","startLine","endLine","embed","name","schema","db","exec","extract","_body","remove","path","prepare","run","store","extracted","insert","c","idx","fetchToFile","url","dest","fetch","then","res","ok","status","Buffer","from","arrayBuffer","staticProvider","model","tokenizerJson","dir","replace","recursive","file","console","error","headerLen","Number","readBigUInt64LE","header","JSON","parse","subarray","toString","entry","Object","entries","find","k","dtype","spec","dims","shape","dataStart","byteOffset","data_offsets","matrix","Float32Array","buffer","Tokenizer","tok","unkId","vocab","unk_token","one","ids","encode","add_special_tokens","id","v","off","d","norm","Math","sqrt","texts","map","apiProvider","keyEnv","base","headers","key","process","env","undefined","authorization","post","method","body","stringify","input","json","data","embedding","providers","Map","getProvider","cfg","e","sig","type","p","get","set","toStore","full","int8","scale","max","abs","embedPending","baseDir","provider","dirty","all","storeDims","min","byPath","row","list","chunk","jobs","chunkIdxs","relPath","absPath","mtimeMs","size","doc","update","batch","vectors","j","job","q","Int8Array","round","err","semanticCandidates","terms","match","t","includes","qv","rows","best","vector","byteLength","dot","score","existing","start_line","end_line","sort","a","b"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { featureEnabled } from '../config.js';
2
+ import { embed } from './embed.js';
2
3
  import { links } from './links.js';
3
4
  import { rank } from './rank.js';
4
5
  import { sections } from './sections.js';
@@ -6,7 +7,8 @@ import { sections } from './sections.js';
6
7
  export const FEATURES = [
7
8
  links,
8
9
  sections,
9
- rank
10
+ rank,
11
+ embed
10
12
  ];
11
13
  export function activeFeatures(cfg) {
12
14
  return FEATURES.filter((feature)=>featureEnabled(cfg, feature.name));
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/index.ts"],"sourcesContent":["import type { Config } from '../config.ts';\nimport { featureEnabled } from '../config.ts';\nimport { links } from './links.ts';\nimport { rank } from './rank.ts';\nimport { sections } from './sections.ts';\nimport type { Feature } from './types.ts';\n\n// Registry order matters: rank reads the links table in afterReconcile.\nexport const FEATURES: Feature[] = [links, sections, rank];\n\nexport function activeFeatures(cfg: Config): Feature[] {\n return FEATURES.filter((feature) => featureEnabled(cfg, feature.name));\n}\n\nexport { linkEdges } from './links.ts';\nexport type { Section } from './sections.ts';\nexport type { Feature } from './types.ts';\n"],"names":["featureEnabled","links","rank","sections","FEATURES","activeFeatures","cfg","filter","feature","name","linkEdges"],"mappings":"AACA,SAASA,cAAc,QAAQ,eAAe;AAC9C,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,QAAQ,QAAQ,gBAAgB;AAGzC,wEAAwE;AACxE,OAAO,MAAMC,WAAsB;IAACH;IAAOE;IAAUD;CAAK,CAAC;AAE3D,OAAO,SAASG,eAAeC,GAAW;IACxC,OAAOF,SAASG,MAAM,CAAC,CAACC,UAAYR,eAAeM,KAAKE,QAAQC,IAAI;AACtE;AAEA,SAASC,SAAS,QAAQ,aAAa"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/index.ts"],"sourcesContent":["import type { Config } from '../config.ts';\nimport { featureEnabled } from '../config.ts';\nimport { embed } from './embed.ts';\nimport { links } from './links.ts';\nimport { rank } from './rank.ts';\nimport { sections } from './sections.ts';\nimport type { Feature } from './types.ts';\n\n// Registry order matters: rank reads the links table in afterReconcile.\nexport const FEATURES: Feature[] = [links, sections, rank, embed];\n\nexport function activeFeatures(cfg: Config): Feature[] {\n return FEATURES.filter((feature) => featureEnabled(cfg, feature.name));\n}\n\nexport { linkEdges } from './links.ts';\nexport type { Section } from './sections.ts';\nexport type { Feature } from './types.ts';\n"],"names":["featureEnabled","embed","links","rank","sections","FEATURES","activeFeatures","cfg","filter","feature","name","linkEdges"],"mappings":"AACA,SAASA,cAAc,QAAQ,eAAe;AAC9C,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,KAAK,QAAQ,aAAa;AACnC,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,QAAQ,QAAQ,gBAAgB;AAGzC,wEAAwE;AACxE,OAAO,MAAMC,WAAsB;IAACH;IAAOE;IAAUD;IAAMF;CAAM,CAAC;AAElE,OAAO,SAASK,eAAeC,GAAW;IACxC,OAAOF,SAASG,MAAM,CAAC,CAACC,UAAYT,eAAeO,KAAKE,QAAQC,IAAI;AACtE;AAEA,SAASC,SAAS,QAAQ,aAAa"}
@@ -4,7 +4,10 @@ import type { FileStat } from '../scan.js';
4
4
  export interface Feature {
5
5
  name: FeatureName;
6
6
  schema(db: DatabaseSync): void;
7
- extract?(raw: string, body: string): unknown;
7
+ extract?(raw: string, body: string, search?: {
8
+ title: string;
9
+ summary: string;
10
+ }): unknown;
8
11
  remove?(db: DatabaseSync, path: string): void;
9
12
  store?(db: DatabaseSync, path: string, extracted: unknown): void;
10
13
  afterReconcile?(db: DatabaseSync, files: FileStat[]): void;
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/types.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName } from '../config.ts';\nimport type { FileStat } from '../scan.ts';\n\n// A feature owns its schema, per-file extraction/storage, and any whole-tree pass.\n// Adding a feature = one module here + one entry in the registry (index.ts).\nexport interface Feature {\n name: FeatureName;\n schema(db: DatabaseSync): void;\n // Pure, per file: raw is the full file, body the prose after frontmatter.\n extract?(raw: string, body: string): unknown;\n remove?(db: DatabaseSync, path: string): void;\n store?(db: DatabaseSync, path: string, extracted: unknown): void;\n // After all rows are current, inside the reconcile transaction (resolution, rank).\n afterReconcile?(db: DatabaseSync, files: FileStat[]): void;\n}\n"],"names":[],"mappings":"AAIA,mFAAmF;AACnF,6EAA6E;AAC7E,WASC"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/features/types.ts"],"sourcesContent":["import type { DatabaseSync } from 'node:sqlite';\nimport type { FeatureName } from '../config.ts';\nimport type { FileStat } from '../scan.ts';\n\n// A feature owns its schema, per-file extraction/storage, and any whole-tree pass.\n// Adding a feature = one module here + one entry in the registry (index.ts).\nexport interface Feature {\n name: FeatureName;\n schema(db: DatabaseSync): void;\n // Pure, per file: raw is the full file, body the prose after frontmatter,\n // search the normalized title/summary strings.\n extract?(raw: string, body: string, search?: { title: string; summary: string }): unknown;\n remove?(db: DatabaseSync, path: string): void;\n store?(db: DatabaseSync, path: string, extracted: unknown): void;\n // After all rows are current, inside the reconcile transaction (resolution, rank).\n afterReconcile?(db: DatabaseSync, files: FileStat[]): void;\n}\n"],"names":[],"mappings":"AAIA,mFAAmF;AACnF,6EAA6E;AAC7E,WAUC"}
@@ -1,18 +1,11 @@
1
- export type { Config, FeatureName, ResolvedConfig } from './config.js';
2
- export { CONFIG_FILENAME, enabledFeatures, FEATURE_NAMES, featureEnabled, findConfigPath, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
1
+ export type { Config, EmbedConfig, FeatureName, ResolvedConfig } from './config.js';
2
+ export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
3
3
  export type { OpenResult } from './db.js';
4
- export { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.js';
4
+ export { open, rebuild } from './db.js';
5
5
  export type { SenseErrorCode } from './errors.js';
6
6
  export { SenseError } from './errors.js';
7
- export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
8
- export type { Section } from './features/sections.js';
9
- export type { Feature } from './features/types.js';
10
- export type { Edge } from './graph.js';
11
- export { pagerank, personalizedRank } from './graph.js';
12
7
  export type { Row } from './output.js';
13
8
  export { printRows } from './output.js';
14
- export type { FileStat, ParsedDoc } from './scan.js';
15
- export { listFiles, parseFile } from './scan.js';
16
9
  export type { FindOptions, Peek, TreeMap } from './verbs.js';
17
10
  export { find, mapTree, peek } from './verbs.js';
18
11
  export type { WatchEvent, WatchOptions } from './watch.js';
package/dist/esm/index.js CHANGED
@@ -1,10 +1,8 @@
1
- // Public library API.
2
- export { CONFIG_FILENAME, enabledFeatures, FEATURE_NAMES, featureEnabled, findConfigPath, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
3
- export { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.js';
1
+ // Public library API. Deliberately small: every export is a stability promise;
2
+ // internals (feature registry, graph, scan, meta) stay module-private.
3
+ export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
4
+ export { open, rebuild } from './db.js';
4
5
  export { SenseError } from './errors.js';
5
- export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
6
- export { pagerank, personalizedRank } from './graph.js';
7
6
  export { printRows } from './output.js';
8
- export { listFiles, parseFile } from './scan.js';
9
7
  export { find, mapTree, peek } from './verbs.js';
10
8
  export { runWatch } from './watch.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME, enabledFeatures, FEATURE_NAMES, featureEnabled, findConfigPath, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\nexport type { OpenResult } from './db.ts';\nexport { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.ts';\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\nexport { activeFeatures, FEATURES, linkEdges } from './features/index.ts';\nexport type { Section } from './features/sections.ts';\nexport type { Feature } from './features/types.ts';\n\nexport type { Edge } from './graph.ts';\nexport { pagerank, personalizedRank } from './graph.ts';\n\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { FileStat, ParsedDoc } from './scan.ts';\nexport { listFiles, parseFile } from './scan.ts';\n\nexport type { FindOptions, Peek, TreeMap } from './verbs.ts';\nexport { find, mapTree, peek } from './verbs.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","enabledFeatures","FEATURE_NAMES","featureEnabled","findConfigPath","initConfig","loadConfig","migrateConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","DB_FILENAME","docCount","getMeta","open","rebuild","reconcile","setMeta","SenseError","activeFeatures","FEATURES","linkEdges","pagerank","personalizedRank","printRows","listFiles","parseFile","find","mapTree","peek","runWatch"],"mappings":"AAAA,sBAAsB;AAGtB,SAASA,eAAe,EAAEC,eAAe,EAAEC,aAAa,EAAEC,cAAc,EAAEC,cAAc,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAG1L,SAASC,WAAW,EAAEC,QAAQ,EAAEC,OAAO,EAAEC,IAAI,EAAEC,OAAO,EAAEC,SAAS,EAAEC,OAAO,QAAQ,UAAU;AAE5F,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,cAAc,EAAEC,QAAQ,EAAEC,SAAS,QAAQ,sBAAsB;AAK1E,SAASC,QAAQ,EAAEC,gBAAgB,QAAQ,aAAa;AAGxD,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,SAAS,EAAEC,SAAS,QAAQ,YAAY;AAGjD,SAASC,IAAI,EAAEC,OAAO,EAAEC,IAAI,QAAQ,aAAa;AAGjD,SAASC,QAAQ,QAAQ,aAAa"}
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 { Config, EmbedConfig, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\nexport type { OpenResult } from './db.ts';\nexport { open, rebuild } from './db.ts';\n\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\n\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { FindOptions, Peek, TreeMap } from './verbs.ts';\nexport { find, mapTree, peek } from './verbs.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","open","rebuild","SenseError","printRows","find","mapTree","peek","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAG1H,SAASC,IAAI,EAAEC,OAAO,QAAQ,UAAU;AAGxC,SAASC,UAAU,QAAQ,cAAc;AAGzC,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,IAAI,EAAEC,OAAO,EAAEC,IAAI,QAAQ,aAAa;AAGjD,SAASC,QAAQ,QAAQ,aAAa"}
@@ -7,6 +7,10 @@ export declare function renderMap(result: {
7
7
  };
8
8
  fields: Row[];
9
9
  fieldsTotal: number;
10
+ features: {
11
+ on: string[];
12
+ off: string[];
13
+ };
10
14
  hubs: Row[];
11
15
  recent: Row[];
12
16
  }): string;
@@ -21,4 +25,5 @@ export declare function renderPeek(result: {
21
25
  outboundTotal: number;
22
26
  backlinksTotal: number;
23
27
  unresolvedTotal: number;
28
+ off: string[];
24
29
  }): string;
@@ -26,13 +26,19 @@ function render(rows, format) {
26
26
  ].join('\n');
27
27
  }
28
28
  // Text renderers for the layer verbs; cli.ts prints what these return.
29
+ function featuresLine(features) {
30
+ const off = features.off.length > 0 ? ` · off: ${features.off.map((name)=>`${name} (features.${name})`).join(', ')}` : '';
31
+ return `features: ${features.on.join(', ')}${off}`;
32
+ }
29
33
  export function renderMap(result) {
30
34
  const parts = [
31
- `docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)\n`,
35
+ `docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`,
36
+ `${featuresLine(result.features)}\n`,
32
37
  render(result.fields, 'table')
33
38
  ];
34
39
  if (result.fieldsTotal > result.fields.length) parts.push(`(+${result.fieldsTotal - result.fields.length} more fields)`);
35
40
  if (result.hubs.length > 0) parts.push('\nhubs (by link rank):', render(result.hubs, 'table'));
41
+ else if (result.features.off.includes('rank')) parts.push('\nhubs: off (features.rank)');
36
42
  parts.push('\nrecent:', render(result.recent, 'table'));
37
43
  return parts.join('\n');
38
44
  }
@@ -44,6 +50,10 @@ export function renderPeek(result) {
44
50
  if (result.sections.length > 0) {
45
51
  lines.push('', 'sections:');
46
52
  for (const s of result.sections)lines.push(` ${'#'.repeat(s.level)} ${s.heading} [L${s.start_line}-${s.end_line}, ~${s.tokens}t]`);
53
+ } else if (result.off.includes('sections')) lines.push('', 'sections: off (features.sections)');
54
+ if (result.off.includes('links')) {
55
+ lines.push('', 'links: off (features.links)');
56
+ return lines.join('\n');
47
57
  }
48
58
  const linkLine = (label, shown, total)=>{
49
59
  if (total > 0) lines.push(`${label} (${total}): ${shown.join(', ')}${total > shown.length ? `, +${total - shown.length} more` : ''}`);
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json\n\nexport type Row = Record<string, unknown>;\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\nexport function printRows(rows: Row[], format: 'table' | 'json'): void {\n const rendered = render(rows, format);\n console.log(rendered);\n if (rendered.length > OVERSIZE_BYTES) {\n console.warn(`warning: result is ${Math.round(rendered.length / 1000)} KB across ${rows.length} row(s); consider a LIMIT, fewer columns, or snippet() instead of whole values`);\n }\n}\n\nfunction render(rows: Row[], format: 'table' | 'json'): string {\n if (format === 'json') return JSON.stringify(rows, null, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const cells = rows.map((row) => columns.map((col) => cell(row[col])));\n const widths = columns.map((col, i) => Math.max(col.length, ...cells.map((row) => row[i].length)));\n\n const formatRow = (values: string[]) => values.map((value, i) => value.padEnd(widths[i])).join(' ');\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...cells.map(formatRow)].join('\\n');\n}\n\n// Text renderers for the layer verbs; cli.ts prints what these return.\n\nexport function renderMap(result: { docs: { count: number; bytes: number }; fields: Row[]; fieldsTotal: number; hubs: Row[]; recent: Row[] }): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)\\n`, render(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):', render(result.hubs, 'table'));\n parts.push('\\nrecent:', render(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: { path: string; tokens: number; frontmatter: Row; sections: Row[]; outbound: string[]; backlinks: string[]; unresolved: string[]; outboundTotal: number; backlinksTotal: number; unresolvedTotal: number }): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\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 }\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 return lines.join('\\n');\n}\n"],"names":["OVERSIZE_BYTES","cell","value","String","replace","printRows","rows","format","rendered","render","console","log","length","warn","Math","round","JSON","stringify","columns","Object","keys","cells","map","row","col","widths","i","max","formatRow","values","padEnd","join","w","repeat","renderMap","result","parts","docs","count","bytes","fields","fieldsTotal","push","hubs","recent","renderPeek","lines","path","tokens","key","entries","frontmatter","sections","s","level","heading","start_line","end_line","linkLine","label","shown","total","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json\n\nexport type Row = Record<string, unknown>;\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\nexport function printRows(rows: Row[], format: 'table' | 'json'): void {\n const rendered = render(rows, format);\n console.log(rendered);\n if (rendered.length > OVERSIZE_BYTES) {\n console.warn(`warning: result is ${Math.round(rendered.length / 1000)} KB across ${rows.length} row(s); consider a LIMIT, fewer columns, or snippet() instead of whole values`);\n }\n}\n\nfunction render(rows: Row[], format: 'table' | 'json'): string {\n if (format === 'json') return JSON.stringify(rows, null, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const cells = rows.map((row) => columns.map((col) => cell(row[col])));\n const widths = columns.map((col, i) => Math.max(col.length, ...cells.map((row) => row[i].length)));\n\n const formatRow = (values: string[]) => values.map((value, i) => value.padEnd(widths[i])).join(' ');\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...cells.map(formatRow)].join('\\n');\n}\n\n// Text renderers for the layer verbs; cli.ts prints what these return.\n\nfunction featuresLine(features: { on: string[]; off: string[] }): string {\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (features.${name})`).join(', ')}` : '';\n return `features: ${features.on.join(', ')}${off}`;\n}\n\nexport function renderMap(result: { docs: { count: number; bytes: number }; fields: Row[]; fieldsTotal: number; features: { on: string[]; off: string[] }; hubs: Row[]; recent: Row[] }): string {\n const parts = [`docs: ${result.docs.count} (${Math.round(result.docs.bytes / 1024)} KB)`, `${featuresLine(result.features)}\\n`, render(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):', render(result.hubs, 'table'));\n else if (result.features.off.includes('rank')) parts.push('\\nhubs: off (features.rank)');\n parts.push('\\nrecent:', render(result.recent, 'table'));\n return parts.join('\\n');\n}\n\nexport function renderPeek(result: { path: string; tokens: number; frontmatter: Row; sections: Row[]; outbound: string[]; backlinks: string[]; unresolved: string[]; outboundTotal: number; backlinksTotal: number; unresolvedTotal: number; off: string[] }): string {\n const lines = [`${result.path} (~${result.tokens} tokens)`];\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 } 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 return lines.join('\\n');\n }\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 return lines.join('\\n');\n}\n"],"names":["OVERSIZE_BYTES","cell","value","String","replace","printRows","rows","format","rendered","render","console","log","length","warn","Math","round","JSON","stringify","columns","Object","keys","cells","map","row","col","widths","i","max","formatRow","values","padEnd","join","w","repeat","featuresLine","features","off","name","on","renderMap","result","parts","docs","count","bytes","fields","fieldsTotal","push","hubs","includes","recent","renderPeek","lines","path","tokens","key","entries","frontmatter","sections","s","level","heading","start_line","end_line","linkLine","label","shown","total","outboundTotal","unresolvedTotal","backlinksTotal","outbound","unresolved","backlinks"],"mappings":"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"}
package/dist/esm/scan.js CHANGED
@@ -99,24 +99,25 @@ export function parseFile(file, extractors = []) {
99
99
  }
100
100
  mapped[key] = mapValue(data[key]);
101
101
  }
102
+ // title/summary are plain YAML strings -- whitespace-collapse only;
103
+ // the prose gets the full markdown strip.
104
+ const search = {
105
+ title: normalizeText(data.title),
106
+ summary: normalizeText(data.summary),
107
+ text: stripText(content)
108
+ };
102
109
  return {
103
110
  doc: {
104
111
  relPath: file.relPath,
105
112
  mtimeMs: file.mtimeMs,
106
113
  size: file.size,
107
114
  data: mapped,
108
- search: {
109
- // title/summary are plain YAML strings -- whitespace-collapse only;
110
- // the prose gets the full markdown strip.
111
- title: normalizeText(data.title),
112
- summary: normalizeText(data.summary),
113
- text: stripText(content)
114
- },
115
+ search,
115
116
  extracted: Object.fromEntries(extractors.filter((f)=>f.extract).map((f)=>{
116
117
  var _f_extract;
117
118
  return [
118
119
  f.name,
119
- (_f_extract = f.extract) === null || _f_extract === void 0 ? void 0 : _f_extract.call(f, raw, content)
120
+ (_f_extract = f.extract) === null || _f_extract === void 0 ? void 0 : _f_extract.call(f, raw, content, search)
120
121
  ];
121
122
  }))
122
123
  },