sensemaking 0.2.0 → 0.3.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.
Files changed (182) hide show
  1. package/README.md +107 -126
  2. package/dist/cjs/cli.js +172 -182
  3. package/dist/cjs/cli.js.map +1 -1
  4. package/dist/cjs/commands/find.d.cts +3 -0
  5. package/dist/cjs/commands/find.d.ts +3 -0
  6. package/dist/cjs/commands/find.js +73 -0
  7. package/dist/cjs/commands/find.js.map +1 -0
  8. package/dist/cjs/commands/index.d.cts +5 -0
  9. package/dist/cjs/commands/index.d.ts +5 -0
  10. package/dist/cjs/commands/index.js +94 -0
  11. package/dist/cjs/commands/index.js.map +1 -0
  12. package/dist/cjs/commands/init.d.cts +3 -0
  13. package/dist/cjs/commands/init.d.ts +3 -0
  14. package/dist/cjs/commands/init.js +18 -0
  15. package/dist/cjs/commands/init.js.map +1 -0
  16. package/dist/cjs/commands/map.d.cts +3 -0
  17. package/dist/cjs/commands/map.d.ts +3 -0
  18. package/dist/cjs/commands/map.js +21 -0
  19. package/dist/cjs/commands/map.js.map +1 -0
  20. package/dist/cjs/commands/named.d.cts +2 -0
  21. package/dist/cjs/commands/named.d.ts +2 -0
  22. package/dist/cjs/commands/named.js +23 -0
  23. package/dist/cjs/commands/named.js.map +1 -0
  24. package/dist/cjs/commands/peek.d.cts +3 -0
  25. package/dist/cjs/commands/peek.d.ts +3 -0
  26. package/dist/cjs/commands/peek.js +69 -0
  27. package/dist/cjs/commands/peek.js.map +1 -0
  28. package/dist/cjs/commands/query.d.cts +3 -0
  29. package/dist/cjs/commands/query.d.ts +3 -0
  30. package/dist/cjs/commands/query.js +43 -0
  31. package/dist/cjs/commands/query.js.map +1 -0
  32. package/dist/cjs/commands/rebuild.d.cts +3 -0
  33. package/dist/cjs/commands/rebuild.d.ts +3 -0
  34. package/dist/cjs/commands/rebuild.js +20 -0
  35. package/dist/cjs/commands/rebuild.js.map +1 -0
  36. package/dist/cjs/commands/status.d.cts +3 -0
  37. package/dist/cjs/commands/status.d.ts +3 -0
  38. package/dist/cjs/commands/status.js +24 -0
  39. package/dist/cjs/commands/status.js.map +1 -0
  40. package/dist/cjs/commands/types.d.cts +15 -0
  41. package/dist/cjs/commands/types.d.ts +15 -0
  42. package/dist/cjs/commands/types.js +5 -0
  43. package/dist/cjs/commands/types.js.map +1 -0
  44. package/dist/cjs/commands/vault.d.cts +6 -0
  45. package/dist/cjs/commands/vault.d.ts +6 -0
  46. package/dist/cjs/commands/vault.js +94 -0
  47. package/dist/cjs/commands/vault.js.map +1 -0
  48. package/dist/cjs/commands/watch.d.cts +3 -0
  49. package/dist/cjs/commands/watch.d.ts +3 -0
  50. package/dist/cjs/commands/watch.js +173 -0
  51. package/dist/cjs/commands/watch.js.map +1 -0
  52. package/dist/cjs/config.d.cts +11 -1
  53. package/dist/cjs/config.d.ts +11 -1
  54. package/dist/cjs/config.js +82 -9
  55. package/dist/cjs/config.js.map +1 -1
  56. package/dist/cjs/db.d.cts +1 -1
  57. package/dist/cjs/db.d.ts +1 -1
  58. package/dist/cjs/db.js +107 -16
  59. package/dist/cjs/db.js.map +1 -1
  60. package/dist/cjs/errors.d.cts +1 -1
  61. package/dist/cjs/errors.d.ts +1 -1
  62. package/dist/cjs/errors.js.map +1 -1
  63. package/dist/cjs/features/index.d.cts +7 -0
  64. package/dist/cjs/features/index.d.ts +7 -0
  65. package/dist/cjs/features/index.js +36 -0
  66. package/dist/cjs/features/index.js.map +1 -0
  67. package/dist/cjs/features/links.d.cts +4 -0
  68. package/dist/cjs/features/links.d.ts +4 -0
  69. package/dist/cjs/features/links.js +217 -0
  70. package/dist/cjs/features/links.js.map +1 -0
  71. package/dist/cjs/features/rank.d.cts +2 -0
  72. package/dist/cjs/features/rank.d.ts +2 -0
  73. package/dist/cjs/features/rank.js +95 -0
  74. package/dist/cjs/features/rank.js.map +1 -0
  75. package/dist/cjs/features/sections.d.cts +9 -0
  76. package/dist/cjs/features/sections.d.ts +9 -0
  77. package/dist/cjs/features/sections.js +54 -0
  78. package/dist/cjs/features/sections.js.map +1 -0
  79. package/dist/cjs/features/types.d.cts +11 -0
  80. package/dist/cjs/features/types.d.ts +11 -0
  81. package/dist/cjs/features/types.js +5 -0
  82. package/dist/cjs/features/types.js.map +1 -0
  83. package/dist/cjs/graph.d.cts +7 -0
  84. package/dist/cjs/graph.d.ts +7 -0
  85. package/dist/cjs/graph.js +237 -0
  86. package/dist/cjs/graph.js.map +1 -0
  87. package/dist/cjs/index.d.cts +9 -2
  88. package/dist/cjs/index.d.ts +9 -2
  89. package/dist/cjs/index.js +39 -0
  90. package/dist/cjs/index.js.map +1 -1
  91. package/dist/cjs/output.d.cts +21 -0
  92. package/dist/cjs/output.d.ts +21 -0
  93. package/dist/cjs/output.js +112 -3
  94. package/dist/cjs/output.js.map +1 -1
  95. package/dist/cjs/scan.d.cts +3 -1
  96. package/dist/cjs/scan.d.ts +3 -1
  97. package/dist/cjs/scan.js +62 -14
  98. package/dist/cjs/scan.js.map +1 -1
  99. package/dist/cjs/verbs.d.cts +31 -0
  100. package/dist/cjs/verbs.d.ts +31 -0
  101. package/dist/cjs/verbs.js +284 -0
  102. package/dist/cjs/verbs.js.map +1 -0
  103. package/dist/esm/cli.js +63 -107
  104. package/dist/esm/cli.js.map +1 -1
  105. package/dist/esm/commands/find.d.ts +3 -0
  106. package/dist/esm/commands/find.js +14 -0
  107. package/dist/esm/commands/find.js.map +1 -0
  108. package/dist/esm/commands/index.d.ts +5 -0
  109. package/dist/esm/commands/index.js +13 -0
  110. package/dist/esm/commands/index.js.map +1 -0
  111. package/dist/esm/commands/init.d.ts +3 -0
  112. package/dist/esm/commands/init.js +7 -0
  113. package/dist/esm/commands/init.js.map +1 -0
  114. package/dist/esm/commands/map.d.ts +3 -0
  115. package/dist/esm/commands/map.js +10 -0
  116. package/dist/esm/commands/map.js.map +1 -0
  117. package/dist/esm/commands/named.d.ts +2 -0
  118. package/dist/esm/commands/named.js +12 -0
  119. package/dist/esm/commands/named.js.map +1 -0
  120. package/dist/esm/commands/peek.d.ts +3 -0
  121. package/dist/esm/commands/peek.js +12 -0
  122. package/dist/esm/commands/peek.js.map +1 -0
  123. package/dist/esm/commands/query.d.ts +3 -0
  124. package/dist/esm/commands/query.js +7 -0
  125. package/dist/esm/commands/query.js.map +1 -0
  126. package/dist/esm/commands/rebuild.d.ts +3 -0
  127. package/dist/esm/commands/rebuild.js +9 -0
  128. package/dist/esm/commands/rebuild.js.map +1 -0
  129. package/dist/esm/commands/status.d.ts +3 -0
  130. package/dist/esm/commands/status.js +13 -0
  131. package/dist/esm/commands/status.js.map +1 -0
  132. package/dist/esm/commands/types.d.ts +15 -0
  133. package/dist/esm/commands/types.js +1 -0
  134. package/dist/esm/commands/types.js.map +1 -0
  135. package/dist/esm/commands/vault.d.ts +6 -0
  136. package/dist/esm/commands/vault.js +29 -0
  137. package/dist/esm/commands/vault.js.map +1 -0
  138. package/dist/esm/commands/watch.d.ts +3 -0
  139. package/dist/esm/commands/watch.js +19 -0
  140. package/dist/esm/commands/watch.js.map +1 -0
  141. package/dist/esm/config.d.ts +11 -1
  142. package/dist/esm/config.js +68 -12
  143. package/dist/esm/config.js.map +1 -1
  144. package/dist/esm/db.d.ts +1 -1
  145. package/dist/esm/db.js +27 -9
  146. package/dist/esm/db.js.map +1 -1
  147. package/dist/esm/errors.d.ts +1 -1
  148. package/dist/esm/errors.js.map +1 -1
  149. package/dist/esm/features/index.d.ts +7 -0
  150. package/dist/esm/features/index.js +14 -0
  151. package/dist/esm/features/index.js.map +1 -0
  152. package/dist/esm/features/links.d.ts +4 -0
  153. package/dist/esm/features/links.js +81 -0
  154. package/dist/esm/features/links.js.map +1 -0
  155. package/dist/esm/features/rank.d.ts +2 -0
  156. package/dist/esm/features/rank.js +17 -0
  157. package/dist/esm/features/rank.js.map +1 -0
  158. package/dist/esm/features/sections.d.ts +9 -0
  159. package/dist/esm/features/sections.js +41 -0
  160. package/dist/esm/features/sections.js.map +1 -0
  161. package/dist/esm/features/types.d.ts +11 -0
  162. package/dist/esm/features/types.js +3 -0
  163. package/dist/esm/features/types.js.map +1 -0
  164. package/dist/esm/graph.d.ts +7 -0
  165. package/dist/esm/graph.js +79 -0
  166. package/dist/esm/graph.js.map +1 -0
  167. package/dist/esm/index.d.ts +9 -2
  168. package/dist/esm/index.js +4 -1
  169. package/dist/esm/index.js.map +1 -1
  170. package/dist/esm/output.d.ts +21 -0
  171. package/dist/esm/output.js +28 -0
  172. package/dist/esm/output.js.map +1 -1
  173. package/dist/esm/scan.d.ts +3 -1
  174. package/dist/esm/scan.js +56 -7
  175. package/dist/esm/scan.js.map +1 -1
  176. package/dist/esm/verbs.d.ts +31 -0
  177. package/dist/esm/verbs.js +139 -0
  178. package/dist/esm/verbs.js.map +1 -0
  179. package/package.json +4 -4
  180. package/schema.json +23 -4
  181. package/skills/sense/EXAMPLES.md +46 -81
  182. package/skills/sense/SKILL.md +58 -92
package/README.md CHANGED
@@ -1,169 +1,150 @@
1
1
  # sensemaking
2
2
 
3
- Query a knowledge base you build with an agent: filter notes by frontmatter, then search inside them.
3
+ SQL over a tree of markdown notes: filter by frontmatter, search the prose, follow the links,
4
+ read the structure. A CLI that starts, answers, and exits — no server, no build step.
4
5
 
5
- ## The problem
6
+ ## Problem
6
7
 
7
- When you work with an AI agent on anything substantial, you end up with a pile of small notes
8
- findings, decisions, sources, summaries. The pile is the point: it's how knowledge accumulates
9
- instead of being re-derived every session.
8
+ Working with AI agents produces piles of small notes. Past a few dozen, finding the right ones
9
+ means grepping or reading whole folders into context. The structure that makes notes navigable
10
+ frontmatter, wikilinks, headings is exactly what an agent needs, but nothing exposes it as a
11
+ query surface.
10
12
 
11
- But accumulation only pays off if it's findable. Past a couple dozen notes, an agent can't tell
12
- which fifteen of two hundred bear on its task, so it greps blindly, swallows the whole folder into
13
- context, or quietly rebuilds knowledge that already exists three files away.
13
+ `sense` indexes all of it into SQLite and reconciles against file timestamps on every query, so
14
+ results are never stale and nothing has to be running.
14
15
 
15
- The classification an agent adds while writing — `status`, `type`, `tags`, `source` — is what keeps
16
- the pile navigable. `sensemaking` is the layer that acts on it, without an app running, a build
17
- step, or you re-explaining anything.
16
+ ## Quick start
18
17
 
19
- ## How it works
18
+ ```bash
19
+ npm install -g sensemaking
20
+ cd your-notes && sense init
21
+ ```
20
22
 
21
- Every file becomes a row; every frontmatter key becomes a column; the prose becomes a full-text
22
- index you can join against. You write named SQL queries once and run them by name.
23
+ ```bash
24
+ sense map # orient: fields, hub notes, recent changes
25
+ sense find "revenue OR earnings" --k 10 # locate: ranked references with excerpts
26
+ sense peek notes/q3-report.md # structure: outline + links, before reading
27
+ sense query "SELECT path FROM frontmatter WHERE has(tags, ?)" urgent
28
+ ```
23
29
 
24
- ```markdown
25
- ---
26
- title: Ship the Q3 report
27
- summary: where the Q3 numbers came from and who signed off
28
- status: active
29
- tags: [urgent, reports]
30
- ---
30
+ ## Model
31
31
 
32
- Notes about the report…
33
- ```
32
+ Every file becomes rows in four tables:
33
+
34
+ | table | holds | for |
35
+ |---|---|---|
36
+ | `frontmatter` | one column per key, plus `path`, `_mtime`, `_size`, `_rank` | filtering |
37
+ | `content` | FTS5 index: `title`, `summary`, `text` | search and ranking |
38
+ | `links` | `src`, `target` as written, `dst` resolved (`NULL` = dead link) | graph |
39
+ | `sections` | heading, `level`, `start_line`, `end_line`, `tokens` estimate | structure |
40
+
41
+ Results are references — path, title, summary, excerpt — never file contents. Reading happens
42
+ afterward through the filesystem, scoped to the line ranges `peek` returns. This is the
43
+ [just-in-time context pattern](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents):
44
+ the agent holds lightweight identifiers and loads payloads only when needed.
45
+
46
+ Measured costs: a `find` row is ~20–40 tokens, a `peek` is ~17% of reading the file, and the
47
+ section it points at is a direct `Read` range.
34
48
 
35
49
  ```sql
36
- -- active notes that actually discuss revenue, best match first
37
- SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit
50
+ -- filter and search compose in one query
51
+ SELECT f.path, content.title, snippet(content, -1, '«', '»', '…', 10) AS hit
38
52
  FROM frontmatter f JOIN content ON content.path = f.path
39
53
  WHERE f.status = 'active' AND content MATCH 'revenue'
40
54
  ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10
41
55
  ```
42
56
 
43
- The filter shrinks the haystack, the search finds the needle, and each row that comes back —
44
- path, title, summary, matching excerpt — is enough to decide whether to open the file, without
45
- carrying the file. On a real 26-note vault that's a few hundred tokens, against ~6,000 to read the
46
- three files it points at. Cheap enough to run before deciding what to open; reading afterward is
47
- the expensive step, and it happens through the filesystem, not SQL.
57
+ ## Verbs
48
58
 
49
- Two properties make it trustworthy:
59
+ | verb | does |
60
+ |---|---|
61
+ | `map` | doc count, frontmatter field coverage, top hubs by link rank, recent changes |
62
+ | `find "<terms>" [--where "<sql>"] [--k n]` | BM25 + link-graph expansion, fused; `via` marks each row `match`, `link`, or `match+link` |
63
+ | `peek <path>` | frontmatter + heading outline (`[L143-162, ~380t]`) + links both ways, capped at 20 per list |
64
+ | `query "<sql>" [params...]` | ad-hoc SQL over all four tables; `?` binds positional args |
50
65
 
51
- - **Never stale.** Every query re-checks the filesystem first, re-reading only what changed. The
52
- SQLite cache under `.sense/` is disposable delete it any time, the next query rebuilds it. An
53
- agent can trust a result without knowing when anything was last indexed.
54
- - **Headless.** Files on disk are the only source of truth. Nothing needs to be open — not
55
- Obsidian, not a server, not a daemon.
66
+ `find` seeds a personalized-PageRank walk with the BM25 matches, so a note that never contains
67
+ the terms but is linked from ones that do still surfaces. Invalid FTS5 syntax falls back to
68
+ OR over the words. `--where` takes a frontmatter condition against alias `f`.
69
+ `--format json` on any verb returns structured output.
56
70
 
57
- ## Use it
71
+ ## Config
58
72
 
59
- ```bash
60
- npm install -g sensemaking
61
- cd your-notes && sense init # writes a minimal sense.config.json (globs only, no queries)
62
- ```
63
-
64
- Query immediately — ad-hoc SQL needs no config beyond the globs:
65
-
66
- ```
67
- sense query "SELECT … FROM frontmatter" # ad-hoc SQL; positional args bind to ? placeholders
68
- sense query "…" --format json # structured output (the default is a table)
69
- sense --list # what named queries exist
70
- sense status | rebuild # cache info / delete .sense/ and re-crawl
71
- ```
72
-
73
- When a query proves worth reusing, name it in `sense.config.json` (plain JSON; the `$schema` line
74
- gives your editor autocomplete and validation) and run it as `sense <name> [params...]`:
73
+ `sense init` writes `sense.config.json`; discovery walks up from cwd like git
74
+ (`--config <path>` overrides). Name reusable queries and run them as `sense <name> [params...]`:
75
75
 
76
76
  ```json
77
77
  {
78
78
  "$schema": "https://unpkg.com/sensemaking/schema.json",
79
- "version": 1,
79
+ "version": 2,
80
80
  "scan": { "include": ["**/*.md"] },
81
+ "features": { "links": true, "sections": true, "rank": true },
81
82
  "queries": {
82
- "by-tag": "SELECT path, title, summary FROM frontmatter WHERE has(tags, ?) ORDER BY path"
83
+ "by-tag": "SELECT path, title FROM frontmatter WHERE has(tags, ?) ORDER BY path"
83
84
  }
84
85
  }
85
86
  ```
86
87
 
87
- Discovery walks up from your cwd git-style, so you can run `sense` from anywhere in the tree
88
- (`--config <path>` overrides). Exit codes: `0` ok, `1` real error (SQLite's message verbatim),
89
- `2` usage error.
90
-
91
- The one custom SQL function is `has(field, value)`: array membership on a JSON-array field (like
92
- `tags`), substring match on a string, always false on a missing key. Reserved names: `path`,
93
- `_mtime`, `_size`, `content`.
88
+ Features toggle independently; disabling one degrades its verb instead of failing it (`find`
89
+ goes BM25-only without `links`, `map` drops hubs without `rank`, `peek` drops the outline
90
+ without `sections`). Toggling rebuilds the cache. Older config versions auto-migrate on load,
91
+ noted on stderr.
94
92
 
95
- ### Content search
93
+ ## Reference
96
94
 
97
- `content` is an FTS5 table with columns `title`, `summary`, `text`, and `path` (for the join).
98
- `content MATCH ?` takes FTS5 syntax (`a OR b`, `"exact phrase"`, `pref*`, `NEAR(a b, 5)`,
99
- `summary: term`), with stemming on so `negotiate` matches "negotiating". Markdown syntax is
100
- stripped at index time — search for the words, not the formatting around them, and excerpts come
101
- back as clean prose. `bm25()` weights follow column order, so `bm25(content, 10.0, 5.0, 1.0)`
102
- ranks a title hit above a passing mention; `snippet(content, -1, )` excerpts whichever column
103
- matched.
95
+ - `has(field, value)` the one custom SQL function: array membership on JSON-array fields,
96
+ substring on strings, false on missing keys.
97
+ - Reserved frontmatter keys (dropped with a warning): `path`, `_mtime`, `_size`, `_rank`,
98
+ `content`, `links`, `sections`.
99
+ - FTS5 syntax in `MATCH`: `a OR b`, `"phrase"`, `pref*`, `NEAR(a b, 5)`, `summary: term`.
100
+ Stemmed; markdown is stripped at index time. `bm25(content, 10.0, 5.0, 1.0)` weights title
101
+ over summary over body.
102
+ - A one-line `summary:` in frontmatter is both a selectable column and a weighted search field.
103
+ - Exit codes: `0` ok, `1` error (SQLite message verbatim), `2` usage.
104
+ - Frontmatter parsing is lenient: syntax errors (an alias starting with `@`, say) are per-file
105
+ warnings and the values are still indexed.
104
106
 
105
- Prose is deliberately **not** a column on `frontmatter`, so `SELECT * FROM frontmatter` can never
106
- dump your notes into an agent's context — reaching it takes an explicit join. Select `path`,
107
- `title`, `summary`, and a `snippet()`, keep a `LIMIT`, and read the files worth reading. `sense`
108
- warns on stderr when a result grows past 50 KB.
107
+ ## Scale
109
108
 
110
- A one-line `summary:` in frontmatter is worth adding as you write what's on the page and when
111
- it's worth opening, like a skill's `description:`. It's both a column you can select (a search
112
- result row often answers the question with no file read at all) and a weighted search field.
109
+ Every query starts with a freshness check against the cache in `.sense/`; only changed files are
110
+ re-parsed. Measured per release in [BENCHMARKING.md](BENCHMARKING.md) at 6,566 notes: full
111
+ crawl ~5 s, warm query ~100 ms, one-file change ~200 ms. `sense watch` (optional) moves
112
+ re-parsing into the background — see [WATCH.md](WATCH.md). `sense rebuild` deletes the cache and
113
+ re-crawls.
113
114
 
114
- ### For AI agents
115
+ ## For AI agents
115
116
 
116
117
  ```bash
117
- npm install -g sensemaking # the CLI
118
- npx skills add kmalakoff/sensemaking # the agent skill (add -g for global, -a claude-code to target)
118
+ npx skills add kmalakoff/sensemaking # -g for global, -a claude-code to target
119
119
  ```
120
120
 
121
- The skill teaches an agent the essentials: discovery, `--list`, `--format json`, `has()`, when to
122
- use ad-hoc `query` versus saving a named one, and when to `rebuild`.
123
-
124
- ### Optional: a background pre-warmer
125
-
126
- `sense watch` runs the same reconcile ahead of time whenever the filesystem changes, so queries
127
- open on an already-warm cache. It's purely an optimizationqueries reconcile on open anyway, so
128
- a missed event can never make a result wrong and under ~1000 files you likely won't notice a
129
- difference. It runs in the foreground and never daemonizes; process supervision belongs to the OS.
130
- launchd and systemd examples: [WATCH.md](WATCH.md).
131
-
132
- ## Why not
133
-
134
- - **Obsidian (Bases/Dataview):** filters this well, but only inside the running Electron app —
135
- scripts and agents can't query it headless. Your vault works unmodified either way; Obsidian
136
- stays a fine viewer for the same files.
137
- - **RAG / semantic search:** ranks by similarity, not hard constraints like "only active notes from
138
- this project"that needs a `WHERE` clause. The two compose: filter first, rank later; vector
139
- similarity, if added later, would join the same way.
140
- - **An LLM-written index file** la Karpathy's llm-wiki `index.md`): a good instinct, but a
141
- second artifact that drifts out of sync. `frontmatter` is that catalog, derived from the notes
142
- themselves on every query.
143
- - **Index-on-build tools (MarkdownDB and similar):** query a snapshot that's stale the moment a
144
- file changes, instead of reconciling live.
145
- - **Note CLIs (zk and similar):** good at their own model — tags, links, full text — but can't
146
- filter on arbitrary frontmatter fields, which is the whole point here.
147
- - **grep / one-off scripts:** fine until you want named, reusable, parameterized queries with real
148
- AND/OR/ORDER BY — at which point you've started writing a worse query engine.
149
-
150
- `sensemaking` is deliberately thin glue: [gray-matter](https://github.com/jonschlinkert/gray-matter)
151
- parses, [remove-markdown](https://github.com/zuchka/remove-markdown) cleans the prose for indexing,
152
- [fast-glob](https://github.com/mrmlnc/fast-glob) walks, and Node's built-in SQLite (`node:sqlite`)
153
- does all the querying. Three small dependencies, no native builds, no background services required.
154
-
155
- ## Roadmap
156
-
157
- Vector similarity is the natural next facet — a `doc_vec` table joined the same way `content` is,
158
- so semantic recall composes with the frontmatter filter instead of living in a separate tool
159
- (`SELECT path, distance`, never the embedding). It's deferred, not planned: today it would require
160
- a native SQLite extension (sqlite-vec is pre-v1 and ships platform binaries), which breaks the
161
- no-native-builds line above. It becomes worth revisiting when Node can do it dependency-free — and
162
- only if BM25 demonstrably misses things; on a curated vault of a few hundred notes it often
163
- doesn't.
164
-
165
- Beyond that, the corpus model isn't tied to markdown — anything carrying structured metadata
166
- (document properties, sidecar JSON) can join it without changing the query surface.
121
+ The skill teaches the descent: `map` to orient, `find` to locate, `peek` before reading,
122
+ `Read` line ranges instead of files.
123
+
124
+ ## Prior art
125
+
126
+ - [Effective context engineering for AI agents](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents)
127
+ (Anthropic): agents should hold lightweight identifiers file paths, links and load payloads
128
+ just in time, because context is a finite resource. The four verbs implement that pattern as a CLI.
129
+ - [llm-wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) (Karpathy): an
130
+ agent-maintained wiki navigated by an `index.md` and links, which he notes needs real search
131
+ infrastructure past a few hundred pages. `sense map` derives that index from the notes instead of
132
+ maintaining it; `find` is the hybrid local search it calls for.
133
+
134
+ ## Alternatives
135
+
136
+ - **Obsidian Bases/Dataview** same filters, but only inside the running app; agents can't
137
+ query it headless.
138
+ - **Index-on-build tools (MarkdownDB)** query a snapshot; `sense` reconciles on every query.
139
+ - **Note CLIs (zk)** fixed schema; `sense` filters on arbitrary frontmatter.
140
+ - **RAG / vector stores** similarity can't express `WHERE status = 'active'`. Vector recall is
141
+ planned as an optional `embed` feature (pure-JS provider package, fused into `find`) — not
142
+ blocked (`node:sqlite` can load extensions since Node 22.13), just deferred until BM25 + links
143
+ measurably miss.
144
+
145
+ Dependencies: [yaml](https://github.com/eemeli/yaml),
146
+ [remove-markdown](https://github.com/zuchka/remove-markdown),
147
+ [fast-glob](https://github.com/mrmlnc/fast-glob), and Node's built-in SQLite. No native builds.
167
148
 
168
149
  ## License
169
150