sensemaking 0.27.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/chunk/extract.js.map +1 -1
- package/dist/cjs/chunk/group.js.map +1 -1
- package/dist/cjs/chunk/index.js.map +1 -1
- package/dist/cjs/chunk/parse.js.map +1 -1
- package/dist/cjs/chunk/parser.js.map +1 -1
- package/dist/cjs/chunk/tokens.js.map +1 -1
- package/dist/cjs/chunk/version.js.map +1 -1
- package/dist/cjs/cli/build.js.map +1 -1
- package/dist/cjs/cli/download.js.map +1 -1
- package/dist/cjs/cli/index.js.map +1 -1
- package/dist/cjs/cli/init.js.map +1 -1
- package/dist/cjs/cli/map.js.map +1 -1
- package/dist/cjs/cli/named.js.map +1 -1
- package/dist/cjs/cli/path.js.map +1 -1
- package/dist/cjs/cli/peek.js.map +1 -1
- package/dist/cjs/cli/related.js.map +1 -1
- package/dist/cjs/cli/search.js.map +1 -1
- package/dist/cjs/cli/shared.js.map +1 -1
- package/dist/cjs/cli/sql.js.map +1 -1
- package/dist/cjs/cli/status.js.map +1 -1
- package/dist/cjs/cli/watch.js.map +1 -1
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/commands/index.js.map +1 -1
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/path.js.map +1 -1
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/related.js.map +1 -1
- package/dist/cjs/commands/scope.js.map +1 -1
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.js.map +1 -1
- package/dist/cjs/commands/snapshot.js.map +1 -1
- package/dist/cjs/commands/status.js.map +1 -1
- package/dist/cjs/config/access.js.map +1 -1
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/config/load.js.map +1 -1
- package/dist/cjs/config/resolve.js.map +1 -1
- package/dist/cjs/config/signals.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/config/validate.js.map +1 -1
- package/dist/cjs/embed/cohere.js.map +1 -1
- package/dist/cjs/embed/distribution.js.map +1 -1
- package/dist/cjs/embed/endpoint.js.map +1 -1
- package/dist/cjs/embed/handoff.js.map +1 -1
- package/dist/cjs/embed/http.js.map +1 -1
- package/dist/cjs/embed/identity.js.map +1 -1
- package/dist/cjs/embed/langfit.js.map +1 -1
- package/dist/cjs/embed/languages.js.map +1 -1
- package/dist/cjs/embed/openai.js.map +1 -1
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/embed/registry.js.map +1 -1
- package/dist/cjs/embed/static.js.map +1 -1
- package/dist/cjs/embed/store.js.map +1 -1
- package/dist/cjs/embed/types.js.map +1 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/features/fences.js.map +1 -1
- package/dist/cjs/features/index.js.map +1 -1
- package/dist/cjs/features/links.js.map +1 -1
- package/dist/cjs/features/rank.js.map +1 -1
- package/dist/cjs/features/sections.js.map +1 -1
- package/dist/cjs/features/tags.js.map +1 -1
- package/dist/cjs/graph/graph.js.map +1 -1
- package/dist/cjs/graph/traverse.js.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/atomic-write.js.map +1 -1
- package/dist/cjs/lib/guarded-tick.js.map +1 -1
- package/dist/cjs/lib/serial-query.js.map +1 -1
- package/dist/cjs/lib/watch-notification.js.map +1 -1
- package/dist/cjs/lib/worker-file.js.map +1 -1
- package/dist/cjs/output/column-hint.js.map +1 -1
- package/dist/cjs/output/output.js.map +1 -1
- package/dist/cjs/output/progress.js.map +1 -1
- package/dist/cjs/output/search-error.js.map +1 -1
- package/dist/cjs/scan/frontmatter.js.map +1 -1
- package/dist/cjs/scan/index.js.map +1 -1
- package/dist/cjs/scan/list.js.map +1 -1
- package/dist/cjs/scan/pool.js.map +1 -1
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/scan/worker-error.js.map +1 -1
- package/dist/cjs/store/batch.js.map +1 -1
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/cache.js.map +1 -1
- package/dist/cjs/store/duckdb/batch.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/fieldStats.js.map +1 -1
- package/dist/cjs/store/duckdb/lexical.d.cts +1 -6
- package/dist/cjs/store/duckdb/lexical.d.ts +1 -6
- package/dist/cjs/store/duckdb/lexical.js +28 -39
- package/dist/cjs/store/duckdb/lexical.js.map +1 -1
- package/dist/cjs/store/duckdb/native.d.cts +1 -2
- package/dist/cjs/store/duckdb/native.d.ts +1 -2
- package/dist/cjs/store/duckdb/native.js +1 -6
- package/dist/cjs/store/duckdb/native.js.map +1 -1
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/ordered-bm25.d.cts +3 -16
- package/dist/cjs/store/duckdb/ordered-bm25.d.ts +3 -16
- package/dist/cjs/store/duckdb/ordered-bm25.js +27 -79
- package/dist/cjs/store/duckdb/ordered-bm25.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/duckdb/sql-functions.js.map +1 -1
- package/dist/cjs/store/duckdb/store.js.map +1 -1
- package/dist/cjs/store/duckdb/vectors.js.map +1 -1
- package/dist/cjs/store/embed-scope.js.map +1 -1
- package/dist/cjs/store/feature-scope.js.map +1 -1
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/lock-wait.js.map +1 -1
- package/dist/cjs/store/native.js.map +1 -1
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/preset-scope.js.map +1 -1
- package/dist/cjs/store/readiness.js.map +1 -1
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/shared.js.map +1 -1
- package/dist/cjs/store/signature.js.map +1 -1
- package/dist/cjs/store/sql-functions.js.map +1 -1
- package/dist/cjs/store/sqlite/connection.js.map +1 -1
- package/dist/cjs/store/sqlite/fieldStats.js.map +1 -1
- package/dist/cjs/store/sqlite/lexical.js.map +1 -1
- 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/sql-functions.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/sqlite/vectors.js.map +1 -1
- package/dist/cjs/store/stages.js.map +1 -1
- package/dist/cjs/store/transaction.js.map +1 -1
- package/dist/cjs/store/turso/connection.js.map +1 -1
- package/dist/cjs/store/turso/fieldStats.js.map +1 -1
- package/dist/cjs/store/turso/lexical-text.js.map +1 -1
- package/dist/cjs/store/turso/lexical.js.map +1 -1
- package/dist/cjs/store/turso/native.js.map +1 -1
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/reconcile.js.map +1 -1
- package/dist/cjs/store/turso/sql-functions.js.map +1 -1
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/turso/vectors.js.map +1 -1
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/store/vectors.js.map +1 -1
- package/dist/cjs/text/segment.js.map +1 -1
- package/dist/cjs/text/strip.js.map +1 -1
- package/dist/cjs/watch-claim.js.map +1 -1
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/cjs/workers/watch-heartbeat.js.map +1 -1
- package/dist/esm/chunk/extract.js.map +1 -1
- package/dist/esm/chunk/group.js.map +1 -1
- package/dist/esm/chunk/index.js.map +1 -1
- package/dist/esm/chunk/parse.js.map +1 -1
- package/dist/esm/chunk/parser.js.map +1 -1
- package/dist/esm/chunk/tokens.js.map +1 -1
- package/dist/esm/chunk/types.js.map +1 -1
- package/dist/esm/chunk/version.js.map +1 -1
- package/dist/esm/cli/build.js.map +1 -1
- package/dist/esm/cli/download.js.map +1 -1
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/cli/init.js.map +1 -1
- package/dist/esm/cli/map.js.map +1 -1
- package/dist/esm/cli/named.js.map +1 -1
- package/dist/esm/cli/path.js.map +1 -1
- package/dist/esm/cli/peek.js.map +1 -1
- package/dist/esm/cli/related.js.map +1 -1
- package/dist/esm/cli/search.js.map +1 -1
- package/dist/esm/cli/shared.js.map +1 -1
- package/dist/esm/cli/sql.js.map +1 -1
- package/dist/esm/cli/status.js.map +1 -1
- package/dist/esm/cli/types.js.map +1 -1
- package/dist/esm/cli/watch.js.map +1 -1
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/commands/index.js.map +1 -1
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/path.js.map +1 -1
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/related.js.map +1 -1
- package/dist/esm/commands/scope.js.map +1 -1
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.js.map +1 -1
- package/dist/esm/commands/snapshot.js.map +1 -1
- package/dist/esm/commands/status.js.map +1 -1
- package/dist/esm/config/access.js.map +1 -1
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/config/load.js.map +1 -1
- package/dist/esm/config/resolve.js.map +1 -1
- package/dist/esm/config/signals.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/config/validate.js.map +1 -1
- package/dist/esm/embed/cohere.js.map +1 -1
- package/dist/esm/embed/distribution.js.map +1 -1
- package/dist/esm/embed/endpoint.js.map +1 -1
- package/dist/esm/embed/handoff.js.map +1 -1
- package/dist/esm/embed/http.js.map +1 -1
- package/dist/esm/embed/identity.js.map +1 -1
- package/dist/esm/embed/langfit.js.map +1 -1
- package/dist/esm/embed/languages.js.map +1 -1
- package/dist/esm/embed/openai.js.map +1 -1
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/embed/registry.js.map +1 -1
- package/dist/esm/embed/static.js.map +1 -1
- package/dist/esm/embed/store.js.map +1 -1
- package/dist/esm/embed/types.js.map +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/features/fences.js.map +1 -1
- package/dist/esm/features/index.js.map +1 -1
- package/dist/esm/features/links.js.map +1 -1
- package/dist/esm/features/rank.js.map +1 -1
- package/dist/esm/features/sections.js.map +1 -1
- package/dist/esm/features/tags.js.map +1 -1
- package/dist/esm/features/types.js.map +1 -1
- package/dist/esm/graph/graph.js.map +1 -1
- package/dist/esm/graph/traverse.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/atomic-write.js.map +1 -1
- package/dist/esm/lib/guarded-tick.js.map +1 -1
- package/dist/esm/lib/serial-query.js.map +1 -1
- package/dist/esm/lib/watch-notification.js.map +1 -1
- package/dist/esm/lib/worker-file.js.map +1 -1
- package/dist/esm/output/column-hint.js.map +1 -1
- package/dist/esm/output/output.js.map +1 -1
- package/dist/esm/output/progress.js.map +1 -1
- package/dist/esm/output/search-error.js.map +1 -1
- package/dist/esm/scan/frontmatter.js.map +1 -1
- package/dist/esm/scan/index.js.map +1 -1
- package/dist/esm/scan/list.js.map +1 -1
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/scan/worker-error.js.map +1 -1
- package/dist/esm/store/batch.js.map +1 -1
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/cache.js.map +1 -1
- package/dist/esm/store/duckdb/batch.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/fieldStats.js.map +1 -1
- package/dist/esm/store/duckdb/lexical.d.ts +1 -6
- package/dist/esm/store/duckdb/lexical.js +13 -17
- package/dist/esm/store/duckdb/lexical.js.map +1 -1
- package/dist/esm/store/duckdb/native.d.ts +1 -2
- package/dist/esm/store/duckdb/native.js +1 -3
- package/dist/esm/store/duckdb/native.js.map +1 -1
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/ordered-bm25.d.ts +3 -16
- package/dist/esm/store/duckdb/ordered-bm25.js +18 -54
- package/dist/esm/store/duckdb/ordered-bm25.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/duckdb/sql-functions.js.map +1 -1
- package/dist/esm/store/duckdb/store.js.map +1 -1
- package/dist/esm/store/duckdb/vectors.js.map +1 -1
- package/dist/esm/store/embed-scope.js.map +1 -1
- package/dist/esm/store/feature-scope.js.map +1 -1
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/lock-wait.js.map +1 -1
- package/dist/esm/store/native.js.map +1 -1
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/preset-scope.js.map +1 -1
- package/dist/esm/store/readiness.js.map +1 -1
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/shared.js.map +1 -1
- package/dist/esm/store/signature.js.map +1 -1
- package/dist/esm/store/sql-functions.js.map +1 -1
- package/dist/esm/store/sqlite/connection.js.map +1 -1
- package/dist/esm/store/sqlite/fieldStats.js.map +1 -1
- package/dist/esm/store/sqlite/lexical.js.map +1 -1
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/sqlite/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/sql-functions.js.map +1 -1
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/sqlite/vectors.js.map +1 -1
- package/dist/esm/store/stages.js.map +1 -1
- package/dist/esm/store/transaction.js.map +1 -1
- package/dist/esm/store/turso/connection.js.map +1 -1
- package/dist/esm/store/turso/fieldStats.js.map +1 -1
- package/dist/esm/store/turso/lexical-text.js.map +1 -1
- package/dist/esm/store/turso/lexical.js.map +1 -1
- package/dist/esm/store/turso/native.js.map +1 -1
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/reconcile.js.map +1 -1
- package/dist/esm/store/turso/sql-functions.js.map +1 -1
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/turso/vectors.js.map +1 -1
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/store/vectors.js.map +1 -1
- package/dist/esm/text/segment.js.map +1 -1
- package/dist/esm/text/strip.js.map +1 -1
- package/dist/esm/watch-claim.js.map +1 -1
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.js.map +1 -1
- package/dist/esm/workers/watch-heartbeat.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/text/segment.ts"],"sourcesContent":["import { stemmer } from 'stemmer';\n\n// Word boundaries FTS5's tokenizers cannot find on their own. Contract: seg(query) must appear in\n// seg(document) for any substring query; word mode is context-dependent and breaks that, so grapheme mode (UAX #29) is used.\n\n// Scripts written without word spaces: a closed set of writing systems.\n// Script_Extensions, not Script: the katakana long-vowel mark ー is Script=Common.\nexport const UNSPACED_SCRIPTS = '\\\\p{scx=Han}\\\\p{scx=Hiragana}\\\\p{scx=Katakana}\\\\p{scx=Thai}\\\\p{scx=Khmer}\\\\p{scx=Lao}\\\\p{scx=Myanmar}';\n// A run is script BASE characters with their combining marks attached; a bare mark after a\n// Latin letter (decomposed é) never starts one.\nconst RUN_BODY = `(?:[${UNSPACED_SCRIPTS}]\\\\p{M}*)+`;\nconst RUN = new RegExp(`(${RUN_BODY})`, 'gu');\nconst HAS_RUN = new RegExp(`[${UNSPACED_SCRIPTS}]`, 'u');\n\n// Whether text holds a script that marks no word boundaries, the predicate every store's\n// sidecar population and query split turns on.\nexport function hasUnspacedRun(text: string): boolean {\n return HAS_RUN.test(text);\n}\n\nexport interface SearchToken {\n text: string;\n start: number;\n end: number;\n unspaced: boolean;\n}\n\n// Search tokens share the native separator contract: letters, marks, and numbers form tokens;\n// underscore, apostrophe, and hyphen separate them. Unspaced-script runs stay whole so callers\n// can apply substring semantics, while adjacent Latin text still gets its own token.\nexport function searchTokens(text: string): SearchToken[] {\n const tokens: SearchToken[] = [];\n for (const match of text.matchAll(/[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N}]*/gu)) {\n const start = match.index;\n const end = start + match[0].length;\n let cursor = start;\n for (const run of text.slice(start, end).matchAll(RUN)) {\n const runStart = start + run.index;\n if (runStart > cursor) tokens.push({ text: text.slice(cursor, runStart), start: cursor, end: runStart, unspaced: false });\n const runEnd = runStart + run[0].length;\n tokens.push({ text: run[0], start: runStart, end: runEnd, unspaced: true });\n cursor = runEnd;\n }\n if (cursor < end) tokens.push({ text: text.slice(cursor, end), start: cursor, end, unspaced: false });\n }\n return tokens;\n}\n\n// Phrase membership is checked after native ranking. A CJK token matches inside one authored\n// run; Latin tokens compare folded Porter stems. Each call scans one field once per candidate.\nexport function matchesSearchPhrase(text: string, phrase: SearchToken[]): boolean {\n const haystack = searchTokens(text);\n if (phrase.length === 0 || haystack.length < phrase.length) return false;\n for (let start = 0; start <= haystack.length - phrase.length; start++) {\n let matches = true;\n for (let i = 0; i < phrase.length; i++) {\n const query = phrase[i];\n const authored = haystack[start + i];\n if (query.unspaced) {\n if (!authored.unspaced || !authored.text.includes(query.text)) matches = false;\n } else if (authored.unspaced || stemmer(foldForSearch(authored.text)) !== stemmer(foldForSearch(query.text))) {\n matches = false;\n }\n if (!matches) break;\n }\n if (matches) return true;\n }\n return false;\n}\n\n// Character spans of unspaced-script runs in text, the same boundary the index segments on --\n// the snippet marker uses this to keep substring semantics inside a run and word/stem matching outside it.\nexport function unspacedRuns(text: string): Array<{ start: number; end: number }> {\n return Array.from(text.matchAll(RUN), (m) => ({ start: m.index, end: m.index + m[0].length }));\n}\n// Grapheme clusters, ECMA-402/UAX #29: base char plus its marks, ZWJ sequences, Hangul jamo.\n// Built on first use and kept: construction is 6.6 ms, and a tree with no unspaced-script run\n// never segments at all, so no command pays it at module load.\nlet graphemeSegmenter: Intl.Segmenter | undefined;\n// unicode61 drops Unicode punctuation as a separator. Some of it (。、「」) has Script_Extensions\n// into an unspaced script, so RUN keeps it -- a grapheme matching this becomes a split point.\nconst PUNCTUATION = /\\p{P}/u;\n// Token barrier between separate runs (or a punctuation split within one) so their graphemes\n// are never phrase-adjacent. U+A7F7: a letter (so FTS5 keeps it as a token) no one types.\nconst BARRIER = 'ꟷ';\n\nfunction graphemes(run: string): string[] {\n graphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n return Array.from(graphemeSegmenter.segment(run), (s) => s.segment);\n}\n\n// A run's graphemes, cut into punctuation-free groups at every punctuation grapheme (dropped,\n// matching unicode61). The one place punctuation is classified, so index and query agree.\nfunction splitOnPunctuation(run: string): string[][] {\n const groups: string[][] = [[]];\n for (const g of graphemes(run)) {\n if (PUNCTUATION.test(g)) groups.push([]);\n else groups[groups.length - 1].push(g);\n }\n return groups.filter((g) => g.length > 0);\n}\n\n// Index side: '' when the field has no unspaced-script run. Otherwise each run explodes into\n// its graphemes, barrier-delimited from its neighbors and from a punctuation split within itself.\nexport function segmentField(text: string): string {\n if (!HAS_RUN.test(text)) return '';\n const out = text.replace(RUN, (run) => {\n const body = splitOnPunctuation(run)\n .map((g) => g.join(' '))\n .join(` ${BARRIER} `);\n return ` ${BARRIER} ${body} ${BARRIER} `;\n });\n return out.replace(/\\s+/g, ' ').trim();\n}\n\n// A `title:`/`summary:`/`text:` qualifier directly before a run that is about to become a\n// quoted grapheme phrase, so the rewrite can retarget it at the matching `_seg` column.\nconst QUALIFIER = /(^|[\\s(])(-?)(title|summary|text)\\s*:\\s*$/;\n\n// Raw title/summary/text drop punctuation as unicode61's token separator, so `数数` and `数。数`\n// falsely match there; only the barriered `_seg` columns are safe from it, hence this fallback target.\nconst SIDECAR_COLUMNS = '{title_seg summary_seg text_seg}:';\nconst AUTHORED_COLUMNS = '{title summary text}:';\nconst QUOTED_RUN_TOKEN = new RegExp(`\"(${RUN_BODY})\"`, 'gu');\n\ninterface PositionalSpan {\n start: number;\n end: number;\n qualified: boolean;\n}\n\nconst FTS_WORD = /[\\p{L}\\p{N}_]/u;\n\nfunction isFtsWord(char: string | undefined): boolean {\n return char !== undefined && FTS_WORD.test(char);\n}\n\nfunction quoteEnd(text: string, start: number): number {\n for (let i = start + 1; i < text.length; i++) {\n if (text[i] !== '\"') continue;\n if (text[i + 1] === '\"') {\n i++;\n continue;\n }\n return i + 1;\n }\n return text.length;\n}\n\nfunction nearEnd(text: string, open: number): number {\n let depth = 1;\n let quoted = false;\n for (let i = open + 1; i < text.length; i++) {\n const char = text[i];\n if (char === '\"') {\n if (quoted && text[i + 1] === '\"') {\n i++;\n } else {\n quoted = !quoted;\n }\n } else if (!quoted && char === '(') {\n depth++;\n } else if (!quoted && char === ')' && --depth === 0) {\n return i + 1;\n }\n }\n return text.length;\n}\n\n// Lexical routing only: SQLite remains responsible for validating the expression. The scanner\n// recognizes NEAR groups and the one FTS string governed by ^, while quoted text shields both.\nfunction positionalSpans(terms: string): PositionalSpan[] {\n const spans: PositionalSpan[] = [];\n for (let i = 0; i < terms.length; i++) {\n if (terms[i] === '\"') {\n i = quoteEnd(terms, i) - 1;\n continue;\n }\n if (terms.startsWith('NEAR', i) && !isFtsWord(terms[i - 1])) {\n let open = i + 4;\n while (/\\s/u.test(terms[open] ?? '')) open++;\n if (terms[open] === '(') {\n const end = nearEnd(terms, open);\n if (HAS_RUN.test(terms.slice(open + 1, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = end - 1;\n continue;\n }\n }\n if (terms[i] === '^' && !isFtsWord(terms[i - 1])) {\n let operand = i + 1;\n while (/\\s/u.test(terms[operand] ?? '')) operand++;\n const end =\n terms[operand] === '\"'\n ? quoteEnd(terms, operand)\n : (() => {\n let cursor = operand;\n while (/[\\p{L}\\p{N}_]/u.test(terms[cursor] ?? '')) cursor++;\n return cursor;\n })();\n if (end > operand && HAS_RUN.test(terms.slice(operand, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = Math.max(i, end - 1);\n }\n }\n return spans;\n}\n\nfunction routePositionalSpans(terms: string): string {\n const spans = positionalSpans(terms);\n if (spans.length === 0) return terms;\n let out = '';\n let cursor = 0;\n for (const span of spans) {\n out += terms.slice(cursor, span.start);\n if (!span.qualified) out += AUTHORED_COLUMNS;\n out += terms.slice(span.start, span.end);\n cursor = span.end;\n }\n return out + terms.slice(cursor);\n}\n\nfunction inPositionalSpan(start: number, spans: PositionalSpan[]): boolean {\n return spans.some((span) => start >= span.start && start < span.end);\n}\n\n// A run's punctuation-free groups, each its own quoted phrase (bare token if one grapheme),\n// space-joined -- the same split points segmentField barriers, so query and index agree.\nfunction runQuery(run: string, columnPrefix: string, quoteSingle = false): string {\n return splitOnPunctuation(run)\n .map((g) => `${columnPrefix}${g.length > 1 || quoteSingle ? `\"${g.join(' ')}\"` : g[0]}`)\n .join(' ');\n}\n\n// Retarget complete quoted run tokens without interpreting the expression around them. Positional\n// spans have already been routed to authored columns, so sidecar barriers cannot change meaning.\nfunction quotedRunTokens(terms: string): string {\n const spans = positionalSpans(terms);\n let out = '';\n let cursor = 0;\n for (const match of terms.matchAll(QUOTED_RUN_TOKEN)) {\n const start = match.index;\n const run = match[1];\n if (inPositionalSpan(start, spans) || PUNCTUATION.test(run)) continue;\n out += terms.slice(cursor, start);\n const qualifier = out.match(QUALIFIER);\n if (qualifier) {\n out = `${out.slice(0, qualifier.index)}${qualifier[1]}${runQuery(run, `${qualifier[2]}${qualifier[3]}_seg:`, true)}`;\n } else {\n out += runQuery(run, SIDECAR_COLUMNS, true);\n }\n cursor = start + match[0].length;\n }\n return cursor === 0 ? terms : out + terms.slice(cursor);\n}\n\n// Query side: each unspaced run becomes phrases of its graphemes, matching segmentField. A\n// qualifier maps to its `_seg` column; unqualified maps to all three (SIDECAR_COLUMNS).\nexport function segmentMatch(terms: string): string {\n if (!HAS_RUN.test(terms)) return terms;\n terms = routePositionalSpans(terms);\n terms = quotedRunTokens(terms);\n const spans = positionalSpans(terms);\n let out = '';\n let quoted = false;\n let offset = 0;\n const pieces = terms.split(RUN); // split keeps captured runs at odd indices\n for (let i = 0; i < pieces.length; i++) {\n if (i % 2 === 0) {\n for (const ch of pieces[i]) if (ch === '\"') quoted = !quoted;\n out += pieces[i];\n offset += pieces[i].length;\n continue;\n }\n const protectedRun = inPositionalSpan(offset, spans);\n if (quoted || protectedRun) {\n out += pieces[i]; // an author's phrase is matched as written\n offset += pieces[i].length;\n continue;\n }\n const m = out.match(QUALIFIER);\n if (m) {\n out = `${out.slice(0, m.index)}${m[1]}${runQuery(pieces[i], `${m[2]}${m[3]}_seg:`)}`;\n } else {\n out += runQuery(pieces[i], SIDECAR_COLUMNS);\n }\n offset += pieces[i].length;\n }\n return out;\n}\n\n// FTS tokenizers compare letters without combining marks. Keep the authored string for output and\n// offsets, but use this key when JS needs to compare a matched word to its source segment.\nexport function foldForSearch(text: string): string {\n return text.normalize('NFD').replace(/\\p{M}/gu, '').toLowerCase();\n}\n"],"names":["UNSPACED_SCRIPTS","foldForSearch","hasUnspacedRun","matchesSearchPhrase","searchTokens","segmentField","segmentMatch","unspacedRuns","RUN_BODY","RUN","RegExp","HAS_RUN","text","test","tokens","matchAll","match","start","index","end","length","cursor","slice","run","runStart","push","unspaced","runEnd","phrase","haystack","matches","i","query","authored","includes","stemmer","Array","from","m","graphemeSegmenter","PUNCTUATION","BARRIER","graphemes","Intl","Segmenter","undefined","granularity","segment","s","splitOnPunctuation","groups","g","filter","out","replace","body","map","join","trim","QUALIFIER","SIDECAR_COLUMNS","AUTHORED_COLUMNS","QUOTED_RUN_TOKEN","FTS_WORD","isFtsWord","char","quoteEnd","nearEnd","open","depth","quoted","positionalSpans","terms","startsWith","spans","qualified","operand","Math","max","routePositionalSpans","span","inPositionalSpan","some","runQuery","columnPrefix","quoteSingle","quotedRunTokens","qualifier","offset","pieces","split","ch","protectedRun","normalize","toLowerCase"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/text/segment.ts"],"sourcesContent":["import { stemmer } from 'stemmer';\n\n// Word boundaries FTS5's tokenizers cannot find on their own. Contract: seg(query) must appear in\n// seg(document) for any substring query; word mode is context-dependent and breaks that, so grapheme mode (UAX #29) is used.\n\n// Scripts written without word spaces: a closed set of writing systems.\n// Script_Extensions, not Script: the katakana long-vowel mark ー is Script=Common.\nexport const UNSPACED_SCRIPTS = '\\\\p{scx=Han}\\\\p{scx=Hiragana}\\\\p{scx=Katakana}\\\\p{scx=Thai}\\\\p{scx=Khmer}\\\\p{scx=Lao}\\\\p{scx=Myanmar}';\n// A run is script BASE characters with their combining marks attached; a bare mark after a\n// Latin letter (decomposed é) never starts one.\nconst RUN_BODY = `(?:[${UNSPACED_SCRIPTS}]\\\\p{M}*)+`;\nconst RUN = new RegExp(`(${RUN_BODY})`, 'gu');\nconst HAS_RUN = new RegExp(`[${UNSPACED_SCRIPTS}]`, 'u');\n\n// Whether text holds a script that marks no word boundaries, the predicate every store's\n// sidecar population and query split turns on.\nexport function hasUnspacedRun(text: string): boolean {\n return HAS_RUN.test(text);\n}\n\nexport interface SearchToken {\n text: string;\n start: number;\n end: number;\n unspaced: boolean;\n}\n\n// Search tokens share the native separator contract: letters, marks, and numbers form tokens;\n// underscore, apostrophe, and hyphen separate them. Unspaced-script runs stay whole so callers\n// can apply substring semantics, while adjacent Latin text still gets its own token.\nexport function searchTokens(text: string): SearchToken[] {\n const tokens: SearchToken[] = [];\n for (const match of text.matchAll(/[\\p{L}\\p{N}][\\p{L}\\p{M}\\p{N}]*/gu)) {\n const start = match.index;\n const end = start + match[0].length;\n let cursor = start;\n for (const run of text.slice(start, end).matchAll(RUN)) {\n const runStart = start + run.index;\n if (runStart > cursor) tokens.push({ text: text.slice(cursor, runStart), start: cursor, end: runStart, unspaced: false });\n const runEnd = runStart + run[0].length;\n tokens.push({ text: run[0], start: runStart, end: runEnd, unspaced: true });\n cursor = runEnd;\n }\n if (cursor < end) tokens.push({ text: text.slice(cursor, end), start: cursor, end, unspaced: false });\n }\n return tokens;\n}\n\n// Phrase membership is checked after native ranking. A CJK token matches inside one authored\n// run; Latin tokens compare folded Porter stems. Each call scans one field once per candidate.\nexport function matchesSearchPhrase(text: string, phrase: SearchToken[]): boolean {\n const haystack = searchTokens(text);\n if (phrase.length === 0 || haystack.length < phrase.length) return false;\n for (let start = 0; start <= haystack.length - phrase.length; start++) {\n let matches = true;\n for (let i = 0; i < phrase.length; i++) {\n const query = phrase[i];\n const authored = haystack[start + i];\n if (query.unspaced) {\n if (!authored.unspaced || !authored.text.includes(query.text)) matches = false;\n } else if (authored.unspaced || stemmer(foldForSearch(authored.text)) !== stemmer(foldForSearch(query.text))) {\n matches = false;\n }\n if (!matches) break;\n }\n if (matches) return true;\n }\n return false;\n}\n\n// Character spans of unspaced-script runs in text, the same boundary the index segments on --\n// the snippet marker uses this to keep substring semantics inside a run and word/stem matching outside it.\nexport function unspacedRuns(text: string): Array<{ start: number; end: number }> {\n return Array.from(text.matchAll(RUN), (m) => ({ start: m.index, end: m.index + m[0].length }));\n}\n// Grapheme clusters, ECMA-402/UAX #29: base char plus its marks, ZWJ sequences, Hangul jamo.\n// Built on first use and kept: construction is 6.6 ms, and a tree with no unspaced-script run\n// never segments at all, so no command pays it at module load.\nlet graphemeSegmenter: Intl.Segmenter | undefined;\n// unicode61 drops Unicode punctuation as a separator. Some of it (。、「」) has Script_Extensions\n// into an unspaced script, so RUN keeps it -- a grapheme matching this becomes a split point.\nconst PUNCTUATION = /\\p{P}/u;\n// Token barrier between separate runs (or a punctuation split within one) so their graphemes\n// are never phrase-adjacent. U+A7F7: a letter (so FTS5 keeps it as a token) no one types.\nconst BARRIER = 'ꟷ';\n\nfunction graphemes(run: string): string[] {\n graphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });\n return Array.from(graphemeSegmenter.segment(run), (s) => s.segment);\n}\n\n// A run's graphemes, cut into punctuation-free groups at every punctuation grapheme (dropped,\n// matching unicode61). The one place punctuation is classified, so index and query agree.\nfunction splitOnPunctuation(run: string): string[][] {\n const groups: string[][] = [[]];\n for (const g of graphemes(run)) {\n if (PUNCTUATION.test(g)) groups.push([]);\n else groups[groups.length - 1].push(g);\n }\n return groups.filter((g) => g.length > 0);\n}\n\n// Index side: '' when the field has no unspaced-script run. Otherwise each run explodes into\n// its graphemes, barrier-delimited from its neighbors and from a punctuation split within itself.\nexport function segmentField(text: string): string {\n if (!HAS_RUN.test(text)) return '';\n const out = text.replace(RUN, (run) => {\n const body = splitOnPunctuation(run)\n .map((g) => g.join(' '))\n .join(` ${BARRIER} `);\n return ` ${BARRIER} ${body} ${BARRIER} `;\n });\n return out.replace(/\\s+/g, ' ').trim();\n}\n\n// A `title:`/`summary:`/`text:` qualifier directly before a run that is about to become a\n// quoted grapheme phrase, so the rewrite can retarget it at the matching `_seg` column.\nconst QUALIFIER = /(^|[\\s(])(-?)(title|summary|text)\\s*:\\s*$/;\n\n// Raw title/summary/text drop punctuation as unicode61's token separator, so `数数` and `数。数`\n// falsely match there; only the barriered `_seg` columns are safe from it, hence this fallback target.\nconst SIDECAR_COLUMNS = '{title_seg summary_seg text_seg}:';\nconst AUTHORED_COLUMNS = '{title summary text}:';\nconst QUOTED_RUN_TOKEN = new RegExp(`\"(${RUN_BODY})\"`, 'gu');\n\ninterface PositionalSpan {\n start: number;\n end: number;\n qualified: boolean;\n}\n\nconst FTS_WORD = /[\\p{L}\\p{N}_]/u;\n\nfunction isFtsWord(char: string | undefined): boolean {\n return char !== undefined && FTS_WORD.test(char);\n}\n\nfunction quoteEnd(text: string, start: number): number {\n for (let i = start + 1; i < text.length; i++) {\n if (text[i] !== '\"') continue;\n if (text[i + 1] === '\"') {\n i++;\n continue;\n }\n return i + 1;\n }\n return text.length;\n}\n\nfunction nearEnd(text: string, open: number): number {\n let depth = 1;\n let quoted = false;\n for (let i = open + 1; i < text.length; i++) {\n const char = text[i];\n if (char === '\"') {\n if (quoted && text[i + 1] === '\"') {\n i++;\n } else {\n quoted = !quoted;\n }\n } else if (!quoted && char === '(') {\n depth++;\n } else if (!quoted && char === ')' && --depth === 0) {\n return i + 1;\n }\n }\n return text.length;\n}\n\n// Lexical routing only: SQLite remains responsible for validating the expression. The scanner\n// recognizes NEAR groups and the one FTS string governed by ^, while quoted text shields both.\nfunction positionalSpans(terms: string): PositionalSpan[] {\n const spans: PositionalSpan[] = [];\n for (let i = 0; i < terms.length; i++) {\n if (terms[i] === '\"') {\n i = quoteEnd(terms, i) - 1;\n continue;\n }\n if (terms.startsWith('NEAR', i) && !isFtsWord(terms[i - 1])) {\n let open = i + 4;\n while (/\\s/u.test(terms[open] ?? '')) open++;\n if (terms[open] === '(') {\n const end = nearEnd(terms, open);\n if (HAS_RUN.test(terms.slice(open + 1, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = end - 1;\n continue;\n }\n }\n if (terms[i] === '^' && !isFtsWord(terms[i - 1])) {\n let operand = i + 1;\n while (/\\s/u.test(terms[operand] ?? '')) operand++;\n const end =\n terms[operand] === '\"'\n ? quoteEnd(terms, operand)\n : (() => {\n let cursor = operand;\n while (/[\\p{L}\\p{N}_]/u.test(terms[cursor] ?? '')) cursor++;\n return cursor;\n })();\n if (end > operand && HAS_RUN.test(terms.slice(operand, end))) spans.push({ start: i, end, qualified: QUALIFIER.test(terms.slice(0, i)) });\n i = Math.max(i, end - 1);\n }\n }\n return spans;\n}\n\nfunction routePositionalSpans(terms: string): string {\n const spans = positionalSpans(terms);\n if (spans.length === 0) return terms;\n let out = '';\n let cursor = 0;\n for (const span of spans) {\n out += terms.slice(cursor, span.start);\n if (!span.qualified) out += AUTHORED_COLUMNS;\n out += terms.slice(span.start, span.end);\n cursor = span.end;\n }\n return out + terms.slice(cursor);\n}\n\nfunction inPositionalSpan(start: number, spans: PositionalSpan[]): boolean {\n return spans.some((span) => start >= span.start && start < span.end);\n}\n\n// A run's punctuation-free groups, each its own quoted phrase (bare token if one grapheme),\n// space-joined -- the same split points segmentField barriers, so query and index agree.\nfunction runQuery(run: string, columnPrefix: string, quoteSingle = false): string {\n return splitOnPunctuation(run)\n .map((g) => `${columnPrefix}${g.length > 1 || quoteSingle ? `\"${g.join(' ')}\"` : g[0]}`)\n .join(' ');\n}\n\n// Retarget complete quoted run tokens without interpreting the expression around them. Positional\n// spans have already been routed to authored columns, so sidecar barriers cannot change meaning.\nfunction quotedRunTokens(terms: string): string {\n const spans = positionalSpans(terms);\n let out = '';\n let cursor = 0;\n for (const match of terms.matchAll(QUOTED_RUN_TOKEN)) {\n const start = match.index;\n const run = match[1];\n if (inPositionalSpan(start, spans) || PUNCTUATION.test(run)) continue;\n out += terms.slice(cursor, start);\n const qualifier = out.match(QUALIFIER);\n if (qualifier) {\n out = `${out.slice(0, qualifier.index)}${qualifier[1]}${runQuery(run, `${qualifier[2]}${qualifier[3]}_seg:`, true)}`;\n } else {\n out += runQuery(run, SIDECAR_COLUMNS, true);\n }\n cursor = start + match[0].length;\n }\n return cursor === 0 ? terms : out + terms.slice(cursor);\n}\n\n// Query side: each unspaced run becomes phrases of its graphemes, matching segmentField. A\n// qualifier maps to its `_seg` column; unqualified maps to all three (SIDECAR_COLUMNS).\nexport function segmentMatch(terms: string): string {\n if (!HAS_RUN.test(terms)) return terms;\n terms = routePositionalSpans(terms);\n terms = quotedRunTokens(terms);\n const spans = positionalSpans(terms);\n let out = '';\n let quoted = false;\n let offset = 0;\n const pieces = terms.split(RUN); // split keeps captured runs at odd indices\n for (let i = 0; i < pieces.length; i++) {\n if (i % 2 === 0) {\n for (const ch of pieces[i]) if (ch === '\"') quoted = !quoted;\n out += pieces[i];\n offset += pieces[i].length;\n continue;\n }\n const protectedRun = inPositionalSpan(offset, spans);\n if (quoted || protectedRun) {\n out += pieces[i]; // an author's phrase is matched as written\n offset += pieces[i].length;\n continue;\n }\n const m = out.match(QUALIFIER);\n if (m) {\n out = `${out.slice(0, m.index)}${m[1]}${runQuery(pieces[i], `${m[2]}${m[3]}_seg:`)}`;\n } else {\n out += runQuery(pieces[i], SIDECAR_COLUMNS);\n }\n offset += pieces[i].length;\n }\n return out;\n}\n\n// FTS tokenizers compare letters without combining marks. Keep the authored string for output and\n// offsets, but use this key when JS needs to compare a matched word to its source segment.\nexport function foldForSearch(text: string): string {\n return text.normalize('NFD').replace(/\\p{M}/gu, '').toLowerCase();\n}\n"],"names":["UNSPACED_SCRIPTS","foldForSearch","hasUnspacedRun","matchesSearchPhrase","searchTokens","segmentField","segmentMatch","unspacedRuns","RUN_BODY","RUN","RegExp","HAS_RUN","text","test","tokens","matchAll","match","start","index","end","length","cursor","slice","run","runStart","push","unspaced","runEnd","phrase","haystack","matches","i","query","authored","includes","stemmer","Array","from","m","graphemeSegmenter","PUNCTUATION","BARRIER","graphemes","Intl","Segmenter","undefined","granularity","segment","s","splitOnPunctuation","groups","g","filter","out","replace","body","map","join","trim","QUALIFIER","SIDECAR_COLUMNS","AUTHORED_COLUMNS","QUOTED_RUN_TOKEN","FTS_WORD","isFtsWord","char","quoteEnd","nearEnd","open","depth","quoted","positionalSpans","terms","startsWith","spans","qualified","operand","Math","max","routePositionalSpans","span","inPositionalSpan","some","runQuery","columnPrefix","quoteSingle","quotedRunTokens","qualifier","offset","pieces","split","ch","protectedRun","normalize","toLowerCase"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/text/strip.ts"],"sourcesContent":["import type { Block } from '../chunk/index.ts';\nimport { extractTexts, parse } from '../chunk/index.ts';\nimport { normalizeText } from '../scan/frontmatter.ts';\n\n// Flat prose from blocks a caller already parsed, via the same extractor the chunker uses\n// (src/chunk) -- words and vectors read one definition of \"the prose of this note\".\nexport function textFromBlocks(blocks: Block[]): string {\n return normalizeText(extractTexts(blocks));\n}\n\n// Thin parse + delegate, for callers with no blocks of their own already.\nexport function stripText(value: string): string {\n return textFromBlocks(parse(value));\n}\n"],"names":["stripText","textFromBlocks","blocks","normalizeText","extractTexts","value","parse"],"mappings":";;;;;;;;;;;QAWgBA;eAAAA;;QALAC;eAAAA;;;uBALoB;6BACN;AAIvB,SAASA,eAAeC,MAAe;IAC5C,OAAOC,IAAAA,4BAAa,EAACC,IAAAA,qBAAY,EAACF;AACpC;AAGO,SAASF,UAAUK,KAAa;IACrC,OAAOJ,eAAeK,IAAAA,cAAK,EAACD;AAC9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch-claim.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { Worker } from 'node:worker_threads';\nimport { SenseError } from './errors.ts';\nimport { resolveWorkerFile } from './lib/worker-file.ts';\nimport { reviveError, type WorkerErrorPayload } from './scan/worker-error.ts';\nimport { createConnection } from './store/sqlite/connection.ts';\nimport { BEGIN_WRITE, withTransaction } from './store/transaction.ts';\n\nexport const WATCH_CLAIM_FILENAME = '.sense-watch.db';\nexport const WATCH_HEARTBEAT_INTERVAL_MS = 5_000;\nexport const WATCH_STALE_HEARTBEAT_MS = 15_000;\nconst CLAIM_BUSY_TIMEOUT_MS = 30_000;\n\nexport interface WatchClaimRecord {\n token: string;\n pid: number;\n heartbeatMs: number;\n}\n\ninterface ClaimRow {\n token: string;\n pid: number;\n heartbeat_ms: number;\n}\n\nexport interface WatchClaimWorkerData {\n configDir: string;\n force: boolean;\n heartbeatIntervalMs: number;\n pid: number;\n token: string;\n}\n\nexport type WatchClaimWorkerMessage = { type: 'acquired' } | { type: 'stopped' } | { type: 'failure'; error: WorkerErrorPayload };\n\nexport interface WatchClaimController {\n close(): Promise<void>;\n}\n\nfunction claimPath(configDir: string): string {\n return join(configDir, WATCH_CLAIM_FILENAME);\n}\n\nfunction changedOne(changes: number | bigint): boolean {\n return changes === 1 || changes === BigInt(1);\n}\n\n// This database is separate from the selected search store so its heartbeat remains writable while\n// a DuckDB, Turso, or SQLite reconciliation owns the cache connection or blocks the main thread.\nexport class WatchClaimDatabase {\n private readonly db: DatabaseSync;\n private readonly conn: ReturnType<typeof createConnection>;\n\n constructor(configDir: string) {\n const db = new DatabaseSync(claimPath(configDir));\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n db.exec('CREATE TABLE IF NOT EXISTS watch_claim (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), token TEXT NOT NULL, pid INTEGER NOT NULL, heartbeat_ms INTEGER NOT NULL)');\n } catch (err) {\n try {\n db.close();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch claim setup and database cleanup both failed');\n }\n throw err;\n }\n this.db = db;\n this.conn = createConnection(db);\n }\n\n async acquire(token: string, pid: number, force: boolean): Promise<void> {\n await withTransaction(\n this.conn,\n async () => {\n const current = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n if (current && !force) {\n const age = Date.now() - current.heartbeat_ms;\n if (age >= 0 && age < WATCH_STALE_HEARTBEAT_MS) throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n this.db.prepare('INSERT INTO watch_claim (singleton, token, pid, heartbeat_ms) VALUES (1, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET token = excluded.token, pid = excluded.pid, heartbeat_ms = excluded.heartbeat_ms').run(token, pid, Date.now());\n },\n BEGIN_WRITE\n );\n }\n\n renew(token: string): boolean {\n return changedOne(this.db.prepare('UPDATE watch_claim SET heartbeat_ms = ? WHERE singleton = 1 AND token = ?').run(Date.now(), token).changes);\n }\n\n release(token: string): boolean {\n return changedOne(this.db.prepare('DELETE FROM watch_claim WHERE singleton = 1 AND token = ?').run(token).changes);\n }\n\n read(): WatchClaimRecord | null {\n const row = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// Status opens the coordinator read-only and therefore cannot create either the file or its schema.\nexport function readWatchClaim(configDir: string): WatchClaimRecord | null {\n const path = claimPath(configDir);\n if (!existsSync(path)) return null;\n let db: DatabaseSync;\n try {\n db = new DatabaseSync(path, { readOnly: true });\n } catch (err) {\n if (!existsSync(path)) return null;\n throw err;\n }\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n const row = db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n } catch (err) {\n if (/no such table: watch_claim/.test((err as Error).message)) return null;\n throw err;\n } finally {\n db.close();\n }\n}\n\nexport async function startWatchClaim(configDir: string, force: boolean, heartbeatIntervalMs: number, onFailure: (err: Error) => void): Promise<WatchClaimController> {\n const workerData: WatchClaimWorkerData = { configDir, force, heartbeatIntervalMs, pid: process.pid, token: randomUUID() };\n const worker = new Worker(resolveWorkerFile('watch-heartbeat'), { workerData });\n let acquired = false;\n let closing = false;\n let exited = false;\n let failureReported = false;\n let closeError: Error | null = null;\n let closePromise: Promise<void> | undefined;\n let resolveExit!: () => void;\n const exit = new Promise<void>((resolve) => {\n resolveExit = resolve;\n });\n let resolveStart!: () => void;\n let rejectStart!: (err: Error) => void;\n const start = new Promise<void>((resolve, reject) => {\n resolveStart = resolve;\n rejectStart = reject;\n });\n\n const fail = (err: Error): void => {\n if (failureReported) return;\n failureReported = true;\n if (!acquired) rejectStart(err);\n else if (closing) closeError ??= err;\n else onFailure(err);\n };\n worker.on('message', (message: WatchClaimWorkerMessage) => {\n if (message.type === 'acquired') {\n acquired = true;\n resolveStart();\n } else if (message.type === 'failure') {\n fail(reviveError(message.error));\n }\n });\n worker.on('error', fail);\n worker.on('exit', (code) => {\n exited = true;\n resolveExit();\n if (closing && code !== 0) closeError ??= new Error(`watch heartbeat worker stopped with exit code ${code}`);\n else if (!closing && !failureReported) fail(new Error(`watch heartbeat worker stopped unexpectedly with exit code ${code}`));\n });\n\n try {\n await start;\n } catch (err) {\n closing = true;\n if (!exited) await worker.terminate();\n await exit;\n throw err;\n }\n\n return {\n close(): Promise<void> {\n closePromise ??= (async () => {\n closing = true;\n if (!exited) {\n try {\n worker.postMessage({ type: 'stop' });\n } catch (err) {\n closeError ??= err instanceof Error ? err : new Error(String(err));\n await worker.terminate();\n }\n }\n await exit;\n if (closeError) throw closeError;\n })();\n return closePromise;\n },\n };\n}\n"],"names":["WATCH_CLAIM_FILENAME","WATCH_HEARTBEAT_INTERVAL_MS","WATCH_STALE_HEARTBEAT_MS","WatchClaimDatabase","readWatchClaim","startWatchClaim","CLAIM_BUSY_TIMEOUT_MS","claimPath","configDir","join","changedOne","changes","BigInt","db","DatabaseSync","exec","err","close","closeError","AggregateError","conn","createConnection","acquire","token","pid","force","withTransaction","current","age","prepare","get","Date","now","heartbeat_ms","SenseError","Math","round","run","BEGIN_WRITE","renew","release","read","row","heartbeatMs","path","existsSync","readOnly","test","message","heartbeatIntervalMs","onFailure","workerData","worker","acquired","closing","exited","failureReported","closePromise","resolveExit","exit","resolveStart","rejectStart","start","fail","process","randomUUID","Worker","resolveWorkerFile","Promise","resolve","reject","on","type","reviveError","error","code","Error","terminate","postMessage","String"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/watch-claim.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { Worker } from 'node:worker_threads';\nimport { SenseError } from './errors.ts';\nimport { resolveWorkerFile } from './lib/worker-file.ts';\nimport { reviveError, type WorkerErrorPayload } from './scan/worker-error.ts';\nimport { createConnection } from './store/sqlite/connection.ts';\nimport { BEGIN_WRITE, withTransaction } from './store/transaction.ts';\n\nexport const WATCH_CLAIM_FILENAME = '.sense-watch.db';\nexport const WATCH_HEARTBEAT_INTERVAL_MS = 5_000;\nexport const WATCH_STALE_HEARTBEAT_MS = 15_000;\nconst CLAIM_BUSY_TIMEOUT_MS = 30_000;\n\nexport interface WatchClaimRecord {\n token: string;\n pid: number;\n heartbeatMs: number;\n}\n\ninterface ClaimRow {\n token: string;\n pid: number;\n heartbeat_ms: number;\n}\n\nexport interface WatchClaimWorkerData {\n configDir: string;\n force: boolean;\n heartbeatIntervalMs: number;\n pid: number;\n token: string;\n}\n\nexport type WatchClaimWorkerMessage = { type: 'acquired' } | { type: 'stopped' } | { type: 'failure'; error: WorkerErrorPayload };\n\nexport interface WatchClaimController {\n close(): Promise<void>;\n}\n\nfunction claimPath(configDir: string): string {\n return join(configDir, WATCH_CLAIM_FILENAME);\n}\n\nfunction changedOne(changes: number | bigint): boolean {\n return changes === 1 || changes === BigInt(1);\n}\n\n// This database is separate from the selected search store so its heartbeat remains writable while\n// a DuckDB, Turso, or SQLite reconciliation owns the cache connection or blocks the main thread.\nexport class WatchClaimDatabase {\n private readonly db: DatabaseSync;\n private readonly conn: ReturnType<typeof createConnection>;\n\n constructor(configDir: string) {\n const db = new DatabaseSync(claimPath(configDir));\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n db.exec('CREATE TABLE IF NOT EXISTS watch_claim (singleton INTEGER PRIMARY KEY CHECK (singleton = 1), token TEXT NOT NULL, pid INTEGER NOT NULL, heartbeat_ms INTEGER NOT NULL)');\n } catch (err) {\n try {\n db.close();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch claim setup and database cleanup both failed');\n }\n throw err;\n }\n this.db = db;\n this.conn = createConnection(db);\n }\n\n async acquire(token: string, pid: number, force: boolean): Promise<void> {\n await withTransaction(\n this.conn,\n async () => {\n const current = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n if (current && !force) {\n const age = Date.now() - current.heartbeat_ms;\n if (age >= 0 && age < WATCH_STALE_HEARTBEAT_MS) throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n this.db.prepare('INSERT INTO watch_claim (singleton, token, pid, heartbeat_ms) VALUES (1, ?, ?, ?) ON CONFLICT(singleton) DO UPDATE SET token = excluded.token, pid = excluded.pid, heartbeat_ms = excluded.heartbeat_ms').run(token, pid, Date.now());\n },\n BEGIN_WRITE\n );\n }\n\n renew(token: string): boolean {\n return changedOne(this.db.prepare('UPDATE watch_claim SET heartbeat_ms = ? WHERE singleton = 1 AND token = ?').run(Date.now(), token).changes);\n }\n\n release(token: string): boolean {\n return changedOne(this.db.prepare('DELETE FROM watch_claim WHERE singleton = 1 AND token = ?').run(token).changes);\n }\n\n read(): WatchClaimRecord | null {\n const row = this.db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n }\n\n close(): void {\n this.db.close();\n }\n}\n\n// Status opens the coordinator read-only and therefore cannot create either the file or its schema.\nexport function readWatchClaim(configDir: string): WatchClaimRecord | null {\n const path = claimPath(configDir);\n if (!existsSync(path)) return null;\n let db: DatabaseSync;\n try {\n db = new DatabaseSync(path, { readOnly: true });\n } catch (err) {\n if (!existsSync(path)) return null;\n throw err;\n }\n try {\n db.exec(`PRAGMA busy_timeout = ${CLAIM_BUSY_TIMEOUT_MS}`);\n const row = db.prepare('SELECT token, pid, heartbeat_ms FROM watch_claim WHERE singleton = 1').get() as ClaimRow | undefined;\n return row ? { token: row.token, pid: row.pid, heartbeatMs: row.heartbeat_ms } : null;\n } catch (err) {\n if (/no such table: watch_claim/.test((err as Error).message)) return null;\n throw err;\n } finally {\n db.close();\n }\n}\n\nexport async function startWatchClaim(configDir: string, force: boolean, heartbeatIntervalMs: number, onFailure: (err: Error) => void): Promise<WatchClaimController> {\n const workerData: WatchClaimWorkerData = { configDir, force, heartbeatIntervalMs, pid: process.pid, token: randomUUID() };\n const worker = new Worker(resolveWorkerFile('watch-heartbeat'), { workerData });\n let acquired = false;\n let closing = false;\n let exited = false;\n let failureReported = false;\n let closeError: Error | null = null;\n let closePromise: Promise<void> | undefined;\n let resolveExit!: () => void;\n const exit = new Promise<void>((resolve) => {\n resolveExit = resolve;\n });\n let resolveStart!: () => void;\n let rejectStart!: (err: Error) => void;\n const start = new Promise<void>((resolve, reject) => {\n resolveStart = resolve;\n rejectStart = reject;\n });\n\n const fail = (err: Error): void => {\n if (failureReported) return;\n failureReported = true;\n if (!acquired) rejectStart(err);\n else if (closing) closeError ??= err;\n else onFailure(err);\n };\n worker.on('message', (message: WatchClaimWorkerMessage) => {\n if (message.type === 'acquired') {\n acquired = true;\n resolveStart();\n } else if (message.type === 'failure') {\n fail(reviveError(message.error));\n }\n });\n worker.on('error', fail);\n worker.on('exit', (code) => {\n exited = true;\n resolveExit();\n if (closing && code !== 0) closeError ??= new Error(`watch heartbeat worker stopped with exit code ${code}`);\n else if (!closing && !failureReported) fail(new Error(`watch heartbeat worker stopped unexpectedly with exit code ${code}`));\n });\n\n try {\n await start;\n } catch (err) {\n closing = true;\n if (!exited) await worker.terminate();\n await exit;\n throw err;\n }\n\n return {\n close(): Promise<void> {\n closePromise ??= (async () => {\n closing = true;\n if (!exited) {\n try {\n worker.postMessage({ type: 'stop' });\n } catch (err) {\n closeError ??= err instanceof Error ? err : new Error(String(err));\n await worker.terminate();\n }\n }\n await exit;\n if (closeError) throw closeError;\n })();\n return closePromise;\n },\n };\n}\n"],"names":["WATCH_CLAIM_FILENAME","WATCH_HEARTBEAT_INTERVAL_MS","WATCH_STALE_HEARTBEAT_MS","WatchClaimDatabase","readWatchClaim","startWatchClaim","CLAIM_BUSY_TIMEOUT_MS","claimPath","configDir","join","changedOne","changes","BigInt","db","DatabaseSync","exec","err","close","closeError","AggregateError","conn","createConnection","acquire","token","pid","force","withTransaction","current","age","prepare","get","Date","now","heartbeat_ms","SenseError","Math","round","run","BEGIN_WRITE","renew","release","read","row","heartbeatMs","path","existsSync","readOnly","test","message","heartbeatIntervalMs","onFailure","workerData","worker","acquired","closing","exited","failureReported","closePromise","resolveExit","exit","resolveStart","rejectStart","start","fail","process","randomUUID","Worker","resolveWorkerFile","Promise","resolve","reject","on","type","reviveError","error","code","Error","terminate","postMessage","String"],"mappings":"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"}
|
package/dist/cjs/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 { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { shouldReconcileWatchNotification } from './lib/watch-notification.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch prepares the index for snapshot queries; filesystem events request debounced builds\n// and periodic reconciliation recovers missed notifications.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\n/** Options for the root {@link runWatch} API. */\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n /** Caller abort cancels provider work, skips final preparation, and resolves after cleanup. */\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\nfunction isCallerAbort(signal: AbortSignal | undefined, reason: unknown): boolean {\n return signal?.aborted === true && Object.is(reason, signal.reason);\n}\n\nfunction errorMessage(reason: unknown): string {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\n/**\n * Runs in the foreground. Caller abort waits for active native work and cleanup; SIGINT/SIGTERM\n * also run one final freshness pass. Cleanup failures reject either shutdown path.\n */\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n if (opts.signal?.aborted) return;\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n let watcher: ReturnType<typeof fsWatch> | undefined;\n let startup = true;\n let startupNotification = false;\n let scheduleReconcile: (() => void) | undefined;\n let stopping = false;\n let callerCanceled = false;\n try {\n opts.signal?.throwIfAborted();\n // Subscribe before the first build. Native watchers have no ready barrier, so startup also\n // performs an unconditional catch-up pass after the initial build.\n watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (!shouldReconcileWatchNotification(filename)) return;\n if (startup) startupNotification = true;\n else scheduleReconcile?.();\n });\n watcher.on('error', (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n\n const startupEvents: Array<{ parsed: number; total: number; warnings: string[] }> = [];\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg, { signal: opts.signal });\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n\n startupEvents.push({ parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n // Always reopen once after subscribing, then drain notifications observed while startup was\n // in progress. Each pass closes before the next one starts, so no stores overlap.\n do {\n startupNotification = false;\n opts.signal?.throwIfAborted();\n const { store, parsed, warnings } = await openStore(cfg, { signal: opts.signal });\n try {\n if (parsed > 0 || warnings.length > 0) startupEvents.push({ parsed, total: await docCount(store), warnings });\n } finally {\n await store.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n } while (startupNotification && !opts.signal?.aborted);\n startup = false;\n\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, { signal: opts.signal });\n try {\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 if (isCallerAbort(opts.signal, err)) return;\n onEvent({ type: 'reconcile-error', message: errorMessage(err) });\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 // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error, canceledByCaller = false) => {\n shutdownReason ??= reason ?? null;\n callerCanceled ||= canceledByCaller;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopForCaller);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher?.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason && !callerCanceled) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n const stopForCaller = () => shutdown(undefined, true);\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) stopForCaller();\n else {\n opts.signal?.addEventListener('abort', stopForCaller, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping) {\n for (const event of startupEvents) {\n if (stopping) break;\n if (event.warnings.length > 0 || event.parsed > 0) onEvent({ type: 'reconciled', ...event });\n }\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n const canceledByCaller = isCallerAbort(opts.signal, err);\n watcher?.close();\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n if (canceledByCaller) return;\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["runWatch","DEBOUNCE_MS","isCallerAbort","signal","reason","aborted","Object","is","errorMessage","Error","message","String","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","claimIntervalMs","rootDir","configDir","claimFailure","requestShutdown","claim","claimCloseStarted","closeClaim","watcher","startup","startupNotification","scheduleReconcile","stopping","callerCanceled","startupEvents","initialStore","dbPath","initialWarnings","initialParsed","initialTotal","store","parsed","warnings","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","reconcileTimer","err","canceledByCaller","closeError","heartbeatIntervalMs","WATCH_HEARTBEAT_INTERVAL_MS","Number","isFinite","SenseError","Math","min","baseDir","startWatchClaim","force","close","throwIfAborted","fsWatch","recursive","_event","filename","shouldReconcileWatchNotification","on","openStore","length","docCount","push","total","alwaysEmit","type","then","again","clearTimeout","setTimeout","setInterval","guardedTick","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","shutdownTask","finalError","finalStore","process","off","stopNormally","removeEventListener","stopForCaller","clearInterval","AggregateError","undefined","once","addEventListener","event"],"mappings":";;;;+BAoCsBA;;;eAAAA;;;sBApCW;wBAEN;6BACC;mCACqB;uBACb;4BACyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE7D,4FAA4F;AAC5F,6DAA6D;AAC7D,IAAMC,cAAc;AAcpB,SAASC,cAAcC,MAA+B,EAAEC,MAAe;IACrE,OAAOD,CAAAA,mBAAAA,6BAAAA,OAAQE,OAAO,MAAK,QAAQC,OAAOC,EAAE,CAACH,QAAQD,OAAOC,MAAM;AACpE;AAEA,SAASI,aAAaJ,MAAe;IACnC,OAAOA,AAAM,YAANA,QAAkBK,SAAQL,OAAOM,OAAO,GAAGC,OAAOP;AAC3D;AAMO,SAAeJ;wCAASY,GAAmB;YAAEC,MAElCA,eACGA,kBACSA,2BAIZD,cACEA,gBAG6BC,aAX3CA,cACEC,SACAC,YACAC,qBAGAC,iBACAC,SACAC,WACFC,cACAC,iBACEC,OAIFC,mBACEC,YAIFC,SACAC,SACAC,qBACAC,mBACAC,UACAC,gBAEFjB,eAsBAA,eAiBiCA,eA1B3BkB,eACoF,MAA3EC,cAAcC,QAAkBC,iBAAyBC,eACpEC,oBAeFvB,eAOAA,eANoC,OAA5BwB,OAAOC,QAAQC,oBAarBC,SACAC,cAEEC,MAgBAC,YAWAC,cASFC,eAUEC,gBAmECC,KACDC,kBAKGC;;;;;oBAlMuCpC,OAAAA,oEAAqB,CAAC;oBACxE,KAAIA,eAAAA,KAAKV,MAAM,cAAXU,mCAAAA,aAAaR,OAAO,EAAE;;;oBACpBS,WAAUD,gBAAAA,KAAKC,OAAO,cAAZD,2BAAAA,gBAAiB,YAAO;oBAClCE,cAAaF,mBAAAA,KAAKE,UAAU,cAAfF,8BAAAA,mBAAmBZ;oBAChCe,uBAAsBH,4BAAAA,KAAKqC,mBAAmB,cAAxBrC,uCAAAA,4BAA4BsC,yCAA2B;oBACnF,IAAI,CAACC,OAAOC,QAAQ,CAACrC,wBAAwBA,uBAAuB,GAAG,MAAM,IAAIsC,oBAAU,CAAC,kBAAkB;oBAC9G,gGAAgG;oBAC1FrC,kBAAkBsC,KAAKC,GAAG,CAACxC,qBAAqBmC,yCAA2B;oBAC3EjC,WAAUN,eAAAA,IAAIM,OAAO,cAAXN,0BAAAA,eAAeA,IAAI6C,OAAO;oBACpCtC,aAAYP,iBAAAA,IAAIO,SAAS,cAAbP,4BAAAA,iBAAiBM;oBAC/BE,eAA6B;oBAEnB;;wBAAMsC,IAAAA,6BAAe,EAACvC,YAAWN,cAAAA,KAAK8C,KAAK,cAAV9C,yBAAAA,cAAc,OAAOI,iBAAiB,SAAC8B;4BACpF3B,yBAAAA,0BAAAA,eAAAA,eAAiB2B;4BACjB1B,4BAAAA,sCAAAA,gBAAkB0B;wBACpB;;;oBAHMzB,QAAQ;oBAIVC,oBAAoB;oBAClBC,aAAa;wBACjBD,oBAAoB;wBACpB,OAAOD,MAAMsC,KAAK;oBACpB;oBAEIlC,UAAU;oBACVC,sBAAsB;oBAEtBE,WAAW;oBACXC,iBAAiB;;;;;;;;;qBAEnBjB,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBAC3B,2FAA2F;oBAC3F,mEAAmE;oBACnEpC,UAAUqC,IAAAA,aAAO,EAAC5C,SAAS;wBAAE6C,WAAW;oBAAK,GAAG,SAACC,QAAQC;wBACvD,IAAI,CAACC,IAAAA,qDAAgC,EAACD,WAAW;wBACjD,IAAIvC,SAASC,sBAAsB;6BAC9BC,8BAAAA,wCAAAA;oBACP;oBACAH,QAAQ0C,EAAE,CAAC,SAAS,SAACpB;wBACnB3B,yBAAAA,0BAAAA,eAAAA,eAAiB2B;wBACjB1B,4BAAAA,sCAAAA,gBAAkB0B;oBACpB;oBAEMhB;oBACoF;;wBAAMqC,IAAAA,kBAAS,EAACxD,KAAK;4BAAET,QAAQU,KAAKV,MAAM;wBAAC;;;oBAA3C,OAAA,eAA3E6B,eAA2E,KAAlFK,OAAqBJ,SAA6D,KAA7DA,QAAkBC,kBAA2C,KAArDK,UAAmCJ,gBAAkB,KAA1BG;oBAC5DF,eAAe;;;;;;;;;yBAGFF,CAAAA,gBAAgBmC,MAAM,GAAG,KAAKlC,gBAAgB,CAAA,GAA9CD;;;;oBAAkD;;wBAAMoC,IAAAA,iBAAQ,EAACtC;;;2BAAf;;;;;;2BAA+B;;;oBADhG,mFAAmF;oBACnFI;;;;;;oBAEA;;wBAAMJ,aAAa4B,KAAK;;;oBAAxB;;;;;qBAEF/C,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBAC3B,IAAIzC,cAAc,MAAMA;oBAExBW,cAAcwC,IAAI,CAAC;wBAAEjC,QAAQH;wBAAeqC,OAAOpC;wBAAcG,UAAUL;oBAAgB;;;oBAIzFP,sBAAsB;qBACtBd,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBACS;;wBAAMO,IAAAA,kBAAS,EAACxD,KAAK;4BAAET,QAAQU,KAAKV,MAAM;wBAAC;;;oBAA3C,QAAA,eAA5BkC,QAA4B,MAA5BA,OAAOC,SAAqB,MAArBA,QAAQC,WAAa,MAAbA;;;;;;;;;yBAEjBD,CAAAA,SAAS,KAAKC,SAAS8B,MAAM,GAAG,CAAA,GAAhC/B;;;;wBAAmCP,cAAcwC,IAAI;;wBAAGjC,QAAAA;;oBAAe;;wBAAMgC,IAAAA,iBAAQ,EAACjC;;;oBAAnDN,QAAAA;+BAA6ByC,QAAO,qBAAuBjC,WAAAA;;;;;;;;;oBAElG;;wBAAMF,MAAMuB,KAAK;;;oBAAjB;;;;;qBAEF/C,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAagD,cAAc;oBAC3B,IAAIzC,cAAc,MAAMA;;;wBACjBO,uBAAuB,GAACd,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAaR,OAAO,GAA5CsB;;;;;;oBACTD,UAAU;oBAEV,6FAA6F;oBAC7F,qFAAqF;oBACjFc,UAAgC;oBAChCC,eAA+B;oBAE7BC,OAAO,cAAO+B;;gCAEoB,MAA5BpC,OAAOC,QAAQC,gBAQhBQ;;;;;;;;;;wCAR6B;;4CAAMqB,IAAAA,kBAAS,EAACxD,KAAK;gDAAET,QAAQU,KAAKV,MAAM;4CAAC;;;wCAA3C,OAAA,eAA5BkC,QAA4B,KAA5BA,OAAOC,SAAqB,KAArBA,QAAQC,WAAa,KAAbA;;;;;;;;;6CAEjBkC,CAAAA,cAAcnC,SAAS,KAAKC,SAAS8B,MAAM,GAAG,CAAA,GAA9CI;;;;;4CACQC,MAAM;4CAAcpC,QAAAA;;wCAAe;;4CAAMgC,IAAAA,iBAAQ,EAACjC;;;wCAA5DvB;kDAAsC0D,QAAO,oBAAuBjC,WAAAA;;;;;;;;;wCAGtE;;4CAAMF,MAAMuB,KAAK;;;wCAAjB;;;;;;;;;;wCAEKb;wCACP,IAAI7C,cAAcW,KAAKV,MAAM,EAAE4C,MAAM;;;wCACrCjC,QAAQ;4CAAE4D,MAAM;4CAAmBhE,SAASF,aAAauC;wCAAK;;;;;;;;;;;wBAElE;;oBAEMJ,aAAa,qBAAC8B;wBAClBjC,UAAUE,KAAK+B,YAAYE,IAAI,CAAC;4BAC9BnC,UAAU;4BACV,IAAIC,iBAAiB,QAAQ,CAACZ,UAAU;gCACtC,IAAM+C,QAAQnC;gCACdA,eAAe;gCACfE,WAAWiC;4BACb;wBACF;oBACF;oBAEMhC,eAAe,sBAAC6B;wBACpB,IAAI5C,UAAU;wBACd,IAAIW,SAAS;4BACXC,eAAeA,gBAAgBgC;4BAC/B;wBACF;wBACA9B,WAAW8B;oBACb;oBAEI5B,gBAAuC;oBAC3C,gGAAgG;oBAChG,mGAAmG;oBACnGjB,oBAAoB;wBAClB,IAAIiB,eAAegC,aAAahC;wBAChCA,gBAAgBiC,WAAW;4BACzBjC,gBAAgB;4BAChBD,aAAa;wBACf,GAAG7B;oBACL;oBACM+B,iBAAiBiC,YACrBC,IAAAA,0BAAW,EACT;;;;;oCAAYpC,aAAa;;;;uBACzB;+BAAMf;wBAERb;oBAGK;;wBAAM,IAAIiE,QAAc,SAACC,iBAAiBC;gCA2CtCtE;4BA1CT,IAAIuE,iBAA+B;4BACnC,IAAMC,WAAW,kBAACjF;oCAAgB4C,oFAAmB;gCACnDoC,2BAAAA,4BAAAA,iBAAAA,iBAAmBhF,mBAAAA,oBAAAA,SAAU;gCAC7B0B,mBAAAA,iBAAmBkB;gCACnB,IAAInB,UAAU;gCACdA,WAAW;gCACX,IAAMyD,eAAe,AAAC;;4CAGpBzE,cAKI0E,YAK8B,MAAfC,YAGVzC,KAKAA;;;;oDApBT0C,QAAQC,GAAG,CAAC,UAAUC;oDACtBF,QAAQC,GAAG,CAAC,WAAWC;qDACvB9E,eAAAA,KAAKV,MAAM,cAAXU,mCAAAA,aAAa+E,mBAAmB,CAAC,SAASC;oDAC1CC,cAAchD;oDACd,IAAID,eAAegC,aAAahC;oDAChCpB,oBAAAA,8BAAAA,QAASmC,KAAK;oDACd;;wDAAMpB;;;oDAAN;;;;;;;;;yDAKM,CAAA,CAAC4C,kBAAkB,CAACtD,cAAa,GAAjC;;;;oDAC4B;;wDAAMsC,IAAAA,kBAAS,EAACxD;;;oDAAhB,OAAA,eAAf4E,aAAe,KAAtBnD;oDACR;;wDAAMmD,WAAW5B,KAAK;;;oDAAtB;;;;;;;;oDAEKb;oDACPwC,aAAaxC;;;;;;;;;;;;oDAGb;;wDAAMvB;;;oDAAN;;;;;;oDACOuB;oDACP,IAAIwC,YAAY,MAAM,IAAIQ;wDAAgBR;wDAAYxC;uDAAM;oDAC5D,MAAMA;;oDAER,IAAIwC,YAAY,MAAMA;oDACtB,IAAIH,gBAAgB,MAAMA;;;;;;oCAC5B;;gCACAE,aAAaX,IAAI,CAACO,iBAAiBC;4BACrC;4BACA,IAAMQ,eAAe;uCAAMN;;4BAC3B,IAAMQ,gBAAgB;uCAAMR,SAASW,WAAW;;4BAChD3E,kBAAkBgE;4BAClBI,QAAQQ,IAAI,CAAC,UAAUN;4BACvBF,QAAQQ,IAAI,CAAC,WAAWN;4BACxB,IAAIvE,cAAciE,SAASjE;iCACtB,KAAIP,eAAAA,KAAKV,MAAM,cAAXU,mCAAAA,aAAaR,OAAO,EAAEwF;iCAC1B;oCACHhF;iCAAAA,gBAAAA,KAAKV,MAAM,cAAXU,oCAAAA,cAAaqF,gBAAgB,CAAC,SAASL,eAAe;oCAAEI,MAAM;gCAAK;gCACnE,IAAI;oCACFnF,QAAQ;wCAAE4D,MAAM;wCAAWxD,SAAAA;wCAASe,QAAAA;oCAAO;oCAC3C,IAAI,CAACJ,UAAU;4CACR,kCAAA,2BAAA;;4CAAL,QAAK,YAAeE,kCAAf,SAAA,6BAAA,QAAA,yBAAA,iCAA8B;gDAA9B,IAAMoE,QAAN;gDACH,IAAItE,UAAU;gDACd,IAAIsE,MAAM5D,QAAQ,CAAC8B,MAAM,GAAG,KAAK8B,MAAM7D,MAAM,GAAG,GAAGxB,QAAQ;oDAAE4D,MAAM;mDAAiByB;4CACtF;;4CAHK;4CAAA;;;qDAAA,6BAAA;oDAAA;;;oDAAA;0DAAA;;;;oCAIP;gCACF,EAAE,OAAOpD,KAAK;oCACZsC,SAAStC,AAAG,YAAHA,KAAetC,SAAQsC,MAAM,IAAItC,MAAME,OAAOoC;gCACzD;4BACF;wBACF;;;oBA1DA;;wBAAO;;;oBA2DAA;oBACDC,mBAAmB9C,cAAcW,KAAKV,MAAM,EAAE4C;oBACpDtB,oBAAAA,8BAAAA,QAASmC,KAAK;oBACd,IAAIrC,mBAAmB,MAAMwB;;;;;;;;;oBAE3B;;wBAAMvB;;;oBAAN;;;;;;oBACOyB;oBACP,MAAM,IAAI8C;wBAAgBhD;wBAAKE;uBAAa;;oBAE9C,IAAID,kBAAkB;;;oBACtB,MAAMD;;oBAEN1B,kBAAkB2E;;;;;;;;;;IAEtB"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config/index.ts';\nimport { SenseError } from './errors.ts';\nimport { guardedTick } from './lib/guarded-tick.ts';\nimport { shouldReconcileWatchNotification } from './lib/watch-notification.ts';\nimport { docCount, openStore } from './store/index.ts';\nimport { startWatchClaim, WATCH_HEARTBEAT_INTERVAL_MS } from './watch-claim.ts';\n\n// Watch prepares the index for snapshot queries; filesystem events request debounced builds\n// and periodic reconciliation recovers missed notifications.\nconst DEBOUNCE_MS = 200;\n\nexport type WatchEvent = { type: 'started'; rootDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\n/** Options for the root {@link runWatch} API. */\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n /** Caller abort cancels provider work, skips final preparation, and resolves after cleanup. */\n signal?: AbortSignal;\n debounceMs?: number;\n heartbeatIntervalMs?: number;\n}\n\nfunction isCallerAbort(signal: AbortSignal | undefined, reason: unknown): boolean {\n return signal?.aborted === true && Object.is(reason, signal.reason);\n}\n\nfunction errorMessage(reason: unknown): string {\n return reason instanceof Error ? reason.message : String(reason);\n}\n\n/**\n * Runs in the foreground. Caller abort waits for active native work and cleanup; SIGINT/SIGTERM\n * also run one final freshness pass. Cleanup failures reject either shutdown path.\n */\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n if (opts.signal?.aborted) return;\n const onEvent = opts.onEvent ?? (() => {});\n const debounceMs = opts.debounceMs ?? DEBOUNCE_MS;\n const reconcileIntervalMs = opts.heartbeatIntervalMs ?? WATCH_HEARTBEAT_INTERVAL_MS;\n if (!Number.isFinite(reconcileIntervalMs) || reconcileIntervalMs <= 0) throw new SenseError('CONFIG_INVALID', 'watch heartbeatIntervalMs must be a finite positive number');\n // Reconciliation may be parked for a caller, but live ownership must renew before fixed expiry.\n const claimIntervalMs = Math.min(reconcileIntervalMs, WATCH_HEARTBEAT_INTERVAL_MS);\n const rootDir = cfg.rootDir ?? cfg.baseDir;\n const configDir = cfg.configDir ?? rootDir;\n let claimFailure: Error | null = null;\n let requestShutdown: ((reason: Error) => void) | undefined;\n const claim = await startWatchClaim(configDir, opts.force ?? false, claimIntervalMs, (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n let claimCloseStarted = false;\n const closeClaim = (): Promise<void> => {\n claimCloseStarted = true;\n return claim.close();\n };\n let watcher: ReturnType<typeof fsWatch> | undefined;\n let startup = true;\n let startupNotification = false;\n let scheduleReconcile: (() => void) | undefined;\n let stopping = false;\n let callerCanceled = false;\n try {\n opts.signal?.throwIfAborted();\n // Subscribe before the first build. Native watchers have no ready barrier, so startup also\n // performs an unconditional catch-up pass after the initial build.\n watcher = fsWatch(rootDir, { recursive: true }, (_event, filename) => {\n if (!shouldReconcileWatchNotification(filename)) return;\n if (startup) startupNotification = true;\n else scheduleReconcile?.();\n });\n watcher.on('error', (err) => {\n claimFailure ??= err;\n requestShutdown?.(err);\n });\n\n const startupEvents: Array<{ parsed: number; total: number; warnings: string[] }> = [];\n const { store: initialStore, dbPath, warnings: initialWarnings, parsed: initialParsed } = await openStore(cfg, { signal: opts.signal });\n let initialTotal = 0;\n try {\n // total is read before close: an event fires only once nothing is held open again.\n initialTotal = initialWarnings.length > 0 || initialParsed > 0 ? await docCount(initialStore) : 0;\n } finally {\n await initialStore.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n\n startupEvents.push({ parsed: initialParsed, total: initialTotal, warnings: initialWarnings });\n // Always reopen once after subscribing, then drain notifications observed while startup was\n // in progress. Each pass closes before the next one starts, so no stores overlap.\n do {\n startupNotification = false;\n opts.signal?.throwIfAborted();\n const { store, parsed, warnings } = await openStore(cfg, { signal: opts.signal });\n try {\n if (parsed > 0 || warnings.length > 0) startupEvents.push({ parsed, total: await docCount(store), warnings });\n } finally {\n await store.close();\n }\n opts.signal?.throwIfAborted();\n if (claimFailure) throw claimFailure;\n } while (startupNotification && !opts.signal?.aborted);\n startup = false;\n\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, { signal: opts.signal });\n try {\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 if (isCallerAbort(opts.signal, err)) return;\n onEvent({ type: 'reconcile-error', message: errorMessage(err) });\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 // Ignore our own state files, or cache and claim writes would retrigger reconciliation forever.\n // An unresolvable filename reconciles because parsing nothing costs less than missing a real edit.\n scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n requestCycle(true);\n }, debounceMs);\n };\n const reconcileTimer = setInterval(\n guardedTick(\n async () => requestCycle(false),\n () => stopping\n ),\n reconcileIntervalMs\n );\n\n return await new Promise<void>((resolveShutdown, rejectShutdown) => {\n let shutdownReason: Error | null = null;\n const shutdown = (reason?: Error, canceledByCaller = false) => {\n shutdownReason ??= reason ?? null;\n callerCanceled ||= canceledByCaller;\n if (stopping) return;\n stopping = true;\n const shutdownTask = (async () => {\n process.off('SIGINT', stopNormally);\n process.off('SIGTERM', stopNormally);\n opts.signal?.removeEventListener('abort', stopForCaller);\n clearInterval(reconcileTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher?.close();\n await running;\n let finalError: unknown;\n try {\n // A normal stop performs one final freshness pass. Ownership loss drains existing work\n // but starts nothing new because the replacement watcher now owns that responsibility.\n if (!shutdownReason && !callerCanceled) {\n const { store: finalStore } = await openStore(cfg);\n await finalStore.close();\n }\n } catch (err) {\n finalError = err;\n }\n try {\n await closeClaim();\n } catch (err) {\n if (finalError) throw new AggregateError([finalError, err], 'watch shutdown and claim cleanup both failed');\n throw err;\n }\n if (finalError) throw finalError;\n if (shutdownReason) throw shutdownReason;\n })();\n shutdownTask.then(resolveShutdown, rejectShutdown);\n };\n const stopNormally = () => shutdown();\n const stopForCaller = () => shutdown(undefined, true);\n requestShutdown = shutdown;\n process.once('SIGINT', stopNormally);\n process.once('SIGTERM', stopNormally);\n if (claimFailure) shutdown(claimFailure);\n else if (opts.signal?.aborted) stopForCaller();\n else {\n opts.signal?.addEventListener('abort', stopForCaller, { once: true });\n try {\n onEvent({ type: 'started', rootDir, dbPath });\n if (!stopping) {\n for (const event of startupEvents) {\n if (stopping) break;\n if (event.warnings.length > 0 || event.parsed > 0) onEvent({ type: 'reconciled', ...event });\n }\n }\n } catch (err) {\n shutdown(err instanceof Error ? err : new Error(String(err)));\n }\n }\n });\n } catch (err) {\n const canceledByCaller = isCallerAbort(opts.signal, err);\n watcher?.close();\n if (claimCloseStarted) throw err;\n try {\n await closeClaim();\n } catch (closeError) {\n throw new AggregateError([err, closeError], 'watch setup and claim cleanup both failed');\n }\n if (canceledByCaller) return;\n throw err;\n } finally {\n requestShutdown = undefined;\n }\n}\n"],"names":["runWatch","DEBOUNCE_MS","isCallerAbort","signal","reason","aborted","Object","is","errorMessage","Error","message","String","cfg","opts","onEvent","debounceMs","reconcileIntervalMs","claimIntervalMs","rootDir","configDir","claimFailure","requestShutdown","claim","claimCloseStarted","closeClaim","watcher","startup","startupNotification","scheduleReconcile","stopping","callerCanceled","startupEvents","initialStore","dbPath","initialWarnings","initialParsed","initialTotal","store","parsed","warnings","running","queuedAlways","tick","startCycle","requestCycle","debounceTimer","reconcileTimer","err","canceledByCaller","closeError","heartbeatIntervalMs","WATCH_HEARTBEAT_INTERVAL_MS","Number","isFinite","SenseError","Math","min","baseDir","startWatchClaim","force","close","throwIfAborted","fsWatch","recursive","_event","filename","shouldReconcileWatchNotification","on","openStore","length","docCount","push","total","alwaysEmit","type","then","again","clearTimeout","setTimeout","setInterval","guardedTick","Promise","resolveShutdown","rejectShutdown","shutdownReason","shutdown","shutdownTask","finalError","finalStore","process","off","stopNormally","removeEventListener","stopForCaller","clearInterval","AggregateError","undefined","once","addEventListener","event"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/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// Each task is a small file batch. Results stay per-file so one failure does not stop the\n// remaining files in that batch, and no token tree crosses the thread boundary.\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 ParseFileResult = { ok: true; doc: ParsedDoc; warnings: string[]; parseMs: number } | { ok: false; error: WorkerErrorPayload };\nexport type ParseTaskResult = ParseFileResult[];\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(files: ParseTask): ParseTaskResult {\n return files.map((file): ParseFileResult => {\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}\n"],"names":["parseTask","Tinypool","workerData","cfg","featureNames","selected","FEATURES","filter","feature","includes","name","files","map","file","start","process","hrtime","bigint","parseFile","featuresForFile","doc","warnings","parseMs","Number","ok","err","error","serializeError"],"mappings":";;;;+BAgCA;;;eAAwBA;;;+DAhCH;uBAEI;wBAEC;yBAEM;6BAED;;;;;;AAkB/B,sCAAsC;AACtC,IAA8BC,uBAAAA,iBAAQ,CAACC,UAAU,EAAzCC,MAAsBF,qBAAtBE,KAAKC,eAAiBH,qBAAjBG;AACb,4FAA4F;AAC5F,qDAAqD;AACrD,IAAMC,WAAWC,iBAAQ,CAACC,MAAM,CAAC,SAACC;WAAYJ,aAAaK,QAAQ,CAACD,QAAQE,IAAI;;AAEjE,SAASV,UAAUW,KAAgB;IAChD,OAAOA,MAAMC,GAAG,CAAC,SAACC;QAChB,IAAI;YACF,IAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;YACnC,IAA0BC,aAAAA,IAAAA,mBAAS,EAACL,MAAMM,IAAAA,0BAAe,EAACd,UAAUF,KAAKU,OAAOV,MAAxEiB,MAAkBF,WAAlBE,KAAKC,WAAaH,WAAbG;YACb,IAAMC,UAAUC,OAAOR,QAAQC,MAAM,CAACC,MAAM,KAAKH,SAAS;YAC1D,OAAO;gBAAEU,IAAI;gBAAMJ,KAAAA;gBAAKC,UAAAA;gBAAUC,SAAAA;YAAQ;QAC5C,EAAE,OAAOG,KAAK;YACZ,OAAO;gBAAED,IAAI;gBAAOE,OAAOC,IAAAA,6BAAc,EAACF;YAAK;QACjD;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/workers/watch-heartbeat.ts"],"sourcesContent":["import { parentPort, workerData } from 'node:worker_threads';\nimport { SenseError } from '../errors.ts';\nimport { serializeError } from '../scan/worker-error.ts';\nimport { WatchClaimDatabase, type WatchClaimWorkerData, type WatchClaimWorkerMessage } from '../watch-claim.ts';\n\nfunction requireParentPort() {\n const port = parentPort;\n if (!port) throw new Error('watch heartbeat worker requires a parent port');\n return port;\n}\n\nconst port = requireParentPort();\nconst data = workerData as WatchClaimWorkerData;\nlet database: WatchClaimDatabase | undefined;\nlet timer: NodeJS.Timeout | undefined;\nlet stopping = false;\nlet renewing = Promise.resolve();\n\nfunction send(message: WatchClaimWorkerMessage): void {\n port.postMessage(message);\n}\n\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction closeDatabase(): Error | undefined {\n if (timer) clearInterval(timer);\n timer = undefined;\n try {\n database?.close();\n database = undefined;\n return undefined;\n } catch (err) {\n return asError(err);\n }\n}\n\nfunction addFailure(primary: Error, secondary: Error | undefined, message: string): Error {\n return secondary ? new AggregateError([primary, secondary], message) : primary;\n}\n\nfunction fail(err: unknown): void {\n if (stopping) return;\n stopping = true;\n let failure = asError(err);\n try {\n database?.release(data.token);\n } catch (releaseError) {\n failure = addFailure(failure, asError(releaseError), 'watch heartbeat and claim release both failed');\n }\n const closeError = closeDatabase();\n failure = addFailure(failure, closeError, 'watch heartbeat and database cleanup both failed');\n try {\n send({ type: 'failure', error: serializeError(failure) });\n } finally {\n port.close();\n }\n}\n\nasync function stop(): Promise<void> {\n if (stopping) return;\n stopping = true;\n if (timer) clearInterval(timer);\n let failure: Error | undefined;\n try {\n await renewing;\n if (!database?.release(data.token)) throw new SenseError('WATCH_ACTIVE', 'watch ownership was replaced by another watcher');\n } catch (err) {\n failure = asError(err);\n } finally {\n const closeError = closeDatabase();\n if (closeError) failure = failure ? addFailure(failure, closeError, 'watch heartbeat shutdown and database cleanup both failed') : closeError;\n try {\n if (failure) send({ type: 'failure', error: serializeError(failure) });\n else send({ type: 'stopped' });\n } finally {\n port.close();\n }\n }\n}\n\nasync function main(): Promise<void> {\n try {\n database = new WatchClaimDatabase(data.configDir);\n await database.acquire(data.token, data.pid, data.force);\n send({ type: 'acquired' });\n timer = setInterval(() => {\n if (stopping) return;\n renewing = renewing.then(() => {\n if (!database?.renew(data.token)) throw new SenseError('WATCH_ACTIVE', 'watch ownership was replaced by another watcher');\n });\n renewing.catch(fail);\n }, data.heartbeatIntervalMs);\n port.on('message', (message: { type?: string }) => {\n if (message.type === 'stop') void stop();\n });\n } catch (err) {\n fail(err);\n }\n}\n\nvoid main();\n"],"names":["requireParentPort","port","parentPort","Error","data","workerData","database","timer","stopping","renewing","Promise","resolve","send","message","postMessage","asError","value","String","closeDatabase","clearInterval","undefined","close","err","addFailure","primary","secondary","AggregateError","fail","failure","release","token","releaseError","closeError","type","error","serializeError","stop","SenseError","main","WatchClaimDatabase","configDir","acquire","pid","force","setInterval","then","renew","catch","heartbeatIntervalMs","on"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/chunk/extract.ts"],"sourcesContent":["import type { Token } from 'markdown-it';\nimport { parser } from './parser.ts';\nimport type { Block } from './types.ts';\n\n// markdown-it, like mdast, has no wikilink, embed, or %%comment%% token -- literal text resolved\n// by regex passes over non-code text (code/inline code is placeholder-held and spliced back verbatim).\nconst COMMENT_PAIR = /%%[\\s\\S]*?%%/g;\n// An embed always yields its target, whatever follows a pipe (a resize suffix, never an alias);\n// must run before the wikilink regex below, bang included, or that regex matches first.\nconst EMBED = /!\\[\\[([^\\]]+)\\]\\]/g;\nconst WIKILINK = /\\[\\[([^\\]]+)\\]\\]/g;\n// A trailing \" ^id\" is an Obsidian block anchor, valid only at a line's end; a caret elsewhere\n// (mid-line) is ordinary text and is left alone.\nconst BLOCK_ID = / \\^[A-Za-z0-9-]+$/gm;\n// A callout marker is only ever the first thing in a blockquote's text (Obsidian's grammar),\n// so this anchors to blockquote output alone -- never applied to prose in general.\nconst CALLOUT_MARKER = /^\\[!\\w[\\w-]*\\][+-]?[ \\t]?/;\n// An html token's content is raw HTML, block or inline: a full <!-- --> comment is dropped, any\n// remaining tags are stripped, and the text a browser would still render survives.\nconst HTML_COMMENT = /<!--[\\s\\S]*?-->/g;\nconst HTML_TAG = /<[^>]+>/g;\n\nfunction stripHtml(value: string): string {\n return value.replace(HTML_COMMENT, '').replace(HTML_TAG, '');\n}\n\n// A `#anchor` keeps its text (hash dropped), unlike links.ts's parseWikilinkInner (the resolver\n// authority), which discards it entirely -- replicated here since that return value doesn't fit.\nfunction anchorText(base: string): string {\n const hashIdx = base.indexOf('#');\n if (hashIdx === -1) return base.trim();\n const target = base.slice(0, hashIdx).trim();\n const anchor = base.slice(hashIdx + 1).trim();\n return target ? `${target} ${anchor}` : anchor;\n}\n\nfunction wikilinkText(inner: string): string {\n const pipeIdx = inner.indexOf('|');\n return pipeIdx === -1 ? anchorText(inner) : inner.slice(pipeIdx + 1).trim();\n}\n\nfunction embedText(inner: string): string {\n const pipeIdx = inner.indexOf('|');\n return anchorText(pipeIdx === -1 ? inner : inner.slice(0, pipeIdx));\n}\n\nfunction resolveFlavor(text: string): string {\n return text\n .replace(HTML_COMMENT, '')\n .replace(COMMENT_PAIR, '')\n .replace(EMBED, (_, inner: string) => embedText(inner))\n .replace(WIKILINK, (_, inner: string) => wikilinkText(inner))\n .replace(BLOCK_ID, '');\n}\n\n// U+E000 (Private Use Area) never appears in real markdown text, so it is a collision-free\n// placeholder delimiter, spliced back to the literal code value after flavor resolution.\nfunction codePlaceholder(value: string, code: string[]): string {\n const idx = code.push(value) - 1;\n return `\\uE000${idx}\\uE000`;\n}\n\n// GFM's autolink literal ends where its path machine stops: a trail of punctuation is a genuine\n// end (link stops before it) only when trailEndsAt accepts it, else the marks are part of the link.\nconst PUNCT = new Set(['!', '\"', '&', \"'\", ')', '*', ',', '.', ';', ':', '<', '?', '_', '~', ']']);\nconst TRAIL_PUNCT = new Set(['!', '\"', \"'\", ')', '*', ',', '.', ';', ':', '?', '_', '~']);\nconst isAlpha = (c: string | undefined): boolean => c !== undefined && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));\nfunction trailEndsAt(s: string, j: number): boolean {\n for (;;) {\n const c = s[j];\n if (c === undefined) return true;\n if (TRAIL_PUNCT.has(c)) {\n j += 1;\n continue;\n }\n if (c === '&') {\n j += 1;\n if (!isAlpha(s[j])) return false;\n j += 1;\n while (isAlpha(s[j])) j += 1;\n if (s[j] !== ';') return false;\n j += 1;\n continue;\n }\n if (c === ']') {\n j += 1;\n const d = s[j];\n return d === undefined || d === '(' || d === '[' || /\\s/u.test(d ?? ' ');\n }\n if (c === '<') return true;\n if (c !== undefined && /^\\s$/u.test(c)) return true;\n return false;\n }\n}\n// Where the GFM autolink ends in S (the linkifier's text plus the following text), old-style:\n// punctuation that fails the trail test, or a ) with closes <= opens, extends the link.\nfunction gfmAutolinkEnd(s: string): number {\n let open = 0;\n let close = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s[i];\n if (c === '(') {\n open += 1;\n continue;\n }\n if (c === ')' && close < open) {\n close += 1;\n continue;\n }\n if (PUNCT.has(c) && trailEndsAt(s, i)) return i;\n if (c !== undefined && /^\\s$/u.test(c)) return i;\n }\n return s.length;\n}\n// linkify-it links more than GFM's autolink literal (bare domains, ftp://, //host); only the\n// GFM forms (http(s)://, www., email) drop their text.\nfunction isGfmUrl(text: string): boolean {\n return /^https?:\\/\\//i.test(text) || /^www\\./i.test(text);\n}\n// Only a bare address is GFM's email autolink: a url keeps its @, since a path can hold one.\nfunction isGfmEmail(text: string): boolean {\n return !isGfmUrl(text) && text.includes('@');\n}\n// The task-lists plugin injects its checkbox as the item's first inline child.\nfunction isTaskCheckbox(token: Token | undefined): boolean {\n return token?.type === 'html_inline' && /^<input\\b/i.test(token.content);\n}\n// A linkify span can swallow the & of a following entity (span ...c& + text amp; z); the\n// re-emitted lead is then a reference the entity rule never saw, so decode it old-style.\nconst LEAD_ENTITY = /^&[a-z#][a-z0-9]{1,31};/i;\n\n// Split a flat token range into its top-level blocks: nesting 1 opens, nesting 0 is one block.\nfunction topLevelBlocks(tokens: Token[]): Token[][] {\n const out: Token[][] = [];\n let i = 0;\n while (i < tokens.length) {\n const token = tokens[i];\n if (token.nesting === 1) {\n let depth = 1;\n let j = i + 1;\n while (j < tokens.length && depth > 0) {\n depth += tokens[j].nesting === 1 ? 1 : tokens[j].nesting === -1 ? -1 : 0;\n j += 1;\n }\n out.push(tokens.slice(i, j));\n i = j;\n } else if (token.nesting === 0) {\n out.push(tokens.slice(i, i + 1));\n i += 1;\n } else {\n i += 1;\n }\n }\n return out;\n}\n\n// A first line with no source content (a bare task checkbox, whitespace) contributes no line to\n// the value, so its softbreak is dropped; a first line whose content extraction removes (an\n// autolink, an image) still leaves its line ending behind.\nfunction paragraphText(children: Token[], code: string[]): string {\n let k = 0;\n const first = children[0];\n if (isTaskCheckbox(first)) k = 1;\n while (k < children.length && children[k].type === 'text' && children[k].content.trim() === '') k += 1;\n if (k > 0 && children[k]?.type === 'softbreak') return inlineText(children.slice(k + 1), code);\n return inlineText(children, code);\n}\n\nfunction blockText(tokens: Token[], code: string[]): string {\n const first = tokens[0];\n if (!first) return '';\n switch (first.type) {\n case 'heading_open':\n case 'paragraph_open': {\n const inline = tokens.find((t) => t.type === 'inline');\n return inline ? paragraphText(inline.children ?? [], code) : '';\n }\n case 'fence':\n case 'code_block':\n // Fence and indented code tokens carry the closing newline their lines were joined with.\n return codePlaceholder(first.content.replace(/\\n$/, ''), code);\n case 'table_open':\n return tableText(tokens, code);\n case 'ordered_list_open':\n case 'bullet_list_open':\n return listItemsText(tokens, code);\n case 'blockquote_open':\n return blocksIn(tokens.slice(1, -1), code).replace(CALLOUT_MARKER, '');\n case 'html_block':\n // A non-final-line html block carries its last line's terminator, which the mdast value lacked.\n return stripHtml(first.content.replace(/\\n$/, ''));\n case 'footnote_reference_open':\n return blocksIn(tokens.slice(1, -1), code);\n default:\n return '';\n }\n}\n\n// Sibling blocks (list items, blockquote children, footnote bodies) joined one per line;\n// empties dropped so a blank child never widens the gap between its neighbors.\nfunction blocksIn(tokens: Token[], code: string[]): string {\n return topLevelBlocks(tokens)\n .map((b) => blockText(b, code))\n .filter((s) => s.length > 0)\n .join('\\n');\n}\n\n// The task-lists plugin slices 3 of the marker's 4 chars, leaving its trailing space on the text\n// after the checkbox token. Returns a copy with that space dropped, never a mutation: a block is\n// extracted more than once (parse pre-extracts headings, group.ts extracts again).\nfunction withoutTaskMarker(item: Token[]): Token[] {\n for (let i = 0; i < item.length; i++) {\n const token = item[i];\n // The checkbox, if any, sits in the item's first inline only.\n if (token.type !== 'inline' || !token.children?.length) continue;\n const second = token.children[1];\n if (!isTaskCheckbox(token.children[0]) || second?.type !== 'text' || !second.content.startsWith(' ')) return item;\n const children = token.children.slice();\n children[1] = { ...second, content: second.content.slice(1) } as Token;\n const copy = item.slice();\n copy[i] = { ...token, children } as Token;\n return copy;\n }\n return item;\n}\n\nfunction listItemsText(tokens: Token[], code: string[]): string {\n const items: string[] = [];\n for (const item of topLevelBlocks(tokens.slice(1, -1))) {\n items.push(blocksIn(withoutTaskMarker(item).slice(1, -1), code));\n }\n return items.filter((s) => s.length > 0).join('\\n');\n}\n\n// Table rows joined by newline, cells by space (mdast's tableRow/tableCell joiners).\nfunction tableText(tokens: Token[], code: string[]): string {\n const rows: string[] = [];\n let cells: string[] = [];\n for (const token of tokens) {\n if (token.type === 'tr_open') cells = [];\n else if (token.type === 'inline') cells.push(inlineText(token.children ?? [], code));\n else if (token.type === 'tr_close') rows.push(cells.join(' '));\n }\n return rows.join('\\n');\n}\n\n// Inline content (headings, paragraphs, emphasis, links) concatenated with no separator: the\n// source text tokens already carry whatever spacing separates them.\nfunction inlineText(tokens: Token[], code: string[], inAlt = false): string {\n let out = '';\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token.type === 'link_open') {\n let skip = 0;\n let j = i + 1;\n let depth = 1;\n while (j < tokens.length && depth > 0) {\n if (tokens[j].type === 'link_open') depth += 1;\n else if (tokens[j].type === 'link_close') depth -= 1;\n j += 1;\n }\n const text = inlineText(tokens.slice(i + 1, j - 1), code, inAlt);\n const markup = token.markup;\n const next = tokens[j];\n if (inAlt) {\n // An image's alt is plain text: mdast kept every link's own text there, autolinks included.\n out += text;\n } else if (markup === 'linkify' && isGfmUrl(text)) {\n // GFM drop: the next text token resumes at the old link's boundary, re-emitting any\n // trimmed trail. <www.x> has no trail to absorb (the brackets bound it), keep the >.\n // Emails keep their whole remainder: GFM's email machine has no trail, so the span\n // already ends at the old boundary and the entity rule has decoded the rest.\n const prev = tokens[i - 1];\n const bracketed = prev?.type === 'text' && prev.content.endsWith('<') && next?.type === 'text' && next.content.startsWith('>');\n const tail = next?.type === 'text' ? text + next.content : text;\n const k = bracketed ? text.length : gfmAutolinkEnd(tail);\n out += tail.slice(k).replace(LEAD_ENTITY, (m) => parser().utils.unescapeAll(m));\n // The trail came out of the next text token, so that token is consumed here, not re-read.\n if (next?.type === 'text') skip = 1;\n } else if (markup === 'linkify' && isGfmEmail(text)) {\n // Email drop: nothing after the span to trim, so the span alone is dropped.\n } else if (markup === 'autolink') {\n // <...> leaf: dropped whole, its brackets live inside the token so nothing follows to trim.\n } else {\n out += text; // user link or non-GFM linkify target: keep the display text\n }\n i = j - 1 + skip;\n } else if (token.type === 'text') {\n out += token.content;\n } else if (token.type === 'softbreak') {\n out += '\\n';\n } else if (token.type === 'hardbreak') {\n out += ' ';\n } else if (token.type === 'code_inline') {\n out += codePlaceholder(token.content, code);\n } else if (token.type === 'html_inline') {\n out += stripHtml(token.content);\n } else if (token.type === 'image') {\n out += inlineText(token.children ?? [], code, true);\n }\n // Emphasis, strikethrough and footnote markers carry no text of their own.\n }\n return out;\n}\n\nconst CODE_PLACEHOLDER = /\\uE000(\\d+)\\uE000/g;\n\n// Plain text of one parsed block: heading/list/table structure is kept as text, markup (emphasis,\n// link targets, task and callout markers) is dropped. Pure, synchronous.\nexport function extractText(block: Block): string {\n return extractTexts([block]);\n}\n\n// Blocks resolved together: a blank line inside %%...%% splits it across blocks, and a per-block\n// strip then never sees the closing %%. Code stays placeholder-held across all of them.\nexport function extractTexts(blocks: Block[]): string {\n const code: string[] = [];\n const joined = blocks\n .map((b) => blockText(b.node, code))\n .filter((s) => s.length > 0)\n .join('\\n');\n const resolved = resolveFlavor(joined);\n return code.length === 0 ? resolved : resolved.replace(CODE_PLACEHOLDER, (_, i: string) => code[Number(i)]);\n}\n"],"names":["parser","COMMENT_PAIR","EMBED","WIKILINK","BLOCK_ID","CALLOUT_MARKER","HTML_COMMENT","HTML_TAG","stripHtml","value","replace","anchorText","base","hashIdx","indexOf","trim","target","slice","anchor","wikilinkText","inner","pipeIdx","embedText","resolveFlavor","text","_","codePlaceholder","code","idx","push","PUNCT","Set","TRAIL_PUNCT","isAlpha","c","undefined","trailEndsAt","s","j","has","d","test","gfmAutolinkEnd","open","close","i","length","isGfmUrl","isGfmEmail","includes","isTaskCheckbox","token","type","content","LEAD_ENTITY","topLevelBlocks","tokens","out","nesting","depth","paragraphText","children","k","first","inlineText","blockText","inline","find","t","tableText","listItemsText","blocksIn","map","b","filter","join","withoutTaskMarker","item","second","startsWith","copy","items","rows","cells","inAlt","skip","markup","next","prev","bracketed","endsWith","tail","m","utils","unescapeAll","CODE_PLACEHOLDER","extractText","block","extractTexts","blocks","joined","node","resolved","Number"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/chunk/extract.ts"],"sourcesContent":["import type { Token } from 'markdown-it';\nimport { parser } from './parser.ts';\nimport type { Block } from './types.ts';\n\n// markdown-it, like mdast, has no wikilink, embed, or %%comment%% token -- literal text resolved\n// by regex passes over non-code text (code/inline code is placeholder-held and spliced back verbatim).\nconst COMMENT_PAIR = /%%[\\s\\S]*?%%/g;\n// An embed always yields its target, whatever follows a pipe (a resize suffix, never an alias);\n// must run before the wikilink regex below, bang included, or that regex matches first.\nconst EMBED = /!\\[\\[([^\\]]+)\\]\\]/g;\nconst WIKILINK = /\\[\\[([^\\]]+)\\]\\]/g;\n// A trailing \" ^id\" is an Obsidian block anchor, valid only at a line's end; a caret elsewhere\n// (mid-line) is ordinary text and is left alone.\nconst BLOCK_ID = / \\^[A-Za-z0-9-]+$/gm;\n// A callout marker is only ever the first thing in a blockquote's text (Obsidian's grammar),\n// so this anchors to blockquote output alone -- never applied to prose in general.\nconst CALLOUT_MARKER = /^\\[!\\w[\\w-]*\\][+-]?[ \\t]?/;\n// An html token's content is raw HTML, block or inline: a full <!-- --> comment is dropped, any\n// remaining tags are stripped, and the text a browser would still render survives.\nconst HTML_COMMENT = /<!--[\\s\\S]*?-->/g;\nconst HTML_TAG = /<[^>]+>/g;\n\nfunction stripHtml(value: string): string {\n return value.replace(HTML_COMMENT, '').replace(HTML_TAG, '');\n}\n\n// A `#anchor` keeps its text (hash dropped), unlike links.ts's parseWikilinkInner (the resolver\n// authority), which discards it entirely -- replicated here since that return value doesn't fit.\nfunction anchorText(base: string): string {\n const hashIdx = base.indexOf('#');\n if (hashIdx === -1) return base.trim();\n const target = base.slice(0, hashIdx).trim();\n const anchor = base.slice(hashIdx + 1).trim();\n return target ? `${target} ${anchor}` : anchor;\n}\n\nfunction wikilinkText(inner: string): string {\n const pipeIdx = inner.indexOf('|');\n return pipeIdx === -1 ? anchorText(inner) : inner.slice(pipeIdx + 1).trim();\n}\n\nfunction embedText(inner: string): string {\n const pipeIdx = inner.indexOf('|');\n return anchorText(pipeIdx === -1 ? inner : inner.slice(0, pipeIdx));\n}\n\nfunction resolveFlavor(text: string): string {\n return text\n .replace(HTML_COMMENT, '')\n .replace(COMMENT_PAIR, '')\n .replace(EMBED, (_, inner: string) => embedText(inner))\n .replace(WIKILINK, (_, inner: string) => wikilinkText(inner))\n .replace(BLOCK_ID, '');\n}\n\n// U+E000 (Private Use Area) never appears in real markdown text, so it is a collision-free\n// placeholder delimiter, spliced back to the literal code value after flavor resolution.\nfunction codePlaceholder(value: string, code: string[]): string {\n const idx = code.push(value) - 1;\n return `\\uE000${idx}\\uE000`;\n}\n\n// GFM's autolink literal ends where its path machine stops: a trail of punctuation is a genuine\n// end (link stops before it) only when trailEndsAt accepts it, else the marks are part of the link.\nconst PUNCT = new Set(['!', '\"', '&', \"'\", ')', '*', ',', '.', ';', ':', '<', '?', '_', '~', ']']);\nconst TRAIL_PUNCT = new Set(['!', '\"', \"'\", ')', '*', ',', '.', ';', ':', '?', '_', '~']);\nconst isAlpha = (c: string | undefined): boolean => c !== undefined && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));\nfunction trailEndsAt(s: string, j: number): boolean {\n for (;;) {\n const c = s[j];\n if (c === undefined) return true;\n if (TRAIL_PUNCT.has(c)) {\n j += 1;\n continue;\n }\n if (c === '&') {\n j += 1;\n if (!isAlpha(s[j])) return false;\n j += 1;\n while (isAlpha(s[j])) j += 1;\n if (s[j] !== ';') return false;\n j += 1;\n continue;\n }\n if (c === ']') {\n j += 1;\n const d = s[j];\n return d === undefined || d === '(' || d === '[' || /\\s/u.test(d ?? ' ');\n }\n if (c === '<') return true;\n if (c !== undefined && /^\\s$/u.test(c)) return true;\n return false;\n }\n}\n// Where the GFM autolink ends in S (the linkifier's text plus the following text), old-style:\n// punctuation that fails the trail test, or a ) with closes <= opens, extends the link.\nfunction gfmAutolinkEnd(s: string): number {\n let open = 0;\n let close = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s[i];\n if (c === '(') {\n open += 1;\n continue;\n }\n if (c === ')' && close < open) {\n close += 1;\n continue;\n }\n if (PUNCT.has(c) && trailEndsAt(s, i)) return i;\n if (c !== undefined && /^\\s$/u.test(c)) return i;\n }\n return s.length;\n}\n// linkify-it links more than GFM's autolink literal (bare domains, ftp://, //host); only the\n// GFM forms (http(s)://, www., email) drop their text.\nfunction isGfmUrl(text: string): boolean {\n return /^https?:\\/\\//i.test(text) || /^www\\./i.test(text);\n}\n// Only a bare address is GFM's email autolink: a url keeps its @, since a path can hold one.\nfunction isGfmEmail(text: string): boolean {\n return !isGfmUrl(text) && text.includes('@');\n}\n// The task-lists plugin injects its checkbox as the item's first inline child.\nfunction isTaskCheckbox(token: Token | undefined): boolean {\n return token?.type === 'html_inline' && /^<input\\b/i.test(token.content);\n}\n// A linkify span can swallow the & of a following entity (span ...c& + text amp; z); the\n// re-emitted lead is then a reference the entity rule never saw, so decode it old-style.\nconst LEAD_ENTITY = /^&[a-z#][a-z0-9]{1,31};/i;\n\n// Split a flat token range into its top-level blocks: nesting 1 opens, nesting 0 is one block.\nfunction topLevelBlocks(tokens: Token[]): Token[][] {\n const out: Token[][] = [];\n let i = 0;\n while (i < tokens.length) {\n const token = tokens[i];\n if (token.nesting === 1) {\n let depth = 1;\n let j = i + 1;\n while (j < tokens.length && depth > 0) {\n depth += tokens[j].nesting === 1 ? 1 : tokens[j].nesting === -1 ? -1 : 0;\n j += 1;\n }\n out.push(tokens.slice(i, j));\n i = j;\n } else if (token.nesting === 0) {\n out.push(tokens.slice(i, i + 1));\n i += 1;\n } else {\n i += 1;\n }\n }\n return out;\n}\n\n// A first line with no source content (a bare task checkbox, whitespace) contributes no line to\n// the value, so its softbreak is dropped; a first line whose content extraction removes (an\n// autolink, an image) still leaves its line ending behind.\nfunction paragraphText(children: Token[], code: string[]): string {\n let k = 0;\n const first = children[0];\n if (isTaskCheckbox(first)) k = 1;\n while (k < children.length && children[k].type === 'text' && children[k].content.trim() === '') k += 1;\n if (k > 0 && children[k]?.type === 'softbreak') return inlineText(children.slice(k + 1), code);\n return inlineText(children, code);\n}\n\nfunction blockText(tokens: Token[], code: string[]): string {\n const first = tokens[0];\n if (!first) return '';\n switch (first.type) {\n case 'heading_open':\n case 'paragraph_open': {\n const inline = tokens.find((t) => t.type === 'inline');\n return inline ? paragraphText(inline.children ?? [], code) : '';\n }\n case 'fence':\n case 'code_block':\n // Fence and indented code tokens carry the closing newline their lines were joined with.\n return codePlaceholder(first.content.replace(/\\n$/, ''), code);\n case 'table_open':\n return tableText(tokens, code);\n case 'ordered_list_open':\n case 'bullet_list_open':\n return listItemsText(tokens, code);\n case 'blockquote_open':\n return blocksIn(tokens.slice(1, -1), code).replace(CALLOUT_MARKER, '');\n case 'html_block':\n // A non-final-line html block carries its last line's terminator, which the mdast value lacked.\n return stripHtml(first.content.replace(/\\n$/, ''));\n case 'footnote_reference_open':\n return blocksIn(tokens.slice(1, -1), code);\n default:\n return '';\n }\n}\n\n// Sibling blocks (list items, blockquote children, footnote bodies) joined one per line;\n// empties dropped so a blank child never widens the gap between its neighbors.\nfunction blocksIn(tokens: Token[], code: string[]): string {\n return topLevelBlocks(tokens)\n .map((b) => blockText(b, code))\n .filter((s) => s.length > 0)\n .join('\\n');\n}\n\n// The task-lists plugin slices 3 of the marker's 4 chars, leaving its trailing space on the text\n// after the checkbox token. Returns a copy with that space dropped, never a mutation: a block is\n// extracted more than once (parse pre-extracts headings, group.ts extracts again).\nfunction withoutTaskMarker(item: Token[]): Token[] {\n for (let i = 0; i < item.length; i++) {\n const token = item[i];\n // The checkbox, if any, sits in the item's first inline only.\n if (token.type !== 'inline' || !token.children?.length) continue;\n const second = token.children[1];\n if (!isTaskCheckbox(token.children[0]) || second?.type !== 'text' || !second.content.startsWith(' ')) return item;\n const children = token.children.slice();\n children[1] = { ...second, content: second.content.slice(1) } as Token;\n const copy = item.slice();\n copy[i] = { ...token, children } as Token;\n return copy;\n }\n return item;\n}\n\nfunction listItemsText(tokens: Token[], code: string[]): string {\n const items: string[] = [];\n for (const item of topLevelBlocks(tokens.slice(1, -1))) {\n items.push(blocksIn(withoutTaskMarker(item).slice(1, -1), code));\n }\n return items.filter((s) => s.length > 0).join('\\n');\n}\n\n// Table rows joined by newline, cells by space (mdast's tableRow/tableCell joiners).\nfunction tableText(tokens: Token[], code: string[]): string {\n const rows: string[] = [];\n let cells: string[] = [];\n for (const token of tokens) {\n if (token.type === 'tr_open') cells = [];\n else if (token.type === 'inline') cells.push(inlineText(token.children ?? [], code));\n else if (token.type === 'tr_close') rows.push(cells.join(' '));\n }\n return rows.join('\\n');\n}\n\n// Inline content (headings, paragraphs, emphasis, links) concatenated with no separator: the\n// source text tokens already carry whatever spacing separates them.\nfunction inlineText(tokens: Token[], code: string[], inAlt = false): string {\n let out = '';\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token.type === 'link_open') {\n let skip = 0;\n let j = i + 1;\n let depth = 1;\n while (j < tokens.length && depth > 0) {\n if (tokens[j].type === 'link_open') depth += 1;\n else if (tokens[j].type === 'link_close') depth -= 1;\n j += 1;\n }\n const text = inlineText(tokens.slice(i + 1, j - 1), code, inAlt);\n const markup = token.markup;\n const next = tokens[j];\n if (inAlt) {\n // An image's alt is plain text: mdast kept every link's own text there, autolinks included.\n out += text;\n } else if (markup === 'linkify' && isGfmUrl(text)) {\n // GFM drop: the next text token resumes at the old link's boundary, re-emitting any\n // trimmed trail. <www.x> has no trail to absorb (the brackets bound it), keep the >.\n // Emails keep their whole remainder: GFM's email machine has no trail, so the span\n // already ends at the old boundary and the entity rule has decoded the rest.\n const prev = tokens[i - 1];\n const bracketed = prev?.type === 'text' && prev.content.endsWith('<') && next?.type === 'text' && next.content.startsWith('>');\n const tail = next?.type === 'text' ? text + next.content : text;\n const k = bracketed ? text.length : gfmAutolinkEnd(tail);\n out += tail.slice(k).replace(LEAD_ENTITY, (m) => parser().utils.unescapeAll(m));\n // The trail came out of the next text token, so that token is consumed here, not re-read.\n if (next?.type === 'text') skip = 1;\n } else if (markup === 'linkify' && isGfmEmail(text)) {\n // Email drop: nothing after the span to trim, so the span alone is dropped.\n } else if (markup === 'autolink') {\n // <...> leaf: dropped whole, its brackets live inside the token so nothing follows to trim.\n } else {\n out += text; // user link or non-GFM linkify target: keep the display text\n }\n i = j - 1 + skip;\n } else if (token.type === 'text') {\n out += token.content;\n } else if (token.type === 'softbreak') {\n out += '\\n';\n } else if (token.type === 'hardbreak') {\n out += ' ';\n } else if (token.type === 'code_inline') {\n out += codePlaceholder(token.content, code);\n } else if (token.type === 'html_inline') {\n out += stripHtml(token.content);\n } else if (token.type === 'image') {\n out += inlineText(token.children ?? [], code, true);\n }\n // Emphasis, strikethrough and footnote markers carry no text of their own.\n }\n return out;\n}\n\nconst CODE_PLACEHOLDER = /\\uE000(\\d+)\\uE000/g;\n\n// Plain text of one parsed block: heading/list/table structure is kept as text, markup (emphasis,\n// link targets, task and callout markers) is dropped. Pure, synchronous.\nexport function extractText(block: Block): string {\n return extractTexts([block]);\n}\n\n// Blocks resolved together: a blank line inside %%...%% splits it across blocks, and a per-block\n// strip then never sees the closing %%. Code stays placeholder-held across all of them.\nexport function extractTexts(blocks: Block[]): string {\n const code: string[] = [];\n const joined = blocks\n .map((b) => blockText(b.node, code))\n .filter((s) => s.length > 0)\n .join('\\n');\n const resolved = resolveFlavor(joined);\n return code.length === 0 ? resolved : resolved.replace(CODE_PLACEHOLDER, (_, i: string) => code[Number(i)]);\n}\n"],"names":["parser","COMMENT_PAIR","EMBED","WIKILINK","BLOCK_ID","CALLOUT_MARKER","HTML_COMMENT","HTML_TAG","stripHtml","value","replace","anchorText","base","hashIdx","indexOf","trim","target","slice","anchor","wikilinkText","inner","pipeIdx","embedText","resolveFlavor","text","_","codePlaceholder","code","idx","push","PUNCT","Set","TRAIL_PUNCT","isAlpha","c","undefined","trailEndsAt","s","j","has","d","test","gfmAutolinkEnd","open","close","i","length","isGfmUrl","isGfmEmail","includes","isTaskCheckbox","token","type","content","LEAD_ENTITY","topLevelBlocks","tokens","out","nesting","depth","paragraphText","children","k","first","inlineText","blockText","inline","find","t","tableText","listItemsText","blocksIn","map","b","filter","join","withoutTaskMarker","item","second","startsWith","copy","items","rows","cells","inAlt","skip","markup","next","prev","bracketed","endsWith","tail","m","utils","unescapeAll","CODE_PLACEHOLDER","extractText","block","extractTexts","blocks","joined","node","resolved","Number"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/chunk/group.ts"],"sourcesContent":["import { extractText } from './extract.ts';\nimport { parse } from './parse.ts';\nimport { DEFAULT_TARGET_TOKENS, estimateTokens } from './tokens.ts';\nimport type { Block, BlockType, Chunk, ChunkOptions } from './types.ts';\n\nconst PGC_GROUP_SIZE = 2;\nconst OVERSIZE_TRIGGER_MULTIPLE = 2;\n\ninterface ResolvedOptions {\n targetTokens: number;\n text: 'extracted' | 'raw';\n}\n\nfunction resolveOptions(opts?: ChunkOptions): ResolvedOptions {\n return {\n targetTokens: opts?.targetTokens ?? DEFAULT_TARGET_TOKENS,\n text: opts?.text ?? 'raw',\n };\n}\n\n// A heading of any depth ends the current scope and starts a new one (D1); the heading block\n// itself is carried into the new scope, where it joins that scope's first group (F7/F10).\nfunction splitScopes(blocks: Block[]): Block[][] {\n const scopes: Block[][] = [];\n let current: Block[] = [];\n for (const block of blocks) {\n if (block.type === 'heading' && current.length > 0) {\n scopes.push(current);\n current = [];\n }\n current.push(block);\n }\n if (current.length > 0) scopes.push(current);\n return scopes;\n}\n\ninterface Part {\n startLine: number;\n endLine: number;\n text: string;\n // True for a sub-line split piece: its text is already the final slice, not the whole line --\n // group()'s extent-based raw re-slice must not touch it (siblings share startLine === endLine).\n final?: boolean;\n}\n\nfunction finalize(parts: Part[]): (Chunk & { final?: boolean }) | undefined {\n if (parts.length === 0) return undefined;\n const first = parts[0];\n const last = parts[parts.length - 1];\n return { startLine: first.startLine, endLine: last.endLine, text: parts.map((p) => p.text).join('\\n'), final: parts.some((p) => p.final) };\n}\n\nconst NEWLINE_TOKENS = estimateTokens('\\n');\n// Built on first use and kept: each construction is ~3.5 ms, and only an oversize block is ever\n// split, so no command pays for a segmenter it never reaches.\nconst SEGMENTERS = new Map<string, Intl.Segmenter>();\n\nfunction segmentsOf(text: string, granularity: 'sentence' | 'word'): string[] {\n let segmenter = SEGMENTERS.get(granularity);\n if (!segmenter) {\n segmenter = new Intl.Segmenter(undefined, { granularity });\n SEGMENTERS.set(granularity, segmenter);\n }\n return Array.from(segmenter.segment(text), (s) => s.segment);\n}\n\n// Greedily packs segments (already contiguous, tiling the source text with no gaps) into groups\n// of at most `working` estimated tokens; a lone segment over `working` still stands alone.\nfunction pack(segments: string[], working: number): string[] {\n const groups: string[] = [];\n let current = '';\n let tokens = 0;\n for (const segment of segments) {\n const segmentTokens = estimateTokens(segment);\n if (current.length > 0 && tokens + segmentTokens > working) {\n groups.push(current);\n current = '';\n tokens = 0;\n }\n current += segment;\n tokens += segmentTokens;\n }\n if (current.length > 0) groups.push(current);\n return groups;\n}\n\n// Line-split alone can't shrink a lone dense line (the CJK case): falls back to sentence then\n// word boundaries (Intl.Segmenter, the same grapheme-safe engine as segment.ts), mode-agnostic on `text`.\nfunction splitLineText(text: string, working: number): string[] {\n const sentences = segmentsOf(text, 'sentence');\n const out: string[] = [];\n let current = '';\n let tokens = 0;\n const flush = () => {\n if (current.length > 0) {\n out.push(current);\n current = '';\n tokens = 0;\n }\n };\n for (const sentence of sentences) {\n const sentenceTokens = estimateTokens(sentence);\n if (sentenceTokens > working) {\n flush();\n out.push(...pack(segmentsOf(sentence, 'word'), working));\n continue;\n }\n if (current.length > 0 && tokens + sentenceTokens > working) flush();\n current += sentence;\n tokens += sentenceTokens;\n }\n flush();\n return out;\n}\n\nconst ATOMIC_TYPES: ReadonlySet<BlockType> = new Set(['code', 'table', 'list']);\n\n// Atomic (code/table/list) pieces are always a raw line slice: re-parsing a table's later pieces\n// without their header/delimiter rows would demote them to paragraph text.\nfunction piece(pieceLines: string[], startLine: number, endLine: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part {\n const text =\n ATOMIC_TYPES.has(blockType) || textMode === 'raw'\n ? pieceLines.join('\\n')\n : parse(pieceLines.join('\\n'))\n .map((b) => extractText(b))\n .join('\\n');\n return { startLine, endLine, text };\n}\n\n// A one-line piece over working can't shrink via another line-boundary pass (rule 5's gap), so it\n// splits at sentence/word boundaries instead; `final` stops group() re-deriving its text by extent (F5).\nfunction finalizePiece(pieceLines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part[] {\n const p = piece(pieceLines, startLine, endLine, blockType, textMode);\n if (pieceLines.length === 1 && estimateTokens(p.text) > working) {\n return splitLineText(p.text, working).map((text) => ({ startLine, endLine, text, final: true }));\n }\n return [p];\n}\n\n// A block over 2x working size splits at line boundaries into pieces each <= working size, never\n// mid-line. `seed`: pending tokens (e.g. a heading) the first piece must join, checked against the limit.\nfunction splitOversizeBlock(lines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw', seed = 0): Part[] {\n const pieces: Part[] = [];\n let pieceLines: string[] = [];\n let pieceStart = startLine;\n let tokens = seed;\n for (let line = startLine; line <= endLine; line++) {\n const lineText = lines[line - 1];\n const sep = pieceLines.length > 0 || tokens > 0 ? NEWLINE_TOKENS : 0;\n const lineTokens = estimateTokens(lineText);\n if (pieceLines.length > 0 && tokens + sep + lineTokens > working) {\n pieces.push(...finalizePiece(pieceLines, pieceStart, line - 1, working, blockType, textMode));\n pieceLines = [];\n tokens = 0;\n pieceStart = line;\n pieceLines.push(lineText);\n tokens += lineTokens;\n continue;\n }\n pieceLines.push(lineText);\n tokens += sep + lineTokens;\n }\n if (pieceLines.length > 0) pieces.push(...finalizePiece(pieceLines, pieceStart, endLine, working, blockType, textMode));\n return pieces;\n}\n\n// One heading scope's groups (D1): a heading opens the first group, and an oversize block\n// (rule 5, including an oversize heading) splits into pieces that each close their own group.\nfunction groupScope(scopeBlocks: Block[], lines: string[], resolved: ResolvedOptions): (Chunk & { final?: boolean })[] {\n const working = resolved.targetTokens;\n const trigger = working * OVERSIZE_TRIGGER_MULTIPLE;\n const finished: (Chunk & { final?: boolean })[] = [];\n let parts: Part[] = [];\n let paragraphCount = 0;\n let tokens = 0;\n\n function close(): void {\n const group = finalize(parts);\n if (group) finished.push(group);\n parts = [];\n paragraphCount = 0;\n tokens = 0;\n }\n\n // tokens tracks the active text mode's own estimate (a newline between parts costs\n // NEWLINE_TOKENS too), so packing decisions size the text the chunk will actually ship as.\n function addPart(text: string, sizeText: string, startLine: number, endLine: number): void {\n tokens += (parts.length > 0 ? NEWLINE_TOKENS : 0) + estimateTokens(sizeText);\n parts.push({ startLine, endLine, text });\n }\n\n for (const block of scopeBlocks) {\n const raw = lines.slice(block.startLine - 1, block.endLine).join('\\n');\n const blockTokens = estimateTokens(raw);\n\n if (blockTokens > trigger) {\n const seed = parts.length > 0 ? tokens : 0;\n for (const p of splitOversizeBlock(lines, block.startLine, block.endLine, working, block.type, resolved.text, seed)) {\n parts.push(p);\n close();\n }\n continue;\n }\n\n const extracted = extractText(block);\n const sizeText = resolved.text === 'raw' ? raw : extracted;\n\n if (block.type === 'heading') {\n addPart(extracted, sizeText, block.startLine, block.endLine);\n continue;\n }\n\n // The 2x-working invariant holds even under pgc's paper-faithful 2-paragraph pairing --\n // close first if the pair about to form would cross it.\n const pairOversize = parts.length > 0 && tokens + NEWLINE_TOKENS + blockTokens > trigger;\n if (pairOversize) close();\n\n addPart(extracted, sizeText, block.startLine, block.endLine);\n paragraphCount++;\n\n if (paragraphCount >= PGC_GROUP_SIZE) close();\n }\n close();\n\n return finished;\n}\n\n// Groups already-parsed blocks per opts (D1/D3), against the same body the blocks were parsed\n// from (line lookups for oversize splitting).\nexport function group(blocks: Block[], body: string, opts?: ChunkOptions): Chunk[] {\n const resolved = resolveOptions(opts);\n const lines = body.split('\\n');\n const chunks: (Chunk & { final?: boolean })[] = [];\n for (const scope of splitScopes(blocks)) chunks.push(...groupScope(scope, lines, resolved));\n // 'raw': the chunk's own source lines verbatim, replacing the flavor-resolved join above (D9).\n // A `final` chunk already carries its own slice's raw text; re-slicing by extent would return the whole shared line.\n const texted =\n resolved.text === 'raw'\n ? chunks.map((c) =>\n c.final\n ? c\n : {\n ...c,\n text: lines\n .slice(c.startLine - 1, c.endLine)\n .join('\\n')\n .trim(),\n }\n )\n : chunks;\n // A group can be all-blank (flavor-stripped to nothing, or a raw slice of pure syntax); it never produces a chunk.\n return texted.filter((c) => c.text.trim().length > 0).map((c) => ({ startLine: c.startLine, endLine: c.endLine, text: c.text }));\n}\n"],"names":["extractText","parse","DEFAULT_TARGET_TOKENS","estimateTokens","PGC_GROUP_SIZE","OVERSIZE_TRIGGER_MULTIPLE","resolveOptions","opts","targetTokens","text","splitScopes","blocks","scopes","current","block","type","length","push","finalize","parts","undefined","first","last","startLine","endLine","map","p","join","final","some","NEWLINE_TOKENS","SEGMENTERS","Map","segmentsOf","granularity","segmenter","get","Intl","Segmenter","set","Array","from","segment","s","pack","segments","working","groups","tokens","segmentTokens","splitLineText","sentences","out","flush","sentence","sentenceTokens","ATOMIC_TYPES","Set","piece","pieceLines","blockType","textMode","has","b","finalizePiece","splitOversizeBlock","lines","seed","pieces","pieceStart","line","lineText","sep","lineTokens","groupScope","scopeBlocks","resolved","trigger","finished","paragraphCount","close","group","addPart","sizeText","raw","slice","blockTokens","extracted","pairOversize","body","split","chunks","scope","texted","c","trim","filter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/chunk/group.ts"],"sourcesContent":["import { extractText } from './extract.ts';\nimport { parse } from './parse.ts';\nimport { DEFAULT_TARGET_TOKENS, estimateTokens } from './tokens.ts';\nimport type { Block, BlockType, Chunk, ChunkOptions } from './types.ts';\n\nconst PGC_GROUP_SIZE = 2;\nconst OVERSIZE_TRIGGER_MULTIPLE = 2;\n\ninterface ResolvedOptions {\n targetTokens: number;\n text: 'extracted' | 'raw';\n}\n\nfunction resolveOptions(opts?: ChunkOptions): ResolvedOptions {\n return {\n targetTokens: opts?.targetTokens ?? DEFAULT_TARGET_TOKENS,\n text: opts?.text ?? 'raw',\n };\n}\n\n// A heading of any depth ends the current scope and starts a new one (D1); the heading block\n// itself is carried into the new scope, where it joins that scope's first group (F7/F10).\nfunction splitScopes(blocks: Block[]): Block[][] {\n const scopes: Block[][] = [];\n let current: Block[] = [];\n for (const block of blocks) {\n if (block.type === 'heading' && current.length > 0) {\n scopes.push(current);\n current = [];\n }\n current.push(block);\n }\n if (current.length > 0) scopes.push(current);\n return scopes;\n}\n\ninterface Part {\n startLine: number;\n endLine: number;\n text: string;\n // True for a sub-line split piece: its text is already the final slice, not the whole line --\n // group()'s extent-based raw re-slice must not touch it (siblings share startLine === endLine).\n final?: boolean;\n}\n\nfunction finalize(parts: Part[]): (Chunk & { final?: boolean }) | undefined {\n if (parts.length === 0) return undefined;\n const first = parts[0];\n const last = parts[parts.length - 1];\n return { startLine: first.startLine, endLine: last.endLine, text: parts.map((p) => p.text).join('\\n'), final: parts.some((p) => p.final) };\n}\n\nconst NEWLINE_TOKENS = estimateTokens('\\n');\n// Built on first use and kept: each construction is ~3.5 ms, and only an oversize block is ever\n// split, so no command pays for a segmenter it never reaches.\nconst SEGMENTERS = new Map<string, Intl.Segmenter>();\n\nfunction segmentsOf(text: string, granularity: 'sentence' | 'word'): string[] {\n let segmenter = SEGMENTERS.get(granularity);\n if (!segmenter) {\n segmenter = new Intl.Segmenter(undefined, { granularity });\n SEGMENTERS.set(granularity, segmenter);\n }\n return Array.from(segmenter.segment(text), (s) => s.segment);\n}\n\n// Greedily packs segments (already contiguous, tiling the source text with no gaps) into groups\n// of at most `working` estimated tokens; a lone segment over `working` still stands alone.\nfunction pack(segments: string[], working: number): string[] {\n const groups: string[] = [];\n let current = '';\n let tokens = 0;\n for (const segment of segments) {\n const segmentTokens = estimateTokens(segment);\n if (current.length > 0 && tokens + segmentTokens > working) {\n groups.push(current);\n current = '';\n tokens = 0;\n }\n current += segment;\n tokens += segmentTokens;\n }\n if (current.length > 0) groups.push(current);\n return groups;\n}\n\n// Line-split alone can't shrink a lone dense line (the CJK case): falls back to sentence then\n// word boundaries (Intl.Segmenter, the same grapheme-safe engine as segment.ts), mode-agnostic on `text`.\nfunction splitLineText(text: string, working: number): string[] {\n const sentences = segmentsOf(text, 'sentence');\n const out: string[] = [];\n let current = '';\n let tokens = 0;\n const flush = () => {\n if (current.length > 0) {\n out.push(current);\n current = '';\n tokens = 0;\n }\n };\n for (const sentence of sentences) {\n const sentenceTokens = estimateTokens(sentence);\n if (sentenceTokens > working) {\n flush();\n out.push(...pack(segmentsOf(sentence, 'word'), working));\n continue;\n }\n if (current.length > 0 && tokens + sentenceTokens > working) flush();\n current += sentence;\n tokens += sentenceTokens;\n }\n flush();\n return out;\n}\n\nconst ATOMIC_TYPES: ReadonlySet<BlockType> = new Set(['code', 'table', 'list']);\n\n// Atomic (code/table/list) pieces are always a raw line slice: re-parsing a table's later pieces\n// without their header/delimiter rows would demote them to paragraph text.\nfunction piece(pieceLines: string[], startLine: number, endLine: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part {\n const text =\n ATOMIC_TYPES.has(blockType) || textMode === 'raw'\n ? pieceLines.join('\\n')\n : parse(pieceLines.join('\\n'))\n .map((b) => extractText(b))\n .join('\\n');\n return { startLine, endLine, text };\n}\n\n// A one-line piece over working can't shrink via another line-boundary pass (rule 5's gap), so it\n// splits at sentence/word boundaries instead; `final` stops group() re-deriving its text by extent (F5).\nfunction finalizePiece(pieceLines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw'): Part[] {\n const p = piece(pieceLines, startLine, endLine, blockType, textMode);\n if (pieceLines.length === 1 && estimateTokens(p.text) > working) {\n return splitLineText(p.text, working).map((text) => ({ startLine, endLine, text, final: true }));\n }\n return [p];\n}\n\n// A block over 2x working size splits at line boundaries into pieces each <= working size, never\n// mid-line. `seed`: pending tokens (e.g. a heading) the first piece must join, checked against the limit.\nfunction splitOversizeBlock(lines: string[], startLine: number, endLine: number, working: number, blockType: BlockType, textMode: 'extracted' | 'raw', seed = 0): Part[] {\n const pieces: Part[] = [];\n let pieceLines: string[] = [];\n let pieceStart = startLine;\n let tokens = seed;\n for (let line = startLine; line <= endLine; line++) {\n const lineText = lines[line - 1];\n const sep = pieceLines.length > 0 || tokens > 0 ? NEWLINE_TOKENS : 0;\n const lineTokens = estimateTokens(lineText);\n if (pieceLines.length > 0 && tokens + sep + lineTokens > working) {\n pieces.push(...finalizePiece(pieceLines, pieceStart, line - 1, working, blockType, textMode));\n pieceLines = [];\n tokens = 0;\n pieceStart = line;\n pieceLines.push(lineText);\n tokens += lineTokens;\n continue;\n }\n pieceLines.push(lineText);\n tokens += sep + lineTokens;\n }\n if (pieceLines.length > 0) pieces.push(...finalizePiece(pieceLines, pieceStart, endLine, working, blockType, textMode));\n return pieces;\n}\n\n// One heading scope's groups (D1): a heading opens the first group, and an oversize block\n// (rule 5, including an oversize heading) splits into pieces that each close their own group.\nfunction groupScope(scopeBlocks: Block[], lines: string[], resolved: ResolvedOptions): (Chunk & { final?: boolean })[] {\n const working = resolved.targetTokens;\n const trigger = working * OVERSIZE_TRIGGER_MULTIPLE;\n const finished: (Chunk & { final?: boolean })[] = [];\n let parts: Part[] = [];\n let paragraphCount = 0;\n let tokens = 0;\n\n function close(): void {\n const group = finalize(parts);\n if (group) finished.push(group);\n parts = [];\n paragraphCount = 0;\n tokens = 0;\n }\n\n // tokens tracks the active text mode's own estimate (a newline between parts costs\n // NEWLINE_TOKENS too), so packing decisions size the text the chunk will actually ship as.\n function addPart(text: string, sizeText: string, startLine: number, endLine: number): void {\n tokens += (parts.length > 0 ? NEWLINE_TOKENS : 0) + estimateTokens(sizeText);\n parts.push({ startLine, endLine, text });\n }\n\n for (const block of scopeBlocks) {\n const raw = lines.slice(block.startLine - 1, block.endLine).join('\\n');\n const blockTokens = estimateTokens(raw);\n\n if (blockTokens > trigger) {\n const seed = parts.length > 0 ? tokens : 0;\n for (const p of splitOversizeBlock(lines, block.startLine, block.endLine, working, block.type, resolved.text, seed)) {\n parts.push(p);\n close();\n }\n continue;\n }\n\n const extracted = extractText(block);\n const sizeText = resolved.text === 'raw' ? raw : extracted;\n\n if (block.type === 'heading') {\n addPart(extracted, sizeText, block.startLine, block.endLine);\n continue;\n }\n\n // The 2x-working invariant holds even under pgc's paper-faithful 2-paragraph pairing --\n // close first if the pair about to form would cross it.\n const pairOversize = parts.length > 0 && tokens + NEWLINE_TOKENS + blockTokens > trigger;\n if (pairOversize) close();\n\n addPart(extracted, sizeText, block.startLine, block.endLine);\n paragraphCount++;\n\n if (paragraphCount >= PGC_GROUP_SIZE) close();\n }\n close();\n\n return finished;\n}\n\n// Groups already-parsed blocks per opts (D1/D3), against the same body the blocks were parsed\n// from (line lookups for oversize splitting).\nexport function group(blocks: Block[], body: string, opts?: ChunkOptions): Chunk[] {\n const resolved = resolveOptions(opts);\n const lines = body.split('\\n');\n const chunks: (Chunk & { final?: boolean })[] = [];\n for (const scope of splitScopes(blocks)) chunks.push(...groupScope(scope, lines, resolved));\n // 'raw': the chunk's own source lines verbatim, replacing the flavor-resolved join above (D9).\n // A `final` chunk already carries its own slice's raw text; re-slicing by extent would return the whole shared line.\n const texted =\n resolved.text === 'raw'\n ? chunks.map((c) =>\n c.final\n ? c\n : {\n ...c,\n text: lines\n .slice(c.startLine - 1, c.endLine)\n .join('\\n')\n .trim(),\n }\n )\n : chunks;\n // A group can be all-blank (flavor-stripped to nothing, or a raw slice of pure syntax); it never produces a chunk.\n return texted.filter((c) => c.text.trim().length > 0).map((c) => ({ startLine: c.startLine, endLine: c.endLine, text: c.text }));\n}\n"],"names":["extractText","parse","DEFAULT_TARGET_TOKENS","estimateTokens","PGC_GROUP_SIZE","OVERSIZE_TRIGGER_MULTIPLE","resolveOptions","opts","targetTokens","text","splitScopes","blocks","scopes","current","block","type","length","push","finalize","parts","undefined","first","last","startLine","endLine","map","p","join","final","some","NEWLINE_TOKENS","SEGMENTERS","Map","segmentsOf","granularity","segmenter","get","Intl","Segmenter","set","Array","from","segment","s","pack","segments","working","groups","tokens","segmentTokens","splitLineText","sentences","out","flush","sentence","sentenceTokens","ATOMIC_TYPES","Set","piece","pieceLines","blockType","textMode","has","b","finalizePiece","splitOversizeBlock","lines","seed","pieces","pieceStart","line","lineText","sep","lineTokens","groupScope","scopeBlocks","resolved","trigger","finished","paragraphCount","close","group","addPart","sizeText","raw","slice","blockTokens","extracted","pairOversize","body","split","chunks","scope","texted","c","trim","filter"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/open-source/ai/sensemaking/src/chunk/index.ts"],"sourcesContent":["import { group } from './group.ts';\nimport { parse } from './parse.ts';\nimport type { Block, Chunk, ChunkOptions } from './types.ts';\n\n// Groups blocks a caller already parsed (e.g. scan/index.ts, sharing one parse with the FTS\n// text path) against the same body they came from, per opts (D1/D3).\nexport function chunkFromBlocks(blocks: Block[], body: string, opts?: ChunkOptions): Chunk[] {\n return group(blocks, body, opts);\n}\n\n// Pure, deterministic function of file content; chunk semantics are version-stamped via\n// CHUNK_VERSION (./version.ts). Algorithm and evidence: BENCHMARKING.md, \"The chunking algorithm\".\nexport function chunk(body: string, opts?: ChunkOptions): Chunk[] {\n return chunkFromBlocks(parse(body), body, opts);\n}\n\nexport { extractText, extractTexts } from './extract.ts';\nexport { parse } from './parse.ts';\nexport { DEFAULT_TARGET_TOKENS, estimateTokens } from './tokens.ts';\nexport type { Block, BlockType, Chunk, ChunkOptions } from './types.ts';\nexport { CHUNK_VERSION } from './version.ts';\n"],"names":["group","parse","chunkFromBlocks","blocks","body","opts","chunk","extractText","extractTexts","DEFAULT_TARGET_TOKENS","estimateTokens","CHUNK_VERSION"],"mappings":"AAAA,SAASA,KAAK,QAAQ,aAAa;AACnC,SAASC,KAAK,QAAQ,aAAa;AAGnC,4FAA4F;AAC5F,qEAAqE;AACrE,OAAO,SAASC,gBAAgBC,MAAe,EAAEC,IAAY,EAAEC,IAAmB;IAChF,OAAOL,MAAMG,QAAQC,MAAMC;AAC7B;AAEA,wFAAwF;AACxF,mGAAmG;AACnG,OAAO,SAASC,MAAMF,IAAY,EAAEC,IAAmB;IACrD,OAAOH,gBAAgBD,MAAMG,OAAOA,MAAMC;AAC5C;AAEA,SAASE,WAAW,EAAEC,YAAY,QAAQ,eAAe;AACzD,SAASP,KAAK,QAAQ,aAAa;AACnC,SAASQ,qBAAqB,EAAEC,cAAc,QAAQ,cAAc;AAEpE,SAASC,aAAa,QAAQ,eAAe"}
|