sensemaking 0.21.1 → 0.22.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 +1 -1
- package/dist/cjs/chunk/group.js +13 -9
- package/dist/cjs/chunk/group.js.map +1 -1
- package/dist/cjs/commands/search.js +1 -1
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/scan/pool.d.cts +1 -0
- package/dist/cjs/scan/pool.d.ts +1 -0
- package/dist/cjs/scan/pool.js +2 -1
- 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 +6 -3
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/store/builder.d.cts +2 -0
- package/dist/cjs/store/builder.d.ts +2 -0
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/index.d.cts +1 -5
- package/dist/cjs/store/index.d.ts +1 -5
- package/dist/cjs/store/index.js +9 -46
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/lock-wait.d.cts +2 -0
- package/dist/cjs/store/lock-wait.d.ts +2 -0
- package/dist/cjs/store/lock-wait.js +51 -0
- package/dist/cjs/store/lock-wait.js.map +1 -0
- package/dist/cjs/store/open.d.cts +2 -4
- package/dist/cjs/store/open.d.ts +2 -4
- package/dist/cjs/store/open.js +29 -66
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/reconcile.d.cts +2 -0
- package/dist/cjs/store/reconcile.d.ts +2 -0
- package/dist/cjs/store/reconcile.js +407 -255
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/sqlite/lexical.js +1 -1
- package/dist/cjs/store/sqlite/lexical.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js +0 -1
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/stages.d.cts +18 -0
- package/dist/cjs/store/stages.d.ts +18 -0
- package/dist/cjs/store/stages.js +372 -0
- package/dist/cjs/store/stages.js.map +1 -0
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/types.d.cts +1 -1
- package/dist/cjs/store/types.d.ts +1 -1
- package/dist/cjs/store/types.js +0 -1
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/text/segment.js +7 -5
- package/dist/cjs/text/segment.js.map +1 -1
- package/dist/cjs/watch.js +162 -107
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.d.cts +1 -0
- package/dist/cjs/workers/parse.d.ts +1 -0
- package/dist/cjs/workers/parse.js +4 -1
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/chunk/group.js +13 -9
- package/dist/esm/chunk/group.js.map +1 -1
- package/dist/esm/commands/search.js +1 -1
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/scan/pool.d.ts +1 -0
- package/dist/esm/scan/pool.js +2 -1
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/scan/reparse.d.ts +1 -0
- package/dist/esm/scan/reparse.js +5 -2
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/store/builder.d.ts +2 -0
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/index.d.ts +1 -5
- package/dist/esm/store/index.js +6 -28
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/lock-wait.d.ts +2 -0
- package/dist/esm/store/lock-wait.js +36 -0
- package/dist/esm/store/lock-wait.js.map +1 -0
- package/dist/esm/store/open.d.ts +2 -4
- package/dist/esm/store/open.js +17 -32
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/reconcile.d.ts +2 -0
- package/dist/esm/store/reconcile.js +72 -44
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/lexical.js +1 -1
- package/dist/esm/store/sqlite/lexical.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +0 -1
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/stages.d.ts +18 -0
- package/dist/esm/store/stages.js +63 -0
- package/dist/esm/store/stages.js.map +1 -0
- package/dist/esm/store/turso/store.js +1 -1
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/types.d.ts +1 -1
- package/dist/esm/store/types.js +0 -1
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/text/segment.js +7 -5
- package/dist/esm/text/segment.js.map +1 -1
- package/dist/esm/watch.js +63 -38
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.d.ts +1 -0
- package/dist/esm/workers/parse.js +4 -1
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +1 -1
- package/skills/sense/SKILL.md +1 -1
- package/skills/sense-setup/SKILL.md +1 -1
package/dist/esm/watch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, getMeta, openStoreForWatch, requireWatchConcurrency, setMeta } from './store/index.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\nexport type WatchEvent = { type: 'started'; baseDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. Throws WATCH_ACTIVE if another watcher's heartbeat is still fresh and --force wasn't given.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n requireWatchConcurrency(cfg);\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;\n const { store, builder, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStoreForWatch(cfg);\n const baseDir = cfg.baseDir;\n\n const existingHeartbeat = await getMeta(store, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n await builder.close();\n await store.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n onEvent({ type: 'started', baseDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: await docCount(store), warnings: initialWarnings });\n }\n\n let stopping = false;\n const touchHeartbeat = async () => {\n await setMeta(store, 'watch_heartbeat', new Date().toISOString());\n await setMeta(store, 'watch_pid', String(process.pid));\n };\n await touchHeartbeat();\n\n let debounceTimer: NodeJS.Timeout | null = null;\n // A reconcile that has already started owns the store, and on a bulk reparse a live worker\n // pool as well. Shutdown waits on this rather than closing the connection underneath it.\n let inFlight: Promise<void> | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n inFlight = (async () => {\n try {\n const { parsed, warnings } = await builder.build();\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n } finally {\n inFlight = null;\n }\n })();\n }, debounceMs);\n };\n\n // Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with\n // an unresolvable filename (null, which fs.watch delivers under load) reconciles: parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(baseDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(\n guardedTick(touchHeartbeat, () => stopping),\n heartbeatIntervalMs\n );\n\n return new Promise<void>((resolveShutdown) => {\n // SIGINT/SIGTERM and an aborted signal all run this same path exactly once; each is\n // unregistered here too so a second runWatch call in the same process starts clean.\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n opts.signal?.removeEventListener('abort', shutdown);\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await inFlight;\n await builder.close();\n await setMeta(store, 'watch_heartbeat', null);\n await setMeta(store, 'watch_pid', null);\n await store.close();\n resolveShutdown();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n if (opts.signal?.aborted) shutdown();\n else opts.signal?.addEventListener('abort', shutdown, { once: true });\n });\n}\n"],"names":["watch","fsWatch","STATE_DIR","SenseError","guardedTick","docCount","getMeta","openStoreForWatch","requireWatchConcurrency","setMeta","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","runWatch","cfg","opts","onEvent","debounceMs","heartbeatIntervalMs","store","builder","dbPath","warnings","initialWarnings","parsed","initialParsed","baseDir","existingHeartbeat","force","age","Date","now","parse","close","Math","round","type","length","total","stopping","touchHeartbeat","toISOString","String","process","pid","debounceTimer","inFlight","scheduleReconcile","clearTimeout","setTimeout","build","err","message","watcher","recursive","_event","filename","startsWith","heartbeatTimer","setInterval","Promise","resolveShutdown","shutdown","off","signal","removeEventListener","clearInterval","once","aborted","addEventListener"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { STATE_DIR } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { docCount, getMeta, openStore, setMeta } from './store/index.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\nexport type WatchEvent = { type: 'started'; baseDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n // Aborting runs the same shutdown path as SIGINT/SIGTERM.\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM/signal abort. duckdb/turso hand the cache file to\n// one process at a time, so every cycle opens, reconciles, writes the heartbeat/pid rows, and closes.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const heartbeatIntervalMs = opts.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;\n const baseDir = cfg.baseDir;\n\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg);\n const existingHeartbeat = await getMeta(initialStore, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n await initialStore.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n // total is read before close: an event fires only once nothing is held open again.\n const initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n await setMeta(initialStore, 'watch_heartbeat', new Date().toISOString());\n await setMeta(initialStore, 'watch_pid', String(process.pid));\n await initialStore.close();\n\n onEvent({ type: 'started', baseDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n }\n\n let stopping = false;\n // At most one open+reconcile+close cycle runs at a time; a trigger that arrives mid-cycle is\n // coalesced into a single rerun instead of opening a second, overlapping connection.\n let running: Promise<void> | null = null;\n let queuedAlways: boolean | null = null;\n\n const tick = async (alwaysEmit: boolean): Promise<void> => {\n try {\n const { store, parsed, warnings } = await openStore(cfg);\n try {\n await setMeta(store, 'watch_heartbeat', new Date().toISOString());\n await setMeta(store, 'watch_pid', String(process.pid));\n if (alwaysEmit || parsed > 0 || warnings.length > 0) {\n onEvent({ type: 'reconciled', parsed, total: await docCount(store), warnings });\n }\n } finally {\n await store.close();\n }\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n };\n\n const startCycle = (alwaysEmit: boolean) => {\n running = tick(alwaysEmit).then(() => {\n running = null;\n if (queuedAlways !== null && !stopping) {\n const again = queuedAlways;\n queuedAlways = null;\n startCycle(again);\n }\n });\n };\n\n const requestCycle = (alwaysEmit: boolean) => {\n if (stopping) return;\n if (running) {\n queuedAlways = queuedAlways || alwaysEmit;\n return;\n }\n startCycle(alwaysEmit);\n };\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n\n // Ignore our own state dir, or the heartbeat write would retrigger itself forever. An event with\n // an unresolvable filename (null, which fs.watch delivers under load) reconciles: parsing nothing costs less than missing a real edit.\n const watcher = fsWatch(baseDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n heartbeatIntervalMs\n );\n\n return new Promise<void>((resolveShutdown) => {\n // SIGINT/SIGTERM and an aborted signal all run this same path exactly once; each is\n // unregistered here too so a second runWatch call in the same process starts clean.\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n opts.signal?.removeEventListener('abort', shutdown);\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n await running;\n const { store: finalStore } = await openStore(cfg);\n await setMeta(finalStore, 'watch_heartbeat', null);\n await setMeta(finalStore, 'watch_pid', null);\n await finalStore.close();\n resolveShutdown();\n };\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n if (opts.signal?.aborted) shutdown();\n else opts.signal?.addEventListener('abort', shutdown, { once: true });\n });\n}\n"],"names":["watch","fsWatch","STATE_DIR","SenseError","guardedTick","docCount","getMeta","openStore","setMeta","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","runWatch","cfg","opts","onEvent","debounceMs","heartbeatIntervalMs","baseDir","store","initialStore","dbPath","warnings","initialWarnings","parsed","initialParsed","existingHeartbeat","force","age","Date","now","parse","close","Math","round","initialTotal","length","toISOString","String","process","pid","type","total","stopping","running","queuedAlways","tick","alwaysEmit","err","message","startCycle","then","again","requestCycle","debounceTimer","scheduleReconcile","clearTimeout","setTimeout","watcher","recursive","_event","filename","startsWith","heartbeatTimer","setInterval","Promise","resolveShutdown","shutdown","off","signal","removeEventListener","clearInterval","finalStore","once","aborted","addEventListener"],"mappings":"AAAA,SAASA,SAASC,OAAO,QAAQ,UAAU;AAE3C,SAASC,SAAS,QAAQ,oBAAoB;AAC9C,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,WAAW,QAAQ,wBAAwB;AACpD,SAASC,QAAQ,EAAEC,OAAO,EAAEC,SAAS,EAAEC,OAAO,QAAQ,mBAAmB;AAEzE,uJAAuJ;AACvJ,MAAMC,cAAc;AACpB,MAAMC,wBAAwB;AAC9B,MAAMC,qBAAqB;AAa3B,gGAAgG;AAChG,sGAAsG;AACtG,OAAO,eAAeC,SAASC,GAAmB,EAAEC,OAAqB,CAAC,CAAC;QACzDA,eACGA,kBACSA;IAF5B,MAAMC,WAAUD,gBAAAA,KAAKC,OAAO,cAAZD,2BAAAA,gBAAiB,KAAO;IACxC,MAAME,cAAaF,mBAAAA,KAAKE,UAAU,cAAfF,8BAAAA,mBAAmBL;IACtC,MAAMQ,uBAAsBH,4BAAAA,KAAKG,mBAAmB,cAAxBH,uCAAAA,4BAA4BJ;IACxD,MAAMQ,UAAUL,IAAIK,OAAO;IAE3B,MAAM,EAAEC,OAAOC,YAAY,EAAEC,MAAM,EAAEC,UAAUC,eAAe,EAAEC,QAAQC,aAAa,EAAE,GAAG,MAAMlB,UAAUM;IAC1G,MAAMa,oBAAoB,MAAMpB,QAAQc,cAAc;IACtD,IAAIM,qBAAqB,CAACZ,KAAKa,KAAK,EAAE;QACpC,MAAMC,MAAMC,KAAKC,GAAG,KAAKD,KAAKE,KAAK,CAACL;QACpC,IAAIE,OAAO,KAAKA,MAAMjB,oBAAoB;YACxC,MAAMS,aAAaY,KAAK;YACxB,MAAM,IAAI7B,WAAW,gBAAgB,CAAC,0CAA0C,EAAE8B,KAAKC,KAAK,CAACN,MAAM,MAAM,+BAA+B,CAAC;QAC3I;IACF;IAEA,mFAAmF;IACnF,MAAMO,eAAeZ,gBAAgBa,MAAM,GAAG,KAAKX,gBAAgB,IAAI,MAAMpB,SAASe,gBAAgB;IACtG,MAAMZ,QAAQY,cAAc,mBAAmB,IAAIS,OAAOQ,WAAW;IACrE,MAAM7B,QAAQY,cAAc,aAAakB,OAAOC,QAAQC,GAAG;IAC3D,MAAMpB,aAAaY,KAAK;IAExBjB,QAAQ;QAAE0B,MAAM;QAAWvB;QAASG;IAAO;IAC3C,IAAIE,gBAAgBa,MAAM,GAAG,KAAKX,gBAAgB,GAAG;QACnDV,QAAQ;YAAE0B,MAAM;YAAcjB,QAAQC;YAAeiB,OAAOP;YAAcb,UAAUC;QAAgB;IACtG;IAEA,IAAIoB,WAAW;IACf,6FAA6F;IAC7F,qFAAqF;IACrF,IAAIC,UAAgC;IACpC,IAAIC,eAA+B;IAEnC,MAAMC,OAAO,OAAOC;QAClB,IAAI;YACF,MAAM,EAAE5B,KAAK,EAAEK,MAAM,EAAEF,QAAQ,EAAE,GAAG,MAAMf,UAAUM;YACpD,IAAI;gBACF,MAAML,QAAQW,OAAO,mBAAmB,IAAIU,OAAOQ,WAAW;gBAC9D,MAAM7B,QAAQW,OAAO,aAAamB,OAAOC,QAAQC,GAAG;gBACpD,IAAIO,cAAcvB,SAAS,KAAKF,SAASc,MAAM,GAAG,GAAG;oBACnDrB,QAAQ;wBAAE0B,MAAM;wBAAcjB;wBAAQkB,OAAO,MAAMrC,SAASc;wBAAQG;oBAAS;gBAC/E;YACF,SAAU;gBACR,MAAMH,MAAMa,KAAK;YACnB;QACF,EAAE,OAAOgB,KAAK;YACZjC,QAAQ;gBAAE0B,MAAM;gBAAmBQ,SAAS,AAACD,IAAcC,OAAO;YAAC;QACrE;IACF;IAEA,MAAMC,aAAa,CAACH;QAClBH,UAAUE,KAAKC,YAAYI,IAAI,CAAC;YAC9BP,UAAU;YACV,IAAIC,iBAAiB,QAAQ,CAACF,UAAU;gBACtC,MAAMS,QAAQP;gBACdA,eAAe;gBACfK,WAAWE;YACb;QACF;IACF;IAEA,MAAMC,eAAe,CAACN;QACpB,IAAIJ,UAAU;QACd,IAAIC,SAAS;YACXC,eAAeA,gBAAgBE;YAC/B;QACF;QACAG,WAAWH;IACb;IAEA,IAAIO,gBAAuC;IAC3C,MAAMC,oBAAoB;QACxB,IAAID,eAAeE,aAAaF;QAChCA,gBAAgBG,WAAW;YACzBH,gBAAgB;YAChBD,aAAa;QACf,GAAGrC;IACL;IAEA,iGAAiG;IACjG,uIAAuI;IACvI,MAAM0C,UAAUzD,QAAQiB,SAAS;QAAEyC,WAAW;IAAK,GAAG,CAACC,QAAQC;QAC7D,IAAI,OAAOA,aAAa,YAAYA,SAASC,UAAU,CAAC5D,YAAY;QACpEqD;IACF;IACA,MAAMQ,iBAAiBC,YACrB5D,YACE,UAAYiD,aAAa,QACzB,IAAMV,WAER1B;IAGF,OAAO,IAAIgD,QAAc,CAACC;YAqBpBpD,cACCA;QArBL,oFAAoF;QACpF,oFAAoF;QACpF,MAAMqD,WAAW;gBAKfrD;YAJA,IAAI6B,UAAU;YACdA,WAAW;YACXJ,QAAQ6B,GAAG,CAAC,UAAUD;YACtB5B,QAAQ6B,GAAG,CAAC,WAAWD;aACvBrD,eAAAA,KAAKuD,MAAM,cAAXvD,mCAAAA,aAAawD,mBAAmB,CAAC,SAASH;YAC1CI,cAAcR;YACd,IAAIT,eAAeE,aAAaF;YAChCI,QAAQ1B,KAAK;YACb,MAAMY;YACN,MAAM,EAAEzB,OAAOqD,UAAU,EAAE,GAAG,MAAMjE,UAAUM;YAC9C,MAAML,QAAQgE,YAAY,mBAAmB;YAC7C,MAAMhE,QAAQgE,YAAY,aAAa;YACvC,MAAMA,WAAWxC,KAAK;YACtBkC;QACF;QACA3B,QAAQkC,IAAI,CAAC,UAAUN;QACvB5B,QAAQkC,IAAI,CAAC,WAAWN;QACxB,KAAIrD,eAAAA,KAAKuD,MAAM,cAAXvD,mCAAAA,aAAa4D,OAAO,EAAEP;cACrBrD,gBAAAA,KAAKuD,MAAM,cAAXvD,oCAAAA,cAAa6D,gBAAgB,CAAC,SAASR,UAAU;YAAEM,MAAM;QAAK;IACrE;AACF"}
|
|
@@ -10,11 +10,14 @@ const { cfg, featureNames } = Tinypool.workerData;
|
|
|
10
10
|
const selected = FEATURES.filter((feature)=>featureNames.includes(feature.name));
|
|
11
11
|
export default function parseTask(file) {
|
|
12
12
|
try {
|
|
13
|
+
const start = process.hrtime.bigint();
|
|
13
14
|
const { doc, warnings } = parseFile(file, featuresForFile(selected, cfg, file), cfg);
|
|
15
|
+
const parseMs = Number(process.hrtime.bigint() - start) / 1e6;
|
|
14
16
|
return {
|
|
15
17
|
ok: true,
|
|
16
18
|
doc,
|
|
17
|
-
warnings
|
|
19
|
+
warnings,
|
|
20
|
+
parseMs
|
|
18
21
|
};
|
|
19
22
|
} catch (err) {
|
|
20
23
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/workers/parse.ts"],"sourcesContent":["import Tinypool from 'tinypool';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport { FEATURES } from '../features/index.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { FileStat } from '../scan/list.ts';\nimport { featuresForFile } from '../scan/reparse.ts';\nimport type { WorkerErrorPayload } from '../scan/worker-error.ts';\nimport { serializeError } from '../scan/worker-error.ts';\n\n// Constant for the whole dispatch, so it crosses once per worker instead of once per task. A\n// Feature carries closures and cannot cross the thread boundary; its name can, and the registry here resolves it back.\nexport interface ParseWorkerData {\n cfg: Config;\n featureNames: FeatureName[];\n}\n\n// tinypool's task, in and out. The task itself is one FileStat. Result carries only what\n// parseFile already returns -- extracted text and per-feature values, never the mdast tree.\nexport type ParseTask = FileStat;\n\nexport type ParseTaskResult = { ok: true; doc: ParsedDoc; warnings: string[] } | { ok: false; error: WorkerErrorPayload };\n\n// Read once per worker, not per task.\nconst { cfg, featureNames } = Tinypool.workerData as ParseWorkerData;\n// Filtering the registry (rather than mapping the names) keeps registry order, which is the\n// order `extracted` keys land in on the serial path.\nconst selected = FEATURES.filter((feature) => featureNames.includes(feature.name));\n\nexport default function parseTask(file: ParseTask): ParseTaskResult {\n try {\n const { doc, warnings } = parseFile(file, featuresForFile(selected, cfg, file), cfg);\n return { ok: true, doc, warnings };\n } catch (err) {\n return { ok: false, error: serializeError(err) };\n }\n}\n"],"names":["Tinypool","FEATURES","parseFile","featuresForFile","serializeError","cfg","featureNames","workerData","selected","filter","feature","includes","name","parseTask","file","doc","warnings","ok","err","error"],"mappings":"AAAA,OAAOA,cAAc,WAAW;AAEhC,SAASC,QAAQ,QAAQ,uBAAuB;AAEhD,SAASC,SAAS,QAAQ,mBAAmB;AAE7C,SAASC,eAAe,QAAQ,qBAAqB;AAErD,SAASC,cAAc,QAAQ,0BAA0B;
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/workers/parse.ts"],"sourcesContent":["import Tinypool from 'tinypool';\nimport type { Config, FeatureName } from '../config/index.ts';\nimport { FEATURES } from '../features/index.ts';\nimport type { ParsedDoc } from '../scan/index.ts';\nimport { parseFile } from '../scan/index.ts';\nimport type { FileStat } from '../scan/list.ts';\nimport { featuresForFile } from '../scan/reparse.ts';\nimport type { WorkerErrorPayload } from '../scan/worker-error.ts';\nimport { serializeError } from '../scan/worker-error.ts';\n\n// Constant for the whole dispatch, so it crosses once per worker instead of once per task. A\n// Feature carries closures and cannot cross the thread boundary; its name can, and the registry here resolves it back.\nexport interface ParseWorkerData {\n cfg: Config;\n featureNames: FeatureName[];\n}\n\n// tinypool's task, in and out. The task itself is one FileStat. Result carries only what\n// parseFile already returns -- extracted text and per-feature values, never the mdast tree.\nexport type ParseTask = FileStat;\n\n// parseMs is the worker's own hrtime for parseFile, excluding dispatch and the return clone --\n// what separates it from the pool's dispatch-to-drain `parse` stage.\nexport type ParseTaskResult = { ok: true; doc: ParsedDoc; warnings: string[]; parseMs: number } | { ok: false; error: WorkerErrorPayload };\n\n// Read once per worker, not per task.\nconst { cfg, featureNames } = Tinypool.workerData as ParseWorkerData;\n// Filtering the registry (rather than mapping the names) keeps registry order, which is the\n// order `extracted` keys land in on the serial path.\nconst selected = FEATURES.filter((feature) => featureNames.includes(feature.name));\n\nexport default function parseTask(file: ParseTask): ParseTaskResult {\n try {\n const start = process.hrtime.bigint();\n const { doc, warnings } = parseFile(file, featuresForFile(selected, cfg, file), cfg);\n const parseMs = Number(process.hrtime.bigint() - start) / 1e6;\n return { ok: true, doc, warnings, parseMs };\n } catch (err) {\n return { ok: false, error: serializeError(err) };\n }\n}\n"],"names":["Tinypool","FEATURES","parseFile","featuresForFile","serializeError","cfg","featureNames","workerData","selected","filter","feature","includes","name","parseTask","file","start","process","hrtime","bigint","doc","warnings","parseMs","Number","ok","err","error"],"mappings":"AAAA,OAAOA,cAAc,WAAW;AAEhC,SAASC,QAAQ,QAAQ,uBAAuB;AAEhD,SAASC,SAAS,QAAQ,mBAAmB;AAE7C,SAASC,eAAe,QAAQ,qBAAqB;AAErD,SAASC,cAAc,QAAQ,0BAA0B;AAiBzD,sCAAsC;AACtC,MAAM,EAAEC,GAAG,EAAEC,YAAY,EAAE,GAAGN,SAASO,UAAU;AACjD,4FAA4F;AAC5F,qDAAqD;AACrD,MAAMC,WAAWP,SAASQ,MAAM,CAAC,CAACC,UAAYJ,aAAaK,QAAQ,CAACD,QAAQE,IAAI;AAEhF,eAAe,SAASC,UAAUC,IAAe;IAC/C,IAAI;QACF,MAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;QACnC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,EAAE,GAAGlB,UAAUY,MAAMX,gBAAgBK,UAAUH,KAAKS,OAAOT;QAChF,MAAMgB,UAAUC,OAAON,QAAQC,MAAM,CAACC,MAAM,KAAKH,SAAS;QAC1D,OAAO;YAAEQ,IAAI;YAAMJ;YAAKC;YAAUC;QAAQ;IAC5C,EAAE,OAAOG,KAAK;QACZ,OAAO;YAAED,IAAI;YAAOE,OAAOrB,eAAeoB;QAAK;IACjD;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sensemaking",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Query and search your markdown notes with context-aware progressive disclosure: SQL over frontmatter, links, and text, plus semantic search and link-graph ranking. No server, no build step",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
package/skills/sense/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: "Query a markdown tree with the sense CLI: filter notes by frontmat
|
|
|
7
7
|
|
|
8
8
|
SQL over a markdown tree, kept fresh by a filesystem check on every query. Every file becomes rows in `frontmatter` (one column per key, plus `path`/`_mtime`/`_ctime`/`_size`/`_rank`/`_parse_error`; `_ctime` is filesystem birthtime, which a clone or copy resets just like `_mtime`), `content` (`title`, `summary`, `text`, `path`; an FTS5 index on the default store, with machine-written `title_seg`/`summary_seg`/`text_seg` sidecars used for matching Chinese, Japanese, Thai, Khmer, Lao, and Burmese text, not for reading), `links` (`src`, `target`, `dst`, `embed`; `NULL` dst = dead link; `embed` 1 for `![[...]]` embeds, 0 for links; one row per distinct written target and kind with alias and anchor stripped, so `[[Foo]]` and `[[Foo|alias]]` are one row while `[[Foo]]` and `[[notes/Foo]]` are two rows that can share a `dst`, and a target both linked and embedded is a row of each kind; extraction matches Obsidian's own graph: comments, code, and link-syntax text yield no rows, `[[#Anchor]]` is a self-edge, a frontmatter value that is exactly `[[X]]` is a link, and a basename collision resolves to the linking note itself, else the shortest path), `tags` (`path`, `tag`: frontmatter and inline `#tags` merged and deduplicated; nested tags stored full, so `book/scifi` matches `tag = 'book' OR tag LIKE 'book/%'`), `sections` (heading outline with line ranges and token estimates), and `preset_files` (`path`, `preset`: which presets cover which files). Features add their own storage; `map` and `status` report which are on.
|
|
9
9
|
|
|
10
|
-
**Stores.** The config's `store` key picks the backing store: `sqlite` (default, zero-dependency, Node's built-in SQLite), or the experimental `duckdb` and `turso` (the first command that opens such a tree installs that engine's package on its own: `@duckdb/node-api`, a one-time native download of about 110 MB, or the much smaller `@tursodatabase/database`). The tables, `?` placeholders, quoted identifiers, and the `scope` binding are the same on all three, so ordinary frontmatter SQL ports as written. Two things do not port. First, FTS5: `content` is an FTS5 table on sqlite and a plain table on the other two, so hand-written `MATCH`, `snippet()`, `bm25()`, and sqlite's date-function forms run only on sqlite (duckdb has its own fts functions and date syntax; turso has Tantivy's `fts_match`/`fts_score`), and `search` text under duckdb and turso rejects FTS5's prefix (`foo*`), boolean (`AND`/`OR`/`NOT`), `NEAR`, initial-token (`^`), and column-filter (`title:foo`) operators with a named error (STORE_CAPABILITY_MISSING) that says how to rephrase or set `store` to `sqlite`; bare words and quoted phrases work on all three. Second, the `has`/`basename`/`segment` functions are registered on sqlite and duckdb but not turso, whose client cannot register SQL functions at all, so a query calling them under turso fails with `no such function`. `sense watch`
|
|
10
|
+
**Stores.** The config's `store` key picks the backing store: `sqlite` (default, zero-dependency, Node's built-in SQLite), or the experimental `duckdb` and `turso` (the first command that opens such a tree installs that engine's package on its own: `@duckdb/node-api`, a one-time native download of about 110 MB, or the much smaller `@tursodatabase/database`). The tables, `?` placeholders, quoted identifiers, and the `scope` binding are the same on all three, so ordinary frontmatter SQL ports as written. Two things do not port. First, FTS5: `content` is an FTS5 table on sqlite and a plain table on the other two, so hand-written `MATCH`, `snippet()`, `bm25()`, and sqlite's date-function forms run only on sqlite (duckdb has its own fts functions and date syntax; turso has Tantivy's `fts_match`/`fts_score`), and `search` text under duckdb and turso rejects FTS5's prefix (`foo*`), boolean (`AND`/`OR`/`NOT`), `NEAR`, initial-token (`^`), and column-filter (`title:foo`) operators with a named error (STORE_CAPABILITY_MISSING) that says how to rephrase or set `store` to `sqlite`; bare words and quoted phrases work on all three. Second, the `has`/`basename`/`segment` functions are registered on sqlite and duckdb but not turso, whose client cannot register SQL functions at all, so a query calling them under turso fails with `no such function`. `sense watch` runs on all three; under duckdb and turso, which lock the cache file per connection, a concurrent command waits out the watcher's current cycle instead of failing.
|
|
11
11
|
|
|
12
12
|
## What each tool is for
|
|
13
13
|
|
|
@@ -10,7 +10,7 @@ Querying an existing tree is the `sense` skill. This one covers making a tree: i
|
|
|
10
10
|
## Setup
|
|
11
11
|
|
|
12
12
|
- `npm install -g sensemaking`, then `sense init` at the tree root writes `sense.config.json`: two presets (`default`, and `large` showing what a big tree tunes) and an `embed` block naming the model. The model fetches once per machine at the first vector search (progress on stderr); `sense download` prefetches it instead where that timing matters (CI, air-gapped setup). Config discovery walks up from cwd; `--config <path>` overrides.
|
|
13
|
-
- **Backing store.** The config's `store` key: `sqlite` (default, zero-dependency, Node's built-in SQLite), or the experimental `duckdb` and `turso`, whose engine package the first command that opens such a tree installs on its own (`@duckdb/node-api`, a one-time native download of about 110 MB; `@tursodatabase/database`, much smaller). The same commands and tables run on all three.
|
|
13
|
+
- **Backing store.** The config's `store` key: `sqlite` (default, zero-dependency, Node's built-in SQLite), or the experimental `duckdb` and `turso`, whose engine package the first command that opens such a tree installs on its own (`@duckdb/node-api`, a one-time native download of about 110 MB; `@tursodatabase/database`, much smaller). The same commands and tables run on all three. Two things do not port, and each one decides a tree. **FTS5 syntax:** under `duckdb` and `turso`, `search` text and raw `MATCH` reject FTS5's prefix, boolean, `NEAR`, initial-token and column-filter operators with a named error, and sqlite's FTS5 SQL (`MATCH`, `snippet()`, `bm25()`) does not run, so saved queries written in that syntax are sqlite dialect; a tree whose saved queries or search vocabulary depend on FTS5 operators stays on `sqlite`. **SQL functions:** `has`/`basename`/`segment` are registered on `sqlite` and `duckdb` but not `turso`, whose client cannot register them at all, so a tree whose queries call them stays off `turso`. `sense watch` runs on all three; `duckdb` and `turso` lock their cache file per connection, so a concurrent command waits out the watcher's current cycle instead of failing. Each store keeps its own cache file (`.sense/cache.db`, `.sense/cache.duckdb`, `.sense/cache.turso.db`); switching stores is a rebuild, not a migration.
|
|
14
14
|
- Globs resolve relative to the config file, never the cwd.
|
|
15
15
|
- `sense status` and `sense map` show each preset's coverage (files matched, embedded count), so what a config actually indexes is always visible in output. A config edit that changes coverage rebuilds the cache and names the preset that caused it on stderr.
|
|
16
16
|
|