sensemaking 0.18.3 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/dist/cjs/chunk/group.js +10 -23
- package/dist/cjs/chunk/group.js.map +1 -1
- package/dist/cjs/chunk/parse.js +2 -5
- package/dist/cjs/chunk/parse.js.map +1 -1
- package/dist/cjs/cli/index.js.map +1 -1
- package/dist/cjs/cli/shared.js +6 -10
- package/dist/cjs/cli/shared.js.map +1 -1
- package/dist/cjs/cli/status.js +44 -10
- package/dist/cjs/cli/status.js.map +1 -1
- package/dist/cjs/cli.js +2 -8
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/commands/map.js +1 -3
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/related.js +3 -5
- package/dist/cjs/commands/related.js.map +1 -1
- package/dist/cjs/commands/scope.js.map +1 -1
- package/dist/cjs/commands/search.js +6 -8
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.js.map +1 -1
- package/dist/cjs/config/access.js.map +1 -1
- package/dist/cjs/config/index.d.cts +1 -1
- package/dist/cjs/config/index.d.ts +1 -1
- package/dist/cjs/config/index.js +3 -0
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/config/load.js +10 -24
- package/dist/cjs/config/load.js.map +1 -1
- package/dist/cjs/config/resolve.js.map +1 -1
- package/dist/cjs/config/signals.js +2 -3
- package/dist/cjs/config/signals.js.map +1 -1
- package/dist/cjs/config/types.d.cts +2 -1
- package/dist/cjs/config/types.d.ts +2 -1
- package/dist/cjs/config/types.js +8 -0
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/config/validate.d.cts +1 -1
- package/dist/cjs/config/validate.d.ts +1 -1
- package/dist/cjs/config/validate.js +9 -10
- package/dist/cjs/config/validate.js.map +1 -1
- package/dist/cjs/embed/cohere.js +2 -2
- package/dist/cjs/embed/cohere.js.map +1 -1
- package/dist/cjs/embed/distribution.js +3 -3
- package/dist/cjs/embed/distribution.js.map +1 -1
- package/dist/cjs/embed/handoff.d.cts +3 -0
- package/dist/cjs/embed/handoff.d.ts +3 -0
- package/dist/cjs/embed/handoff.js +39 -0
- package/dist/cjs/embed/handoff.js.map +1 -0
- package/dist/cjs/embed/identity.d.cts +11 -10
- package/dist/cjs/embed/identity.d.ts +11 -10
- package/dist/cjs/embed/identity.js +30 -15
- package/dist/cjs/embed/identity.js.map +1 -1
- package/dist/cjs/embed/langfit.js +2 -3
- package/dist/cjs/embed/langfit.js.map +1 -1
- package/dist/cjs/embed/languages.js +2 -4
- package/dist/cjs/embed/languages.js.map +1 -1
- package/dist/cjs/embed/openai.js +2 -2
- package/dist/cjs/embed/openai.js.map +1 -1
- package/dist/cjs/embed/query.js +58 -20
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/embed/registry.js +1 -2
- package/dist/cjs/embed/registry.js.map +1 -1
- package/dist/cjs/embed/static.d.cts +1 -1
- package/dist/cjs/embed/static.d.ts +1 -1
- package/dist/cjs/embed/static.js +8 -10
- package/dist/cjs/embed/static.js.map +1 -1
- package/dist/cjs/embed/store.d.cts +2 -2
- package/dist/cjs/embed/store.d.ts +2 -2
- package/dist/cjs/embed/store.js +48 -34
- package/dist/cjs/embed/store.js.map +1 -1
- package/dist/cjs/embed/types.js +1 -3
- package/dist/cjs/embed/types.js.map +1 -1
- package/dist/cjs/errors.d.cts +1 -1
- package/dist/cjs/errors.d.ts +1 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/features/embed.js +19 -17
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/features/fences.js +5 -10
- package/dist/cjs/features/fences.js.map +1 -1
- package/dist/cjs/features/links.js +20 -37
- package/dist/cjs/features/links.js.map +1 -1
- package/dist/cjs/features/sections.js +21 -19
- package/dist/cjs/features/sections.js.map +1 -1
- package/dist/cjs/features/tags.js +5 -8
- package/dist/cjs/features/tags.js.map +1 -1
- package/dist/cjs/graph/traverse.js +1 -2
- package/dist/cjs/graph/traverse.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output/column-hint.js +3 -6
- package/dist/cjs/output/column-hint.js.map +1 -1
- package/dist/cjs/output/output.js +7 -14
- package/dist/cjs/output/output.js.map +1 -1
- package/dist/cjs/output/search-error.js +2 -5
- package/dist/cjs/output/search-error.js.map +1 -1
- package/dist/cjs/scan/frontmatter.d.cts +1 -0
- package/dist/cjs/scan/frontmatter.d.ts +1 -0
- package/dist/cjs/scan/frontmatter.js +14 -18
- package/dist/cjs/scan/frontmatter.js.map +1 -1
- package/dist/cjs/scan/list.js +2 -3
- package/dist/cjs/scan/list.js.map +1 -1
- package/dist/cjs/scan/reparse.js +12 -26
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/scan/worker-error.js.map +1 -1
- package/dist/cjs/store/cache.d.cts +2 -0
- package/dist/cjs/store/cache.d.ts +2 -0
- package/dist/cjs/store/cache.js +20 -0
- package/dist/cjs/store/cache.js.map +1 -0
- package/dist/cjs/store/duckdb/batch.js +4 -16
- package/dist/cjs/store/duckdb/batch.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js +8 -20
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/fieldStats.js +2 -8
- package/dist/cjs/store/duckdb/fieldStats.js.map +1 -1
- package/dist/cjs/store/duckdb/lexical.js +21 -29
- package/dist/cjs/store/duckdb/lexical.js.map +1 -1
- package/dist/cjs/store/duckdb/native.js.map +1 -1
- package/dist/cjs/store/duckdb/open.js +12 -13
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js +4 -11
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/duckdb/sql-functions.js +2 -4
- package/dist/cjs/store/duckdb/sql-functions.js.map +1 -1
- package/dist/cjs/store/duckdb/store.js +2 -3
- package/dist/cjs/store/duckdb/store.js.map +1 -1
- package/dist/cjs/store/duckdb/vectors.js +8 -18
- package/dist/cjs/store/duckdb/vectors.js.map +1 -1
- package/dist/cjs/store/index.d.cts +3 -2
- package/dist/cjs/store/index.d.ts +3 -2
- package/dist/cjs/store/index.js +7 -8
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/native.js +6 -16
- package/dist/cjs/store/native.js.map +1 -1
- package/dist/cjs/store/sql-functions.js.map +1 -1
- package/dist/cjs/store/sqlite/connection.js +4 -8
- 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 +3 -5
- package/dist/cjs/store/sqlite/lexical.js.map +1 -1
- package/dist/cjs/store/sqlite/open.d.cts +0 -1
- package/dist/cjs/store/sqlite/open.d.ts +0 -1
- package/dist/cjs/store/sqlite/open.js +22 -40
- package/dist/cjs/store/sqlite/open.js.map +1 -1
- package/dist/cjs/store/sqlite/reconcile.js +2 -3
- package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
- package/dist/cjs/store/sqlite/sql-functions.js +2 -4
- 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/transaction.js +1 -3
- package/dist/cjs/store/transaction.js.map +1 -1
- package/dist/cjs/store/turso/connection.js +2 -6
- 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.d.cts +2 -0
- package/dist/cjs/store/turso/lexical.d.ts +2 -0
- package/dist/cjs/store/turso/lexical.js +347 -0
- package/dist/cjs/store/turso/lexical.js.map +1 -0
- package/dist/cjs/store/turso/native.js.map +1 -1
- package/dist/cjs/store/turso/open.d.cts +1 -1
- package/dist/cjs/store/turso/open.d.ts +1 -1
- package/dist/cjs/store/turso/open.js +81 -54
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/reconcile.js +22 -22
- package/dist/cjs/store/turso/reconcile.js.map +1 -1
- package/dist/cjs/store/turso/store.js +18 -41
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/turso/vectors.d.cts +8 -0
- package/dist/cjs/store/turso/vectors.d.ts +8 -0
- package/dist/cjs/store/turso/vectors.js +435 -0
- package/dist/cjs/store/turso/vectors.js.map +1 -0
- package/dist/cjs/store/types.js +2 -3
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/store/vectors.js.map +1 -1
- package/dist/cjs/text/segment.d.cts +1 -0
- package/dist/cjs/text/segment.d.ts +1 -0
- package/dist/cjs/text/segment.js +10 -14
- package/dist/cjs/text/segment.js.map +1 -1
- package/dist/cjs/watch.js +2 -5
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/chunk/group.js +10 -23
- package/dist/esm/chunk/group.js.map +1 -1
- package/dist/esm/chunk/parse.js +4 -8
- package/dist/esm/chunk/parse.js.map +1 -1
- package/dist/esm/cli/index.js +3 -5
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/cli/shared.js +12 -22
- package/dist/esm/cli/shared.js.map +1 -1
- package/dist/esm/cli/status.js +43 -9
- package/dist/esm/cli/status.js.map +1 -1
- package/dist/esm/cli.js +2 -8
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/commands/map.js +1 -3
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/related.js +3 -5
- package/dist/esm/commands/related.js.map +1 -1
- package/dist/esm/commands/scope.js +3 -5
- package/dist/esm/commands/scope.js.map +1 -1
- package/dist/esm/commands/search.js +10 -16
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.js +3 -7
- package/dist/esm/commands/signals.js.map +1 -1
- package/dist/esm/config/access.js +8 -13
- package/dist/esm/config/access.js.map +1 -1
- package/dist/esm/config/index.d.ts +1 -1
- package/dist/esm/config/index.js +1 -1
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/config/load.js +10 -24
- package/dist/esm/config/load.js.map +1 -1
- package/dist/esm/config/resolve.js +2 -3
- package/dist/esm/config/resolve.js.map +1 -1
- package/dist/esm/config/signals.js +3 -5
- package/dist/esm/config/signals.js.map +1 -1
- package/dist/esm/config/types.d.ts +2 -1
- package/dist/esm/config/types.js +8 -2
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/config/validate.d.ts +1 -1
- package/dist/esm/config/validate.js +7 -10
- package/dist/esm/config/validate.js.map +1 -1
- package/dist/esm/embed/cohere.js +2 -2
- package/dist/esm/embed/cohere.js.map +1 -1
- package/dist/esm/embed/distribution.js +1 -1
- package/dist/esm/embed/distribution.js.map +1 -1
- package/dist/esm/embed/handoff.d.ts +3 -0
- package/dist/esm/embed/handoff.js +21 -0
- package/dist/esm/embed/handoff.js.map +1 -0
- package/dist/esm/embed/identity.d.ts +11 -10
- package/dist/esm/embed/identity.js +29 -26
- package/dist/esm/embed/identity.js.map +1 -1
- package/dist/esm/embed/langfit.js +2 -3
- package/dist/esm/embed/langfit.js.map +1 -1
- package/dist/esm/embed/languages.js +2 -4
- package/dist/esm/embed/languages.js.map +1 -1
- package/dist/esm/embed/openai.js +2 -2
- package/dist/esm/embed/openai.js.map +1 -1
- package/dist/esm/embed/query.js +21 -6
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/embed/registry.js +1 -2
- package/dist/esm/embed/registry.js.map +1 -1
- package/dist/esm/embed/static.d.ts +1 -1
- package/dist/esm/embed/static.js +8 -10
- package/dist/esm/embed/static.js.map +1 -1
- package/dist/esm/embed/store.d.ts +2 -2
- package/dist/esm/embed/store.js +28 -23
- package/dist/esm/embed/store.js.map +1 -1
- package/dist/esm/embed/types.js +1 -3
- package/dist/esm/embed/types.js.map +1 -1
- package/dist/esm/errors.d.ts +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/features/embed.js +16 -16
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/features/fences.js +9 -19
- package/dist/esm/features/fences.js.map +1 -1
- package/dist/esm/features/links.js +24 -49
- package/dist/esm/features/links.js.map +1 -1
- package/dist/esm/features/sections.js +20 -20
- package/dist/esm/features/sections.js.map +1 -1
- package/dist/esm/features/tags.js +5 -8
- package/dist/esm/features/tags.js.map +1 -1
- package/dist/esm/features/types.js.map +1 -1
- package/dist/esm/graph/traverse.js +1 -2
- package/dist/esm/graph/traverse.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output/column-hint.js +3 -6
- package/dist/esm/output/column-hint.js.map +1 -1
- package/dist/esm/output/output.js +9 -20
- package/dist/esm/output/output.js.map +1 -1
- package/dist/esm/output/search-error.js +2 -5
- package/dist/esm/output/search-error.js.map +1 -1
- package/dist/esm/scan/frontmatter.d.ts +1 -0
- package/dist/esm/scan/frontmatter.js +13 -18
- package/dist/esm/scan/frontmatter.js.map +1 -1
- package/dist/esm/scan/list.js +2 -3
- package/dist/esm/scan/list.js.map +1 -1
- package/dist/esm/scan/reparse.js +16 -34
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/scan/worker-error.js.map +1 -1
- package/dist/esm/store/cache.d.ts +2 -0
- package/dist/esm/store/cache.js +11 -0
- package/dist/esm/store/cache.js.map +1 -0
- package/dist/esm/store/duckdb/batch.js +4 -16
- package/dist/esm/store/duckdb/batch.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js +8 -20
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/fieldStats.js +4 -12
- package/dist/esm/store/duckdb/fieldStats.js.map +1 -1
- package/dist/esm/store/duckdb/lexical.js +29 -36
- package/dist/esm/store/duckdb/lexical.js.map +1 -1
- package/dist/esm/store/duckdb/native.js +2 -7
- package/dist/esm/store/duckdb/native.js.map +1 -1
- package/dist/esm/store/duckdb/open.js +16 -23
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js +4 -11
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/duckdb/sql-functions.js +4 -9
- package/dist/esm/store/duckdb/sql-functions.js.map +1 -1
- package/dist/esm/store/duckdb/store.js +6 -14
- package/dist/esm/store/duckdb/store.js.map +1 -1
- package/dist/esm/store/duckdb/vectors.js +14 -31
- package/dist/esm/store/duckdb/vectors.js.map +1 -1
- package/dist/esm/store/index.d.ts +3 -2
- package/dist/esm/store/index.js +5 -6
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/native.js +10 -25
- package/dist/esm/store/native.js.map +1 -1
- package/dist/esm/store/sql-functions.js +3 -5
- package/dist/esm/store/sql-functions.js.map +1 -1
- package/dist/esm/store/sqlite/connection.js +4 -8
- package/dist/esm/store/sqlite/connection.js.map +1 -1
- package/dist/esm/store/sqlite/fieldStats.js +2 -6
- package/dist/esm/store/sqlite/fieldStats.js.map +1 -1
- package/dist/esm/store/sqlite/lexical.js +5 -9
- package/dist/esm/store/sqlite/lexical.js.map +1 -1
- package/dist/esm/store/sqlite/open.d.ts +0 -1
- package/dist/esm/store/sqlite/open.js +21 -41
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/sqlite/reconcile.js +6 -13
- package/dist/esm/store/sqlite/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/sql-functions.js +2 -4
- package/dist/esm/store/sqlite/sql-functions.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +1 -2
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/transaction.js +2 -5
- package/dist/esm/store/transaction.js.map +1 -1
- package/dist/esm/store/turso/connection.js +2 -6
- package/dist/esm/store/turso/connection.js.map +1 -1
- package/dist/esm/store/turso/fieldStats.js +2 -5
- package/dist/esm/store/turso/fieldStats.js.map +1 -1
- package/dist/esm/store/turso/lexical.d.ts +2 -0
- package/dist/esm/store/turso/lexical.js +128 -0
- package/dist/esm/store/turso/lexical.js.map +1 -0
- package/dist/esm/store/turso/native.js +2 -7
- package/dist/esm/store/turso/native.js.map +1 -1
- package/dist/esm/store/turso/open.d.ts +1 -1
- package/dist/esm/store/turso/open.js +36 -29
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/reconcile.js +22 -22
- package/dist/esm/store/turso/reconcile.js.map +1 -1
- package/dist/esm/store/turso/store.js +17 -28
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/turso/vectors.d.ts +8 -0
- package/dist/esm/store/turso/vectors.js +98 -0
- package/dist/esm/store/turso/vectors.js.map +1 -0
- package/dist/esm/store/types.js +2 -3
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/store/vectors.js +2 -3
- package/dist/esm/store/vectors.js.map +1 -1
- package/dist/esm/text/segment.d.ts +1 -0
- package/dist/esm/text/segment.js +13 -21
- package/dist/esm/text/segment.js.map +1 -1
- package/dist/esm/watch.js +2 -5
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +3 -1
- package/schema.json +2 -2
- package/skills/sense/SKILL.md +2 -2
- package/skills/sense-setup/SKILL.md +3 -3
- package/dist/cjs/store/meta.d.cts +0 -3
- package/dist/cjs/store/meta.d.ts +0 -3
- package/dist/cjs/store/meta.js +0 -215
- package/dist/cjs/store/meta.js.map +0 -1
- package/dist/esm/store/meta.d.ts +0 -3
- package/dist/esm/store/meta.js +0 -17
- package/dist/esm/store/meta.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/types.ts"],"sourcesContent":["import type { SignalWeights } from './signals.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 5;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; commands degrade when one is off.\n// links/sections/tags/rank are opt-out toggles in
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/types.ts"],"sourcesContent":["import type { SignalWeights } from './signals.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 5;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; commands degrade when one is off.\n// links/sections/tags/rank are opt-out toggles in `features`; embed is on exactly when an `embed` block names a model.\nexport const FEATURE_NAMES = ['links', 'sections', 'tags', 'rank', 'embed'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\n\n// `embed` names the model and gives the tree vectors at all; a preset's `signals` says which\n// engines that scope's searches use.\nexport interface EmbedConfig {\n model?: string;\n provider?: 'static' | 'openai' | 'cohere';\n url?: string; // openai provider: OpenAI-compatible base URL, e.g. http://localhost:11434/v1\n key?: string; // openai/cohere provider: name of the env var holding the bearer token, if any\n languages?: string[]; // owner-declared model languages; enables the language-fit check where no card exists\n chunkTokens?: number; // chunk size ceiling in estimated tokens for small-context models; default 500\n}\n\nexport const DEFAULT_EMBED_MODEL = 'minishlab/potion-retrieval-32M';\n\n// The single declaration every other store name derives from (Store.name, the store/index.ts\n// registry, validate.ts's accepted set). An array, not a bare union, so runtime checks read the same list.\nexport const STORE_NAMES = ['sqlite', 'duckdb', 'turso'] as const;\nexport type StoreName = (typeof STORE_NAMES)[number];\n\n// A named, self-contained file-selection scope: include/exclude are globby patterns resolved\n// relative to the config file. No inheritance between presets; they may overlap freely (views, not partitions).\nexport interface Preset {\n include: string[];\n exclude?: string[];\n k?: number; // result count for `search` scoped to this preset; default 10\n // Exhaustive when present: every signal this preset's searches compose, each mapped to its RRF\n // weight (finite > 0; presence is enablement). Absent = every signal whose prerequisite holds, at weight 1 (words always, links when on, vectors when a model is named).\n signals?: SignalWeights;\n where?: string; // standing SQL condition against frontmatter alias `f`\n}\n\n// A saved `search` invocation: `sense <name>` runs like\n// `sense search <search> [--preset] [--include] [--exclude] [--where] [--k]`.\nexport interface SavedSearch {\n search: string;\n preset?: string;\n include?: string[];\n exclude?: string[];\n where?: string;\n k?: number;\n}\n\n// An entry names the verb it runs, one to one with the commands: { sql } runs like\n// `sense sql`, { search } like `sense search`.\nexport type SavedQuery = { sql: string } | SavedSearch;\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n // File selection, index-time, and per-preset search defaults. A file is indexed iff any\n // preset's include/exclude covers it (union). `default` is used when a command names no preset.\n presets: Record<string, Preset>;\n // Global feature defaults; absent block or key means enabled. embed is not a member here --\n // see the `embed` block.\n features?: { links?: boolean; sections?: boolean; tags?: boolean; rank?: boolean };\n // Names the embedding model. Present means every indexed file gets vectors; absent means none do.\n embed?: EmbedConfig;\n // Settings for the `content` FTS5 table. `tokenize` decides which languages the tree can be\n // word-searched in at all; changing it rebuilds the index.\n content?: { tokenize?: string };\n // Backing store engine: \"sqlite\" (default, zero-dependency), \"duckdb\", or \"turso\" (optional\n // dependency, installed on first use). What differs per store is declared through CAPABILITIES (store/types.ts).\n store?: StoreName;\n queries: Record<string, SavedQuery>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Keys the file declares that this build does not read; cli reports them.\n unknownKeys?: string[];\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\nexport interface SearchOverrides {\n preset?: string;\n k?: number;\n where?: string;\n include?: string[];\n exclude?: string[];\n noExclude?: boolean; // drop the preset's exclude for this command; --exclude still applies\n}\n\nexport interface EffectiveSearch {\n presetName: string;\n k: number;\n where?: string;\n include: string[];\n exclude?: string[];\n signals: SignalWeights; // this preset's effective signal weights\n}\n"],"names":["CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","FEATURE_NAMES","DEFAULT_EMBED_MODEL","STORE_NAMES"],"mappings":"AAEA,OAAO,MAAMA,kBAAkB,oBAAoB;AACnD,OAAO,MAAMC,YAAY,SAAS;AAElC,mGAAmG;AACnG,OAAO,MAAMC,2BAA2B,EAAE;AAE1C,6GAA6G;AAC7G,uHAAuH;AACvH,OAAO,MAAMC,gBAAgB;IAAC;IAAS;IAAY;IAAQ;IAAQ;CAAQ,CAAU;AAcrF,OAAO,MAAMC,sBAAsB,iCAAiC;AAEpE,6FAA6F;AAC7F,2GAA2G;AAC3G,OAAO,MAAMC,cAAc;IAAC;IAAU;IAAU;CAAQ,CAAU"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Config } from './types.js';
|
|
2
2
|
export declare const KNOWN_EMBED_KEYS: Set<string>;
|
|
3
3
|
export declare function unknownConfigKeys(cfg: Record<string, unknown>): string[];
|
|
4
4
|
export declare function validateLegacyScan(parsed: unknown, configPath: string): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SenseError } from '../errors.js';
|
|
2
2
|
import { SIGNAL_NAMES, SIGNAL_PREREQUISITES } from './signals.js';
|
|
3
|
+
import { STORE_NAMES } from './types.js';
|
|
3
4
|
// Shape check for hand-edited files, so a typo names itself instead of surfacing as a
|
|
4
5
|
// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.
|
|
5
6
|
const KNOWN_KEYS = new Set([
|
|
@@ -76,12 +77,9 @@ function validateContentBlock(value, configPath) {
|
|
|
76
77
|
throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize must be a non-empty string, e.g. "trigram" or "unicode61 tokenchars '-_'"`);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
// "turso" is implemented but not offered: it has no lexical or vector search yet, so a tree
|
|
80
|
-
// naming it could not search. Registered in the store registry and exercised by the parity
|
|
81
|
-
// suite; re-add here and in schema.json's enum when those land.
|
|
82
80
|
function validateStoreKey(value, configPath) {
|
|
83
|
-
if (value
|
|
84
|
-
throw new SenseError('CONFIG_INVALID', `${configPath}: store must be "
|
|
81
|
+
if (!STORE_NAMES.includes(value)) {
|
|
82
|
+
throw new SenseError('CONFIG_INVALID', `${configPath}: store must be ${STORE_NAMES.map((name)=>`"${name}"`).join(' or ')}`);
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
function validateFeaturesBlock(value, configPath) {
|
|
@@ -143,9 +141,8 @@ function isSignalName(value) {
|
|
|
143
141
|
function isSignalWeight(value) {
|
|
144
142
|
return typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
145
143
|
}
|
|
146
|
-
// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that
|
|
147
|
-
//
|
|
148
|
-
// without words is always empty.
|
|
144
|
+
// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that doesn't
|
|
145
|
+
// need the top-level embed block: links is seeded by word-match rows, so declaring it without words is always empty.
|
|
149
146
|
function validatePresetSignals(name, value, configPath) {
|
|
150
147
|
if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {
|
|
151
148
|
throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least "words", or omit "signals" to use every signal whose prerequisites hold at weight 1`);
|
|
@@ -192,8 +189,8 @@ function validatePreset(name, value, configPath) {
|
|
|
192
189
|
}
|
|
193
190
|
// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.
|
|
194
191
|
function validateSavedQuery(name, value, configPath) {
|
|
195
|
-
// A bare string
|
|
196
|
-
//
|
|
192
|
+
// A bare string is rejected rather than inferred to be SQL: an entry says which of the two
|
|
193
|
+
// verbs it runs, the same choice the CLI makes explicit.
|
|
197
194
|
if (typeof value === 'string') {
|
|
198
195
|
throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { "sql": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { "search": "..." } for a ranked search`);
|
|
199
196
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport type { Config } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'presets', 'features', 'embed', 'content', 'store', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\n// Exported so a docs test can assert every key here has a schema.json property -- one source\n// of truth for what the embed block accepts, checked against the other for drift.\nexport const KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages', 'chunkTokens']);\nconst KNOWN_CONTENT_KEYS = new Set(['tokenize']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\n// Only the shape is checked here. Whether the linked SQLite accepts the tokenizer is settled\n// by probing it in store/sqlite/open.ts, so this never has to carry a table of which version added what.\nfunction validateContentBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content must be an object of { tokenize?: string }`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_CONTENT_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content has unknown key(s) ${unknown.join(', ')}; content takes tokenize`);\n }\n if (block.tokenize !== undefined && (typeof block.tokenize !== 'string' || block.tokenize.trim() === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize must be a non-empty string, e.g. \"trigram\" or \"unicode61 tokenchars '-_'\"`);\n }\n}\n\n// \"turso\" is implemented but not offered: it has no lexical or vector search yet, so a tree\n// naming it could not search. Registered in the store registry and exercised by the parity\n// suite; re-add here and in schema.json's enum when those land.\nfunction validateStoreKey(value: unknown, configPath: string): void {\n if (value !== 'sqlite' && value !== 'duckdb') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: store must be \"sqlite\" or \"duckdb\"`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.chunkTokens must be a positive integer`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that\n// doesn't need the top-level embed block: links is seeded by word-match rows, so declaring it\n// without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n // A bare string used to mean SQL. It now fails rather than being inferred: an entry says\n // which of the two verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.content !== undefined) {\n validateContentBlock(cfg.content, configPath);\n }\n if (cfg.store !== undefined) {\n validateStoreKey(cfg.store, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["SenseError","SIGNAL_NAMES","SIGNAL_PREREQUISITES","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_EMBED_KEYS","KNOWN_CONTENT_KEYS","SAVED_SEARCH_KEYS","unknownConfigKeys","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","validateLegacyScan","parsed","configPath","scan","include","validateContentBlock","block","unknown","join","tokenize","undefined","trim","validateStoreKey","validateFeaturesBlock","name","v","entries","validateEmbedBlock","embed","model","provider","url","key","languages","some","l","chunkTokens","Number","isInteger","isSignalName","includes","isSignalWeight","isFinite","validatePresetSignals","map","signal","weight","names","requires","validatePreset","preset","exclude","signals","where","validateSavedQuery","JSON","stringify","slice","entry","sql","search","validateConfig","checks","presets","default","queries","features","content","store"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config/validate.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { SIGNAL_NAMES, SIGNAL_PREREQUISITES, type SignalName } from './signals.ts';\nimport { type Config, STORE_NAMES, type StoreName } from './types.ts';\n\n// Shape check for hand-edited files, so a typo names itself instead of surfacing as a\n// TypeError. Unknown top-level keys warn (forward compat); unknown keys inside a block error.\nconst KNOWN_KEYS = new Set(['$schema', 'version', 'presets', 'features', 'embed', 'content', 'store', 'queries']);\nconst KNOWN_PRESET_KEYS = new Set(['include', 'exclude', 'k', 'signals', 'where']);\nconst KNOWN_FEATURE_KEYS = new Set(['links', 'sections', 'tags', 'rank']);\n// Exported so a docs test can assert every key here has a schema.json property -- one source\n// of truth for what the embed block accepts, checked against the other for drift.\nexport const KNOWN_EMBED_KEYS = new Set(['model', 'provider', 'url', 'key', 'languages', 'chunkTokens']);\nconst KNOWN_CONTENT_KEYS = new Set(['tokenize']);\nconst SAVED_SEARCH_KEYS = new Set(['search', 'preset', 'include', 'exclude', 'where', 'k']);\n\nexport function unknownConfigKeys(cfg: Record<string, unknown>): string[] {\n return Object.keys(cfg).filter((k) => !KNOWN_KEYS.has(k));\n}\n\nfunction isNonEmptyStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.length > 0 && value.every((g) => typeof g === 'string' && g.length > 0);\n}\n\n// Pre-v3 shape check, just enough for migrateConfig to run safely (it reads scan.include\n// directly). The full v3 shape is checked by validateConfig once migration has produced it.\nexport function validateLegacyScan(parsed: unknown, configPath: string): void {\n const cfg = typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null;\n const scan = cfg?.scan as { include?: unknown } | undefined;\n if (!cfg || !scan || !isNonEmptyStringArray(scan.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n}\n\n// Only the shape is checked here. Whether the linked SQLite accepts the tokenizer is settled\n// by probing it in store/sqlite/open.ts, so this never has to carry a table of which version added what.\nfunction validateContentBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content must be an object of { tokenize?: string }`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_CONTENT_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content has unknown key(s) ${unknown.join(', ')}; content takes tokenize`);\n }\n if (block.tokenize !== undefined && (typeof block.tokenize !== 'string' || block.tokenize.trim() === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: content.tokenize must be a non-empty string, e.g. \"trigram\" or \"unicode61 tokenchars '-_'\"`);\n }\n}\n\nfunction validateStoreKey(value: unknown, configPath: string): void {\n if (!STORE_NAMES.includes(value as StoreName)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: store must be ${STORE_NAMES.map((name) => `\"${name}\"`).join(' or ')}`);\n }\n}\n\nfunction validateFeaturesBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n const block = value as Record<string, unknown>;\n const unknown = Object.keys(block).filter((k) => !KNOWN_FEATURE_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features has unknown key(s) ${unknown.join(', ')}; embed is not a features key -- see the top-level \"embed\" block`);\n }\n for (const [name, v] of Object.entries(block)) {\n if (typeof v !== 'boolean') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features.${name} must be a boolean`);\n }\n }\n}\n\nfunction validateEmbedBlock(value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed must be an object of { model?, provider?: \"static\"|\"openai\"|\"cohere\", url?, key? }`);\n }\n const embed = value as Record<string, unknown>;\n const unknown = Object.keys(embed).filter((k) => !KNOWN_EMBED_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed has unknown key(s) ${unknown.join(', ')}`);\n }\n if (embed.model !== undefined && typeof embed.model !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model must be a string`);\n }\n // The block exists to name a model; provider settings without one would read as \"vectors\n // configured\" while embedding stays off, which is the ambiguity v4 removed.\n if (embed.model === undefined || embed.model === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.model is required when the \"embed\" block is present (it is what turns vectors on); remove the block to index without vectors`);\n }\n if (embed.provider !== undefined && embed.provider !== 'static' && embed.provider !== 'openai' && embed.provider !== 'cohere') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider must be \"static\", \"openai\", or \"cohere\"`);\n }\n if (embed.url !== undefined && typeof embed.url !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.url must be a string`);\n }\n if (embed.key !== undefined && typeof embed.key !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.key must be a string`);\n }\n // openai has no default host, so a missing url would otherwise only surface at the first\n // vector search; a config error belongs at config time.\n if (embed.provider === 'openai' && (embed.url === undefined || embed.url === '')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.provider \"openai\" requires embed.url (e.g. \"http://localhost:11434/v1\" for Ollama)`);\n }\n if (embed.languages !== undefined && (!Array.isArray(embed.languages) || embed.languages.length === 0 || embed.languages.some((l) => typeof l !== 'string' || l === ''))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.languages must be a non-empty array of language tags (e.g. [\"en\", \"zh\"])`);\n }\n if (embed.chunkTokens !== undefined && (typeof embed.chunkTokens !== 'number' || !Number.isInteger(embed.chunkTokens) || embed.chunkTokens <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: embed.chunkTokens must be a positive integer`);\n }\n}\n\nfunction isSignalName(value: unknown): value is SignalName {\n return typeof value === 'string' && (SIGNAL_NAMES as readonly string[]).includes(value);\n}\n\nfunction isSignalWeight(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\n// Shape (an object of signal name -> weight) plus the one cross-signal prerequisite that doesn't\n// need the top-level embed block: links is seeded by word-match rows, so declaring it without words is always empty.\nfunction validatePresetSignals(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value) || Object.keys(value).length === 0) {\n throw new SenseError(\n 'CONFIG_INVALID',\n `${configPath}: presets.${name}.signals must be a non-empty object of signal name -> weight (a finite number > 0), naming ${SIGNAL_NAMES.join(', ')} -- a preset that searches nothing is a mistake; name at least \"words\", or omit \"signals\" to use every signal whose prerequisites hold at weight 1`\n );\n }\n const entries = Object.entries(value as Record<string, unknown>);\n const unknown = entries.filter(([k]) => !isSignalName(k)).map(([k]) => k);\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals names unknown signal(s) ${unknown.join(', ')}; valid signals are ${SIGNAL_NAMES.join(', ')}`);\n }\n for (const [signal, weight] of entries) {\n if (!isSignalWeight(weight)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals.${signal} must be a finite number > 0`);\n }\n }\n const names = new Set(entries.map(([k]) => k));\n for (const [signal, requires] of Object.entries(SIGNAL_PREREQUISITES) as Array<[SignalName, SignalName]>) {\n if (names.has(signal) && !names.has(requires)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals has \"${signal}\" without \"${requires}\"; add \"${requires}\" to presets.${name}.signals, or drop \"${signal}\"`);\n }\n }\n}\n\nfunction validatePreset(name: string, value: unknown, configPath: string): void {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} must be an object`);\n }\n const preset = value as Record<string, unknown>;\n const unknown = Object.keys(preset).filter((k) => !KNOWN_PRESET_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name} has unknown key(s) ${unknown.join(', ')}; a preset takes include, exclude, k, signals, where`);\n }\n if (!isNonEmptyStringArray(preset.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.include must be a non-empty array of glob strings`);\n }\n if (preset.exclude !== undefined && !isNonEmptyStringArray(preset.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (preset.signals !== undefined) validatePresetSignals(name, preset.signals, configPath);\n if (preset.k !== undefined && (typeof preset.k !== 'number' || !Number.isInteger(preset.k) || preset.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.k must be a positive integer`);\n }\n if (preset.where !== undefined && typeof preset.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.where must be a SQL condition string`);\n }\n}\n\n// A queries.<name> entry: { sql } or a saved search { search, preset?, include?, exclude?, where?, k? }.\nfunction validateSavedQuery(name: string, value: unknown, configPath: string): void {\n // A bare string is rejected rather than inferred to be SQL: an entry says which of the two\n // verbs it runs, the same choice the CLI makes explicit.\n if (typeof value === 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must say which verb it runs: { \"sql\": ${JSON.stringify(value.length > 40 ? `${value.slice(0, 37)}...` : value)} } to run it as SQL, or { \"search\": \"...\" } for a ranked search`);\n }\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n }\n const entry = value as Record<string, unknown>;\n if ('sql' in entry) {\n const unknown = Object.keys(entry).filter((k) => k !== 'sql');\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a { sql } query takes only sql`);\n }\n if (typeof entry.sql !== 'string' || entry.sql.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.sql must be a non-empty string`);\n }\n return;\n }\n if ('search' in entry) {\n const unknown = Object.keys(entry).filter((k) => !SAVED_SEARCH_KEYS.has(k));\n if (unknown.length > 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} has unknown key(s) ${unknown.join(', ')}; a saved search takes search, preset, include, exclude, where, k`);\n }\n // A saved entry saves a question; a scope without a question is just flags.\n if (typeof entry.search !== 'string' || entry.search.trim() === '') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.search must be non-empty text`);\n }\n if (entry.preset !== undefined && (typeof entry.preset !== 'string' || entry.preset.length === 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.preset must be a preset name`);\n }\n if (entry.include !== undefined && !isNonEmptyStringArray(entry.include)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.include must be a non-empty array of glob strings`);\n }\n if (entry.exclude !== undefined && !isNonEmptyStringArray(entry.exclude)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.exclude must be a non-empty array of glob strings`);\n }\n if (entry.where !== undefined && typeof entry.where !== 'string') {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.where must be a SQL condition string`);\n }\n if (entry.k !== undefined && (typeof entry.k !== 'number' || !Number.isInteger(entry.k) || entry.k <= 0)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name}.k must be a positive integer`);\n }\n return;\n }\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries.${name} must be { sql } or { search, preset?, include?, exclude?, where?, k? }`);\n}\n\nexport function validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n\n // `checks` is rejected by name, not warned: silence would hide that assertions are gone.\n if (cfg.checks !== undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: checks was removed in v3 -- sense check no longer asserts on saved queries; a returned row set is the reader's judgment`);\n }\n\n const presets = cfg.presets as Record<string, unknown> | undefined;\n if (!presets || typeof presets !== 'object' || Array.isArray(presets) || Object.keys(presets).length === 0) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must be a non-empty object of preset name -> { include, exclude?, k?, signals?, where? }`);\n }\n for (const [name, value] of Object.entries(presets)) {\n validatePreset(name, value, configPath);\n }\n if (presets.default === undefined) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets must include a \"default\" preset`);\n }\n\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> { sql } or { search }`);\n }\n const queries = cfg.queries as Record<string, unknown>;\n for (const [name, value] of Object.entries(queries)) {\n validateSavedQuery(name, value, configPath);\n }\n\n if (cfg.features !== undefined) {\n validateFeaturesBlock(cfg.features, configPath);\n }\n if (cfg.embed !== undefined) {\n validateEmbedBlock(cfg.embed, configPath);\n }\n if (cfg.content !== undefined) {\n validateContentBlock(cfg.content, configPath);\n }\n if (cfg.store !== undefined) {\n validateStoreKey(cfg.store, configPath);\n }\n\n // vectors' prerequisite is a named model, checked here once embed is known valid (or absent).\n if (cfg.embed === undefined) {\n for (const [name, value] of Object.entries(presets)) {\n const signals = (value as Record<string, unknown>).signals as Record<string, unknown> | undefined;\n if (signals && 'vectors' in signals) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: presets.${name}.signals includes \"vectors\", but no \"embed\" block names a model; add one, or drop \"vectors\" from presets.${name}.signals`);\n }\n }\n }\n\n return cfg as unknown as Config;\n}\n"],"names":["SenseError","SIGNAL_NAMES","SIGNAL_PREREQUISITES","STORE_NAMES","KNOWN_KEYS","Set","KNOWN_PRESET_KEYS","KNOWN_FEATURE_KEYS","KNOWN_EMBED_KEYS","KNOWN_CONTENT_KEYS","SAVED_SEARCH_KEYS","unknownConfigKeys","cfg","Object","keys","filter","k","has","isNonEmptyStringArray","value","Array","isArray","length","every","g","validateLegacyScan","parsed","configPath","scan","include","validateContentBlock","block","unknown","join","tokenize","undefined","trim","validateStoreKey","includes","map","name","validateFeaturesBlock","v","entries","validateEmbedBlock","embed","model","provider","url","key","languages","some","l","chunkTokens","Number","isInteger","isSignalName","isSignalWeight","isFinite","validatePresetSignals","signal","weight","names","requires","validatePreset","preset","exclude","signals","where","validateSavedQuery","JSON","stringify","slice","entry","sql","search","validateConfig","checks","presets","default","queries","features","content","store"],"mappings":"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"}
|
package/dist/esm/embed/cohere.js
CHANGED
|
@@ -25,8 +25,8 @@ export async function cohereProvider(model, url, keyEnv) {
|
|
|
25
25
|
});
|
|
26
26
|
if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/v2/embed -> HTTP ${res.status}`);
|
|
27
27
|
const body = await res.json();
|
|
28
|
-
// Cohere returns one vector per text in order, with no index field to re-sort by;
|
|
29
|
-
//
|
|
28
|
+
// Cohere returns one vector per text in order, with no index field to re-sort by; a caller
|
|
29
|
+
// maps them positionally, so the count below must be trustworthy.
|
|
30
30
|
const vectors = body.embeddings.float;
|
|
31
31
|
if (vectors.length !== texts.length) throw new SenseError('EMBED_MODEL', `${base}/v2/embed returned ${vectors.length} embeddings for ${texts.length} inputs`);
|
|
32
32
|
return vectors.map((v)=>Float32Array.from(v));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/cohere.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { fetchWithRetry } from './http.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 96;\n\n// Cohere's native /v2/embed: input_type distinguishes doc vs query embeddings, which the\n// OpenAI-compatible shape cannot express.\nexport async function cohereProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n const base = (url ?? 'https://api.cohere.com').replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[], inputType: 'search_document' | 'search_query'): Promise<Float32Array[]> {\n const res = await fetchWithRetry(`${base}/v2/embed`, { method: 'POST', headers, body: JSON.stringify({ model, input_type: inputType, texts, embedding_types: ['float'] }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/v2/embed -> HTTP ${res.status}`);\n const body = (await res.json()) as { embeddings: { float: number[][] } };\n // Cohere returns one vector per text in order, with no index field to re-sort by;
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/cohere.ts"],"sourcesContent":["import { SenseError } from '../errors.ts';\nimport { fetchWithRetry } from './http.ts';\nimport type { EmbedProvider } from './types.ts';\n\nconst BATCH_CAP = 96;\n\n// Cohere's native /v2/embed: input_type distinguishes doc vs query embeddings, which the\n// OpenAI-compatible shape cannot express.\nexport async function cohereProvider(model: string, url: string | undefined, keyEnv: string | undefined): Promise<EmbedProvider> {\n const base = (url ?? 'https://api.cohere.com').replace(/\\/+$/, '');\n const headers: Record<string, string> = { 'content-type': 'application/json' };\n const key = keyEnv ? process.env[keyEnv] : undefined;\n if (key) headers.authorization = `Bearer ${key}`;\n\n async function post(texts: string[], inputType: 'search_document' | 'search_query'): Promise<Float32Array[]> {\n const res = await fetchWithRetry(`${base}/v2/embed`, { method: 'POST', headers, body: JSON.stringify({ model, input_type: inputType, texts, embedding_types: ['float'] }) });\n if (!res.ok) throw new SenseError('EMBED_MODEL', `${base}/v2/embed -> HTTP ${res.status}`);\n const body = (await res.json()) as { embeddings: { float: number[][] } };\n // Cohere returns one vector per text in order, with no index field to re-sort by; a caller\n // maps them positionally, so the count below must be trustworthy.\n const vectors = body.embeddings.float;\n if (vectors.length !== texts.length) throw new SenseError('EMBED_MODEL', `${base}/v2/embed returned ${vectors.length} embeddings for ${texts.length} inputs`);\n return vectors.map((v) => Float32Array.from(v));\n }\n\n const dims = (await post(['dimension probe'], 'search_query'))[0].length;\n return { id: `cohere:${base}:${model}`, dims, batchCap: BATCH_CAP, embedDocuments: (texts) => post(texts, 'search_document'), embedQuery: async (text) => (await post([text], 'search_query'))[0] };\n}\n"],"names":["SenseError","fetchWithRetry","BATCH_CAP","cohereProvider","model","url","keyEnv","base","replace","headers","key","process","env","undefined","authorization","post","texts","inputType","res","method","body","JSON","stringify","input_type","embedding_types","ok","status","json","vectors","embeddings","float","length","map","v","Float32Array","from","dims","id","batchCap","embedDocuments","embedQuery","text"],"mappings":"AAAA,SAASA,UAAU,QAAQ,eAAe;AAC1C,SAASC,cAAc,QAAQ,YAAY;AAG3C,MAAMC,YAAY;AAElB,yFAAyF;AACzF,0CAA0C;AAC1C,OAAO,eAAeC,eAAeC,KAAa,EAAEC,GAAuB,EAAEC,MAA0B;IACrG,MAAMC,OAAO,CAACF,gBAAAA,iBAAAA,MAAO,0BAA0BG,OAAO,CAAC,QAAQ;IAC/D,MAAMC,UAAkC;QAAE,gBAAgB;IAAmB;IAC7E,MAAMC,MAAMJ,SAASK,QAAQC,GAAG,CAACN,OAAO,GAAGO;IAC3C,IAAIH,KAAKD,QAAQK,aAAa,GAAG,CAAC,OAAO,EAAEJ,KAAK;IAEhD,eAAeK,KAAKC,KAAe,EAAEC,SAA6C;QAChF,MAAMC,MAAM,MAAMjB,eAAe,GAAGM,KAAK,SAAS,CAAC,EAAE;YAAEY,QAAQ;YAAQV;YAASW,MAAMC,KAAKC,SAAS,CAAC;gBAAElB;gBAAOmB,YAAYN;gBAAWD;gBAAOQ,iBAAiB;oBAAC;iBAAQ;YAAC;QAAG;QAC1K,IAAI,CAACN,IAAIO,EAAE,EAAE,MAAM,IAAIzB,WAAW,eAAe,GAAGO,KAAK,kBAAkB,EAAEW,IAAIQ,MAAM,EAAE;QACzF,MAAMN,OAAQ,MAAMF,IAAIS,IAAI;QAC5B,2FAA2F;QAC3F,kEAAkE;QAClE,MAAMC,UAAUR,KAAKS,UAAU,CAACC,KAAK;QACrC,IAAIF,QAAQG,MAAM,KAAKf,MAAMe,MAAM,EAAE,MAAM,IAAI/B,WAAW,eAAe,GAAGO,KAAK,mBAAmB,EAAEqB,QAAQG,MAAM,CAAC,gBAAgB,EAAEf,MAAMe,MAAM,CAAC,OAAO,CAAC;QAC5J,OAAOH,QAAQI,GAAG,CAAC,CAACC,IAAMC,aAAaC,IAAI,CAACF;IAC9C;IAEA,MAAMG,OAAO,AAAC,CAAA,MAAMrB,KAAK;QAAC;KAAkB,EAAE,eAAc,CAAE,CAAC,EAAE,CAACgB,MAAM;IACxE,OAAO;QAAEM,IAAI,CAAC,OAAO,EAAE9B,KAAK,CAAC,EAAEH,OAAO;QAAEgC;QAAME,UAAUpC;QAAWqC,gBAAgB,CAACvB,QAAUD,KAAKC,OAAO;QAAoBwB,YAAY,OAAOC,OAAS,AAAC,CAAA,MAAM1B,KAAK;gBAAC0B;aAAK,EAAE,eAAc,CAAE,CAAC,EAAE;IAAC;AACpM"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getMeta, setMeta } from '../store/
|
|
1
|
+
import { getMeta, setMeta } from '../store/shared.js';
|
|
2
2
|
// Split out of langfit.ts so reading the persisted counts (status.ts's only need) never pulls in
|
|
3
3
|
// franc-min; only checkLanguageFit's classify path needs the classifier itself.
|
|
4
4
|
const META_KEY = 'embed_languages';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/distribution.ts"],"sourcesContent":["import { getMeta, setMeta } from '../store/
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/distribution.ts"],"sourcesContent":["import { getMeta, setMeta } from '../store/shared.ts';\nimport type { Store } from '../store/types.ts';\n\n// Split out of langfit.ts so reading the persisted counts (status.ts's only need) never pulls in\n// franc-min; only checkLanguageFit's classify path needs the classifier itself.\nconst META_KEY = 'embed_languages';\n\nexport type LangCounts = Record<string, number>;\n\nexport async function languageDistribution(store: Store): Promise<LangCounts | undefined> {\n const raw = await getMeta(store, META_KEY);\n return raw ? JSON.parse(raw) : undefined;\n}\n\nexport async function saveLanguageDistribution(store: Store, counts: LangCounts): Promise<void> {\n await setMeta(store, META_KEY, JSON.stringify(counts));\n}\n"],"names":["getMeta","setMeta","META_KEY","languageDistribution","store","raw","JSON","parse","undefined","saveLanguageDistribution","counts","stringify"],"mappings":"AAAA,SAASA,OAAO,EAAEC,OAAO,QAAQ,qBAAqB;AAGtD,iGAAiG;AACjG,gFAAgF;AAChF,MAAMC,WAAW;AAIjB,OAAO,eAAeC,qBAAqBC,KAAY;IACrD,MAAMC,MAAM,MAAML,QAAQI,OAAOF;IACjC,OAAOG,MAAMC,KAAKC,KAAK,CAACF,OAAOG;AACjC;AAEA,OAAO,eAAeC,yBAAyBL,KAAY,EAAEM,MAAkB;IAC7E,MAAMT,QAAQG,OAAOF,UAAUI,KAAKK,SAAS,CAACD;AAChD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Chunk text from reconcile to embed time when both run in one process, so the text is neither
|
|
2
|
+
// re-derived nor persisted. Keyed by the store it was stashed against, so an entry dies with it.
|
|
3
|
+
const HANDOFF = new WeakMap();
|
|
4
|
+
export function stashChunkText(key, texts) {
|
|
5
|
+
if (texts.size > 0) HANDOFF.set(key, texts);
|
|
6
|
+
}
|
|
7
|
+
// Moves an entry from one key to another: reconcile runs before the store object exists, so it
|
|
8
|
+
// stashes against the connection and open() re-keys onto the store it then builds.
|
|
9
|
+
export function rekeyChunkText(from, to) {
|
|
10
|
+
const texts = HANDOFF.get(from);
|
|
11
|
+
if (!texts) return;
|
|
12
|
+
HANDOFF.delete(from);
|
|
13
|
+
HANDOFF.set(to, texts);
|
|
14
|
+
}
|
|
15
|
+
// Read once and drop: the text is consumed by the embed pass that follows reconcile, and holding
|
|
16
|
+
// it past that would pin the tree's prose in memory for the life of the store.
|
|
17
|
+
export function takeChunkText(key) {
|
|
18
|
+
const texts = HANDOFF.get(key);
|
|
19
|
+
HANDOFF.delete(key);
|
|
20
|
+
return texts;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/handoff.ts"],"sourcesContent":["// Chunk text from reconcile to embed time when both run in one process, so the text is neither\n// re-derived nor persisted. Keyed by the store it was stashed against, so an entry dies with it.\nconst HANDOFF = new WeakMap<object, Map<string, string[]>>();\n\nexport function stashChunkText(key: object, texts: Map<string, string[]>): void {\n if (texts.size > 0) HANDOFF.set(key, texts);\n}\n\n// Moves an entry from one key to another: reconcile runs before the store object exists, so it\n// stashes against the connection and open() re-keys onto the store it then builds.\nexport function rekeyChunkText(from: object, to: object): void {\n const texts = HANDOFF.get(from);\n if (!texts) return;\n HANDOFF.delete(from);\n HANDOFF.set(to, texts);\n}\n\n// Read once and drop: the text is consumed by the embed pass that follows reconcile, and holding\n// it past that would pin the tree's prose in memory for the life of the store.\nexport function takeChunkText(key: object): Map<string, string[]> | undefined {\n const texts = HANDOFF.get(key);\n HANDOFF.delete(key);\n return texts;\n}\n"],"names":["HANDOFF","WeakMap","stashChunkText","key","texts","size","set","rekeyChunkText","from","to","get","delete","takeChunkText"],"mappings":"AAAA,+FAA+F;AAC/F,iGAAiG;AACjG,MAAMA,UAAU,IAAIC;AAEpB,OAAO,SAASC,eAAeC,GAAW,EAAEC,KAA4B;IACtE,IAAIA,MAAMC,IAAI,GAAG,GAAGL,QAAQM,GAAG,CAACH,KAAKC;AACvC;AAEA,+FAA+F;AAC/F,mFAAmF;AACnF,OAAO,SAASG,eAAeC,IAAY,EAAEC,EAAU;IACrD,MAAML,QAAQJ,QAAQU,GAAG,CAACF;IAC1B,IAAI,CAACJ,OAAO;IACZJ,QAAQW,MAAM,CAACH;IACfR,QAAQM,GAAG,CAACG,IAAIL;AAClB;AAEA,iGAAiG;AACjG,+EAA+E;AAC/E,OAAO,SAASQ,cAAcT,GAAW;IACvC,MAAMC,QAAQJ,QAAQU,GAAG,CAACP;IAC1BH,QAAQW,MAAM,CAACR;IACf,OAAOC;AACT"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export declare const MODEL_FILES: string[];
|
|
2
2
|
export declare const MODEL_FILENAMES: string;
|
|
3
3
|
export declare function isDownloadable(model: string): boolean;
|
|
4
|
-
export declare
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function
|
|
4
|
+
export declare const DEFAULT_MODELS_ROOT: string;
|
|
5
|
+
export declare function refsMainPath(model: string, root?: string): string;
|
|
6
|
+
export declare function readRef(model: string, root?: string): string | undefined;
|
|
7
|
+
export declare function writeRef(model: string, sha: string, root?: string): void;
|
|
8
|
+
export declare function snapshotDir(model: string, sha: string, root?: string): string;
|
|
9
|
+
export declare function languagesPath(model: string, root?: string): string;
|
|
10
|
+
export declare function readLanguages(model: string, root?: string): string[] | undefined;
|
|
11
|
+
export declare function writeLanguages(model: string, languages: string[], root?: string): void;
|
|
12
|
+
export declare function modelDir(model: string, root?: string): string;
|
|
13
|
+
export declare function hasModelFiles(model: string, root?: string): boolean;
|
|
14
|
+
export declare function modelIdentity(model: string, root?: string): string | undefined;
|
|
@@ -16,40 +16,43 @@ const HF_MODEL_ID = /^[A-Za-z0-9][\w.-]*\/[A-Za-z0-9][\w.-]*$/;
|
|
|
16
16
|
export function isDownloadable(model) {
|
|
17
17
|
return HF_MODEL_ID.test(model);
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
// The one production default; tests pass their own scratch root through the same parameter
|
|
20
|
+
// rather than seeding fixture ids into this machine cache.
|
|
21
|
+
export const DEFAULT_MODELS_ROOT = join(homedir(), '.sense', 'models');
|
|
22
|
+
function repoDir(model, root = DEFAULT_MODELS_ROOT) {
|
|
20
23
|
const [org, name] = model.split('/');
|
|
21
|
-
return join(
|
|
24
|
+
return join(root, `models--${org}--${name}`);
|
|
22
25
|
}
|
|
23
|
-
export function refsMainPath(model) {
|
|
24
|
-
return join(repoDir(model), 'refs', 'main');
|
|
26
|
+
export function refsMainPath(model, root = DEFAULT_MODELS_ROOT) {
|
|
27
|
+
return join(repoDir(model, root), 'refs', 'main');
|
|
25
28
|
}
|
|
26
29
|
// What `main` last resolved to, read from disk -- no network. Undefined until some resolution
|
|
27
30
|
// (a download or an explicit prefetch) has written it once.
|
|
28
|
-
export function readRef(model) {
|
|
29
|
-
const p = refsMainPath(model);
|
|
31
|
+
export function readRef(model, root = DEFAULT_MODELS_ROOT) {
|
|
32
|
+
const p = refsMainPath(model, root);
|
|
30
33
|
if (!existsSync(p)) return undefined;
|
|
31
34
|
const sha = readFileSync(p, 'utf8').trim();
|
|
32
35
|
return sha.length > 0 ? sha : undefined;
|
|
33
36
|
}
|
|
34
37
|
// A recorded ref pins until its snapshot directory is removed; nothing here re-resolves it.
|
|
35
|
-
export function writeRef(model, sha) {
|
|
36
|
-
mkdirSync(join(repoDir(model), 'refs'), {
|
|
38
|
+
export function writeRef(model, sha, root = DEFAULT_MODELS_ROOT) {
|
|
39
|
+
mkdirSync(join(repoDir(model, root), 'refs'), {
|
|
37
40
|
recursive: true
|
|
38
41
|
});
|
|
39
|
-
writeFileAtomic(refsMainPath(model), sha);
|
|
42
|
+
writeFileAtomic(refsMainPath(model, root), sha);
|
|
40
43
|
}
|
|
41
|
-
export function snapshotDir(model, sha) {
|
|
42
|
-
return join(repoDir(model), 'snapshots', sha);
|
|
44
|
+
export function snapshotDir(model, sha, root = DEFAULT_MODELS_ROOT) {
|
|
45
|
+
return join(repoDir(model, root), 'snapshots', sha);
|
|
43
46
|
}
|
|
44
47
|
// Languages describe the repo, not one weight snapshot, so this lives beside refs/, not
|
|
45
48
|
// inside a snapshot dir.
|
|
46
|
-
export function languagesPath(model) {
|
|
47
|
-
return join(repoDir(model), 'languages.json');
|
|
49
|
+
export function languagesPath(model, root = DEFAULT_MODELS_ROOT) {
|
|
50
|
+
return join(repoDir(model, root), 'languages.json');
|
|
48
51
|
}
|
|
49
52
|
// undefined means "never resolved yet"; an empty array is a resolved fact (checked, none
|
|
50
53
|
// declared) and is cached the same way, so a languageless model is not re-fetched every run.
|
|
51
|
-
export function readLanguages(model) {
|
|
52
|
-
const p = languagesPath(model);
|
|
54
|
+
export function readLanguages(model, root = DEFAULT_MODELS_ROOT) {
|
|
55
|
+
const p = languagesPath(model, root);
|
|
53
56
|
if (!existsSync(p)) return undefined;
|
|
54
57
|
try {
|
|
55
58
|
return JSON.parse(readFileSync(p, 'utf8'));
|
|
@@ -57,29 +60,29 @@ export function readLanguages(model) {
|
|
|
57
60
|
return undefined;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
export function writeLanguages(model, languages) {
|
|
61
|
-
mkdirSync(repoDir(model), {
|
|
63
|
+
export function writeLanguages(model, languages, root = DEFAULT_MODELS_ROOT) {
|
|
64
|
+
mkdirSync(repoDir(model, root), {
|
|
62
65
|
recursive: true
|
|
63
66
|
});
|
|
64
|
-
writeFileAtomic(languagesPath(model), JSON.stringify(languages));
|
|
67
|
+
writeFileAtomic(languagesPath(model, root), JSON.stringify(languages));
|
|
65
68
|
}
|
|
66
69
|
// A local directory the caller manages, or the resolved snapshot once `main` is pinned; an HF
|
|
67
70
|
// id with no recorded ref yet has no known snapshot, so this names the (fileless) repo dir.
|
|
68
|
-
export function modelDir(model) {
|
|
71
|
+
export function modelDir(model, root = DEFAULT_MODELS_ROOT) {
|
|
69
72
|
if (!isDownloadable(model)) return model;
|
|
70
|
-
const sha = readRef(model);
|
|
71
|
-
return sha ? snapshotDir(model, sha) : repoDir(model);
|
|
73
|
+
const sha = readRef(model, root);
|
|
74
|
+
return sha ? snapshotDir(model, sha, root) : repoDir(model, root);
|
|
72
75
|
}
|
|
73
76
|
// Both files, so an interrupted download reads as absent and the next one resumes it.
|
|
74
|
-
export function hasModelFiles(model) {
|
|
75
|
-
const dir = modelDir(model);
|
|
77
|
+
export function hasModelFiles(model, root = DEFAULT_MODELS_ROOT) {
|
|
78
|
+
const dir = modelDir(model, root);
|
|
76
79
|
return MODEL_FILES.every((file)=>existsSync(join(dir, file)));
|
|
77
80
|
}
|
|
78
81
|
// Weight identity for the signature, no network: a HF id's resolved sha, or the two
|
|
79
82
|
// files' size+mtime for a local path. Undefined when nothing is known yet.
|
|
80
|
-
export function modelIdentity(model) {
|
|
81
|
-
if (isDownloadable(model)) return readRef(model);
|
|
82
|
-
const dir = modelDir(model);
|
|
83
|
+
export function modelIdentity(model, root = DEFAULT_MODELS_ROOT) {
|
|
84
|
+
if (isDownloadable(model)) return readRef(model, root);
|
|
85
|
+
const dir = modelDir(model, root);
|
|
83
86
|
const parts = [];
|
|
84
87
|
for (const file of MODEL_FILES){
|
|
85
88
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/identity.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { writeFileAtomic } from '../lib/atomic-write.ts';\n\n// HF cache layout, no network: ~/.sense/models/models--<org>--<name>/{refs/main,\n// snapshots/<sha>/<files>}, files stored directly (hf_hub's blobs/symlinks layer skipped).\n\nexport const MODEL_FILES = ['model.safetensors', 'tokenizer.json'];\n// The pair, for messages that name what a local model directory must contain.\nexport const MODEL_FILENAMES = MODEL_FILES.join(' and ');\n\n// A Hugging Face repo id: one slash, HF's charset. Anything else is a path, used as one rather\n// than resolved against the HF cache.\nconst HF_MODEL_ID = /^[A-Za-z0-9][\\w.-]*\\/[A-Za-z0-9][\\w.-]*$/;\n\nexport function isDownloadable(model: string): boolean {\n return HF_MODEL_ID.test(model);\n}\n\nfunction repoDir(model: string): string {\n const [org, name] = model.split('/');\n return join(
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/identity.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { writeFileAtomic } from '../lib/atomic-write.ts';\n\n// HF cache layout, no network: ~/.sense/models/models--<org>--<name>/{refs/main,\n// snapshots/<sha>/<files>}, files stored directly (hf_hub's blobs/symlinks layer skipped).\n\nexport const MODEL_FILES = ['model.safetensors', 'tokenizer.json'];\n// The pair, for messages that name what a local model directory must contain.\nexport const MODEL_FILENAMES = MODEL_FILES.join(' and ');\n\n// A Hugging Face repo id: one slash, HF's charset. Anything else is a path, used as one rather\n// than resolved against the HF cache.\nconst HF_MODEL_ID = /^[A-Za-z0-9][\\w.-]*\\/[A-Za-z0-9][\\w.-]*$/;\n\nexport function isDownloadable(model: string): boolean {\n return HF_MODEL_ID.test(model);\n}\n\n// The one production default; tests pass their own scratch root through the same parameter\n// rather than seeding fixture ids into this machine cache.\nexport const DEFAULT_MODELS_ROOT = join(homedir(), '.sense', 'models');\n\nfunction repoDir(model: string, root: string = DEFAULT_MODELS_ROOT): string {\n const [org, name] = model.split('/');\n return join(root, `models--${org}--${name}`);\n}\n\nexport function refsMainPath(model: string, root: string = DEFAULT_MODELS_ROOT): string {\n return join(repoDir(model, root), 'refs', 'main');\n}\n\n// What `main` last resolved to, read from disk -- no network. Undefined until some resolution\n// (a download or an explicit prefetch) has written it once.\nexport function readRef(model: string, root: string = DEFAULT_MODELS_ROOT): string | undefined {\n const p = refsMainPath(model, root);\n if (!existsSync(p)) return undefined;\n const sha = readFileSync(p, 'utf8').trim();\n return sha.length > 0 ? sha : undefined;\n}\n\n// A recorded ref pins until its snapshot directory is removed; nothing here re-resolves it.\nexport function writeRef(model: string, sha: string, root: string = DEFAULT_MODELS_ROOT): void {\n mkdirSync(join(repoDir(model, root), 'refs'), { recursive: true });\n writeFileAtomic(refsMainPath(model, root), sha);\n}\n\nexport function snapshotDir(model: string, sha: string, root: string = DEFAULT_MODELS_ROOT): string {\n return join(repoDir(model, root), 'snapshots', sha);\n}\n\n// Languages describe the repo, not one weight snapshot, so this lives beside refs/, not\n// inside a snapshot dir.\nexport function languagesPath(model: string, root: string = DEFAULT_MODELS_ROOT): string {\n return join(repoDir(model, root), 'languages.json');\n}\n\n// undefined means \"never resolved yet\"; an empty array is a resolved fact (checked, none\n// declared) and is cached the same way, so a languageless model is not re-fetched every run.\nexport function readLanguages(model: string, root: string = DEFAULT_MODELS_ROOT): string[] | undefined {\n const p = languagesPath(model, root);\n if (!existsSync(p)) return undefined;\n try {\n return JSON.parse(readFileSync(p, 'utf8'));\n } catch {\n return undefined;\n }\n}\n\nexport function writeLanguages(model: string, languages: string[], root: string = DEFAULT_MODELS_ROOT): void {\n mkdirSync(repoDir(model, root), { recursive: true });\n writeFileAtomic(languagesPath(model, root), JSON.stringify(languages));\n}\n\n// A local directory the caller manages, or the resolved snapshot once `main` is pinned; an HF\n// id with no recorded ref yet has no known snapshot, so this names the (fileless) repo dir.\nexport function modelDir(model: string, root: string = DEFAULT_MODELS_ROOT): string {\n if (!isDownloadable(model)) return model;\n const sha = readRef(model, root);\n return sha ? snapshotDir(model, sha, root) : repoDir(model, root);\n}\n\n// Both files, so an interrupted download reads as absent and the next one resumes it.\nexport function hasModelFiles(model: string, root: string = DEFAULT_MODELS_ROOT): boolean {\n const dir = modelDir(model, root);\n return MODEL_FILES.every((file) => existsSync(join(dir, file)));\n}\n\n// Weight identity for the signature, no network: a HF id's resolved sha, or the two\n// files' size+mtime for a local path. Undefined when nothing is known yet.\nexport function modelIdentity(model: string, root: string = DEFAULT_MODELS_ROOT): string | undefined {\n if (isDownloadable(model)) return readRef(model, root);\n const dir = modelDir(model, root);\n const parts: string[] = [];\n for (const file of MODEL_FILES) {\n try {\n const st = statSync(join(dir, file));\n parts.push(`${st.size}:${Math.trunc(st.mtimeMs)}`);\n } catch {\n return undefined;\n }\n }\n return parts.join(',');\n}\n"],"names":["existsSync","mkdirSync","readFileSync","statSync","homedir","join","writeFileAtomic","MODEL_FILES","MODEL_FILENAMES","HF_MODEL_ID","isDownloadable","model","test","DEFAULT_MODELS_ROOT","repoDir","root","org","name","split","refsMainPath","readRef","p","undefined","sha","trim","length","writeRef","recursive","snapshotDir","languagesPath","readLanguages","JSON","parse","writeLanguages","languages","stringify","modelDir","hasModelFiles","dir","every","file","modelIdentity","parts","st","push","size","Math","trunc","mtimeMs"],"mappings":"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"}
|
|
@@ -8,9 +8,8 @@ const SAMPLE_CHARS = 300;
|
|
|
8
8
|
// A majority computed over a handful of chunks is noise, not a corpus signal; the decision rule
|
|
9
9
|
// never fires below this many classified chunks, declared languages or not.
|
|
10
10
|
const MIN_CLASSIFIED = 10;
|
|
11
|
-
// franc-min is a 148K static import; a top-level import put it on every command's module
|
|
12
|
-
// regardless of whether the semantic path ran,
|
|
13
|
-
// synchronous require inside checkLanguageFit so only the embed path pays for it.
|
|
11
|
+
// franc-min is a 148K static import; a top-level import would put it on every command's module
|
|
12
|
+
// graph regardless of whether the semantic path ran, so it's deferred to a synchronous require.
|
|
14
13
|
const _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;
|
|
15
14
|
function classify(franc, text) {
|
|
16
15
|
const code = franc(text.slice(0, SAMPLE_CHARS));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/langfit.ts"],"sourcesContent":["import Module from 'node:module';\nimport { SenseError } from '../errors.ts';\nimport type { Store } from '../store/types.ts';\nimport { languageDistribution, saveLanguageDistribution } from './distribution.ts';\nimport { toIso3 } from './languages.ts';\nimport type { EmbedProvider } from './types.ts';\n\n// franc's own reliability floor is a handful of characters; this is a cheap upper bound, not a\n// per-chunk minimum -- a short chunk is simply more likely to come back 'und' (unclassified).\nconst SAMPLE_CHARS = 300;\n// A majority computed over a handful of chunks is noise, not a corpus signal; the decision rule\n// never fires below this many classified chunks, declared languages or not.\nconst MIN_CLASSIFIED = 10;\n\n// franc-min is a 148K static import; a top-level import put it on every command's module
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/langfit.ts"],"sourcesContent":["import Module from 'node:module';\nimport { SenseError } from '../errors.ts';\nimport type { Store } from '../store/types.ts';\nimport { languageDistribution, saveLanguageDistribution } from './distribution.ts';\nimport { toIso3 } from './languages.ts';\nimport type { EmbedProvider } from './types.ts';\n\n// franc's own reliability floor is a handful of characters; this is a cheap upper bound, not a\n// per-chunk minimum -- a short chunk is simply more likely to come back 'und' (unclassified).\nconst SAMPLE_CHARS = 300;\n// A majority computed over a handful of chunks is noise, not a corpus signal; the decision rule\n// never fires below this many classified chunks, declared languages or not.\nconst MIN_CLASSIFIED = 10;\n\n// franc-min is a 148K static import; a top-level import would put it on every command's module\n// graph regardless of whether the semantic path ran, so it's deferred to a synchronous require.\nconst _require = typeof require === 'undefined' ? Module.createRequire(import.meta.url) : require;\n\nfunction classify(franc: typeof import('franc-min')['franc'], text: string): string | undefined {\n const code = franc(text.slice(0, SAMPLE_CHARS));\n return code === 'und' ? undefined : code;\n}\n\n// Merges chunk texts into the persisted distribution; throws (without persisting) if the model\n// declares languages and a clear majority classified so far is not among them.\nexport async function checkLanguageFit(store: Store, provider: EmbedProvider, texts: string[]): Promise<void> {\n if (texts.length === 0) return;\n const { franc } = _require('franc-min') as typeof import('franc-min');\n const persisted = (await languageDistribution(store)) ?? {};\n const merged = { ...persisted };\n for (const text of texts) {\n const code = classify(franc, text);\n if (code) merged[code] = (merged[code] ?? 0) + 1;\n }\n\n const total = Object.values(merged).reduce((a, b) => a + b, 0);\n if (provider.languages && provider.languages.length > 0 && total >= MIN_CLASSIFIED) {\n const declared = new Set(provider.languages.map(toIso3));\n const [majorityLang, majorityCount] = Object.entries(merged).sort((a, b) => b[1] - a[1])[0];\n if (majorityCount / total >= 0.5 && !declared.has(majorityLang)) {\n const pct = Math.round((majorityCount / total) * 100);\n throw new SenseError(\n 'EMBED_MODEL_MISMATCH',\n `embed model ${provider.id} declares languages [${provider.languages.join(', ')}], but ${pct}% of this tree's classified text is \"${majorityLang}\" (ISO 639-3), which is not among them; choose a model for this tree's languages: \\`sense init --model ...\\`, see the sense-setup skill or INTEGRATIONS.md`\n );\n }\n }\n await saveLanguageDistribution(store, merged);\n}\n"],"names":["Module","SenseError","languageDistribution","saveLanguageDistribution","toIso3","SAMPLE_CHARS","MIN_CLASSIFIED","_require","require","createRequire","url","classify","franc","text","code","slice","undefined","checkLanguageFit","store","provider","texts","length","persisted","merged","total","Object","values","reduce","a","b","languages","declared","Set","map","majorityLang","majorityCount","entries","sort","has","pct","Math","round","id","join"],"mappings":"AAAA,OAAOA,YAAY,cAAc;AACjC,SAASC,UAAU,QAAQ,eAAe;AAE1C,SAASC,oBAAoB,EAAEC,wBAAwB,QAAQ,oBAAoB;AACnF,SAASC,MAAM,QAAQ,iBAAiB;AAGxC,+FAA+F;AAC/F,8FAA8F;AAC9F,MAAMC,eAAe;AACrB,gGAAgG;AAChG,4EAA4E;AAC5E,MAAMC,iBAAiB;AAEvB,+FAA+F;AAC/F,gGAAgG;AAChG,MAAMC,WAAW,OAAOC,YAAY,cAAcR,OAAOS,aAAa,CAAC,YAAYC,GAAG,IAAIF;AAE1F,SAASG,SAASC,KAA0C,EAAEC,IAAY;IACxE,MAAMC,OAAOF,MAAMC,KAAKE,KAAK,CAAC,GAAGV;IACjC,OAAOS,SAAS,QAAQE,YAAYF;AACtC;AAEA,+FAA+F;AAC/F,+EAA+E;AAC/E,OAAO,eAAeG,iBAAiBC,KAAY,EAAEC,QAAuB,EAAEC,KAAe;QAGxE;IAFnB,IAAIA,MAAMC,MAAM,KAAK,GAAG;IACxB,MAAM,EAAET,KAAK,EAAE,GAAGL,SAAS;IAC3B,MAAMe,aAAa,OAAA,MAAMpB,qBAAqBgB,oBAA3B,kBAAA,OAAsC,CAAC;IAC1D,MAAMK,SAAS;QAAE,GAAGD,SAAS;IAAC;IAC9B,KAAK,MAAMT,QAAQO,MAAO;YAEEG;QAD1B,MAAMT,OAAOH,SAASC,OAAOC;QAC7B,IAAIC,MAAMS,MAAM,CAACT,KAAK,GAAG,EAACS,eAAAA,MAAM,CAACT,KAAK,cAAZS,0BAAAA,eAAgB,KAAK;IACjD;IAEA,MAAMC,QAAQC,OAAOC,MAAM,CAACH,QAAQI,MAAM,CAAC,CAACC,GAAGC,IAAMD,IAAIC,GAAG;IAC5D,IAAIV,SAASW,SAAS,IAAIX,SAASW,SAAS,CAACT,MAAM,GAAG,KAAKG,SAASlB,gBAAgB;QAClF,MAAMyB,WAAW,IAAIC,IAAIb,SAASW,SAAS,CAACG,GAAG,CAAC7B;QAChD,MAAM,CAAC8B,cAAcC,cAAc,GAAGV,OAAOW,OAAO,CAACb,QAAQc,IAAI,CAAC,CAACT,GAAGC,IAAMA,CAAC,CAAC,EAAE,GAAGD,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;QAC3F,IAAIO,gBAAgBX,SAAS,OAAO,CAACO,SAASO,GAAG,CAACJ,eAAe;YAC/D,MAAMK,MAAMC,KAAKC,KAAK,CAAC,AAACN,gBAAgBX,QAAS;YACjD,MAAM,IAAIvB,WACR,wBACA,CAAC,YAAY,EAAEkB,SAASuB,EAAE,CAAC,qBAAqB,EAAEvB,SAASW,SAAS,CAACa,IAAI,CAAC,MAAM,OAAO,EAAEJ,IAAI,qCAAqC,EAAEL,aAAa,0JAA0J,CAAC;QAEhT;IACF;IACA,MAAM/B,yBAAyBe,OAAOK;AACxC"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// HF model cards declare languages as ISO 639-1
|
|
2
|
-
//
|
|
3
|
-
// data (README table, https://github.com/wooorm/franc), so every value here is a code franc-min
|
|
4
|
-
// can actually emit; a declared tag with no entry here is compared as-is.
|
|
1
|
+
// HF model cards declare languages as ISO 639-1 (e.g. "zh"); franc-min reports ISO 639-3 (e.g.
|
|
2
|
+
// "cmn"). Limited to franc-min's own 82-language table (https://github.com/wooorm/franc).
|
|
5
3
|
const ISO_639_1_TO_3 = {
|
|
6
4
|
ar: 'arb',
|
|
7
5
|
am: 'amh',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/languages.ts"],"sourcesContent":["// HF model cards declare languages as ISO 639-1
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/embed/languages.ts"],"sourcesContent":["// HF model cards declare languages as ISO 639-1 (e.g. \"zh\"); franc-min reports ISO 639-3 (e.g.\n// \"cmn\"). Limited to franc-min's own 82-language table (https://github.com/wooorm/franc).\nconst ISO_639_1_TO_3: Record<string, string> = {\n ar: 'arb',\n am: 'amh',\n az: 'azj',\n be: 'bel',\n bg: 'bul',\n bn: 'ben',\n bs: 'bos',\n cs: 'ces',\n de: 'deu',\n el: 'ell',\n en: 'eng',\n es: 'spa',\n fa: 'pes',\n fr: 'fra',\n gu: 'guj',\n ha: 'hau',\n hi: 'hin',\n hr: 'hrv',\n hu: 'hun',\n id: 'ind',\n ig: 'ibo',\n it: 'ita',\n ja: 'jpn',\n jv: 'jav',\n kk: 'kaz',\n kn: 'kan',\n ko: 'kor',\n ml: 'mal',\n mr: 'mar',\n ms: 'zlm',\n my: 'mya',\n ne: 'npi',\n nl: 'nld',\n pa: 'pan',\n pl: 'pol',\n pt: 'por',\n ro: 'ron',\n ru: 'rus',\n rw: 'kin',\n si: 'sin',\n so: 'som',\n sr: 'srp',\n su: 'sun',\n sv: 'swe',\n sw: 'swh',\n ta: 'tam',\n te: 'tel',\n th: 'tha',\n tl: 'tgl',\n tr: 'tur',\n uk: 'ukr',\n ur: 'urd',\n uz: 'uzn',\n vi: 'vie',\n yo: 'yor',\n zh: 'cmn',\n zu: 'zul',\n};\n\nexport function isKnownLanguageTag(tag: string): boolean {\n return tag in ISO_639_1_TO_3;\n}\n\n// Normalizes a declared or detected language code to ISO 639-3 for comparison; strips a\n// region/script subtag (\"zh-cn\" -> \"zh\") first. Codes already in 639-3 form pass through.\nexport function toIso3(code: string): string {\n const base = code.toLowerCase().split('-')[0];\n return ISO_639_1_TO_3[base] ?? base;\n}\n\n// Languages this project measured for models whose cards declare none, so the fit check is\n// not silently off for the shipped default; asserted here because it was measured, not attested.\nexport const MEASURED_MODEL_LANGUAGES: Record<string, string[]> = {\n 'minishlab/potion-retrieval-32M': ['en'], // benchmark/reports/2026-08-27-embedding-model-selection.md\n};\n"],"names":["ISO_639_1_TO_3","ar","am","az","be","bg","bn","bs","cs","de","el","en","es","fa","fr","gu","ha","hi","hr","hu","id","ig","it","ja","jv","kk","kn","ko","ml","mr","ms","my","ne","nl","pa","pl","pt","ro","ru","rw","si","so","sr","su","sv","sw","ta","te","th","tl","tr","uk","ur","uz","vi","yo","zh","zu","isKnownLanguageTag","tag","toIso3","code","base","toLowerCase","split","MEASURED_MODEL_LANGUAGES"],"mappings":"AAAA,+FAA+F;AAC/F,0FAA0F;AAC1F,MAAMA,iBAAyC;IAC7CC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;AACN;AAEA,OAAO,SAASC,mBAAmBC,GAAW;IAC5C,OAAOA,OAAO3D;AAChB;AAEA,wFAAwF;AACxF,0FAA0F;AAC1F,OAAO,SAAS4D,OAAOC,IAAY;QAE1B7D;IADP,MAAM8D,OAAOD,KAAKE,WAAW,GAAGC,KAAK,CAAC,IAAI,CAAC,EAAE;IAC7C,QAAOhE,uBAAAA,cAAc,CAAC8D,KAAK,cAApB9D,kCAAAA,uBAAwB8D;AACjC;AAEA,2FAA2F;AAC3F,iGAAiG;AACjG,OAAO,MAAMG,2BAAqD;IAChE,kCAAkC;QAAC;KAAK;AAC1C,EAAE"}
|
package/dist/esm/embed/openai.js
CHANGED
|
@@ -35,8 +35,8 @@ export async function openaiProvider(model, url, keyEnv) {
|
|
|
35
35
|
const dims = (await post([
|
|
36
36
|
'dimension probe'
|
|
37
37
|
]))[0].length;
|
|
38
|
-
//
|
|
39
|
-
//
|
|
38
|
+
// embedQuery and embedDocuments are symmetric here; per-model prefixes like nomic's
|
|
39
|
+
// search_document/search_query are not applied.
|
|
40
40
|
return {
|
|
41
41
|
id: `openai:${base}:${model}`,
|
|
42
42
|
dims,
|