sensemaking 0.27.1 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/cli.js +16 -7
- package/dist/cjs/cli/constants.d.cts +2 -0
- package/dist/cjs/cli/constants.d.ts +2 -0
- package/dist/cjs/cli/constants.js +21 -0
- package/dist/cjs/cli/constants.js.map +1 -0
- package/dist/cjs/cli/named.js +2 -1
- package/dist/cjs/cli/named.js.map +1 -1
- package/dist/cjs/cli/shared.js +5 -4
- package/dist/cjs/cli/shared.js.map +1 -1
- package/dist/cjs/cli.js +10 -9
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/embed/query.js.map +1 -1
- package/dist/cjs/store/duckdb/engine.d.cts +11 -0
- package/dist/cjs/store/duckdb/engine.d.ts +11 -0
- package/dist/cjs/store/duckdb/engine.js +193 -0
- package/dist/cjs/store/duckdb/engine.js.map +1 -0
- package/dist/cjs/store/duckdb/lexical.js +34 -20
- package/dist/cjs/store/duckdb/lexical.js.map +1 -1
- package/dist/cjs/store/duckdb/native.d.cts +5 -2
- package/dist/cjs/store/duckdb/native.d.ts +5 -2
- package/dist/cjs/store/duckdb/native.js +157 -13
- package/dist/cjs/store/duckdb/native.js.map +1 -1
- package/dist/cjs/store/duckdb/open.js +151 -3
- package/dist/cjs/store/duckdb/open.js.map +1 -1
- package/dist/cjs/store/duckdb/ordered-bm25.d.cts +3 -3
- package/dist/cjs/store/duckdb/ordered-bm25.d.ts +3 -3
- package/dist/cjs/store/duckdb/ordered-bm25.js +4 -8
- package/dist/cjs/store/duckdb/ordered-bm25.js.map +1 -1
- package/dist/cjs/store/engine.d.cts +28 -0
- package/dist/cjs/store/engine.d.ts +28 -0
- package/dist/cjs/store/engine.js +362 -0
- package/dist/cjs/store/engine.js.map +1 -0
- package/dist/cjs/store/native.d.cts +8 -5
- package/dist/cjs/store/native.d.ts +8 -5
- package/dist/cjs/store/native.js +78 -73
- package/dist/cjs/store/native.js.map +1 -1
- package/dist/cjs/store/open.js +100 -40
- package/dist/cjs/store/open.js.map +1 -1
- package/dist/cjs/store/sqlite/engine.d.cts +12 -0
- package/dist/cjs/store/sqlite/engine.d.ts +12 -0
- package/dist/cjs/store/sqlite/engine.js +53 -0
- package/dist/cjs/store/sqlite/engine.js.map +1 -0
- package/dist/cjs/store/sqlite/lexical.js +3 -4
- package/dist/cjs/store/sqlite/lexical.js.map +1 -1
- package/dist/cjs/store/sqlite/open.js +119 -6
- package/dist/cjs/store/sqlite/open.js.map +1 -1
- package/dist/cjs/store/turso/engine.d.cts +9 -0
- package/dist/cjs/store/turso/engine.d.ts +9 -0
- package/dist/cjs/store/turso/engine.js +189 -0
- package/dist/cjs/store/turso/engine.js.map +1 -0
- package/dist/cjs/store/turso/lexical.js +16 -11
- package/dist/cjs/store/turso/lexical.js.map +1 -1
- package/dist/cjs/store/turso/native.d.cts +5 -1
- package/dist/cjs/store/turso/native.d.ts +5 -1
- package/dist/cjs/store/turso/native.js +157 -8
- package/dist/cjs/store/turso/native.js.map +1 -1
- package/dist/cjs/store/turso/open.js +172 -27
- package/dist/cjs/store/turso/open.js.map +1 -1
- package/dist/cjs/store/turso/reconcile.d.cts +0 -1
- package/dist/cjs/store/turso/reconcile.d.ts +0 -1
- package/dist/cjs/store/turso/reconcile.js +22 -23
- package/dist/cjs/store/turso/reconcile.js.map +1 -1
- package/dist/cjs/store/types.d.cts +2 -0
- package/dist/cjs/store/types.d.ts +2 -0
- package/dist/cjs/store/types.js.map +1 -1
- package/dist/esm/cli/constants.d.ts +2 -0
- package/dist/esm/cli/constants.js +2 -0
- package/dist/esm/cli/constants.js.map +1 -0
- package/dist/esm/cli/named.js +2 -1
- package/dist/esm/cli/named.js.map +1 -1
- package/dist/esm/cli/shared.js +6 -5
- package/dist/esm/cli/shared.js.map +1 -1
- package/dist/esm/cli.js +10 -9
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/embed/query.js +1 -1
- package/dist/esm/embed/query.js.map +1 -1
- package/dist/esm/store/duckdb/engine.d.ts +11 -0
- package/dist/esm/store/duckdb/engine.js +18 -0
- package/dist/esm/store/duckdb/engine.js.map +1 -0
- package/dist/esm/store/duckdb/lexical.js +11 -11
- package/dist/esm/store/duckdb/lexical.js.map +1 -1
- package/dist/esm/store/duckdb/native.d.ts +5 -2
- package/dist/esm/store/duckdb/native.js +16 -12
- package/dist/esm/store/duckdb/native.js.map +1 -1
- package/dist/esm/store/duckdb/open.js +40 -2
- package/dist/esm/store/duckdb/open.js.map +1 -1
- package/dist/esm/store/duckdb/ordered-bm25.d.ts +3 -3
- package/dist/esm/store/duckdb/ordered-bm25.js +4 -9
- package/dist/esm/store/duckdb/ordered-bm25.js.map +1 -1
- package/dist/esm/store/engine.d.ts +28 -0
- package/dist/esm/store/engine.js +85 -0
- package/dist/esm/store/engine.js.map +1 -0
- package/dist/esm/store/native.d.ts +8 -5
- package/dist/esm/store/native.js +59 -57
- package/dist/esm/store/native.js.map +1 -1
- package/dist/esm/store/open.js +24 -7
- package/dist/esm/store/open.js.map +1 -1
- package/dist/esm/store/sqlite/engine.d.ts +12 -0
- package/dist/esm/store/sqlite/engine.js +16 -0
- package/dist/esm/store/sqlite/engine.js.map +1 -0
- package/dist/esm/store/sqlite/lexical.js +2 -3
- package/dist/esm/store/sqlite/lexical.js.map +1 -1
- package/dist/esm/store/sqlite/open.js +38 -7
- package/dist/esm/store/sqlite/open.js.map +1 -1
- package/dist/esm/store/turso/engine.d.ts +9 -0
- package/dist/esm/store/turso/engine.js +14 -0
- package/dist/esm/store/turso/engine.js.map +1 -0
- package/dist/esm/store/turso/lexical.js +9 -9
- package/dist/esm/store/turso/lexical.js.map +1 -1
- package/dist/esm/store/turso/native.d.ts +5 -1
- package/dist/esm/store/turso/native.js +16 -10
- package/dist/esm/store/turso/native.js.map +1 -1
- package/dist/esm/store/turso/open.js +44 -3
- package/dist/esm/store/turso/open.js.map +1 -1
- package/dist/esm/store/turso/reconcile.d.ts +0 -1
- package/dist/esm/store/turso/reconcile.js +3 -8
- package/dist/esm/store/turso/reconcile.js.map +1 -1
- package/dist/esm/store/types.d.ts +2 -0
- package/dist/esm/store/types.js.map +1 -1
- package/package.json +21 -7
- package/skills/sense/EXAMPLES.md +0 -99
- package/skills/sense/SKILL.md +0 -145
- package/skills/sense/references/search.md +0 -63
- package/skills/sense/references/sql.md +0 -120
- package/skills/sense/references/stores/duckdb.md +0 -35
- package/skills/sense/references/stores/sqlite.md +0 -66
- package/skills/sense/references/stores/turso.md +0 -36
- package/skills/sense/references/terminology.md +0 -32
- package/skills/sense-bases/EXAMPLES.md +0 -103
- package/skills/sense-bases/SKILL.md +0 -147
- package/skills/sense-setup/EXAMPLES.md +0 -129
- package/skills/sense-setup/SKILL.md +0 -112
- package/skills/sense-setup/references/embeddings.md +0 -51
- package/skills/sense-setup/references/store-benchmarks.md +0 -16
- package/skills/sense-setup/references/stores/duckdb.md +0 -28
- package/skills/sense-setup/references/stores/sqlite.md +0 -21
- package/skills/sense-setup/references/stores/turso.md +0 -25
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# SQLite query guide
|
|
2
|
-
|
|
3
|
-
Read this guide when `store` is `sqlite` or omitted.
|
|
4
|
-
|
|
5
|
-
SQLite is the default store. It uses Node's built-in SQLite, keeps the cache at `.sense/cache.db`, supports concurrent sense commands through WAL, and implements the full search grammar documented here.
|
|
6
|
-
|
|
7
|
-
## `sense search` grammar
|
|
8
|
-
|
|
9
|
-
Search text uses FTS5 syntax:
|
|
10
|
-
|
|
11
|
-
- Bare words are joined with AND. If one word is absent, the lexical signal returns no row.
|
|
12
|
-
- `a OR b` matches either word.
|
|
13
|
-
- `pref*` matches a token prefix.
|
|
14
|
-
- `"exact phrase"` requires adjacent tokens.
|
|
15
|
-
- `NEAR(a b, 5)` limits token distance.
|
|
16
|
-
- `^term` requires the token at the start of a column.
|
|
17
|
-
- `title:term` and `summary:term` filter by indexed column.
|
|
18
|
-
- Parentheses group expressions.
|
|
19
|
-
|
|
20
|
-
Double-quote terms containing punctuation. Bare `customer-facing` treats the hyphen as syntax, and a bare apostrophe is invalid.
|
|
21
|
-
|
|
22
|
-
## Raw text search
|
|
23
|
-
|
|
24
|
-
`content` is an FTS5 virtual table. Refer to it by its table name in `MATCH`, even when the `FROM` clause has an alias:
|
|
25
|
-
|
|
26
|
-
```sql
|
|
27
|
-
SELECT content.path, content.title
|
|
28
|
-
FROM content
|
|
29
|
-
WHERE content MATCH ?
|
|
30
|
-
ORDER BY bm25(content, 10.0, 5.0, 1.0)
|
|
31
|
-
LIMIT 20;
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
The three BM25 weights rank title above summary above body. For queries that can match the machine-written unspaced-script sidecars, use all eight weights so the sidecars receive the same field weighting:
|
|
35
|
-
|
|
36
|
-
```sql
|
|
37
|
-
ORDER BY bm25(content, 10.0, 5.0, 1.0, 0, 10.0, 5.0, 1.0)
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Raw `snippet(content, 2, '«', '»', '…', 10)` targets the authored body column. Avoid `-1` because it may choose a machine-written sidecar. SQLite's `snippet()` tokenizes the matched text again, so prefer `sense search` for large notes. Sense builds its own bounded passages on every store.
|
|
41
|
-
|
|
42
|
-
## Functions and unspaced scripts
|
|
43
|
-
|
|
44
|
-
`has()`, `basename()`, and `segment()` are registered SQL functions. `sense search` handles Chinese, Japanese, Thai, Khmer, Lao, and Burmese automatically. Hand-written `MATCH` cannot be rewritten, so pass such terms through `segment()`:
|
|
45
|
-
|
|
46
|
-
```sql
|
|
47
|
-
SELECT path FROM content WHERE content MATCH segment(?);
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
`segment()` leaves other text unchanged.
|
|
51
|
-
|
|
52
|
-
## Dates and values
|
|
53
|
-
|
|
54
|
-
Use SQLite's `datetime()` to compare ISO 8601 values with different offsets:
|
|
55
|
-
|
|
56
|
-
```sql
|
|
57
|
-
WHERE datetime(created) >= datetime(?)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
SQLite's `now` is UTC. A calendar-day query in the machine's local timezone needs `localtime`:
|
|
61
|
-
|
|
62
|
-
```sql
|
|
63
|
-
WHERE date(created) = date('now', 'localtime')
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Frontmatter values use SQLite storage classes. Booleans are integers, so compare true with 1. Lists and maps are JSON text and can be expanded with `json_each()`.
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Turso query guide
|
|
2
|
-
|
|
3
|
-
Read this guide when `store` is `turso`.
|
|
4
|
-
|
|
5
|
-
Sense stores the cache at `.sense/cache.turso.db` and uses `@tursodatabase/database` with Tantivy full-text indexes. Use this experimental adapter when the tree benefits from the embedded Turso engine or its text-index behavior.
|
|
6
|
-
|
|
7
|
-
The first open installs the native package if needed. Turso holds the cache file for the connection's lifetime, so another Sense command waits for the current command or watcher cycle to close it. The upstream engine supports more deployment and concurrency options than this adapter currently exposes. Sense opens a local file and does not configure sync, remote access, concurrent writers, or encryption.
|
|
8
|
-
|
|
9
|
-
## `sense search` grammar
|
|
10
|
-
|
|
11
|
-
Bare words and quoted phrases work. Sense applies shared stemming and joins bare words with AND. It verifies phrases against authored text and uses n-gram sidecars for unspaced scripts.
|
|
12
|
-
|
|
13
|
-
The portable search command does not accept FTS5 prefix, boolean, `NEAR`, initial-token or boost, column-filter, or grouping operators. Examples include `foo*`, `a OR b`, `NEAR(a b)`, `^term`, `title:term`, and `(a b)`. Sense raises `STORE_CAPABILITY_MISSING` instead of passing those forms to Tantivy.
|
|
14
|
-
|
|
15
|
-
To express alternatives, run separate bounded searches and combine the paths, or use a frontmatter filter when the distinction is structured.
|
|
16
|
-
|
|
17
|
-
## Raw text search
|
|
18
|
-
|
|
19
|
-
`content` is a regular table with store-owned stem and n-gram columns. SQLite's `content MATCH`, `snippet()`, and `bm25()` do not run here. Use `sense search` for ranked text retrieval.
|
|
20
|
-
|
|
21
|
-
Turso exposes native `fts_match()` and `fts_score()`, but their valid scoring forms depend on the exact index projection. Sense owns those calls so that scores do not silently flatten. Saved queries should not call the store-owned text indexes.
|
|
22
|
-
|
|
23
|
-
## Functions and types
|
|
24
|
-
|
|
25
|
-
`has()` and `basename()` work in raw SQL. Sense rewrites them into portable SQL because the client cannot register user-defined functions. Nested calls and placeholders are supported.
|
|
26
|
-
|
|
27
|
-
`segment()` is unavailable. A query that calls it raises `STORE_CAPABILITY_MISSING`. `sense search` still handles unspaced scripts without it.
|
|
28
|
-
|
|
29
|
-
Turso follows SQLite syntax for common date and JSON operations. Use `datetime()` for ISO 8601 comparisons and add `localtime` when a calendar-day query means the machine's local day:
|
|
30
|
-
|
|
31
|
-
```sql
|
|
32
|
-
WHERE datetime(created) >= datetime(?)
|
|
33
|
-
WHERE date(created) = date('now', 'localtime')
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Frontmatter lists and maps are stored as JSON text and can be expanded with `json_each()`.
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Sense terminology
|
|
2
|
-
|
|
3
|
-
This is the shared vocabulary for Sense's README, command help, API documentation and skills. Use these names when describing results and output controls.
|
|
4
|
-
|
|
5
|
-
| Term | Meaning |
|
|
6
|
-
|---|---|
|
|
7
|
-
| Note | One Markdown file. A document is the same unit; SQL and some status fields use that name. |
|
|
8
|
-
| Tree | The directory of notes configured as Sense's root. |
|
|
9
|
-
| Frontmatter | The YAML metadata at the beginning of a note, such as its title, tags or status. |
|
|
10
|
-
| Search result | One matching note, returned with its path, ranking information and any snippets. A result is not a section or a chunk. |
|
|
11
|
-
| Snippet | A short passage selected around matching words, marked with `«»`. A note can return multiple snippets. A snippet need not contain a whole section. |
|
|
12
|
-
| Heading | A Markdown heading such as `## Installation`. Its level is its depth, from 1 to 6. |
|
|
13
|
-
| Section | A heading and the content following it until the next heading of any level, or the end of the note. Text before the first heading is not a section in the `sections` table. A parent section does not include subsequent subsections. |
|
|
14
|
-
| Section description | The heading text, level, start and end lines, and estimated token count returned by `peek`. It does not contain the section's prose. |
|
|
15
|
-
| Outline | The section descriptions in document order. It is a view of the note's structure, not another stored type of content. |
|
|
16
|
-
| Chunk | A span of note content prepared for embedding. Chunking follows document structure and a token budget; a chunk is not necessarily one section or one snippet. |
|
|
17
|
-
| Line range | Inclusive, one-based lines in the indexed source note, such as `L12-28`. Search can point to a section or an embedding chunk. A null range provides no narrower location. |
|
|
18
|
-
| Outbound link | A link from the current note to another indexed note. `peek.outbound` returns distinct destination paths. |
|
|
19
|
-
| Backlink | A link from another indexed note to the current note. `peek.backlinks` returns distinct source paths. |
|
|
20
|
-
| Unresolved link | A written link target that did not resolve to an indexed note. This does not establish that the link is broken: attachments and notes outside the index can also be unresolved. `peek.unresolved` returns written targets. |
|
|
21
|
-
| Scope | The indexed notes selected for an operation by its preset and filters. Scope is not an access-control boundary. |
|
|
22
|
-
| Search signal | One source of ranking evidence: word matches, link relationships or vector similarity. A signal's rank is a candidate's position within that signal, not the final combined position. |
|
|
23
|
-
|
|
24
|
-
## Reading a section description
|
|
25
|
-
|
|
26
|
-
The illustrative entry `## Installation [L12-28, ~90t]` describes a section headed “Installation”, starting at line 12 and ending at line 28, with an estimated 90 tokens. `peek` returns that description. Reading the indicated lines retrieves the content. With nested headings, the description ends before the next heading, even when that heading is deeper.
|
|
27
|
-
|
|
28
|
-
## Naming output controls
|
|
29
|
-
|
|
30
|
-
Name the object and the unit: a snippet count is how many passages are returned; a snippet character limit controls passage length; a section count is how many section descriptions are returned; a link count controls link entries. A count limit is not pagination. An offset, when supported by an operation, skips entries before returning the requested count. Raw SQL supports `LIMIT` and `OFFSET`; search and peek do not currently expose offset options.
|
|
31
|
-
|
|
32
|
-
Peek count limits request prefixes of the ordered section and link lists. Each search or peek reads one committed index snapshot; separately issued operations can observe later commits. See [finite retrieval and paging](search.md#finite-retrieval-and-paging) for search budgets and paging retained results.
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# sense-bases: worked translations
|
|
2
|
-
|
|
3
|
-
Two real `.base` shapes translated end to end. Field names are illustrative; your tree
|
|
4
|
-
defines its own.
|
|
5
|
-
|
|
6
|
-
## A. Filtered, sorted table with formulas
|
|
7
|
-
|
|
8
|
-
The base: notes categorized as Movies, excluding templates, with a "To-watch" view of unseen
|
|
9
|
-
entries sorted by score.
|
|
10
|
-
|
|
11
|
-
```yaml
|
|
12
|
-
filters:
|
|
13
|
-
and:
|
|
14
|
-
- categories.contains(link("Movies"))
|
|
15
|
-
- '!file.name.contains("Template")'
|
|
16
|
-
views:
|
|
17
|
-
- type: table
|
|
18
|
-
name: To-watch
|
|
19
|
-
filters:
|
|
20
|
-
and:
|
|
21
|
-
- last.isEmpty()
|
|
22
|
-
- rating.isEmpty()
|
|
23
|
-
order: [file.name, year, scoreImdb, runtime, director]
|
|
24
|
-
sort:
|
|
25
|
-
- { property: scoreImdb, direction: DESC }
|
|
26
|
-
- { property: file.name, direction: ASC }
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
One view, one query. Base filters AND view filters; `order` is the SELECT list; `sort` the
|
|
30
|
-
ORDER BY. The link comparison is the literal written text (`[[Movies]]`), the name test uses
|
|
31
|
-
`basename` so a folder named Templates does not false-positive:
|
|
32
|
-
|
|
33
|
-
```sql
|
|
34
|
-
SELECT basename(f.path, '.md') AS name, f.year, f.scoreImdb, f.runtime, f.director
|
|
35
|
-
FROM frontmatter f
|
|
36
|
-
WHERE EXISTS (SELECT 1 FROM json_each(f.categories) WHERE value = '[[Movies]]')
|
|
37
|
-
AND instr(basename(f.path), 'Template') = 0
|
|
38
|
-
AND (f.last IS NULL OR f.last IN ('', '[]', '[null]'))
|
|
39
|
-
AND (f.rating IS NULL OR f.rating IN ('', '[]', '[null]'))
|
|
40
|
-
ORDER BY f.scoreImdb DESC, name ASC
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Saved in `sense.config.json` under `queries` as e.g. `to-watch`, it runs as `sense to-watch`.
|
|
44
|
-
|
|
45
|
-
## B. `this`-relative: the Related pattern
|
|
46
|
-
|
|
47
|
-
The base: for the current note, every other note ranked by shared outgoing links, including
|
|
48
|
-
anything it links or that links to it.
|
|
49
|
-
|
|
50
|
-
```yaml
|
|
51
|
-
filters:
|
|
52
|
-
and:
|
|
53
|
-
- file.path != this.file.path
|
|
54
|
-
formulas:
|
|
55
|
-
Related: list(this.file.links).filter(list(file.links).containsAny(value)).unique()
|
|
56
|
-
LinksOverlap: formula.Related.length
|
|
57
|
-
views:
|
|
58
|
-
- type: table
|
|
59
|
-
name: Related
|
|
60
|
-
filters:
|
|
61
|
-
or:
|
|
62
|
-
- formula.LinksOverlap > 2
|
|
63
|
-
- file.hasLink(this)
|
|
64
|
-
- this.file.hasLink(file)
|
|
65
|
-
sort:
|
|
66
|
-
- { property: formula.LinksOverlap, direction: DESC }
|
|
67
|
-
limit: 20
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
`this` becomes one bound path. The chained list formula (`filter(...containsAny).unique()`)
|
|
71
|
-
is set intersection, which SQL states as an IN-filtered count; the `Related` list itself is
|
|
72
|
-
the same rows through `GROUP_CONCAT`:
|
|
73
|
-
|
|
74
|
-
```sql
|
|
75
|
-
WITH me AS (SELECT ? AS p),
|
|
76
|
-
mine AS (SELECT DISTINCT dst FROM links, me WHERE src = me.p AND dst IS NOT NULL),
|
|
77
|
-
scored AS (
|
|
78
|
-
SELECT f.path,
|
|
79
|
-
(SELECT COUNT(DISTINCT l.dst) FROM links l
|
|
80
|
-
WHERE l.src = f.path AND l.dst IN (SELECT dst FROM mine)) AS overlap,
|
|
81
|
-
EXISTS (SELECT 1 FROM links, me WHERE src = f.path AND dst = me.p) AS links_to_me,
|
|
82
|
-
EXISTS (SELECT 1 FROM links, me WHERE src = me.p AND dst = f.path) AS linked_by_me,
|
|
83
|
-
(SELECT GROUP_CONCAT(DISTINCT l.dst) FROM links l
|
|
84
|
-
WHERE l.src = f.path AND l.dst IN (SELECT dst FROM mine)) AS shared_links
|
|
85
|
-
FROM frontmatter f, me WHERE f.path != me.p)
|
|
86
|
-
SELECT path, overlap, shared_links FROM scored
|
|
87
|
-
WHERE overlap > 2 OR links_to_me OR linked_by_me
|
|
88
|
-
ORDER BY overlap DESC
|
|
89
|
-
LIMIT 20
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Run as `sense sql "..." current-note.md`, or saved with the `?` in place and the path passed
|
|
93
|
-
as the parameter. The `or:` block reproduces Obsidian's semantics exactly: rows qualify by
|
|
94
|
-
overlap, by linking to the note, or by being linked from it, so leaf notes the current note
|
|
95
|
-
links to appear even at zero overlap, as they do in Obsidian.
|
|
96
|
-
|
|
97
|
-
## Notes that recur across translations
|
|
98
|
-
|
|
99
|
-
- Multiple views over the same base share the base-level filter; write it once per query
|
|
100
|
-
rather than factoring it out. Saved queries are self-contained by design.
|
|
101
|
-
- `sort` keys referencing formulas sort by the SELECT alias.
|
|
102
|
-
- A `groupBy` view keeps its rows; see the window-function shape in SKILL.md. Only a
|
|
103
|
-
deliberately collapsed report wants `GROUP BY`.
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sense-bases
|
|
3
|
-
description: "Translate an Obsidian Bases .base file into sense SQL that returns the same rows. Use when asked to convert, import, or reproduce a .base file (or a Bases view, filter, or formula) with sense, or when a markdown tree contains .base files whose views someone wants queryable outside Obsidian."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# sense-bases
|
|
7
|
-
|
|
8
|
-
A `.base` file is YAML: filters selecting notes, formulas computing values, and views ordering
|
|
9
|
-
and grouping them. Obsidian evaluates it against its own metadata cache; sense holds the same
|
|
10
|
-
data in SQL tables. Every Bases construct that selects or computes rows has a SQL equivalent.
|
|
11
|
-
What has none is presentation (`columnSize`, card layout), those change pixels, not rows, so
|
|
12
|
-
a translation loses nothing by ignoring them.
|
|
13
|
-
|
|
14
|
-
Translate one view to one query: base-level `filters` AND the view's `filters`, the view's
|
|
15
|
-
`order`/`sort`/`limit` as the SELECT list, ORDER BY, and LIMIT. A base with four views becomes
|
|
16
|
-
four queries; saving them under the base's name in `sense.config.json`'s `queries` block keeps
|
|
17
|
-
them runnable as `sense <name>`. The general query surface is the `sense` skill; this one only
|
|
18
|
-
maps Bases constructs onto it.
|
|
19
|
-
|
|
20
|
-
## Properties
|
|
21
|
-
|
|
22
|
-
| Bases | sense | notes |
|
|
23
|
-
|---|---|---|
|
|
24
|
-
| `note.field` / bare `field` | `f.<field>` | frontmatter column, same name |
|
|
25
|
-
| `file.name` | `basename(f.path, '.md')` | the Name column: no directory, no extension |
|
|
26
|
-
| a name with extension | `basename(f.path)` | the suffix arg is Unix basename's: present, stripped |
|
|
27
|
-
| `file.folder` | `f.path LIKE 'Folder/%'` | for `inFolder("Folder")` |
|
|
28
|
-
| `file.ext` | `'md'` | sense indexes only markdown |
|
|
29
|
-
| `file.size` | `f._size` | bytes |
|
|
30
|
-
| `file.mtime` | `datetime(f._mtime / 1000, 'unixepoch')` | stored as ms |
|
|
31
|
-
| `file.ctime` | `datetime(f._ctime / 1000, 'unixepoch')` | see the caveat below |
|
|
32
|
-
| `file.tags` | the `tags` table | frontmatter + inline `#tags`, deduplicated, Obsidian's grain |
|
|
33
|
-
| `file.links` | `links WHERE embed = 0` | one row per distinct written target |
|
|
34
|
-
| `file.embeds` | `links WHERE embed = 1` | |
|
|
35
|
-
| `file.backlinks` | `links WHERE dst = f.path` | |
|
|
36
|
-
| `this` | a bound `?` | see below |
|
|
37
|
-
|
|
38
|
-
`file.ctime` is the filesystem's creation time. A `git clone` or a copy resets it, so on a
|
|
39
|
-
tree that arrived as a checkout, every ctime is the checkout date and age formulas are wrong
|
|
40
|
-
about authorship without being wrong about the filesystem. `map` warns when modification
|
|
41
|
-
times have this shape; creation times share it.
|
|
42
|
-
|
|
43
|
-
## Predicates
|
|
44
|
-
|
|
45
|
-
| Bases | sense |
|
|
46
|
-
|---|---|
|
|
47
|
-
| `==` `!=` `>` `<` `>=` `<=` | same |
|
|
48
|
-
| `&&` `\|\|` `!` | `AND` `OR` `NOT` |
|
|
49
|
-
| `and:` / `or:` / `not:` filter blocks | parenthesized `AND` / `OR` / `NOT` |
|
|
50
|
-
| `field.isEmpty()` | `(f.field IS NULL OR f.field IN ('', '[]', '[null]'))` |
|
|
51
|
-
| `file.hasTag("book")` | `EXISTS (SELECT 1 FROM tags WHERE tags.path = f.path AND (tag = 'book' OR tag LIKE 'book/%'))` |
|
|
52
|
-
| `file.hasLink("Note")` | `EXISTS (SELECT 1 FROM links WHERE src = f.path AND dst = 'Note.md')` |
|
|
53
|
-
| `list.contains(x)` | `EXISTS (SELECT 1 FROM json_each(f.list) WHERE value = x)` |
|
|
54
|
-
| `string.contains(x)` | `instr(f.string, x) > 0` |
|
|
55
|
-
| `list.containsAny(...)` | the `json_each` EXISTS with `value IN (...)` |
|
|
56
|
-
| `/regex/.matches(x)` | no SQLite regex; `LIKE`/`GLOB` cover anchored and wildcard shapes |
|
|
57
|
-
| `value.isType("object")` | `json_each`'s own `type` column: `... FROM json_each(f.field) j WHERE j.type = 'object'` |
|
|
58
|
-
|
|
59
|
-
A field no note in the tree declares has no column at all, so a filter naming it errors with
|
|
60
|
-
`no such column` instead of treating every row as empty. Obsidian's evaluator returns empty
|
|
61
|
-
for unknown properties; SQL does not. Dropping the clause states the same thing the error
|
|
62
|
-
did, and `SELECT name FROM pragma_table_info('frontmatter')` lists what exists.
|
|
63
|
-
|
|
64
|
-
`isEmpty()` has four true cases because empty is stored three ways: NULL (key absent), `''`
|
|
65
|
-
(empty string value), `'[]'` (a list written `[]`), and `'[null]'` (a list key above a bare
|
|
66
|
-
`-`). Obsidian's `isEmpty()` is true for all of them; `IS NULL` alone finds only the first.
|
|
67
|
-
The IN list is the whole test. `json_array_length()` is not: it reads `'[null]'` as length
|
|
68
|
-
1 and throws on plain strings. The same trap inside a list: `json_each` hands a string member
|
|
69
|
-
to `value` as plain text, so `json_type(value)` throws `malformed JSON` on it; the scan's own
|
|
70
|
-
`type` column is the discriminator.
|
|
71
|
-
|
|
72
|
-
`contains(link("Movies"))` compares against a link value. In frontmatter, a list of links
|
|
73
|
-
holds the written text, so the `json_each` comparison value is the literal `[[Movies]]`.
|
|
74
|
-
`hasLink` compares resolved paths: `links.dst` is the resolved target (`NULL` for dead
|
|
75
|
-
links), so the comparison value is the target's path, not its display name.
|
|
76
|
-
|
|
77
|
-
sense's own `has(field, x)` is looser than both `contains` variants: exact membership on JSON
|
|
78
|
-
arrays but substring on strings, so `has(f.status, 'active')` also matches `inactive`. It
|
|
79
|
-
reads shorter when the field is known to be a list; the `json_each` form is the exact
|
|
80
|
-
translation.
|
|
81
|
-
|
|
82
|
-
## Formulas
|
|
83
|
-
|
|
84
|
-
Formulas are SELECT expressions. The Bases functions map onto SQLite's:
|
|
85
|
-
|
|
86
|
-
| Bases | sense |
|
|
87
|
-
|---|---|
|
|
88
|
-
| `if(c, a, b)` | `iif(c, a, b)` or `CASE WHEN` |
|
|
89
|
-
| `now()` / `today()` | `datetime('now')` / `date('now')` |
|
|
90
|
-
| `date(x)` | `datetime(x)` |
|
|
91
|
-
| `(date1 - date2).days` | `julianday(date1) - julianday(date2)` |
|
|
92
|
-
| `(now() - acquired).months` | `(julianday('now') - julianday(f.acquired)) / 30.44` |
|
|
93
|
-
| `x.round(n)` / `x.toFixed(n)` | `round(x, n)` / `printf('%.2f', x)` |
|
|
94
|
-
| `d.format("YYYY-MM-DD")` | `strftime('%Y-%m-%d', d)` |
|
|
95
|
-
| `x.toString()` | `CAST(x AS TEXT)` |
|
|
96
|
-
| `list.length` | `json_array_length(f.list)` on a JSON column, a COUNT subquery on a table |
|
|
97
|
-
| `list(a).filter(list(b).containsAny(value)).unique()` | an EXISTS-joined subquery; worked example in EXAMPLES.md |
|
|
98
|
-
|
|
99
|
-
Bases durations are typed; SQL date arithmetic is julianday day-fractions. A formula chaining
|
|
100
|
-
duration fields (`.days.round()`) flattens to arithmetic on the julianday difference.
|
|
101
|
-
|
|
102
|
-
A formula referencing another formula becomes a CTE layer: SQL cannot read a SELECT alias in
|
|
103
|
-
the same SELECT list, so each dependency level computes its formulas as columns and the next
|
|
104
|
-
level reads them (`WITH t AS (SELECT ..., <level-1 formulas> FROM frontmatter) SELECT ...,
|
|
105
|
-
<level-2 formulas> FROM t`). A five-formula chain is however many *levels* it has, not five
|
|
106
|
-
CTEs. Formulas that only read base columns share one layer.
|
|
107
|
-
|
|
108
|
-
## Views
|
|
109
|
-
|
|
110
|
-
- `sort:` (multi-key, each with direction) -> `ORDER BY a DESC, b ASC`. `limit:` -> `LIMIT`.
|
|
111
|
-
- `groupBy` does not collapse rows. Obsidian shows every row bucketed under headers with
|
|
112
|
-
per-group summaries. The SQL producing the same rows and numbers is ordering plus window
|
|
113
|
-
functions, not GROUP BY:
|
|
114
|
-
|
|
115
|
-
```sql
|
|
116
|
-
SELECT f.path, f.status, f.days,
|
|
117
|
-
AVG(f.days) OVER (PARTITION BY f.status) AS group_avg,
|
|
118
|
-
COUNT(*) OVER (PARTITION BY f.status) AS group_n
|
|
119
|
-
FROM frontmatter f WHERE f.days IS NOT NULL
|
|
120
|
-
ORDER BY f.status, f.days DESC
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
A collapsed one-row-per-group report is plain `GROUP BY`. That is a different result than
|
|
124
|
-
the Bases view shows.
|
|
125
|
-
- View `summaries` (Sum, Average, Median via ordering, Unique, Filled, Checked) are the
|
|
126
|
-
matching aggregates, windowed as above to keep the rows, or a separate aggregate query.
|
|
127
|
-
|
|
128
|
-
## `this`
|
|
129
|
-
|
|
130
|
-
`this` is the note the base is evaluated against: the embedding note, or Obsidian's active
|
|
131
|
-
pane. sense has no pane, so the caller supplies the path as a bound parameter: `sense sql
|
|
132
|
-
"..." <path>`, or a saved query run as `sense <name> <path>`. A CTE keeps it single-bind:
|
|
133
|
-
|
|
134
|
-
```sql
|
|
135
|
-
WITH me AS (SELECT ? AS p)
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
and every `this.file.*` expression joins `me`. The full Related.base translation in
|
|
139
|
-
EXAMPLES.md is the worked case: link overlap, mutual-link filters, and the shared-target list
|
|
140
|
-
via `GROUP_CONCAT`, all from one bound path.
|
|
141
|
-
|
|
142
|
-
## Not translatable, and why it costs nothing
|
|
143
|
-
|
|
144
|
-
- `columnSize`, card/table/list/map chrome: pixel layout over the same rows.
|
|
145
|
-
- Obsidian choosing `this` from the active pane: there is no pane; the path parameter is the
|
|
146
|
-
resolution.
|
|
147
|
-
- Live re-evaluation as files change: default CLI queries incrementally prepare the needed capabilities; `--no-build` reads the last completed generation and requires its requested readiness without scanning live files. Nothing re-renders on its own.
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# sense-setup: worked configurations
|
|
2
|
-
|
|
3
|
-
Four tree shapes, each with its config and the commands an agent actually runs. Field names and folder names are illustrative; your tree defines its own.
|
|
4
|
-
|
|
5
|
-
## A. Compiled wiki over immutable sources (the llm-wiki pattern)
|
|
6
|
-
|
|
7
|
-
`raw/` holds ingested sources: big, never hand-edited. `wiki/` holds agent-compiled pages: linked, curated. The human drops sources and asks questions; the agent compiles and cites.
|
|
8
|
-
|
|
9
|
-
```json
|
|
10
|
-
{
|
|
11
|
-
"version": 5,
|
|
12
|
-
"presets": {
|
|
13
|
-
"default": { "include": ["wiki/**/*.md"], "k": 10 },
|
|
14
|
-
"raw": { "include": ["raw/**/*.md"], "k": 5 }
|
|
15
|
-
},
|
|
16
|
-
"embed": { "model": "minishlab/potion-retrieval-32M", "provider": "static" },
|
|
17
|
-
"queries": {
|
|
18
|
-
"uncompiled": { "sql": "SELECT path, _mtime FROM frontmatter WHERE path LIKE 'raw/%' AND path NOT IN (SELECT dst FROM links WHERE dst IS NOT NULL) ORDER BY _mtime DESC" },
|
|
19
|
-
"stubs": { "sql": "SELECT path, _size FROM frontmatter WHERE path LIKE 'wiki/%' AND _size < 500 ORDER BY _size" },
|
|
20
|
-
"dead-links": { "sql": "SELECT src, target FROM links WHERE dst IS NULL ORDER BY src" }
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
sense uncompiled # compile queue: raw files nothing cites yet
|
|
27
|
-
sense search "how does attention scale" # wiki only (default preset)
|
|
28
|
-
sense search "rotary embeddings" --preset raw # cite from sources, k=5
|
|
29
|
-
sense dead-links # rows are broken citations to fix
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
What the shape buys: bare search never ranks raw noise above compiled pages; the compile queue, stub list, and citation integrity are one saved query each. The maintenance loop is `uncompiled` → write the wiki page citing its sources → `dead-links` stays empty.
|
|
33
|
-
|
|
34
|
-
## B. Nightly agent memory, consolidated (the dreaming pattern)
|
|
35
|
-
|
|
36
|
-
`memory/` accumulates small notes written at session end, each with `project`, `created`, and `kind` (observation / steer / decision) frontmatter. A consolidation agent runs periodically: prune, merge, surface contradictions for the human. Retired notes move to `archive/`: still queryable under their own preset, out of the default scope.
|
|
37
|
-
|
|
38
|
-
```json
|
|
39
|
-
{
|
|
40
|
-
"version": 5,
|
|
41
|
-
"presets": {
|
|
42
|
-
"default": { "include": ["memory/**/*.md"], "k": 10 },
|
|
43
|
-
"archive": { "include": ["archive/**/*.md"], "k": 10 }
|
|
44
|
-
},
|
|
45
|
-
"embed": { "model": "minishlab/potion-retrieval-32M", "provider": "static" },
|
|
46
|
-
"queries": {
|
|
47
|
-
"project": { "sql": "SELECT path, kind, created, title FROM frontmatter WHERE project = ? ORDER BY created DESC" },
|
|
48
|
-
"steers": { "sql": "SELECT path, created, title FROM frontmatter WHERE kind = 'steer' AND project = ? ORDER BY created" },
|
|
49
|
-
"retirement": { "sql": "SELECT path, project, created FROM frontmatter WHERE datetime(created) < datetime('now','-90 day') AND path NOT IN (SELECT dst FROM links WHERE dst IS NOT NULL)" },
|
|
50
|
-
"today": { "sql": "SELECT path, title FROM frontmatter WHERE date(created) = date('now','localtime') ORDER BY created" },
|
|
51
|
-
"unfiled": { "sql": "SELECT path FROM frontmatter WHERE project IS NULL" }
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
sense project acme-app # one project's notes, newest first
|
|
58
|
-
sense today # `now` is UTC: a query whose boundary is a day needs 'localtime'
|
|
59
|
-
# `retirement` above is a 90-day window, so it does not
|
|
60
|
-
sense search "prefers terse commit messages" --k 5
|
|
61
|
-
→ memory/acme-app/2026-08-02-commits.md via: match
|
|
62
|
-
→ memory/acme-app/2026-06-11-style.md via: vector similarity: 0.71 # near-duplicate → merge candidate
|
|
63
|
-
sense steers acme-app # oldest first: does a new steer override an old one?
|
|
64
|
-
sense retirement # old + uncited → move to archive/
|
|
65
|
-
sense unfiled # rows are notes missing a project. File them
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Presets are structural (live vs archived); per-project filtering is metadata (`project = ?`). One tree serves every project. Semantic search over the memory preset is the near-duplicate detector: search a new note's own summary and read `similarity` within the results. Whether an old steer was overridden is a question for the human, found by search, never decided by it.
|
|
69
|
-
|
|
70
|
-
## C. Evidence corpus: claims trace to sources
|
|
71
|
-
|
|
72
|
-
`sources/` (immutable imports), `notes/` (one reading note per source), `reviews/` (synthesis whose claims must cite notes). The same shape fits incident reports and postmortems, user research and findings, due diligence and memos.
|
|
73
|
-
|
|
74
|
-
```json
|
|
75
|
-
{
|
|
76
|
-
"version": 5,
|
|
77
|
-
"presets": {
|
|
78
|
-
"default": { "include": ["reviews/**/*.md", "notes/**/*.md"], "k": 10 },
|
|
79
|
-
"source": { "include": ["sources/**/*.md"], "k": 5 }
|
|
80
|
-
},
|
|
81
|
-
"embed": { "model": "minishlab/potion-retrieval-32M", "provider": "static" },
|
|
82
|
-
"queries": {
|
|
83
|
-
"unsupported": { "sql": "SELECT path, title FROM frontmatter WHERE path LIKE 'reviews/%' AND path NOT IN (SELECT src FROM links WHERE dst LIKE 'notes/%')" },
|
|
84
|
-
"unread": { "sql": "SELECT path FROM frontmatter WHERE path LIKE 'sources/%' AND path NOT IN (SELECT dst FROM links WHERE dst IS NOT NULL)" },
|
|
85
|
-
"by-topic": { "sql": "SELECT path, title FROM frontmatter WHERE path NOT LIKE 'sources/%' AND has(topics, ?) ORDER BY path" }
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
sense search "replication failures in priming studies" # reviews + notes; sources never dilute
|
|
92
|
-
sense search "the claim's exact phrasing" --preset source # citation pull on demand
|
|
93
|
-
sense unsupported # rows are synthesis claims with no note behind them
|
|
94
|
-
sense unread # the reading queue: sources no note cites
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## D. A plain vault: zero configuration
|
|
98
|
-
|
|
99
|
-
Someone else's Obsidian vault, heterogeneous with no structure worth declaring. The `sense init` starter is left untouched. The workflow is discovery:
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
sense map # fields in use, hub notes, recent changes
|
|
103
|
-
sense search "dataview queries" # words + links + meaning, one ranked list
|
|
104
|
-
sense sql "SELECT j.value AS tag, COUNT(*) n FROM frontmatter, json_each(frontmatter.tags) j GROUP BY j.value ORDER BY n DESC LIMIT 20"
|
|
105
|
-
sense peek "Plugins/dataview.md" # outline + links before reading
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Presets earn their place only when a tree has parts deserving different treatment; a tree that is one kind of thing needs none of the vocabulary above. On a big vault, raise `default`'s `k` and read `lines` ranges instead of whole files, or start from the starter's `large` preset.
|
|
109
|
-
|
|
110
|
-
## E. One vault, separate consumers
|
|
111
|
-
|
|
112
|
-
Keep each consumer's configuration and cache under its own configuration directory while indexing one shared vault. `root` is relative to this config file, so saved SQL continues to use stable vault-relative paths.
|
|
113
|
-
|
|
114
|
-
`~/.config/obsidian/tasks/sense.config.json`:
|
|
115
|
-
|
|
116
|
-
```json
|
|
117
|
-
{
|
|
118
|
-
"version": 5,
|
|
119
|
-
"root": "../../../.obsidian",
|
|
120
|
-
"presets": {
|
|
121
|
-
"default": { "include": ["vault/tasks/**/*.md"], "k": 20 }
|
|
122
|
-
},
|
|
123
|
-
"queries": {
|
|
124
|
-
"active": { "sql": "SELECT path, title FROM frontmatter WHERE path LIKE 'vault/tasks/%' AND status = 'active' ORDER BY path" }
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
The database is `~/.config/obsidian/tasks/.sense/cache.db`; the indexed row remains `vault/tasks/...`, never a path back out through the configuration directory. A `general/` sibling can point at the same root with different presets and a separate `.sense/` cache.
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sense-setup
|
|
3
|
-
description: "Set up the sense CLI on a markdown tree and make the decisions that shape its index: run sense init, choose sqlite, duckdb, or turso, define presets and search signals, configure embeddings, and design queryable note conventions. Use when creating or restructuring a markdown knowledge base, editing sense.config.json, choosing a store or embedding model, or deciding how agents should write notes for later retrieval."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# sense setup and tree design
|
|
7
|
-
|
|
8
|
-
Use this skill to create or change a sense configuration. Querying an existing tree belongs to the `sense` skill. Worked configurations for common tree shapes are in [EXAMPLES.md](EXAMPLES.md).
|
|
9
|
-
|
|
10
|
-
## Set up the tree
|
|
11
|
-
|
|
12
|
-
Install the CLI and initialize it at the markdown root:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
npm install -g sensemaking
|
|
16
|
-
cd path/to/notes
|
|
17
|
-
sense init
|
|
18
|
-
sense status
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
`sense init` writes `sense.config.json`. Config discovery walks upward from the current directory. `--config <path>` selects another file.
|
|
22
|
-
|
|
23
|
-
By default, the config indexes its own directory. A top-level `root` can point at another markdown tree. Relative roots resolve from the config directory, and `.sense/` remains beside the config. Use this when separate consumers need their own presets, saved queries, or caches over one tree.
|
|
24
|
-
|
|
25
|
-
Globs and indexed paths are relative to the selected root. Run `sense status` and `sense map` after changing the config to confirm preset coverage and the selected store. A change that affects indexed content rebuilds the relevant cache and reports the reason. A build-enabled command replaces an incompatible derived cache format and repairs incomplete feature state; config `"build": false` or `--no-build` reports the required build without rebuilding or repairing the Sense index.
|
|
26
|
-
|
|
27
|
-
## Choose the store
|
|
28
|
-
|
|
29
|
-
Choose from the intended use of the cache. Start with `sqlite` when no surrounding workflow favors
|
|
30
|
-
another engine, then consider interoperability, SQL, connection behavior, and representative Sense
|
|
31
|
-
measurements. A current timing result does not define an engine's long-term suitability.
|
|
32
|
-
|
|
33
|
-
| Store | Choose it when | Main trade-off |
|
|
34
|
-
|---|---|---|
|
|
35
|
-
| `sqlite` | The tree needs the smallest setup, advanced FTS5 search syntax, or concurrent Sense commands | Included with Node; raw SQL is SQLite |
|
|
36
|
-
| `duckdb` | The cache belongs in DuckDB analytical work over large datasets or in a local/cloud workflow, or needs DuckDB types and SQL | Experimental Sense adapter, large native install, one open connection at a time |
|
|
37
|
-
| `turso` | The tree benefits from the embedded Rust engine or Tantivy index, or is evaluating the engine as its adapter evolves | Experimental Sense adapter, restricted search grammar, one open connection at a time |
|
|
38
|
-
|
|
39
|
-
Read the matching selection guide before recommending or configuring a non-default store:
|
|
40
|
-
|
|
41
|
-
- [SQLite selection guide](references/stores/sqlite.md)
|
|
42
|
-
- [DuckDB selection guide](references/stores/duckdb.md)
|
|
43
|
-
- [Turso selection guide](references/stores/turso.md)
|
|
44
|
-
|
|
45
|
-
Read the [current store benchmark summary](references/store-benchmarks.md) only when performance could change the choice. The release assessment generates that shipped summary from the latest accepted all-store run.
|
|
46
|
-
|
|
47
|
-
Each store uses a separate cache file. Changing `store` rebuilds the index instead of migrating the old cache. Commands and public tables are shared, while raw SQL and advanced word-search syntax follow the selected engine.
|
|
48
|
-
|
|
49
|
-
## Design presets
|
|
50
|
-
|
|
51
|
-
A preset is a named, self-contained scope. The required `default` preset serves bare commands. Other presets are selected by name.
|
|
52
|
-
|
|
53
|
-
| Field | Meaning | Default |
|
|
54
|
-
|---|---|---|
|
|
55
|
-
| `include` and `exclude` | Files covered by the preset | `include` is required |
|
|
56
|
-
| `k` | Search result count | 10 |
|
|
57
|
-
| `signals` | Enabled search signals and their reciprocal-rank weights | Every available signal at weight 1 |
|
|
58
|
-
| `where` | Standing frontmatter filter | None |
|
|
59
|
-
|
|
60
|
-
A file is indexed when any preset includes it. Presets can overlap. Files outside every preset do not enter the index.
|
|
61
|
-
|
|
62
|
-
Use paths for stable layers such as `raw/`, `notes/`, and `archive/`. Use frontmatter filters for changing state such as `status`, `project`, and dates. A preset controls indexing, so its coverage must be computable from paths before frontmatter queries run.
|
|
63
|
-
|
|
64
|
-
Use separate presets when parts of the tree need different search signals or result counts. A single-purpose tree does not need extra preset vocabulary.
|
|
65
|
-
|
|
66
|
-
## Configure vectors
|
|
67
|
-
|
|
68
|
-
Vectors require two choices. The top-level `embed` block names the model and provider. Each preset's `signals` decides whether that scope uses vectors.
|
|
69
|
-
|
|
70
|
-
```json
|
|
71
|
-
{
|
|
72
|
-
"embed": {
|
|
73
|
-
"model": "minishlab/potion-retrieval-32M",
|
|
74
|
-
"provider": "static"
|
|
75
|
-
},
|
|
76
|
-
"presets": {
|
|
77
|
-
"default": {
|
|
78
|
-
"include": ["notes/**/*.md"],
|
|
79
|
-
"signals": { "words": 1, "links": 1, "vectors": 1 }
|
|
80
|
-
},
|
|
81
|
-
"raw": {
|
|
82
|
-
"include": ["raw/**/*.md"],
|
|
83
|
-
"signals": { "words": 1, "links": 1 }
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
The config's `"build": true` default lets CLI queries prepare their needed capabilities. Set `"build": false` for manual-build or watch workflows. The first build-enabled vector search downloads a named static model and embeds its eligible scope. `sense build` and `sense watch` prepare all configured capabilities regardless of this setting; `sense download` fetches the model earlier when CI, offline work, or timing makes that useful. A config change that alters the provider, model, effective endpoint, vector coverage, or chunking can invalidate or rebuild vectors. Config `"build": false` or `--no-build` requires the requested vector scope to be ready and does not read newer live files.
|
|
90
|
-
|
|
91
|
-
Read [embedding setup](references/embeddings.md) when choosing a provider or model, supporting a non-English tree, changing chunk size, or tuning signal weights.
|
|
92
|
-
|
|
93
|
-
## Design queryable notes
|
|
94
|
-
|
|
95
|
-
Sense accepts heterogeneous markdown. These choices decide which queries will be reliable:
|
|
96
|
-
|
|
97
|
-
- Consistent frontmatter fields make exact filters and saved reports possible. Inspect actual coverage and types with `sense map`.
|
|
98
|
-
- ISO 8601 dates can be compared by the selected store's date functions. Mixed date formats can still be stored, but do not make a dependable time range.
|
|
99
|
-
- A one-line `summary` appears in results and receives more lexical weight than body text. Its cost is keeping it current.
|
|
100
|
-
- Folders are the natural unit for preset coverage. Frontmatter is the natural unit for status and ownership.
|
|
101
|
-
- Small notes give precise hits and cheap whole-file reads. Large notes still work because `peek`, `sections`, and search `lines` point at ranges, and vector chunking splits long sections.
|
|
102
|
-
- Save recurring questions under `queries`. Use `{ "sql": "..." }` for deterministic filters and reports, and `{ "search": "..." }` for ranked retrieval.
|
|
103
|
-
|
|
104
|
-
Field names in examples are illustrative. The tree defines its own schema. Reserved frontmatter keys are `path`, `_mtime`, `_ctime`, `_size`, `_rank`, `_parse_error`, `content`, `links`, and `sections`; sense drops them with a warning.
|
|
105
|
-
|
|
106
|
-
## Validate the result
|
|
107
|
-
|
|
108
|
-
Run `sense status` to confirm the store, cache, document count, embedding state, watcher state, and preset coverage. Run `sense map` to confirm the discovered fields and their observed types.
|
|
109
|
-
|
|
110
|
-
Run every saved query after editing it. A parameterized SQL query can use any value because preparing the statement validates its columns and syntax before the parameter changes the result.
|
|
111
|
-
|
|
112
|
-
Record choices that should outlive the setup conversation in the tree's maintained agent guidance or in an authored note. The sense config should contain executable settings and reusable queries, not prose policy.
|