sensemaking 0.25.1 → 0.27.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 +48 -12
- package/dist/cjs/cli/index.d.cts +3 -3
- package/dist/cjs/cli/index.d.ts +3 -3
- package/dist/cjs/cli/index.js +3 -3
- package/dist/cjs/cli/index.js.map +1 -1
- package/dist/cjs/cli/path.js +5 -29
- package/dist/cjs/cli/path.js.map +1 -1
- package/dist/cjs/cli/peek.js +43 -2
- package/dist/cjs/cli/peek.js.map +1 -1
- package/dist/cjs/cli/related.js +4 -3
- package/dist/cjs/cli/related.js.map +1 -1
- package/dist/cjs/cli/search.js +38 -6
- package/dist/cjs/cli/search.js.map +1 -1
- package/dist/cjs/commands/index.d.cts +6 -3
- package/dist/cjs/commands/index.d.ts +6 -3
- package/dist/cjs/commands/index.js +4 -0
- package/dist/cjs/commands/index.js.map +1 -1
- package/dist/cjs/commands/map.d.cts +14 -5
- package/dist/cjs/commands/map.d.ts +14 -5
- package/dist/cjs/commands/map.js +42 -35
- package/dist/cjs/commands/map.js.map +1 -1
- package/dist/cjs/commands/path.d.cts +14 -0
- package/dist/cjs/commands/path.d.ts +14 -0
- package/dist/cjs/commands/path.js +209 -0
- package/dist/cjs/commands/path.js.map +1 -0
- package/dist/cjs/commands/peek.d.cts +25 -2
- package/dist/cjs/commands/peek.d.ts +25 -2
- package/dist/cjs/commands/peek.js +131 -50
- package/dist/cjs/commands/peek.js.map +1 -1
- package/dist/cjs/commands/related.d.cts +16 -3
- package/dist/cjs/commands/related.d.ts +16 -3
- package/dist/cjs/commands/related.js +131 -98
- package/dist/cjs/commands/related.js.map +1 -1
- package/dist/cjs/commands/scope.d.cts +1 -0
- package/dist/cjs/commands/scope.d.ts +1 -0
- package/dist/cjs/commands/scope.js +149 -0
- package/dist/cjs/commands/scope.js.map +1 -1
- package/dist/cjs/commands/search.d.cts +30 -3
- package/dist/cjs/commands/search.d.ts +30 -3
- package/dist/cjs/commands/search.js +240 -73
- package/dist/cjs/commands/search.js.map +1 -1
- package/dist/cjs/commands/signals.d.cts +22 -7
- package/dist/cjs/commands/signals.d.ts +22 -7
- package/dist/cjs/commands/signals.js +77 -15
- package/dist/cjs/commands/signals.js.map +1 -1
- package/dist/cjs/commands/snapshot.d.cts +3 -0
- package/dist/cjs/commands/snapshot.d.ts +3 -0
- package/dist/cjs/commands/snapshot.js +180 -0
- package/dist/cjs/commands/snapshot.js.map +1 -0
- package/dist/cjs/config/load.js +6 -0
- package/dist/cjs/config/load.js.map +1 -1
- package/dist/cjs/embed/cohere.js +41 -16
- package/dist/cjs/embed/cohere.js.map +1 -1
- package/dist/cjs/embed/endpoint.d.cts +2 -0
- package/dist/cjs/embed/endpoint.d.ts +2 -0
- package/dist/cjs/embed/endpoint.js +30 -0
- package/dist/cjs/embed/endpoint.js.map +1 -0
- package/dist/cjs/embed/handoff.d.cts +7 -2
- package/dist/cjs/embed/handoff.d.ts +7 -2
- package/dist/cjs/embed/handoff.js +10 -0
- package/dist/cjs/embed/handoff.js.map +1 -1
- package/dist/cjs/embed/http.d.cts +18 -1
- package/dist/cjs/embed/http.d.ts +18 -1
- package/dist/cjs/embed/http.js +239 -40
- package/dist/cjs/embed/http.js.map +1 -1
- package/dist/cjs/embed/openai.js +39 -17
- package/dist/cjs/embed/openai.js.map +1 -1
- package/dist/cjs/embed/query.d.cts +9 -3
- package/dist/cjs/embed/query.d.ts +9 -3
- package/dist/cjs/embed/query.js +257 -90
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/embed/registry.d.cts +2 -2
- package/dist/cjs/embed/registry.d.ts +2 -2
- package/dist/cjs/embed/registry.js +36 -4
- package/dist/cjs/embed/registry.js.map +1 -1
- package/dist/cjs/embed/static.js +6 -2
- package/dist/cjs/embed/static.js.map +1 -1
- package/dist/cjs/embed/store.js +42 -77
- package/dist/cjs/embed/store.js.map +1 -1
- package/dist/cjs/embed/types.d.cts +5 -2
- package/dist/cjs/embed/types.d.ts +5 -2
- package/dist/cjs/embed/types.js.map +1 -1
- package/dist/cjs/features/embed.js +290 -43
- package/dist/cjs/features/embed.js.map +1 -1
- package/dist/cjs/graph/traverse.d.cts +2 -2
- package/dist/cjs/graph/traverse.d.ts +2 -2
- package/dist/cjs/graph/traverse.js +15 -5
- package/dist/cjs/graph/traverse.js.map +1 -1
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output/output.d.cts +4 -36
- package/dist/cjs/output/output.d.ts +4 -36
- package/dist/cjs/output/output.js +1 -2
- package/dist/cjs/output/output.js.map +1 -1
- package/dist/cjs/scan/pool.d.cts +2 -1
- package/dist/cjs/scan/pool.d.ts +2 -1
- package/dist/cjs/scan/pool.js +116 -27
- package/dist/cjs/scan/pool.js.map +1 -1
- package/dist/cjs/scan/reparse.d.cts +1 -0
- package/dist/cjs/scan/reparse.d.ts +1 -0
- package/dist/cjs/scan/reparse.js +75 -21
- package/dist/cjs/scan/reparse.js.map +1 -1
- package/dist/cjs/store/builder.d.cts +11 -21
- package/dist/cjs/store/builder.d.ts +11 -21
- package/dist/cjs/store/builder.js +1077 -438
- package/dist/cjs/store/builder.js.map +1 -1
- package/dist/cjs/store/duckdb/connection.js +16 -2
- package/dist/cjs/store/duckdb/connection.js.map +1 -1
- package/dist/cjs/store/duckdb/open.d.cts +1 -1
- package/dist/cjs/store/duckdb/open.d.ts +1 -1
- package/dist/cjs/store/duckdb/open.js +73 -65
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/reconcile.js +533 -15
- package/dist/cjs/store/duckdb/reconcile.js.map +1 -1
- package/dist/cjs/store/embed-scope.js +19 -9
- package/dist/cjs/store/embed-scope.js.map +1 -1
- package/dist/cjs/store/index.d.cts +6 -0
- package/dist/cjs/store/index.d.ts +6 -0
- package/dist/cjs/store/index.js +8 -4
- package/dist/cjs/store/index.js.map +1 -1
- package/dist/cjs/store/open.d.cts +6 -1
- package/dist/cjs/store/open.d.ts +6 -1
- package/dist/cjs/store/open.js +230 -307
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/readiness.d.cts +5 -0
- package/dist/cjs/store/readiness.d.ts +5 -0
- package/dist/cjs/store/readiness.js +39 -0
- package/dist/cjs/store/readiness.js.map +1 -0
- package/dist/cjs/store/reconcile.d.cts +26 -1
- package/dist/cjs/store/reconcile.d.ts +26 -1
- package/dist/cjs/store/reconcile.js +670 -576
- package/dist/cjs/store/reconcile.js.map +1 -1
- package/dist/cjs/store/signature.d.cts +1 -0
- package/dist/cjs/store/signature.d.ts +1 -0
- package/dist/cjs/store/signature.js +9 -0
- package/dist/cjs/store/signature.js.map +1 -1
- package/dist/cjs/store/sqlite/open.d.cts +1 -1
- package/dist/cjs/store/sqlite/open.d.ts +1 -1
- package/dist/cjs/store/sqlite/open.js +84 -90
- package/dist/cjs/store/sqlite/open.js.map +1 -1
- package/dist/cjs/store/sqlite/reconcile.js.map +1 -1
- package/dist/cjs/store/sqlite/store.js +63 -60
- package/dist/cjs/store/sqlite/store.js.map +1 -1
- package/dist/cjs/store/sqlite/vectors.d.cts +2 -2
- package/dist/cjs/store/sqlite/vectors.d.ts +2 -2
- package/dist/cjs/store/sqlite/vectors.js +8 -4
- package/dist/cjs/store/sqlite/vectors.js.map +1 -1
- package/dist/cjs/store/stages.js.map +1 -1
- package/dist/cjs/store/turso/connection.d.cts +1 -0
- package/dist/cjs/store/turso/connection.d.ts +1 -0
- package/dist/cjs/store/turso/connection.js +319 -94
- package/dist/cjs/store/turso/connection.js.map +1 -1
- package/dist/cjs/store/turso/open.d.cts +1 -1
- package/dist/cjs/store/turso/open.d.ts +1 -1
- package/dist/cjs/store/turso/open.js +70 -74
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/store.js +25 -23
- package/dist/cjs/store/turso/store.js.map +1 -1
- package/dist/cjs/store/turso/vectors.d.cts +2 -2
- package/dist/cjs/store/turso/vectors.d.ts +2 -2
- package/dist/cjs/store/turso/vectors.js +26 -16
- package/dist/cjs/store/turso/vectors.js.map +1 -1
- package/dist/cjs/store/types.d.cts +4 -1
- package/dist/cjs/store/types.d.ts +4 -1
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/cjs/store/vectors.d.cts +14 -1
- package/dist/cjs/store/vectors.d.ts +14 -1
- package/dist/cjs/store/vectors.js +61 -0
- package/dist/cjs/store/vectors.js.map +1 -1
- package/dist/cjs/watch.d.cts +6 -0
- package/dist/cjs/watch.d.ts +6 -0
- package/dist/cjs/watch.js +42 -10
- package/dist/cjs/watch.js.map +1 -1
- package/dist/cjs/workers/parse.d.cts +4 -3
- package/dist/cjs/workers/parse.d.ts +4 -3
- package/dist/cjs/workers/parse.js +19 -17
- package/dist/cjs/workers/parse.js.map +1 -1
- package/dist/esm/cli/index.d.ts +3 -3
- package/dist/esm/cli/index.js +3 -3
- package/dist/esm/cli/index.js.map +1 -1
- package/dist/esm/cli/path.js +4 -10
- package/dist/esm/cli/path.js.map +1 -1
- package/dist/esm/cli/peek.js +20 -2
- package/dist/esm/cli/peek.js.map +1 -1
- package/dist/esm/cli/related.js +5 -3
- package/dist/esm/cli/related.js.map +1 -1
- package/dist/esm/cli/search.js +12 -2
- package/dist/esm/cli/search.js.map +1 -1
- package/dist/esm/commands/index.d.ts +6 -3
- package/dist/esm/commands/index.js +1 -0
- package/dist/esm/commands/index.js.map +1 -1
- package/dist/esm/commands/map.d.ts +14 -5
- package/dist/esm/commands/map.js +2 -0
- package/dist/esm/commands/map.js.map +1 -1
- package/dist/esm/commands/path.d.ts +14 -0
- package/dist/esm/commands/path.js +33 -0
- package/dist/esm/commands/path.js.map +1 -0
- package/dist/esm/commands/peek.d.ts +25 -2
- package/dist/esm/commands/peek.js +59 -19
- package/dist/esm/commands/peek.js.map +1 -1
- package/dist/esm/commands/related.d.ts +16 -3
- package/dist/esm/commands/related.js +45 -28
- package/dist/esm/commands/related.js.map +1 -1
- package/dist/esm/commands/scope.d.ts +1 -0
- package/dist/esm/commands/scope.js +58 -0
- package/dist/esm/commands/scope.js.map +1 -1
- package/dist/esm/commands/search.d.ts +30 -3
- package/dist/esm/commands/search.js +95 -20
- package/dist/esm/commands/search.js.map +1 -1
- package/dist/esm/commands/signals.d.ts +22 -7
- package/dist/esm/commands/signals.js +74 -17
- package/dist/esm/commands/signals.js.map +1 -1
- package/dist/esm/commands/snapshot.d.ts +3 -0
- package/dist/esm/commands/snapshot.js +25 -0
- package/dist/esm/commands/snapshot.js.map +1 -0
- package/dist/esm/config/load.js +6 -0
- package/dist/esm/config/load.js.map +1 -1
- package/dist/esm/embed/cohere.js +35 -10
- package/dist/esm/embed/cohere.js.map +1 -1
- package/dist/esm/embed/endpoint.d.ts +2 -0
- package/dist/esm/embed/endpoint.js +11 -0
- package/dist/esm/embed/endpoint.js.map +1 -0
- package/dist/esm/embed/handoff.d.ts +7 -2
- package/dist/esm/embed/handoff.js +7 -0
- package/dist/esm/embed/handoff.js.map +1 -1
- package/dist/esm/embed/http.d.ts +18 -1
- package/dist/esm/embed/http.js +137 -17
- package/dist/esm/embed/http.js.map +1 -1
- package/dist/esm/embed/openai.js +34 -12
- package/dist/esm/embed/openai.js.map +1 -1
- package/dist/esm/embed/query.d.ts +9 -3
- package/dist/esm/embed/query.js +98 -26
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/embed/registry.d.ts +2 -2
- package/dist/esm/embed/registry.js +34 -5
- package/dist/esm/embed/registry.js.map +1 -1
- package/dist/esm/embed/static.js +10 -2
- package/dist/esm/embed/static.js.map +1 -1
- package/dist/esm/embed/store.js +30 -21
- package/dist/esm/embed/store.js.map +1 -1
- package/dist/esm/embed/types.d.ts +5 -2
- package/dist/esm/embed/types.js.map +1 -1
- package/dist/esm/features/embed.js +82 -21
- package/dist/esm/features/embed.js.map +1 -1
- package/dist/esm/graph/traverse.d.ts +2 -2
- package/dist/esm/graph/traverse.js +4 -2
- package/dist/esm/graph/traverse.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output/output.d.ts +4 -36
- package/dist/esm/output/output.js +1 -2
- package/dist/esm/output/output.js.map +1 -1
- package/dist/esm/scan/pool.d.ts +2 -1
- package/dist/esm/scan/pool.js +80 -23
- package/dist/esm/scan/pool.js.map +1 -1
- package/dist/esm/scan/reparse.d.ts +1 -0
- package/dist/esm/scan/reparse.js +40 -12
- package/dist/esm/scan/reparse.js.map +1 -1
- package/dist/esm/store/builder.d.ts +11 -21
- package/dist/esm/store/builder.js +337 -133
- package/dist/esm/store/builder.js.map +1 -1
- package/dist/esm/store/duckdb/connection.js +9 -1
- package/dist/esm/store/duckdb/connection.js.map +1 -1
- package/dist/esm/store/duckdb/open.d.ts +1 -1
- package/dist/esm/store/duckdb/open.js +13 -9
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/reconcile.js +145 -10
- package/dist/esm/store/duckdb/reconcile.js.map +1 -1
- package/dist/esm/store/embed-scope.js +16 -6
- package/dist/esm/store/embed-scope.js.map +1 -1
- package/dist/esm/store/index.d.ts +6 -0
- package/dist/esm/store/index.js +10 -4
- package/dist/esm/store/index.js.map +1 -1
- package/dist/esm/store/open.d.ts +6 -1
- package/dist/esm/store/open.js +77 -87
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/readiness.d.ts +5 -0
- package/dist/esm/store/readiness.js +11 -0
- package/dist/esm/store/readiness.js.map +1 -0
- package/dist/esm/store/reconcile.d.ts +26 -1
- package/dist/esm/store/reconcile.js +142 -120
- package/dist/esm/store/reconcile.js.map +1 -1
- package/dist/esm/store/signature.d.ts +1 -0
- package/dist/esm/store/signature.js +8 -0
- package/dist/esm/store/signature.js.map +1 -1
- package/dist/esm/store/sqlite/open.d.ts +1 -1
- package/dist/esm/store/sqlite/open.js +12 -12
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/sqlite/reconcile.js +2 -2
- package/dist/esm/store/sqlite/reconcile.js.map +1 -1
- package/dist/esm/store/sqlite/store.js +21 -18
- package/dist/esm/store/sqlite/store.js.map +1 -1
- package/dist/esm/store/sqlite/vectors.d.ts +2 -2
- package/dist/esm/store/sqlite/vectors.js +7 -5
- package/dist/esm/store/sqlite/vectors.js.map +1 -1
- package/dist/esm/store/stages.js.map +1 -1
- package/dist/esm/store/turso/connection.d.ts +1 -0
- package/dist/esm/store/turso/connection.js +97 -28
- package/dist/esm/store/turso/connection.js.map +1 -1
- package/dist/esm/store/turso/open.d.ts +1 -1
- package/dist/esm/store/turso/open.js +9 -8
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/store.js +16 -14
- package/dist/esm/store/turso/store.js.map +1 -1
- package/dist/esm/store/turso/vectors.d.ts +2 -2
- package/dist/esm/store/turso/vectors.js +27 -17
- package/dist/esm/store/turso/vectors.js.map +1 -1
- package/dist/esm/store/types.d.ts +4 -1
- package/dist/esm/store/types.js.map +1 -1
- package/dist/esm/store/vectors.d.ts +14 -1
- package/dist/esm/store/vectors.js +55 -0
- package/dist/esm/store/vectors.js.map +1 -1
- package/dist/esm/watch.d.ts +6 -0
- package/dist/esm/watch.js +40 -14
- package/dist/esm/watch.js.map +1 -1
- package/dist/esm/workers/parse.d.ts +4 -3
- package/dist/esm/workers/parse.js +19 -17
- package/dist/esm/workers/parse.js.map +1 -1
- package/package.json +1 -1
- package/skills/sense/EXAMPLES.md +3 -3
- package/skills/sense/SKILL.md +11 -3
- package/skills/sense/references/search.md +17 -1
- package/skills/sense/references/sql.md +1 -1
- package/skills/sense/references/stores/duckdb.md +1 -1
- package/skills/sense/references/terminology.md +32 -0
- package/skills/sense-setup/SKILL.md +2 -2
- package/skills/sense-setup/references/embeddings.md +1 -1
- package/skills/sense-setup/references/store-benchmarks.md +5 -5
- package/skills/sense-setup/references/stores/duckdb.md +1 -1
|
@@ -3,7 +3,8 @@ import { estimateTokens } from '../chunk/index.js';
|
|
|
3
3
|
import { featureEnabled } from '../config/index.js';
|
|
4
4
|
import { SenseError } from '../errors.js';
|
|
5
5
|
import { serialQuery } from '../lib/serial-query.js';
|
|
6
|
-
import { INTERNAL_COLUMNS, scopedPaths } from './scope.js';
|
|
6
|
+
import { INTERNAL_COLUMNS, materializeScope, scopedPaths } from './scope.js';
|
|
7
|
+
import { assertQuerySnapshot } from './snapshot.js';
|
|
7
8
|
// Note resolution shared by peek and path: an exact path, or a unique basename (case
|
|
8
9
|
// insensitive, .md stripped).
|
|
9
10
|
export function resolveNote(paths, arg) {
|
|
@@ -15,19 +16,56 @@ export function resolveNote(paths, arg) {
|
|
|
15
16
|
if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `"${arg}" is ambiguous: ${matches.join(', ')}`);
|
|
16
17
|
throw new SenseError('NOTE_NOT_FOUND', `no note matches "${arg}"`);
|
|
17
18
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const PEEK_COUNT_LIMIT = 20;
|
|
20
|
+
/**
|
|
21
|
+
* Returns indexed metadata for a note in scope. Exact excluded paths fail before scoped basename
|
|
22
|
+
* resolution; resolved neighbors stay in scope, while unresolved written targets remain visible.
|
|
23
|
+
*/ export function peek(store, cfg, pathArg, overrides = {}) {
|
|
22
24
|
return serialQuery(store, ()=>peekIndexed(store, cfg, pathArg, overrides));
|
|
23
25
|
}
|
|
24
26
|
async function peekIndexed(store, cfg, pathArg, overrides) {
|
|
27
|
+
var _overrides_sectionCountLimit, _overrides_linkCountLimit;
|
|
28
|
+
for (const name of [
|
|
29
|
+
'sectionCountLimit',
|
|
30
|
+
'linkCountLimit'
|
|
31
|
+
]){
|
|
32
|
+
const value = overrides[name];
|
|
33
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {
|
|
34
|
+
throw new SenseError('SEARCH_OPTION_INVALID', `peek option "${name}" must be a positive safe integer, got ${String(value)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const sectionCountLimit = (_overrides_sectionCountLimit = overrides.sectionCountLimit) !== null && _overrides_sectionCountLimit !== void 0 ? _overrides_sectionCountLimit : PEEK_COUNT_LIMIT;
|
|
38
|
+
const linkCountLimit = (_overrides_linkCountLimit = overrides.linkCountLimit) !== null && _overrides_linkCountLimit !== void 0 ? _overrides_linkCountLimit : PEEK_COUNT_LIMIT;
|
|
25
39
|
return store.transaction(async ()=>{
|
|
26
40
|
var _row__parse_error, _row__size;
|
|
27
41
|
var _this;
|
|
42
|
+
await assertQuerySnapshot(store, cfg);
|
|
28
43
|
const pathsStmt = await store.prepare('SELECT "path" FROM frontmatter');
|
|
29
44
|
const paths = (await pathsStmt.all()).map((r)=>r.path);
|
|
30
|
-
const
|
|
45
|
+
const allowed = await scopedPaths(store, cfg, overrides);
|
|
46
|
+
if (paths.includes(pathArg) && !allowed.has(pathArg)) {
|
|
47
|
+
throw new SenseError('NOTE_NOT_FOUND', `note "${pathArg}" exists but is outside the current scope; change the preset, include/exclude patterns, or where condition`);
|
|
48
|
+
}
|
|
49
|
+
let path;
|
|
50
|
+
try {
|
|
51
|
+
path = resolveNote([
|
|
52
|
+
...allowed
|
|
53
|
+
], pathArg);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err instanceof SenseError && err.code === 'NOTE_NOT_FOUND') {
|
|
56
|
+
let excludedPath;
|
|
57
|
+
try {
|
|
58
|
+
excludedPath = resolveNote(paths, pathArg);
|
|
59
|
+
} catch (unscopedErr) {
|
|
60
|
+
if (unscopedErr instanceof SenseError && unscopedErr.code === 'NOTE_AMBIGUOUS') {
|
|
61
|
+
throw new SenseError('NOTE_NOT_FOUND', `no note in the current scope matches "${pathArg}"; indexed matches exist outside the scope, so change the preset, include/exclude patterns, or where condition`);
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
throw new SenseError('NOTE_NOT_FOUND', `note "${excludedPath}" exists but is outside the current scope; change the preset, include/exclude patterns, or where condition`);
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
31
69
|
const rowStmt = await store.prepare('SELECT * FROM frontmatter WHERE "path" = ?');
|
|
32
70
|
const row = await rowStmt.get(path);
|
|
33
71
|
const parseError = (_row__parse_error = row._parse_error) !== null && _row__parse_error !== void 0 ? _row__parse_error : null;
|
|
@@ -39,21 +77,23 @@ async function peekIndexed(store, cfg, pathArg, overrides) {
|
|
|
39
77
|
let sections = [];
|
|
40
78
|
if (featureEnabled(cfg, 'sections')) {
|
|
41
79
|
sectionsTotal = (await (await store.prepare('SELECT COUNT(*) AS n FROM sections WHERE "path" = ?')).get(path)).n;
|
|
42
|
-
sections = await (await store.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE "path" = ? ORDER BY idx LIMIT ?')).all(path,
|
|
80
|
+
sections = await (await store.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE "path" = ? ORDER BY idx LIMIT ?')).all(path, sectionCountLimit);
|
|
43
81
|
}
|
|
44
82
|
let outbound = [];
|
|
45
83
|
let backlinks = [];
|
|
46
84
|
let unresolved = [];
|
|
85
|
+
let outboundTotal = 0;
|
|
47
86
|
let backlinksTotal = 0;
|
|
48
|
-
|
|
87
|
+
let unresolvedTotal = 0;
|
|
49
88
|
if (featureEnabled(cfg, 'links')) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
await materializeScope(store, '_peek_scope', allowed);
|
|
90
|
+
outboundTotal = (await (await store.prepare('SELECT COUNT(DISTINCT dst) AS n FROM links WHERE src = ? AND dst != src AND dst IN (SELECT "path" FROM _peek_scope)')).get(path)).n;
|
|
91
|
+
// The first written target determines each resolved destination's position.
|
|
92
|
+
outbound = (await (await store.prepare('SELECT dst FROM links WHERE src = ? AND dst != src AND dst IN (SELECT "path" FROM _peek_scope) GROUP BY dst ORDER BY MIN(target) LIMIT ?')).all(path, linkCountLimit)).map((r)=>r.dst);
|
|
93
|
+
unresolvedTotal = (await (await store.prepare('SELECT COUNT(*) AS n FROM links WHERE src = ? AND dst IS NULL')).get(path)).n;
|
|
94
|
+
unresolved = (await (await store.prepare('SELECT target FROM links WHERE src = ? AND dst IS NULL ORDER BY target LIMIT ?')).all(path, linkCountLimit)).map((r)=>r.target);
|
|
95
|
+
backlinksTotal = (await (await store.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ? AND src != dst AND src IN (SELECT "path" FROM _peek_scope)')).get(path)).n;
|
|
96
|
+
backlinks = (await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst AND src IN (SELECT "path" FROM _peek_scope) ORDER BY src LIMIT ?')).all(path, linkCountLimit)).map((r)=>r.src);
|
|
57
97
|
}
|
|
58
98
|
// content.text is the stripped body already computed at reconcile time (no extra file read),
|
|
59
99
|
// floored by the byte/4 estimate so stripped-away syntax and frontmatter can't vanish from the price.
|
|
@@ -66,13 +106,13 @@ async function peekIndexed(store, cfg, pathArg, overrides) {
|
|
|
66
106
|
frontmatter,
|
|
67
107
|
parseError,
|
|
68
108
|
sections,
|
|
69
|
-
outbound
|
|
109
|
+
outbound,
|
|
70
110
|
backlinks,
|
|
71
|
-
unresolved
|
|
111
|
+
unresolved,
|
|
72
112
|
sectionsTotal,
|
|
73
|
-
outboundTotal
|
|
113
|
+
outboundTotal,
|
|
74
114
|
backlinksTotal,
|
|
75
|
-
unresolvedTotal
|
|
115
|
+
unresolvedTotal,
|
|
76
116
|
off: [
|
|
77
117
|
'sections',
|
|
78
118
|
'links'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport { estimateTokens } from '../chunk/index.ts';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Row } from '../output/output.ts';\nimport type { Store } from '../store/types.ts';\nimport { INTERNAL_COLUMNS, scopedPaths } from './scope.ts';\n\n// Note resolution shared by peek and path: an exact path, or a unique basename (case\n// insensitive, .md stripped).\nexport function resolveNote(paths: string[], arg: string): string {\n const exact = paths.find((p) => p === arg);\n if (exact) return exact;\n const base = posix.basename(arg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) return matches[0];\n if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${arg}\" is ambiguous: ${matches.join(', ')}`);\n throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${arg}\"`);\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n // Set when the note's frontmatter was refused, so an empty frontmatter block reads as \"did\n // not parse\" rather than \"has none\". Same reason `_parse_error` sits in the row.\n parseError: string | null;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks (or a note thousands of\n // headings), and peek's whole point is bounded output.\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n // Bounded k-hop expansion beyond the immediate ring already shown by outbound/backlinks\n // (depth starts at 2).\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LIST_LIMIT = 20;\n\n// peek: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides = {}): Promise<Peek> {\n return serialQuery(store, () => peekIndexed(store, cfg, pathArg, overrides));\n}\n\nasync function peekIndexed(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: SearchOverrides): Promise<Peek> {\n return store.transaction(async () => {\n const pathsStmt = await store.prepare('SELECT \"path\" FROM frontmatter');\n const paths = ((await pathsStmt.all()) as Array<{ path: string }>).map((r) => r.path);\n const path = resolveNote(paths, pathArg);\n\n const rowStmt = await store.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?');\n const row = (await rowStmt.get(path)) as Row;\n const parseError = (row._parse_error as string | null) ?? null;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n let sectionsTotal = 0;\n let sections: Row[] = [];\n if (featureEnabled(cfg, 'sections')) {\n sectionsTotal = ((await (await store.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?')).get(path)) as { n: number }).n;\n sections = (await (await store.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?')).all(path, PEEK_LIST_LIMIT)) as Row[];\n }\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n await scopedPaths(store, cfg, overrides); // validates --preset/--where/--include/--exclude even though peek's own output isn't scope-filtered\n if (featureEnabled(cfg, 'links')) {\n const out = (await (await store.prepare('SELECT target, dst FROM links WHERE src = ? AND (dst IS NULL OR dst != src) ORDER BY target')).all(path)) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = ((await (await store.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ? AND src != dst')).get(path)) as { n: number }).n;\n backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst ORDER BY src LIMIT ?')).all(path, PEEK_LIST_LIMIT)) as Array<{ src: string }>).map((r) => r.src);\n }\n\n // content.text is the stripped body already computed at reconcile time (no extra file read),\n // floored by the byte/4 estimate so stripped-away syntax and frontmatter can't vanish from the price.\n const body = ((await (await store.prepare('SELECT text FROM content WHERE \"path\" = ?')).get(path)) as { text: string } | undefined)?.text;\n const byteTokens = Math.ceil(((row._size as number) ?? 0) / 4);\n const tokens = body !== undefined ? Math.max(Math.ceil(estimateTokens(body)), byteTokens) : byteTokens;\n\n return {\n path,\n tokens,\n frontmatter,\n parseError,\n sections,\n outbound: outbound.slice(0, PEEK_LIST_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LIST_LIMIT),\n sectionsTotal,\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n });\n}\n"],"names":["posix","estimateTokens","featureEnabled","SenseError","serialQuery","INTERNAL_COLUMNS","scopedPaths","resolveNote","paths","arg","exact","find","p","base","basename","replace","toLowerCase","matches","filter","length","join","PEEK_LIST_LIMIT","peek","store","cfg","pathArg","overrides","peekIndexed","transaction","row","pathsStmt","prepare","all","map","r","path","rowStmt","get","parseError","_parse_error","frontmatter","key","value","Object","entries","has","sectionsTotal","sections","n","outbound","backlinks","unresolved","backlinksTotal","out","Set","l","dst","target","src","body","text","byteTokens","Math","ceil","_size","tokens","undefined","max","slice","outboundTotal","unresolvedTotal","off","name"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport { estimateTokens } from '../chunk/index.ts';\nimport type { FeatureName, ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { featureEnabled } from '../config/index.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Row } from '../output/output.ts';\nimport type { Store } from '../store/types.ts';\nimport { INTERNAL_COLUMNS, materializeScope, scopedPaths } from './scope.ts';\nimport { assertQuerySnapshot } from './snapshot.ts';\n\n// Note resolution shared by peek and path: an exact path, or a unique basename (case\n// insensitive, .md stripped).\nexport function resolveNote(paths: string[], arg: string): string {\n const exact = paths.find((p) => p === arg);\n if (exact) return exact;\n const base = posix.basename(arg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) return matches[0];\n if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${arg}\" is ambiguous: ${matches.join(', ')}`);\n throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${arg}\"`);\n}\n\n/** Structured, bounded metadata returned by the root {@link peek} API. */\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n /** Set when frontmatter parsing failed, distinguishing that case from an empty block. */\n parseError: string | null;\n sections: PeekSection[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n /** Totals before each bounded list is truncated. */\n sectionsTotal: number;\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n /** Disabled features whose result blocks are omitted rather than returned empty. */\n off: FeatureName[];\n}\n\n/** A section description, with an inclusive source line range and token estimate, not section prose. */\nexport interface PeekSection {\n level: number;\n heading: string;\n start_line: number;\n end_line: number;\n tokens: number;\n}\n\n/** Options for the root {@link peek} API. */\nexport interface PeekOptions extends SearchOverrides {\n /** Maximum section descriptions. Defaults to 20; must be a positive safe integer. */\n sectionCountLimit?: number;\n /** Maximum entries in each link group independently. Defaults to 20; must be a positive safe integer. */\n linkCountLimit?: number;\n}\n\nconst PEEK_COUNT_LIMIT = 20;\n\n/**\n * Returns indexed metadata for a note in scope. Exact excluded paths fail before scoped basename\n * resolution; resolved neighbors stay in scope, while unresolved written targets remain visible.\n */\nexport function peek(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: PeekOptions = {}): Promise<Peek> {\n return serialQuery(store, () => peekIndexed(store, cfg, pathArg, overrides));\n}\n\nasync function peekIndexed(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: PeekOptions): Promise<Peek> {\n for (const name of ['sectionCountLimit', 'linkCountLimit'] as const) {\n const value = overrides[name];\n if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {\n throw new SenseError('SEARCH_OPTION_INVALID', `peek option \"${name}\" must be a positive safe integer, got ${String(value)}`);\n }\n }\n const sectionCountLimit = overrides.sectionCountLimit ?? PEEK_COUNT_LIMIT;\n const linkCountLimit = overrides.linkCountLimit ?? PEEK_COUNT_LIMIT;\n return store.transaction(async () => {\n await assertQuerySnapshot(store, cfg);\n const pathsStmt = await store.prepare('SELECT \"path\" FROM frontmatter');\n const paths = ((await pathsStmt.all()) as Array<{ path: string }>).map((r) => r.path);\n const allowed = await scopedPaths(store, cfg, overrides);\n if (paths.includes(pathArg) && !allowed.has(pathArg)) {\n throw new SenseError('NOTE_NOT_FOUND', `note \"${pathArg}\" exists but is outside the current scope; change the preset, include/exclude patterns, or where condition`);\n }\n let path: string;\n try {\n path = resolveNote([...allowed], pathArg);\n } catch (err) {\n if (err instanceof SenseError && err.code === 'NOTE_NOT_FOUND') {\n let excludedPath: string;\n try {\n excludedPath = resolveNote(paths, pathArg);\n } catch (unscopedErr) {\n if (unscopedErr instanceof SenseError && unscopedErr.code === 'NOTE_AMBIGUOUS') {\n throw new SenseError('NOTE_NOT_FOUND', `no note in the current scope matches \"${pathArg}\"; indexed matches exist outside the scope, so change the preset, include/exclude patterns, or where condition`);\n }\n throw err;\n }\n throw new SenseError('NOTE_NOT_FOUND', `note \"${excludedPath}\" exists but is outside the current scope; change the preset, include/exclude patterns, or where condition`);\n }\n throw err;\n }\n\n const rowStmt = await store.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?');\n const row = (await rowStmt.get(path)) as Row;\n const parseError = (row._parse_error as string | null) ?? null;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n let sectionsTotal = 0;\n let sections: PeekSection[] = [];\n if (featureEnabled(cfg, 'sections')) {\n sectionsTotal = ((await (await store.prepare('SELECT COUNT(*) AS n FROM sections WHERE \"path\" = ?')).get(path)) as { n: number }).n;\n sections = (await (await store.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx LIMIT ?')).all(path, sectionCountLimit)) as PeekSection[];\n }\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let outboundTotal = 0;\n let backlinksTotal = 0;\n let unresolvedTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n await materializeScope(store, '_peek_scope', allowed);\n outboundTotal = ((await (await store.prepare('SELECT COUNT(DISTINCT dst) AS n FROM links WHERE src = ? AND dst != src AND dst IN (SELECT \"path\" FROM _peek_scope)')).get(path)) as { n: number }).n;\n // The first written target determines each resolved destination's position.\n outbound = ((await (await store.prepare('SELECT dst FROM links WHERE src = ? AND dst != src AND dst IN (SELECT \"path\" FROM _peek_scope) GROUP BY dst ORDER BY MIN(target) LIMIT ?')).all(path, linkCountLimit)) as Array<{ dst: string }>).map((r) => r.dst);\n unresolvedTotal = ((await (await store.prepare('SELECT COUNT(*) AS n FROM links WHERE src = ? AND dst IS NULL')).get(path)) as { n: number }).n;\n unresolved = ((await (await store.prepare('SELECT target FROM links WHERE src = ? AND dst IS NULL ORDER BY target LIMIT ?')).all(path, linkCountLimit)) as Array<{ target: string }>).map((r) => r.target);\n backlinksTotal = ((await (await store.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ? AND src != dst AND src IN (SELECT \"path\" FROM _peek_scope)')).get(path)) as { n: number }).n;\n backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst AND src IN (SELECT \"path\" FROM _peek_scope) ORDER BY src LIMIT ?')).all(path, linkCountLimit)) as Array<{ src: string }>).map((r) => r.src);\n }\n\n // content.text is the stripped body already computed at reconcile time (no extra file read),\n // floored by the byte/4 estimate so stripped-away syntax and frontmatter can't vanish from the price.\n const body = ((await (await store.prepare('SELECT text FROM content WHERE \"path\" = ?')).get(path)) as { text: string } | undefined)?.text;\n const byteTokens = Math.ceil(((row._size as number) ?? 0) / 4);\n const tokens = body !== undefined ? Math.max(Math.ceil(estimateTokens(body)), byteTokens) : byteTokens;\n\n return {\n path,\n tokens,\n frontmatter,\n parseError,\n sections,\n outbound,\n backlinks,\n unresolved,\n sectionsTotal,\n outboundTotal,\n backlinksTotal,\n unresolvedTotal,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n });\n}\n"],"names":["posix","estimateTokens","featureEnabled","SenseError","serialQuery","INTERNAL_COLUMNS","materializeScope","scopedPaths","assertQuerySnapshot","resolveNote","paths","arg","exact","find","p","base","basename","replace","toLowerCase","matches","filter","length","join","PEEK_COUNT_LIMIT","peek","store","cfg","pathArg","overrides","peekIndexed","name","value","undefined","Number","isSafeInteger","String","sectionCountLimit","linkCountLimit","transaction","row","pathsStmt","prepare","all","map","r","path","allowed","includes","has","err","code","excludedPath","unscopedErr","rowStmt","get","parseError","_parse_error","frontmatter","key","Object","entries","sectionsTotal","sections","n","outbound","backlinks","unresolved","outboundTotal","backlinksTotal","unresolvedTotal","dst","target","src","body","text","byteTokens","Math","ceil","_size","tokens","max","off"],"mappings":"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"}
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import type { ResolvedConfig, SearchOverrides } from '../config/index.js';
|
|
2
2
|
import type { Store } from '../store/types.js';
|
|
3
|
-
export declare
|
|
4
|
-
|
|
3
|
+
export declare const RELATED_DEFAULT_K = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link relatedNotes}. Scope fields filter result candidates, not the seed note;
|
|
6
|
+
* `k` defaults to 5, matching the CLI.
|
|
7
|
+
*/
|
|
8
|
+
export interface RelatedOptions extends SearchOverrides {
|
|
9
|
+
k?: number;
|
|
10
|
+
}
|
|
11
|
+
export type RelatedResult = {
|
|
5
12
|
path: string;
|
|
6
13
|
similarity: number;
|
|
7
|
-
}
|
|
14
|
+
};
|
|
15
|
+
export declare function resolveRelatedSeed(store: Store, pathArg: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the notes most similar to an indexed seed, excluding the seed and notes linked either
|
|
18
|
+
* way. The seed resolves across the indexed tree even when it is outside the candidate scope.
|
|
19
|
+
*/
|
|
20
|
+
export declare function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, options?: RelatedOptions): Promise<RelatedResult[]>;
|
|
@@ -4,25 +4,26 @@ import { localModelMissing, MODEL_FILENAMES } from '../embed/store.js';
|
|
|
4
4
|
import { SenseError } from '../errors.js';
|
|
5
5
|
import { serialQuery } from '../lib/serial-query.js';
|
|
6
6
|
import { resolveNote } from './peek.js';
|
|
7
|
-
import { scopedPaths, scopeHasEmbeddings } from './scope.js';
|
|
7
|
+
import { scopedPaths, scopeHasEmbeddings, withMaterializedVectorScope } from './scope.js';
|
|
8
|
+
import { assertQuerySnapshot } from './snapshot.js';
|
|
9
|
+
export const RELATED_DEFAULT_K = 5;
|
|
8
10
|
export async function resolveRelatedSeed(store, pathArg) {
|
|
9
11
|
const paths = (await (await store.prepare('SELECT "path" FROM frontmatter')).all()).map((row)=>row.path);
|
|
10
12
|
return resolveNote(paths, pathArg);
|
|
11
13
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Returns the notes most similar to an indexed seed, excluding the seed and notes linked either
|
|
16
|
+
* way. The seed resolves across the indexed tree even when it is outside the candidate scope.
|
|
17
|
+
*/ export function relatedNotes(store, cfg, pathArg, options = {}) {
|
|
18
|
+
return serialQuery(store, ()=>{
|
|
19
|
+
var _options_k;
|
|
20
|
+
return relatedIndexed(store, cfg, pathArg, options, (_options_k = options.k) !== null && _options_k !== void 0 ? _options_k : RELATED_DEFAULT_K);
|
|
21
|
+
});
|
|
16
22
|
}
|
|
17
23
|
async function relatedIndexed(store, cfg, pathArg, overrides, k) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const exclude = new Set([
|
|
22
|
-
path,
|
|
23
|
-
...outbound,
|
|
24
|
-
...backlinks
|
|
25
|
-
]);
|
|
24
|
+
if (!Number.isFinite(k) || !Number.isInteger(k) || k <= 0) {
|
|
25
|
+
throw new SenseError('SEARCH_OPTION_INVALID', `related option "k" must be a positive finite integer, got ${String(k)}`);
|
|
26
|
+
}
|
|
26
27
|
// Vectors are the only signal `related` has, so every way of not having them is an error
|
|
27
28
|
// naming the cause; an empty result means one thing, nothing near in meaning.
|
|
28
29
|
const effective = resolveSearch(cfg, overrides);
|
|
@@ -40,20 +41,36 @@ async function relatedIndexed(store, cfg, pathArg, overrides, k) {
|
|
|
40
41
|
if (localModelMissing(e)) {
|
|
41
42
|
throw new SenseError('EMBED_MODEL_MISSING', `related ranks notes by meaning, so it needs the embedding model, but the local model path "${e.model}" is missing ${MODEL_FILENAMES}; point embed.model at a directory containing them (search still works without it, on words and links)`);
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
return store.transaction(async ()=>{
|
|
45
|
+
await assertQuerySnapshot(store, cfg);
|
|
46
|
+
// Repeat effective preset validation inside the snapshot that supplies its durable scope.
|
|
47
|
+
const current = resolveSearch(cfg, overrides);
|
|
48
|
+
if (current.signals.vectors === undefined) {
|
|
49
|
+
throw new SenseError('PRESET_NOT_SEMANTIC', `preset "${current.presetName}" has no "vectors" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add "vectors" to that preset's signals`);
|
|
50
|
+
}
|
|
51
|
+
const path = await resolveRelatedSeed(store, pathArg);
|
|
52
|
+
const outbound = (await (await store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')).all(path)).map((r)=>r.dst);
|
|
53
|
+
const backlinks = (await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')).all(path)).map((r)=>r.src);
|
|
54
|
+
const exclude = new Set([
|
|
55
|
+
path,
|
|
56
|
+
...outbound,
|
|
57
|
+
...backlinks
|
|
58
|
+
]);
|
|
59
|
+
const allowed = await scopedPaths(store, cfg, overrides);
|
|
60
|
+
// The seed may sit outside a narrowed candidate scope; both it and every eligible result must
|
|
61
|
+
// be ready, while pending vectors in unrelated notes do not block this operation.
|
|
62
|
+
await ensureDocumentEmbeddings(store, cfg, new Set([
|
|
63
|
+
...allowed,
|
|
64
|
+
path
|
|
65
|
+
]));
|
|
66
|
+
if (!await hasEmbedding(store, path)) {
|
|
67
|
+
throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);
|
|
68
|
+
}
|
|
69
|
+
if (!await scopeHasEmbeddings(store, cfg, allowed)) return [];
|
|
70
|
+
return withMaterializedVectorScope(store, allowed, ()=>similarNotes(store, cfg, path, {
|
|
71
|
+
exclude,
|
|
72
|
+
allowed,
|
|
73
|
+
k
|
|
74
|
+
}));
|
|
58
75
|
});
|
|
59
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedConfig, embedEnabled, resolveSearch } from '../config/index.ts';\nimport { ensureDocumentEmbeddings, hasEmbedding, similarNotes } from '../embed/query.ts';\nimport { localModelMissing, MODEL_FILENAMES } from '../embed/store.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Store } from '../store/types.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings } from './scope.ts';\n\nexport async function resolveRelatedSeed(store: Store, pathArg: string): Promise<string> {\n const paths = ((await (await store.prepare('SELECT \"path\" FROM frontmatter')).all()) as Array<{ path: string }>).map((row) => row.path);\n return resolveNote(paths, pathArg);\n}\n\n
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/related.ts"],"sourcesContent":["import type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { embedConfig, embedEnabled, resolveSearch } from '../config/index.ts';\nimport { ensureDocumentEmbeddings, hasEmbedding, similarNotes } from '../embed/query.ts';\nimport { localModelMissing, MODEL_FILENAMES } from '../embed/store.ts';\nimport { SenseError } from '../errors.ts';\nimport { serialQuery } from '../lib/serial-query.ts';\nimport type { Store } from '../store/types.ts';\nimport { resolveNote } from './peek.ts';\nimport { scopedPaths, scopeHasEmbeddings, withMaterializedVectorScope } from './scope.ts';\nimport { assertQuerySnapshot } from './snapshot.ts';\n\nexport const RELATED_DEFAULT_K = 5;\n\n/**\n * Options for {@link relatedNotes}. Scope fields filter result candidates, not the seed note;\n * `k` defaults to 5, matching the CLI.\n */\nexport interface RelatedOptions extends SearchOverrides {\n k?: number;\n}\n\nexport type RelatedResult = {\n path: string;\n similarity: number;\n};\n\nexport async function resolveRelatedSeed(store: Store, pathArg: string): Promise<string> {\n const paths = ((await (await store.prepare('SELECT \"path\" FROM frontmatter')).all()) as Array<{ path: string }>).map((row) => row.path);\n return resolveNote(paths, pathArg);\n}\n\n/**\n * Returns the notes most similar to an indexed seed, excluding the seed and notes linked either\n * way. The seed resolves across the indexed tree even when it is outside the candidate scope.\n */\nexport function relatedNotes(store: Store, cfg: ResolvedConfig, pathArg: string, options: RelatedOptions = {}): Promise<RelatedResult[]> {\n return serialQuery(store, () => relatedIndexed(store, cfg, pathArg, options, options.k ?? RELATED_DEFAULT_K));\n}\n\nasync function relatedIndexed(store: Store, cfg: ResolvedConfig, pathArg: string, overrides: RelatedOptions, k: number): Promise<RelatedResult[]> {\n if (!Number.isFinite(k) || !Number.isInteger(k) || k <= 0) {\n throw new SenseError('SEARCH_OPTION_INVALID', `related option \"k\" must be a positive finite integer, got ${String(k)}`);\n }\n // Vectors are the only signal `related` has, so every way of not having them is an error\n // naming the cause; an empty result means one thing, nothing near in meaning.\n const effective = resolveSearch(cfg, overrides);\n if (!embedEnabled(cfg)) {\n throw new SenseError('EMBED_DISABLED', 'related ranks notes by meaning, and this tree has no embedding model; add an \"embed\" block naming one to sense.config.json (a Hugging Face id fetches automatically at first use; `sense download` prefetches it) -- search works without it, on words and links');\n }\n // search gates on the same signal, so a preset's declared signals mean one thing: without\n // this check an overlapping vectors-on preset would answer for a scope that declined them.\n if (effective.signals.vectors === undefined) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${effective.presetName}\" has no \"vectors\" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add \"vectors\" to that preset's signals`);\n }\n // A downloadable HF id can be fetched by preparation. Only a local path with missing files\n // can never fetch itself.\n const e = embedConfig(cfg); // embedEnabled(cfg) above guarantees this is set\n if (localModelMissing(e)) {\n throw new SenseError('EMBED_MODEL_MISSING', `related ranks notes by meaning, so it needs the embedding model, but the local model path \"${e.model}\" is missing ${MODEL_FILENAMES}; point embed.model at a directory containing them (search still works without it, on words and links)`);\n }\n\n return store.transaction(async () => {\n await assertQuerySnapshot(store, cfg);\n // Repeat effective preset validation inside the snapshot that supplies its durable scope.\n const current = resolveSearch(cfg, overrides);\n if (current.signals.vectors === undefined) {\n throw new SenseError('PRESET_NOT_SEMANTIC', `preset \"${current.presetName}\" has no \"vectors\" signal, so this scope has no vectors and related has no other signal; search it instead (words and links), or add \"vectors\" to that preset's signals`);\n }\n\n const path = await resolveRelatedSeed(store, pathArg);\n const outbound = ((await (await store.prepare('SELECT DISTINCT dst FROM links WHERE src = ? AND dst IS NOT NULL AND dst != src')).all(path)) as Array<{ dst: string }>).map((r) => r.dst);\n const backlinks = ((await (await store.prepare('SELECT DISTINCT src FROM links WHERE dst = ? AND src != dst')).all(path)) as Array<{ src: string }>).map((r) => r.src);\n const exclude = new Set([path, ...outbound, ...backlinks]);\n const allowed = await scopedPaths(store, cfg, overrides);\n // The seed may sit outside a narrowed candidate scope; both it and every eligible result must\n // be ready, while pending vectors in unrelated notes do not block this operation.\n await ensureDocumentEmbeddings(store, cfg, new Set([...allowed, path]));\n if (!(await hasEmbedding(store, path))) {\n throw new SenseError('NOTE_NOT_EMBEDDED', `${path} has no indexed text to compare -- a note that is frontmatter only, or empty, has nothing to rank by meaning`);\n }\n if (!(await scopeHasEmbeddings(store, cfg, allowed))) return [];\n return withMaterializedVectorScope(store, allowed, () => similarNotes(store, cfg, path, { exclude, allowed, k }));\n });\n}\n"],"names":["embedConfig","embedEnabled","resolveSearch","ensureDocumentEmbeddings","hasEmbedding","similarNotes","localModelMissing","MODEL_FILENAMES","SenseError","serialQuery","resolveNote","scopedPaths","scopeHasEmbeddings","withMaterializedVectorScope","assertQuerySnapshot","RELATED_DEFAULT_K","resolveRelatedSeed","store","pathArg","paths","prepare","all","map","row","path","relatedNotes","cfg","options","relatedIndexed","k","overrides","Number","isFinite","isInteger","String","effective","signals","vectors","undefined","presetName","e","model","transaction","current","outbound","r","dst","backlinks","src","exclude","Set","allowed"],"mappings":"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"}
|
|
@@ -7,4 +7,5 @@ export declare function rawScope(store: Store, cfg: ResolvedConfig, overrides: S
|
|
|
7
7
|
export declare function narrowByWhere(store: Store, paths: Set<string>, where: string | undefined): Promise<Set<string>>;
|
|
8
8
|
export declare function scopedPaths(store: Store, cfg: ResolvedConfig, overrides: SearchOverrides): Promise<Set<string>>;
|
|
9
9
|
export declare function materializeScope(store: Store, table: string, paths: Set<string>): Promise<void>;
|
|
10
|
+
export declare function withMaterializedVectorScope<T>(store: Store, allowed: Set<string>, callback: () => Promise<T>, knownTotal?: number): Promise<T>;
|
|
10
11
|
export declare function setupMapScope(store: Store, paths: Set<string>): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { matchesGlob } from 'node:path';
|
|
2
2
|
import { anyPresetEmbeds, resolveSearch } from '../config/index.js';
|
|
3
3
|
import { columnHint } from '../output/column-hint.js';
|
|
4
|
+
import { NATIVE_VECTOR_SCOPE_TABLE, reserveNativeVectorScope } from '../store/vectors.js';
|
|
4
5
|
export const INTERNAL_COLUMNS = new Set([
|
|
5
6
|
'path',
|
|
6
7
|
'_mtime',
|
|
@@ -70,6 +71,63 @@ export async function materializeScope(store, table, paths) {
|
|
|
70
71
|
p
|
|
71
72
|
]));
|
|
72
73
|
}
|
|
74
|
+
// Native vector adapters can consume a small exact scope through their connection-local temp
|
|
75
|
+
// table. The reservation is tied to this Store's VectorStore wrapper, so another Store cannot
|
|
76
|
+
// activate it by receiving the same Set object.
|
|
77
|
+
export async function withMaterializedVectorScope(store, allowed, callback, knownTotal) {
|
|
78
|
+
const reservation = reserveNativeVectorScope(store.vectors, allowed);
|
|
79
|
+
if (!reservation) return callback();
|
|
80
|
+
if (allowed.size === 0) {
|
|
81
|
+
reservation.invalidate();
|
|
82
|
+
return callback();
|
|
83
|
+
}
|
|
84
|
+
let total = knownTotal;
|
|
85
|
+
if (total === undefined) {
|
|
86
|
+
try {
|
|
87
|
+
const stmt = await store.prepare('SELECT COUNT(*) AS count FROM frontmatter');
|
|
88
|
+
const row = await stmt.get();
|
|
89
|
+
total = Number(row.count);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
reservation.invalidate();
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Materialization only buys work when this is a nonempty true narrowing and the allowed side
|
|
96
|
+
// is no larger than its complement. Direct adapter calls and broader scopes retain JS filtering.
|
|
97
|
+
if (allowed.size >= total || allowed.size > total - allowed.size) {
|
|
98
|
+
reservation.invalidate();
|
|
99
|
+
return callback();
|
|
100
|
+
}
|
|
101
|
+
let operationFailed = false;
|
|
102
|
+
let operationError;
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
await materializeScope(store, NATIVE_VECTOR_SCOPE_TABLE, allowed);
|
|
106
|
+
reservation.activate();
|
|
107
|
+
result = await callback();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
operationFailed = true;
|
|
110
|
+
operationError = err;
|
|
111
|
+
}
|
|
112
|
+
let cleanupFailed = false;
|
|
113
|
+
let cleanupError;
|
|
114
|
+
try {
|
|
115
|
+
reservation.deactivate();
|
|
116
|
+
await store.exec(`DROP TABLE IF EXISTS ${NATIVE_VECTOR_SCOPE_TABLE}`);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
cleanupFailed = true;
|
|
119
|
+
cleanupError = err;
|
|
120
|
+
} finally{
|
|
121
|
+
reservation.invalidate();
|
|
122
|
+
}
|
|
123
|
+
if (operationFailed && cleanupFailed) throw new AggregateError([
|
|
124
|
+
operationError,
|
|
125
|
+
cleanupError
|
|
126
|
+
], 'native vector scope operation and cleanup failed');
|
|
127
|
+
if (operationFailed) throw operationError;
|
|
128
|
+
if (cleanupFailed) throw cleanupError;
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
73
131
|
export async function setupMapScope(store, paths) {
|
|
74
132
|
await materializeScope(store, '_map_scope', paths);
|
|
75
133
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/scope.ts"],"sourcesContent":["import { matchesGlob } from 'node:path';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { anyPresetEmbeds, resolveSearch } from '../config/index.ts';\nimport { columnHint } from '../output/column-hint.ts';\nimport type { Store } from '../store/types.ts';\n\nexport const INTERNAL_COLUMNS = new Set(['path', '_mtime', '_ctime', '_size', '_rank', '_parse_error']);\n\n// node:path's matchesGlob is experimental (stable behind an unstable-API flag) at the\n// Node >=22.20 engines floor; used here in JS rather than a directory walk in the query path.\nexport function inScope(path: string, include: string[], exclude?: string[]): boolean {\n if (!include.some((g) => matchesGlob(path, g))) return false;\n if (exclude?.some((g) => matchesGlob(path, g))) return false;\n return true;\n}\n\nexport async function scopeHasEmbeddings(store: Store, cfg: ResolvedConfig, scopedPaths: Set<string>): Promise<boolean> {\n if (!anyPresetEmbeds(cfg)) return false; // the embeddings table doesn't exist at all in this case\n const stmt = await store.prepare('SELECT DISTINCT \"path\" FROM embeddings WHERE vector IS NOT NULL');\n const rows = (await stmt.all()) as Array<{ path: string }>;\n return rows.some((r) => scopedPaths.has(r.path));\n}\n\n// Scope only (no --where): preset_files for a named preset, JS glob matching for an ad hoc\n// include/exclude override. Shared by scopedPaths() and search()'s candidate-pool sizing.\nexport async function rawScope(store: Store, cfg: ResolvedConfig, overrides: SearchOverrides, allPaths?: string[]): Promise<Set<string>> {\n const effective = resolveSearch(cfg, overrides);\n const { include, exclude } = effective;\n const adHocScope = overrides.include !== undefined || overrides.exclude !== undefined || overrides.noExclude === true;\n if (!adHocScope) {\n const stmt = await store.prepare('SELECT \"path\" FROM preset_files WHERE preset = ?');\n return new Set(((await stmt.all(effective.presetName)) as Array<{ path: string }>).map((r) => r.path));\n }\n const paths = allPaths ?? (await allFrontmatterPaths(store));\n return new Set(paths.filter((p) => inScope(p, include, exclude)));\n}\n\nasync function allFrontmatterPaths(store: Store): Promise<string[]> {\n const stmt = await store.prepare('SELECT \"path\" FROM frontmatter');\n return ((await stmt.all()) as Array<{ path: string }>).map((r) => r.path);\n}\n\nexport async function narrowByWhere(store: Store, paths: Set<string>, where: string | undefined): Promise<Set<string>> {\n if (!where) return paths;\n let whereRows: Array<{ path: string }>;\n try {\n const stmt = await store.prepare(`SELECT \"path\" FROM frontmatter f WHERE (${where})`);\n whereRows = (await stmt.all()) as Array<{ path: string }>;\n } catch (err) {\n throw columnHint(await store.docs.columns(), err as Error);\n }\n const wherePaths = new Set(whereRows.map((r) => r.path));\n return new Set([...paths].filter((p) => wherePaths.has(p)));\n}\n\n// The scope resolver for non-search commands (path, peek, map): same coverage rule search()\n// applies, then narrowed by the resolved `where`.\nexport async function scopedPaths(store: Store, cfg: ResolvedConfig, overrides: SearchOverrides): Promise<Set<string>> {\n const effective = resolveSearch(cfg, overrides);\n return narrowByWhere(store, await rawScope(store, cfg, overrides), effective.where);\n}\n\n// Materializes a path set into a named temp table (same shape as traverse.ts's allowed_nodes)\n// so a query can join/filter against it cheaply instead of binding a parameter per path.\nexport async function materializeScope(store: Store, table: string, paths: Set<string>): Promise<void> {\n await store.exec(`DROP TABLE IF EXISTS ${table}`);\n await store.exec(`CREATE TEMP TABLE ${table} (\"path\" TEXT PRIMARY KEY)`);\n await store.runBatch(\n `INSERT INTO ${table} (\"path\") VALUES (?)`,\n [...paths].map((p) => [p])\n );\n}\n\nexport async function setupMapScope(store: Store, paths: Set<string>): Promise<void> {\n await materializeScope(store, '_map_scope', paths);\n}\n"],"names":["matchesGlob","anyPresetEmbeds","resolveSearch","columnHint","INTERNAL_COLUMNS","Set","inScope","path","include","exclude","some","g","scopeHasEmbeddings","store","cfg","scopedPaths","stmt","prepare","rows","all","r","has","rawScope","overrides","allPaths","effective","adHocScope","undefined","noExclude","presetName","map","paths","allFrontmatterPaths","filter","p","narrowByWhere","where","whereRows","err","docs","columns","wherePaths","materializeScope","table","exec","runBatch","setupMapScope"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/scope.ts"],"sourcesContent":["import { matchesGlob } from 'node:path';\nimport type { ResolvedConfig, SearchOverrides } from '../config/index.ts';\nimport { anyPresetEmbeds, resolveSearch } from '../config/index.ts';\nimport { columnHint } from '../output/column-hint.ts';\nimport type { Store } from '../store/types.ts';\nimport { NATIVE_VECTOR_SCOPE_TABLE, reserveNativeVectorScope } from '../store/vectors.ts';\n\nexport const INTERNAL_COLUMNS = new Set(['path', '_mtime', '_ctime', '_size', '_rank', '_parse_error']);\n\n// node:path's matchesGlob is experimental (stable behind an unstable-API flag) at the\n// Node >=22.20 engines floor; used here in JS rather than a directory walk in the query path.\nexport function inScope(path: string, include: string[], exclude?: string[]): boolean {\n if (!include.some((g) => matchesGlob(path, g))) return false;\n if (exclude?.some((g) => matchesGlob(path, g))) return false;\n return true;\n}\n\nexport async function scopeHasEmbeddings(store: Store, cfg: ResolvedConfig, scopedPaths: Set<string>): Promise<boolean> {\n if (!anyPresetEmbeds(cfg)) return false; // the embeddings table doesn't exist at all in this case\n const stmt = await store.prepare('SELECT DISTINCT \"path\" FROM embeddings WHERE vector IS NOT NULL');\n const rows = (await stmt.all()) as Array<{ path: string }>;\n return rows.some((r) => scopedPaths.has(r.path));\n}\n\n// Scope only (no --where): preset_files for a named preset, JS glob matching for an ad hoc\n// include/exclude override. Shared by scopedPaths() and search()'s candidate-pool sizing.\nexport async function rawScope(store: Store, cfg: ResolvedConfig, overrides: SearchOverrides, allPaths?: string[]): Promise<Set<string>> {\n const effective = resolveSearch(cfg, overrides);\n const { include, exclude } = effective;\n const adHocScope = overrides.include !== undefined || overrides.exclude !== undefined || overrides.noExclude === true;\n if (!adHocScope) {\n const stmt = await store.prepare('SELECT \"path\" FROM preset_files WHERE preset = ?');\n return new Set(((await stmt.all(effective.presetName)) as Array<{ path: string }>).map((r) => r.path));\n }\n const paths = allPaths ?? (await allFrontmatterPaths(store));\n return new Set(paths.filter((p) => inScope(p, include, exclude)));\n}\n\nasync function allFrontmatterPaths(store: Store): Promise<string[]> {\n const stmt = await store.prepare('SELECT \"path\" FROM frontmatter');\n return ((await stmt.all()) as Array<{ path: string }>).map((r) => r.path);\n}\n\nexport async function narrowByWhere(store: Store, paths: Set<string>, where: string | undefined): Promise<Set<string>> {\n if (!where) return paths;\n let whereRows: Array<{ path: string }>;\n try {\n const stmt = await store.prepare(`SELECT \"path\" FROM frontmatter f WHERE (${where})`);\n whereRows = (await stmt.all()) as Array<{ path: string }>;\n } catch (err) {\n throw columnHint(await store.docs.columns(), err as Error);\n }\n const wherePaths = new Set(whereRows.map((r) => r.path));\n return new Set([...paths].filter((p) => wherePaths.has(p)));\n}\n\n// The scope resolver for non-search commands (path, peek, map): same coverage rule search()\n// applies, then narrowed by the resolved `where`.\nexport async function scopedPaths(store: Store, cfg: ResolvedConfig, overrides: SearchOverrides): Promise<Set<string>> {\n const effective = resolveSearch(cfg, overrides);\n return narrowByWhere(store, await rawScope(store, cfg, overrides), effective.where);\n}\n\n// Materializes a path set into a named temp table (same shape as traverse.ts's allowed_nodes)\n// so a query can join/filter against it cheaply instead of binding a parameter per path.\nexport async function materializeScope(store: Store, table: string, paths: Set<string>): Promise<void> {\n await store.exec(`DROP TABLE IF EXISTS ${table}`);\n await store.exec(`CREATE TEMP TABLE ${table} (\"path\" TEXT PRIMARY KEY)`);\n await store.runBatch(\n `INSERT INTO ${table} (\"path\") VALUES (?)`,\n [...paths].map((p) => [p])\n );\n}\n\n// Native vector adapters can consume a small exact scope through their connection-local temp\n// table. The reservation is tied to this Store's VectorStore wrapper, so another Store cannot\n// activate it by receiving the same Set object.\nexport async function withMaterializedVectorScope<T>(store: Store, allowed: Set<string>, callback: () => Promise<T>, knownTotal?: number): Promise<T> {\n const reservation = reserveNativeVectorScope(store.vectors, allowed);\n if (!reservation) return callback();\n\n if (allowed.size === 0) {\n reservation.invalidate();\n return callback();\n }\n\n let total = knownTotal;\n if (total === undefined) {\n try {\n const stmt = await store.prepare('SELECT COUNT(*) AS count FROM frontmatter');\n const row = (await stmt.get()) as { count: number | bigint };\n total = Number(row.count);\n } catch (err) {\n reservation.invalidate();\n throw err;\n }\n }\n // Materialization only buys work when this is a nonempty true narrowing and the allowed side\n // is no larger than its complement. Direct adapter calls and broader scopes retain JS filtering.\n if (allowed.size >= total || allowed.size > total - allowed.size) {\n reservation.invalidate();\n return callback();\n }\n\n let operationFailed = false;\n let operationError: unknown;\n let result!: T;\n try {\n await materializeScope(store, NATIVE_VECTOR_SCOPE_TABLE, allowed);\n reservation.activate();\n result = await callback();\n } catch (err) {\n operationFailed = true;\n operationError = err;\n }\n\n let cleanupFailed = false;\n let cleanupError: unknown;\n try {\n reservation.deactivate();\n await store.exec(`DROP TABLE IF EXISTS ${NATIVE_VECTOR_SCOPE_TABLE}`);\n } catch (err) {\n cleanupFailed = true;\n cleanupError = err;\n } finally {\n reservation.invalidate();\n }\n\n if (operationFailed && cleanupFailed) throw new AggregateError([operationError, cleanupError], 'native vector scope operation and cleanup failed');\n if (operationFailed) throw operationError;\n if (cleanupFailed) throw cleanupError;\n return result;\n}\n\nexport async function setupMapScope(store: Store, paths: Set<string>): Promise<void> {\n await materializeScope(store, '_map_scope', paths);\n}\n"],"names":["matchesGlob","anyPresetEmbeds","resolveSearch","columnHint","NATIVE_VECTOR_SCOPE_TABLE","reserveNativeVectorScope","INTERNAL_COLUMNS","Set","inScope","path","include","exclude","some","g","scopeHasEmbeddings","store","cfg","scopedPaths","stmt","prepare","rows","all","r","has","rawScope","overrides","allPaths","effective","adHocScope","undefined","noExclude","presetName","map","paths","allFrontmatterPaths","filter","p","narrowByWhere","where","whereRows","err","docs","columns","wherePaths","materializeScope","table","exec","runBatch","withMaterializedVectorScope","allowed","callback","knownTotal","reservation","vectors","size","invalidate","total","row","get","Number","count","operationFailed","operationError","result","activate","cleanupFailed","cleanupError","deactivate","AggregateError","setupMapScope"],"mappings":"AAAA,SAASA,WAAW,QAAQ,YAAY;AAExC,SAASC,eAAe,EAAEC,aAAa,QAAQ,qBAAqB;AACpE,SAASC,UAAU,QAAQ,2BAA2B;AAEtD,SAASC,yBAAyB,EAAEC,wBAAwB,QAAQ,sBAAsB;AAE1F,OAAO,MAAMC,mBAAmB,IAAIC,IAAI;IAAC;IAAQ;IAAU;IAAU;IAAS;IAAS;CAAe,EAAE;AAExG,sFAAsF;AACtF,8FAA8F;AAC9F,OAAO,SAASC,QAAQC,IAAY,EAAEC,OAAiB,EAAEC,OAAkB;IACzE,IAAI,CAACD,QAAQE,IAAI,CAAC,CAACC,IAAMb,YAAYS,MAAMI,KAAK,OAAO;IACvD,IAAIF,oBAAAA,8BAAAA,QAASC,IAAI,CAAC,CAACC,IAAMb,YAAYS,MAAMI,KAAK,OAAO;IACvD,OAAO;AACT;AAEA,OAAO,eAAeC,mBAAmBC,KAAY,EAAEC,GAAmB,EAAEC,WAAwB;IAClG,IAAI,CAAChB,gBAAgBe,MAAM,OAAO,OAAO,yDAAyD;IAClG,MAAME,OAAO,MAAMH,MAAMI,OAAO,CAAC;IACjC,MAAMC,OAAQ,MAAMF,KAAKG,GAAG;IAC5B,OAAOD,KAAKR,IAAI,CAAC,CAACU,IAAML,YAAYM,GAAG,CAACD,EAAEb,IAAI;AAChD;AAEA,2FAA2F;AAC3F,0FAA0F;AAC1F,OAAO,eAAee,SAAST,KAAY,EAAEC,GAAmB,EAAES,SAA0B,EAAEC,QAAmB;IAC/G,MAAMC,YAAYzB,cAAcc,KAAKS;IACrC,MAAM,EAAEf,OAAO,EAAEC,OAAO,EAAE,GAAGgB;IAC7B,MAAMC,aAAaH,UAAUf,OAAO,KAAKmB,aAAaJ,UAAUd,OAAO,KAAKkB,aAAaJ,UAAUK,SAAS,KAAK;IACjH,IAAI,CAACF,YAAY;QACf,MAAMV,OAAO,MAAMH,MAAMI,OAAO,CAAC;QACjC,OAAO,IAAIZ,IAAI,AAAE,CAAA,MAAMW,KAAKG,GAAG,CAACM,UAAUI,UAAU,CAAA,EAA+BC,GAAG,CAAC,CAACV,IAAMA,EAAEb,IAAI;IACtG;IACA,MAAMwB,QAAQP,qBAAAA,sBAAAA,WAAa,MAAMQ,oBAAoBnB;IACrD,OAAO,IAAIR,IAAI0B,MAAME,MAAM,CAAC,CAACC,IAAM5B,QAAQ4B,GAAG1B,SAASC;AACzD;AAEA,eAAeuB,oBAAoBnB,KAAY;IAC7C,MAAMG,OAAO,MAAMH,MAAMI,OAAO,CAAC;IACjC,OAAO,AAAE,CAAA,MAAMD,KAAKG,GAAG,EAAC,EAA+BW,GAAG,CAAC,CAACV,IAAMA,EAAEb,IAAI;AAC1E;AAEA,OAAO,eAAe4B,cAActB,KAAY,EAAEkB,KAAkB,EAAEK,KAAyB;IAC7F,IAAI,CAACA,OAAO,OAAOL;IACnB,IAAIM;IACJ,IAAI;QACF,MAAMrB,OAAO,MAAMH,MAAMI,OAAO,CAAC,CAAC,wCAAwC,EAAEmB,MAAM,CAAC,CAAC;QACpFC,YAAa,MAAMrB,KAAKG,GAAG;IAC7B,EAAE,OAAOmB,KAAK;QACZ,MAAMrC,WAAW,MAAMY,MAAM0B,IAAI,CAACC,OAAO,IAAIF;IAC/C;IACA,MAAMG,aAAa,IAAIpC,IAAIgC,UAAUP,GAAG,CAAC,CAACV,IAAMA,EAAEb,IAAI;IACtD,OAAO,IAAIF,IAAI;WAAI0B;KAAM,CAACE,MAAM,CAAC,CAACC,IAAMO,WAAWpB,GAAG,CAACa;AACzD;AAEA,4FAA4F;AAC5F,kDAAkD;AAClD,OAAO,eAAenB,YAAYF,KAAY,EAAEC,GAAmB,EAAES,SAA0B;IAC7F,MAAME,YAAYzB,cAAcc,KAAKS;IACrC,OAAOY,cAActB,OAAO,MAAMS,SAAST,OAAOC,KAAKS,YAAYE,UAAUW,KAAK;AACpF;AAEA,8FAA8F;AAC9F,yFAAyF;AACzF,OAAO,eAAeM,iBAAiB7B,KAAY,EAAE8B,KAAa,EAAEZ,KAAkB;IACpF,MAAMlB,MAAM+B,IAAI,CAAC,CAAC,qBAAqB,EAAED,OAAO;IAChD,MAAM9B,MAAM+B,IAAI,CAAC,CAAC,kBAAkB,EAAED,MAAM,0BAA0B,CAAC;IACvE,MAAM9B,MAAMgC,QAAQ,CAClB,CAAC,YAAY,EAAEF,MAAM,oBAAoB,CAAC,EAC1C;WAAIZ;KAAM,CAACD,GAAG,CAAC,CAACI,IAAM;YAACA;SAAE;AAE7B;AAEA,6FAA6F;AAC7F,8FAA8F;AAC9F,gDAAgD;AAChD,OAAO,eAAeY,4BAA+BjC,KAAY,EAAEkC,OAAoB,EAAEC,QAA0B,EAAEC,UAAmB;IACtI,MAAMC,cAAc/C,yBAAyBU,MAAMsC,OAAO,EAAEJ;IAC5D,IAAI,CAACG,aAAa,OAAOF;IAEzB,IAAID,QAAQK,IAAI,KAAK,GAAG;QACtBF,YAAYG,UAAU;QACtB,OAAOL;IACT;IAEA,IAAIM,QAAQL;IACZ,IAAIK,UAAU3B,WAAW;QACvB,IAAI;YACF,MAAMX,OAAO,MAAMH,MAAMI,OAAO,CAAC;YACjC,MAAMsC,MAAO,MAAMvC,KAAKwC,GAAG;YAC3BF,QAAQG,OAAOF,IAAIG,KAAK;QAC1B,EAAE,OAAOpB,KAAK;YACZY,YAAYG,UAAU;YACtB,MAAMf;QACR;IACF;IACA,6FAA6F;IAC7F,iGAAiG;IACjG,IAAIS,QAAQK,IAAI,IAAIE,SAASP,QAAQK,IAAI,GAAGE,QAAQP,QAAQK,IAAI,EAAE;QAChEF,YAAYG,UAAU;QACtB,OAAOL;IACT;IAEA,IAAIW,kBAAkB;IACtB,IAAIC;IACJ,IAAIC;IACJ,IAAI;QACF,MAAMnB,iBAAiB7B,OAAOX,2BAA2B6C;QACzDG,YAAYY,QAAQ;QACpBD,SAAS,MAAMb;IACjB,EAAE,OAAOV,KAAK;QACZqB,kBAAkB;QAClBC,iBAAiBtB;IACnB;IAEA,IAAIyB,gBAAgB;IACpB,IAAIC;IACJ,IAAI;QACFd,YAAYe,UAAU;QACtB,MAAMpD,MAAM+B,IAAI,CAAC,CAAC,qBAAqB,EAAE1C,2BAA2B;IACtE,EAAE,OAAOoC,KAAK;QACZyB,gBAAgB;QAChBC,eAAe1B;IACjB,SAAU;QACRY,YAAYG,UAAU;IACxB;IAEA,IAAIM,mBAAmBI,eAAe,MAAM,IAAIG,eAAe;QAACN;QAAgBI;KAAa,EAAE;IAC/F,IAAIL,iBAAiB,MAAMC;IAC3B,IAAIG,eAAe,MAAMC;IACzB,OAAOH;AACT;AAEA,OAAO,eAAeM,cAActD,KAAY,EAAEkB,KAAkB;IAClE,MAAMW,iBAAiB7B,OAAO,cAAckB;AAC9C"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ResolvedConfig } from '../config/index.js';
|
|
2
|
-
import type { Row } from '../output/output.js';
|
|
3
2
|
import type { Store } from '../store/types.js';
|
|
3
|
+
import type { SearchResultVia, SearchSignalContribution } from './signals.js';
|
|
4
|
+
export type { SearchResultVia, SearchSignalContribution } from './signals.js';
|
|
4
5
|
export declare const SNIPPET_CHAR_LIMIT_DEFAULT = 80;
|
|
5
6
|
export declare const SNIPPET_COUNT_LIMIT_DEFAULT = 1;
|
|
6
7
|
export declare function computeSnippets(text: string, terms: string[], charLimit: number, countLimit: number): {
|
|
@@ -8,8 +9,11 @@ export declare function computeSnippets(text: string, terms: string[], charLimit
|
|
|
8
9
|
offset: number;
|
|
9
10
|
};
|
|
10
11
|
export declare function lineNumberAt(text: string, offset: number): number;
|
|
12
|
+
/** Options for the root {@link search} API. */
|
|
11
13
|
export interface SearchOptions {
|
|
12
14
|
k?: number;
|
|
15
|
+
/** Collects ranking contributions for returned candidates; omitted or false preserves ordinary output. */
|
|
16
|
+
explain?: boolean;
|
|
13
17
|
where?: string;
|
|
14
18
|
preset?: string;
|
|
15
19
|
include?: string[];
|
|
@@ -17,6 +21,29 @@ export interface SearchOptions {
|
|
|
17
21
|
noExclude?: boolean;
|
|
18
22
|
snippetCharLimit?: number;
|
|
19
23
|
snippetCountLimit?: number;
|
|
24
|
+
/** Cancels supported provider I/O and stops between phases after active native work finishes. */
|
|
25
|
+
signal?: AbortSignal;
|
|
20
26
|
}
|
|
21
|
-
export
|
|
22
|
-
|
|
27
|
+
export type SearchResult = {
|
|
28
|
+
path: string;
|
|
29
|
+
title: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
snippets: string[];
|
|
32
|
+
via: SearchResultVia;
|
|
33
|
+
score: number;
|
|
34
|
+
lines: string | null;
|
|
35
|
+
similarity?: number | null;
|
|
36
|
+
/** Present only with explain: true, in words/links/vectors order; contributions can differ from via labels. */
|
|
37
|
+
explanation?: SearchSignalContribution[];
|
|
38
|
+
};
|
|
39
|
+
export interface SearchHydrationRow {
|
|
40
|
+
path: string;
|
|
41
|
+
snippets?: string[];
|
|
42
|
+
lines?: string | null;
|
|
43
|
+
}
|
|
44
|
+
export declare function hydrateSearchRows(store: Store, cfg: ResolvedConfig, rows: SearchHydrationRow[], matchedPaths: Set<string>, terms: string, opts?: Pick<SearchOptions, 'snippetCharLimit' | 'snippetCountLimit'>): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Searches one committed published generation. Provider work precedes the final read transaction;
|
|
47
|
+
* cancellation waits for active native work and transaction cleanup before rejection.
|
|
48
|
+
*/
|
|
49
|
+
export declare function search(store: Store, cfg: ResolvedConfig, terms: string, opts?: SearchOptions): Promise<SearchResult[]>;
|