sensemaking 0.7.2 → 0.8.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 (201) hide show
  1. package/README.md +61 -41
  2. package/dist/cjs/cli/check.js +386 -0
  3. package/dist/cjs/cli/check.js.map +1 -0
  4. package/dist/cjs/{commands → cli}/find.js +3 -4
  5. package/dist/cjs/cli/find.js.map +1 -0
  6. package/dist/cjs/cli/index.js.map +1 -0
  7. package/dist/cjs/{commands → cli}/init.js +3 -0
  8. package/dist/cjs/cli/init.js.map +1 -0
  9. package/dist/cjs/{commands → cli}/map.js +2 -2
  10. package/dist/cjs/cli/map.js.map +1 -0
  11. package/dist/cjs/{commands → cli}/named.d.cts +1 -1
  12. package/dist/cjs/{commands → cli}/named.d.ts +1 -1
  13. package/dist/cjs/cli/named.js +209 -0
  14. package/dist/cjs/cli/named.js.map +1 -0
  15. package/dist/cjs/{commands → cli}/peek.js +2 -2
  16. package/dist/cjs/cli/peek.js.map +1 -0
  17. package/dist/cjs/cli/query.js.map +1 -0
  18. package/dist/cjs/cli/rebuild.js.map +1 -0
  19. package/dist/cjs/{commands → cli}/shared.d.cts +1 -0
  20. package/dist/{esm/commands → cjs/cli}/shared.d.ts +1 -0
  21. package/dist/cjs/{commands → cli}/shared.js +9 -0
  22. package/dist/cjs/cli/shared.js.map +1 -0
  23. package/dist/cjs/{commands → cli}/status.js +7 -1
  24. package/dist/cjs/cli/status.js.map +1 -0
  25. package/dist/cjs/cli/watch.js.map +1 -0
  26. package/dist/cjs/cli.js +22 -17
  27. package/dist/cjs/cli.js.map +1 -1
  28. package/dist/{esm/verbs.d.ts → cjs/commands.d.cts} +5 -4
  29. package/dist/cjs/{verbs.d.cts → commands.d.ts} +5 -4
  30. package/dist/cjs/{verbs.js → commands.js} +246 -21
  31. package/dist/cjs/commands.js.map +1 -0
  32. package/dist/cjs/config.d.cts +7 -1
  33. package/dist/cjs/config.d.ts +7 -1
  34. package/dist/cjs/config.js +75 -24
  35. package/dist/cjs/config.js.map +1 -1
  36. package/dist/cjs/db.d.cts +1 -1
  37. package/dist/cjs/db.d.ts +1 -1
  38. package/dist/cjs/db.js +88 -17
  39. package/dist/cjs/db.js.map +1 -1
  40. package/dist/cjs/errors.d.cts +1 -1
  41. package/dist/cjs/errors.d.ts +1 -1
  42. package/dist/cjs/errors.js.map +1 -1
  43. package/dist/cjs/features/embed.js +7 -1
  44. package/dist/cjs/features/embed.js.map +1 -1
  45. package/dist/cjs/features/links.js +201 -24
  46. package/dist/cjs/features/links.js.map +1 -1
  47. package/dist/cjs/features/rank.js +8 -1
  48. package/dist/cjs/features/rank.js.map +1 -1
  49. package/dist/cjs/features/types.d.cts +10 -3
  50. package/dist/cjs/features/types.d.ts +10 -3
  51. package/dist/cjs/index.d.cts +3 -3
  52. package/dist/cjs/index.d.ts +3 -3
  53. package/dist/cjs/index.js +4 -4
  54. package/dist/cjs/index.js.map +1 -1
  55. package/dist/cjs/output.d.cts +1 -0
  56. package/dist/cjs/output.d.ts +1 -0
  57. package/dist/cjs/output.js +2 -1
  58. package/dist/cjs/output.js.map +1 -1
  59. package/dist/cjs/progress.d.cts +5 -0
  60. package/dist/cjs/progress.d.ts +5 -0
  61. package/dist/cjs/progress.js +54 -0
  62. package/dist/cjs/progress.js.map +1 -0
  63. package/dist/cjs/scan.d.cts +1 -0
  64. package/dist/cjs/scan.d.ts +1 -0
  65. package/dist/cjs/scan.js +3 -2
  66. package/dist/cjs/scan.js.map +1 -1
  67. package/dist/esm/{commands → cli}/check.js +43 -3
  68. package/dist/esm/cli/check.js.map +1 -0
  69. package/dist/esm/{commands → cli}/find.js +3 -4
  70. package/dist/esm/cli/find.js.map +1 -0
  71. package/dist/esm/cli/index.js.map +1 -0
  72. package/dist/esm/{commands → cli}/init.js +3 -0
  73. package/dist/esm/cli/init.js.map +1 -0
  74. package/dist/esm/{commands → cli}/map.js +1 -1
  75. package/dist/esm/cli/map.js.map +1 -0
  76. package/dist/esm/{commands → cli}/named.d.ts +1 -1
  77. package/dist/esm/cli/named.js +33 -0
  78. package/dist/esm/cli/named.js.map +1 -0
  79. package/dist/esm/{commands → cli}/peek.js +1 -1
  80. package/dist/esm/cli/peek.js.map +1 -0
  81. package/dist/esm/cli/query.js.map +1 -0
  82. package/dist/esm/cli/rebuild.js.map +1 -0
  83. package/dist/{cjs/commands → esm/cli}/shared.d.ts +1 -0
  84. package/dist/esm/{commands → cli}/shared.js +9 -0
  85. package/dist/esm/cli/shared.js.map +1 -0
  86. package/dist/esm/{commands → cli}/status.js +7 -1
  87. package/dist/esm/cli/status.js.map +1 -0
  88. package/dist/esm/cli/types.js.map +1 -0
  89. package/dist/esm/cli/watch.js.map +1 -0
  90. package/dist/esm/cli.js +8 -6
  91. package/dist/esm/cli.js.map +1 -1
  92. package/dist/{cjs/verbs.d.ts → esm/commands.d.ts} +5 -4
  93. package/dist/esm/{verbs.js → commands.js} +165 -21
  94. package/dist/esm/commands.js.map +1 -0
  95. package/dist/esm/config.d.ts +7 -1
  96. package/dist/esm/config.js +38 -4
  97. package/dist/esm/config.js.map +1 -1
  98. package/dist/esm/db.d.ts +1 -1
  99. package/dist/esm/db.js +78 -16
  100. package/dist/esm/db.js.map +1 -1
  101. package/dist/esm/errors.d.ts +1 -1
  102. package/dist/esm/errors.js.map +1 -1
  103. package/dist/esm/features/embed.js +6 -0
  104. package/dist/esm/features/embed.js.map +1 -1
  105. package/dist/esm/features/links.js +130 -20
  106. package/dist/esm/features/links.js.map +1 -1
  107. package/dist/esm/features/rank.js +8 -1
  108. package/dist/esm/features/rank.js.map +1 -1
  109. package/dist/esm/features/types.d.ts +10 -3
  110. package/dist/esm/features/types.js.map +1 -1
  111. package/dist/esm/index.d.ts +3 -3
  112. package/dist/esm/index.js +1 -1
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/output.d.ts +1 -0
  115. package/dist/esm/output.js +2 -1
  116. package/dist/esm/output.js.map +1 -1
  117. package/dist/esm/progress.d.ts +5 -0
  118. package/dist/esm/progress.js +44 -0
  119. package/dist/esm/progress.js.map +1 -0
  120. package/dist/esm/scan.d.ts +1 -0
  121. package/dist/esm/scan.js +5 -2
  122. package/dist/esm/scan.js.map +1 -1
  123. package/package.json +6 -2
  124. package/schema.json +19 -4
  125. package/skills/sense/EXAMPLES.md +9 -7
  126. package/skills/sense/SKILL.md +43 -27
  127. package/skills/sense-setup/SKILL.md +16 -2
  128. package/dist/cjs/commands/check.js +0 -143
  129. package/dist/cjs/commands/check.js.map +0 -1
  130. package/dist/cjs/commands/find.js.map +0 -1
  131. package/dist/cjs/commands/index.js.map +0 -1
  132. package/dist/cjs/commands/init.js.map +0 -1
  133. package/dist/cjs/commands/map.js.map +0 -1
  134. package/dist/cjs/commands/named.js +0 -23
  135. package/dist/cjs/commands/named.js.map +0 -1
  136. package/dist/cjs/commands/peek.js.map +0 -1
  137. package/dist/cjs/commands/query.js.map +0 -1
  138. package/dist/cjs/commands/rebuild.js.map +0 -1
  139. package/dist/cjs/commands/shared.js.map +0 -1
  140. package/dist/cjs/commands/status.js.map +0 -1
  141. package/dist/cjs/commands/watch.js.map +0 -1
  142. package/dist/cjs/verbs.js.map +0 -1
  143. package/dist/esm/commands/check.js.map +0 -1
  144. package/dist/esm/commands/find.js.map +0 -1
  145. package/dist/esm/commands/index.js.map +0 -1
  146. package/dist/esm/commands/init.js.map +0 -1
  147. package/dist/esm/commands/map.js.map +0 -1
  148. package/dist/esm/commands/named.js +0 -12
  149. package/dist/esm/commands/named.js.map +0 -1
  150. package/dist/esm/commands/peek.js.map +0 -1
  151. package/dist/esm/commands/query.js.map +0 -1
  152. package/dist/esm/commands/rebuild.js.map +0 -1
  153. package/dist/esm/commands/shared.js.map +0 -1
  154. package/dist/esm/commands/status.js.map +0 -1
  155. package/dist/esm/commands/types.js.map +0 -1
  156. package/dist/esm/commands/watch.js.map +0 -1
  157. package/dist/esm/verbs.js.map +0 -1
  158. /package/dist/cjs/{commands → cli}/check.d.cts +0 -0
  159. /package/dist/cjs/{commands → cli}/check.d.ts +0 -0
  160. /package/dist/cjs/{commands → cli}/find.d.cts +0 -0
  161. /package/dist/cjs/{commands → cli}/find.d.ts +0 -0
  162. /package/dist/cjs/{commands → cli}/index.d.cts +0 -0
  163. /package/dist/cjs/{commands → cli}/index.d.ts +0 -0
  164. /package/dist/cjs/{commands → cli}/index.js +0 -0
  165. /package/dist/cjs/{commands → cli}/init.d.cts +0 -0
  166. /package/dist/cjs/{commands → cli}/init.d.ts +0 -0
  167. /package/dist/cjs/{commands → cli}/map.d.cts +0 -0
  168. /package/dist/cjs/{commands → cli}/map.d.ts +0 -0
  169. /package/dist/cjs/{commands → cli}/peek.d.cts +0 -0
  170. /package/dist/cjs/{commands → cli}/peek.d.ts +0 -0
  171. /package/dist/cjs/{commands → cli}/query.d.cts +0 -0
  172. /package/dist/cjs/{commands → cli}/query.d.ts +0 -0
  173. /package/dist/cjs/{commands → cli}/query.js +0 -0
  174. /package/dist/cjs/{commands → cli}/rebuild.d.cts +0 -0
  175. /package/dist/cjs/{commands → cli}/rebuild.d.ts +0 -0
  176. /package/dist/cjs/{commands → cli}/rebuild.js +0 -0
  177. /package/dist/cjs/{commands → cli}/status.d.cts +0 -0
  178. /package/dist/cjs/{commands → cli}/status.d.ts +0 -0
  179. /package/dist/cjs/{commands → cli}/types.d.cts +0 -0
  180. /package/dist/cjs/{commands → cli}/types.d.ts +0 -0
  181. /package/dist/cjs/{commands → cli}/types.js +0 -0
  182. /package/dist/cjs/{commands → cli}/types.js.map +0 -0
  183. /package/dist/cjs/{commands → cli}/watch.d.cts +0 -0
  184. /package/dist/cjs/{commands → cli}/watch.d.ts +0 -0
  185. /package/dist/cjs/{commands → cli}/watch.js +0 -0
  186. /package/dist/esm/{commands → cli}/check.d.ts +0 -0
  187. /package/dist/esm/{commands → cli}/find.d.ts +0 -0
  188. /package/dist/esm/{commands → cli}/index.d.ts +0 -0
  189. /package/dist/esm/{commands → cli}/index.js +0 -0
  190. /package/dist/esm/{commands → cli}/init.d.ts +0 -0
  191. /package/dist/esm/{commands → cli}/map.d.ts +0 -0
  192. /package/dist/esm/{commands → cli}/peek.d.ts +0 -0
  193. /package/dist/esm/{commands → cli}/query.d.ts +0 -0
  194. /package/dist/esm/{commands → cli}/query.js +0 -0
  195. /package/dist/esm/{commands → cli}/rebuild.d.ts +0 -0
  196. /package/dist/esm/{commands → cli}/rebuild.js +0 -0
  197. /package/dist/esm/{commands → cli}/status.d.ts +0 -0
  198. /package/dist/esm/{commands → cli}/types.d.ts +0 -0
  199. /package/dist/esm/{commands → cli}/types.js +0 -0
  200. /package/dist/esm/{commands → cli}/watch.d.ts +0 -0
  201. /package/dist/esm/{commands → cli}/watch.js +0 -0
package/schema.json CHANGED
@@ -32,7 +32,7 @@
32
32
  "features": {
33
33
  "type": "object",
34
34
  "additionalProperties": false,
35
- "description": "Independently toggleable features. Absent block or key means enabled; set false to disable. Toggling a feature rebuilds the cache. Verbs degrade rather than fail: `find` without links is BM25-only, `map` without rank omits hubs, `peek` without sections omits the outline.",
35
+ "description": "Independently toggleable features. Absent block or key means enabled; set false to disable. Toggling a feature rebuilds the cache. Commands degrade rather than fail: `find` without links is BM25-only, `map` without rank omits hubs, `peek` without sections omits the outline.",
36
36
  "properties": {
37
37
  "links": {
38
38
  "type": "boolean",
@@ -67,7 +67,7 @@
67
67
  "defaults": {
68
68
  "type": "object",
69
69
  "additionalProperties": false,
70
- "description": "Tree-declared defaults for the verbs.",
70
+ "description": "Tree-declared defaults for the commands.",
71
71
  "properties": {
72
72
  "find": {
73
73
  "type": "object",
@@ -89,8 +89,23 @@
89
89
  },
90
90
  "queries": {
91
91
  "type": "object",
92
- "description": "Named SQL queries runnable as `sense <name> [params...]`. Tables: `frontmatter` (one row per file, one column per discovered frontmatter key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5: `title`, `summary`, `text`, `path`), `links` (`src`, `target`, `dst`), and `sections` (`path`, `idx`, `level`, `heading`, `start_line`, `end_line`, `tokens`). `?` placeholders bind to CLI positional args in order. `has(field, value)`: array membership on a JSON-array field, substring match on a string (so has(f.status, 'active') also matches 'inactive'), false on NULL. Exact matches: `=` for scalars, `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)` for array members. Canonical query: `SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit FROM frontmatter f JOIN content ON content.path = f.path WHERE content MATCH ? ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10`. Reserved frontmatter keys: `path`, `_mtime`, `_size`, `_rank`, `content`, `links`, `sections`. Reserved query names (unreachable as subcommands): `init`, `query`, `find`, `map`, `peek`, `watch`, `status`, `rebuild`, `check`.",
93
- "additionalProperties": { "type": "string" }
92
+ "description": "Named queries runnable as `sense <name> [params...]`, either a SQL string or a saved find. SQL string: `?` placeholders bind to CLI positional args in order. Tables: `frontmatter` (one row per file, one column per discovered frontmatter key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5: `title`, `summary`, `text`, `path`), `links` (`src`, `target`, `dst`), and `sections` (`path`, `idx`, `level`, `heading`, `start_line`, `end_line`, `tokens`). `has(field, value)`: array membership on a JSON-array field, substring match on a string (so has(f.status, 'active') also matches 'inactive'), false on NULL. Exact matches: `=` for scalars, `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)` for array members. Canonical query: `SELECT f.path, content.title, content.summary, CASE WHEN length(content.text) <= 16384 THEN snippet(content, -1, '«', '»', '…', 10) END AS hit FROM frontmatter f JOIN content ON content.path = f.path WHERE content MATCH ? ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10`. The CASE bounds snippet(), which re-tokenizes each matched document and costs seconds per query once a tree holds a megabyte-scale note; `find` applies the same bound internally. Saved find object: `sense <name>` behaves like `sense find <find> [--k] [--where] [--semantic]` with zero flags; it takes no positional parameters. Reserved frontmatter keys: `path`, `_mtime`, `_size`, `_rank`, `content`, `links`, `sections`. Reserved query names (unreachable as subcommands, either form): `init`, `query`, `find`, `map`, `peek`, `watch`, `status`, `rebuild`, `check`.",
93
+ "additionalProperties": {
94
+ "oneOf": [
95
+ { "type": "string" },
96
+ {
97
+ "type": "object",
98
+ "additionalProperties": false,
99
+ "required": ["find"],
100
+ "properties": {
101
+ "find": { "type": "string", "description": "FTS5 MATCH terms, verbatim -- same syntax as `sense find \"<terms>\"`." },
102
+ "k": { "type": "integer", "minimum": 1, "description": "Result count, same as `find --k`. Defaults to 10. A --k on the invocation overrides this." },
103
+ "where": { "type": "string", "description": "SQL condition against frontmatter alias `f`, same as `find --where`. A --where on the invocation replaces this rather than ANDing with it." },
104
+ "semantic": { "type": "boolean", "description": "Adds vector expansion, same as `find --semantic`. Requires features.embed; `sense check` reports a saved find with semantic true as failed when embed is off, without running it." }
105
+ }
106
+ }
107
+ ]
108
+ }
94
109
  }
95
110
  }
96
111
  }
@@ -12,14 +12,15 @@ sense find "pricing OR billing OR invoicing" --k 10 --format json
12
12
  ```json
13
13
  [
14
14
  { "path": "notes/pricing-model.md", "title": "Pricing model",
15
- "summary": "Tiered per-seat pricing; floor and discount rules", "hit": "«Pricing» floor for annual…", "via": "match+link", "score": 0.0333 },
15
+ "summary": "Tiered per-seat pricing; floor and discount rules", "hit": "«Pricing» floor for annual…", "via": "match+link", "score": 0.0333, "lines": null },
16
16
  { "path": "notes/renewal-playbook.md", "title": "Renewal playbook",
17
- "summary": "Renewal sequence and owners", "hit": "…«billing» contact confirms the PO…", "via": "match", "score": 0.0313 }
17
+ "summary": "Renewal sequence and owners", "hit": "…«billing» contact confirms the PO…", "via": "match", "score": 0.0313, "lines": "L81-140" }
18
18
  ]
19
19
  ```
20
20
 
21
- ~30 tokens per row; often the `summary` answers the question with no read at all. A row with
22
- `via: "link"` never contained the terms — it is linked from notes that did.
21
+ Tens of tokens per row; often the `summary` answers the question with no read at all. A row with
22
+ `via: "link"` never contained the terms — it is linked from notes that did. `lines`, when
23
+ set, is the section that earned the row, a direct `Read` range.
23
24
 
24
25
  ## B. Known-field filtering (no search)
25
26
 
@@ -84,18 +85,19 @@ sense find "children dying from poor nutrition" --semantic --k 3 --format json
84
85
  ```json
85
86
  [
86
87
  { "path": "notes/malnutrition-outcomes.md", "title": "Malnutrition outcomes",
87
- "summary": "Stunting and mortality by region", "hit": null, "via": "vector", "score": 0.0167, "lines": "L14-52" }
88
+ "summary": "Stunting and mortality by region", "hit": null, "via": "vector", "score": 0.0167, "similarity": 0.61, "lines": "L14-52" }
88
89
  ]
89
90
  ```
90
91
 
91
- A `via: "vector"` row never contained the terms — it is semantically near them; `lines` is the
92
- best-matching section, a direct `Read` range. Only on trees whose config enables `features.embed`.
92
+ A `via: "vector"` row never contained the terms — it is semantically near them; `similarity` is
93
+ the cosine against the chunk `lines` names, a direct `Read` range. Only on trees whose config enables `features.embed`.
93
94
 
94
95
  ## Consequences
95
96
 
96
97
  | query | what happens | bounded alternative |
97
98
  |---|---|---|
98
99
  | `SELECT text FROM content` | returns the tree's entire prose | `snippet(content, -1, '«', '»', '…', 10)` excerpts the match |
100
+ | `snippet()` on a tree holding a megabyte-scale note | re-tokenizes the whole document per matched row: seconds per query | bound it: `CASE WHEN length(content.text) <= 16384 THEN snippet(...) END`, or select `summary` instead |
99
101
  | `sense find "pricing"` | matches only that word's stem | OR-in synonyms and instances: `"pricing OR billing OR invoicing"` |
100
102
  | `sense find "pricing model details"` | bare words AND-join; one absent word = zero rows | OR the words, or quote an exact phrase |
101
103
  | `sense find "customer-facing OR on-site"` | bare punctuation is FTS5 syntax (`-` reads as a column filter) | double-quote: `"customer-facing" OR "on-site"` |
@@ -5,16 +5,18 @@ description: Query a markdown tree with the sense CLI — filter notes by frontm
5
5
 
6
6
  # sense
7
7
 
8
- SQL over a markdown tree, kept fresh by a filesystem check on every query. Four tables per file:
9
- `frontmatter` (one column per key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5:
10
- `title`, `summary`, `text`), `links` (`src`, `target`, `dst` — `NULL` dst = dead link),
11
- `sections` (heading outline with line ranges and token estimates).
8
+ SQL over a markdown tree, kept fresh by a filesystem check on every query. Every file becomes
9
+ rows in `frontmatter` (one column per key, plus `path`/`_mtime`/`_size`/`_rank`), `content`
10
+ (FTS5: `title`, `summary`, `text`), `links` (`src`, `target`, `dst` — `NULL` dst = dead link),
11
+ and `sections` (heading outline with line ranges and token estimates). Features add their own
12
+ storage; `map` and `status` report which are on.
12
13
 
13
14
  ## What each tool is for
14
15
 
15
16
  Every result is a reference (path, metadata, excerpt), never file contents; prose enters
16
- context only when you Read it. Costs: `map` is fixed-size, a `find` row ~30 tokens, `peek`
17
- ~17% of reading the file. Which tool fits is a property of the question:
17
+ context only when you Read it. Costs: `map` is fixed-size, a `find` row is tens of tokens,
18
+ and a `peek` stays flat however large the note is. Which tool fits is a property of the
19
+ question:
18
20
 
19
21
  - A deterministic, factual answer over known fields — counts, filters, "which notes have
20
22
  X" — is SQL: `sense query`, a named query, or `find --where`. Enumerates every match;
@@ -29,7 +31,9 @@ context only when you Read it. Costs: `map` is fixed-size, a `find` row ~30 toke
29
31
  - `map` answers "what is this tree" — fields, hub notes, recent changes — when the tree is
30
32
  unfamiliar.
31
33
  - `peek <path>` prices a file before you pay for it: outline with `[L143-162, ~380t]`
32
- ranges, links both ways.
34
+ ranges, links both ways. Every list shows its first 20 with the true total; the
35
+ `sections` and `links` tables hold the rest, so a peek costs a few hundred tokens on any
36
+ note — heading-dense monsters included.
33
37
  - When you know the file and need its contents, `Read` it — sense adds nothing there. On
34
38
  large files peek's ranges let you read just one section; small files are often cheaper
35
39
  whole.
@@ -37,14 +41,14 @@ context only when you Read it. Costs: `map` is fixed-size, a `find` row ~30 toke
37
41
  Output defaults to a table, built for humans; `--format json` returns the same rows
38
42
  machine-parseable.
39
43
 
40
- ## Verbs
44
+ ## Commands
41
45
 
42
46
  ```
43
47
  sense find "pricing OR billing OR invoicing" --where "f.status = 'active'" --k 10
44
48
  sense peek notes/pricing-model.md # a unique basename also works
45
49
  sense map
46
50
  sense query "<sql>" [params...] # ad-hoc SQL; ? binds positional args, count-checked
47
- sense <name> [params...] # named query from sense.config.json
51
+ sense <name> [params...] # named query or saved find from sense.config.json
48
52
  sense --list | status | rebuild | check
49
53
  ```
50
54
 
@@ -54,44 +58,52 @@ sense --list | status | rebuild | check
54
58
  rules apply to search commands you write into subagent briefs.
55
59
  - When a search misses, the recall levers are: OR-in synonyms and concrete instances (the
56
60
  index only knows the words in the files — a note about a specific tool rarely names its
57
- category), raise `--k` (a row costs ~30 tokens), and on embed-enabled trees `--semantic`
61
+ category), raise `--k` (a row costs tens of tokens), and on embed-enabled trees `--semantic`
58
62
  (matches meaning where term overlap fails). Each widening adds candidates and dilutes
59
63
  ranking, so the noise trade-off runs both ways.
60
64
  - A frontmatter query enumerates its matches deterministically; search ranks by term overlap,
61
65
  so results shift as phrasing shifts. Trade-off: a query needs a known field, search doesn't.
62
66
  - `find` fuses BM25 with link-graph expansion; the `via` column says what produced each row —
63
67
  `match` (terms hit), `link` (connected to notes that hit), `match+link` (both). With
64
- `--semantic`, `vector` joins the composition and rows gain a `lines` column pointing at the
65
- best-matching section, a direct `Read` range.
68
+ `--semantic`, `vector` joins the composition. The `lines` column, when set, points at the
69
+ section that earned the row — the best-matching chunk on vector rows, the term cluster's
70
+ section on large lexical notes — and is a direct `Read` range; null means the whole note
71
+ is the reference.
66
72
  - `--where` takes any SQL condition against frontmatter alias `f` — not only field equality:
67
73
  `"f.status = 'active' AND has(f.tags, 'x')"`, `"f.path NOT LIKE 'generated/%'"`,
68
74
  `"f.created >= datetime(?)"`. A tree can declare a default scope in `sense.config.json`
69
75
  (`defaults.find.where`); an explicit `--where` replaces it, so `--where "1=1"` searches
70
76
  everything. `sense status` prints the active default.
71
77
  - `score` is a rank-fusion value: it ranks rows within one result set and is not comparable
72
- across queries, not a relevance magnitude — a perfect lexical hit and a weak vector-only
73
- hit can both read ~0.017, because the number encodes how many signals fired and at what
74
- rank. With `--semantic`, rows carry `similarity`: the cosine (-1 to 1) of the query
75
- against that file's best-matching chunk — the same chunk the `lines` range points at.
76
- It orders vector evidence within a result set; its absolute range depends on the corpus.
77
- Measured: on thousands of notes, unrelated queries ~0.2 and genuine matches ~0.6; on a
78
- few dozen notes the ranges compress and can overlap, because even a nonsense query has a
79
- moderately near neighbour somewhere. Compare similarities within a result set rather than
80
- against a fixed cutoff carried between trees.
78
+ across queries, not a relevance magnitude — it encodes how many signals fired and at what
79
+ rank, so a perfect lexical hit and a weak vector-only hit can read the same number. With
80
+ `--semantic`, rows carry `similarity`: the cosine (-1 to 1) of the query against that
81
+ file's best-matching chunk — the same chunk the `lines` range points at. It orders vector
82
+ evidence within a result set; the range it spans depends on the corpus and the embedding
83
+ model, and compresses on small trees, where even a nonsense query has a moderately near
84
+ neighbour somewhere. Compare similarities within a result set rather than against a fixed
85
+ cutoff carried between trees.
81
86
  - Lexical `find` returns 0 rows when nothing matches, so it answers "is this in the tree at
82
87
  all". `--semantic` always returns up to `k` rows — nearest-neighbour search has a nearest
83
88
  neighbour for any input — so absence is a lexical question; `similarity` and the snippet
84
89
  are the evidence for judging whether a vector row is a real hit.
90
+ - Besides SQL strings, a config entry can save a whole `find` invocation:
91
+ `"hot": { "find": "pricing OR billing", "k": 20, "where": "...", "semantic": true }` runs
92
+ as `sense hot` — the scenario's settings ride along with the name, so repeat runs need no
93
+ flags. An invocation-level `--k`, `--where`, or `--semantic` overrides the saved value;
94
+ `--list` marks these entries `(find)`.
85
95
  - `sense check` prepares every saved query (catching syntax and unknown-column errors), runs
86
96
  the ones taking no parameters, and prints row counts: a saved query returning 0 rows looks
87
- the same as a true empty result until something distinguishes them. For queries that
88
- encode invariants (a dead-link list, an unsupported-claims list rows are violations),
89
- `checks: { "<name>": "empty" }` in the config inverts the meaning: `check` fails when the
90
- query returns rows, making it usable as a test suite rather than a linter.
97
+ the same as a true empty result until something distinguishes them. Saved finds are probed
98
+ lexically with k=1 (a bad `where` column or FTS5 syntax fails here, not mid-task); their
99
+ semantic pass is never run by `check`, only checked against `features.embed`. For queries
100
+ that encode invariants (a dead-link list, an unsupported-claims list rows are
101
+ violations), `checks: { "<name>": "empty" }` in the config inverts the meaning: `check`
102
+ fails when the query returns rows, making it usable as a test suite rather than a linter.
91
103
 
92
104
  ## SQL
93
105
 
94
- The verbs are shorthands over the same four tables; anything they don't express, SQL does.
106
+ The commands are shorthands over those tables; anything they don't express, SQL does.
95
107
 
96
108
  ```
97
109
  sense query "SELECT name FROM pragma_table_info('frontmatter')" # what fields exist
@@ -108,7 +120,11 @@ sense query "SELECT j.value, COUNT(*) n FROM frontmatter, json_each(frontmatter.
108
120
  punctuation — bare `customer-facing` errors (`-` reads as a column filter), bare
109
121
  apostrophes are syntax errors: write `"customer-facing"`, `"founder's"`.
110
122
  - Rank with `ORDER BY bm25(content, 10.0, 5.0, 1.0)` (title > summary > body); excerpt with
111
- `snippet(content, -1, '«', '»', '…', 10)`.
123
+ `snippet(content, -1, '«', '»', '…', 10)`. snippet() re-tokenizes each matched doc and its
124
+ cost grows superlinearly with doc size — measured ~10 s per query on a tree holding one
125
+ 1 MB note. `find` bounds this itself (docs past 16 KB get an equivalent excerpt another
126
+ way); in hand-written SQL, guard it: `CASE WHEN length(text) <= 16384 THEN snippet(...)
127
+ END`, or select `title`/`summary` instead of an excerpt.
112
128
  - Select `content.title`/`content.summary` (always exist, empty when absent) rather than
113
129
  `f.title`/`f.summary` (discovered columns — error on trees that never declare them).
114
130
  - Frontmatter values keep their YAML type: strings are TEXT, whole numbers and booleans are
@@ -25,7 +25,7 @@ installing, configuring features, and the design decisions a tree owner faces.
25
25
  | `links` | backlinks, dead-link queries, `find`'s link expansion, `peek`'s link lists | link re-resolution at reconcile | `features.links` (default on) |
26
26
  | `sections` | `peek`'s outline, line-range reads, per-section token estimates | heading extraction at parse | `features.sections` (default on) |
27
27
  | `rank` | `map`'s hubs, `_rank` in any ORDER BY | PageRank pass at reconcile; requires `links` | `features.rank` (default on) |
28
- | `embed` | `find --semantic` (meaning-based expansion) | vectors computed at reconcile; model download to `~/.cache/sensemaking` on first use; ~40 ms model load per semantic invocation | `features.embed` (default off) |
28
+ | `embed` | `find --semantic` (meaning-based expansion) | the first semantic query embeds the whole tree (minutes on tens of thousands of notes, progress on stderr); after that, a model load per invocation (measured in BENCHMARKING.md as `semantic_find_ms` minus `find_ms`) | `features.embed` (default off) |
29
29
 
30
30
  - `embed` accepts `true` (built-in static model) or an object: `model` (Hugging
31
31
  Face id or local path), `type` (`static` pure-JS built-in, or `api` for any
@@ -47,6 +47,12 @@ installing, configuring features, and the design decisions a tree owner faces.
47
47
  - Toggling any feature rebuilds the cache on the next query (safe, automatic).
48
48
  - Disabled features degrade output, visibly: `peek` prints
49
49
  `sections: off (features.sections)` rather than an empty outline.
50
+ - Every query reconciles for itself, so nothing has to be running. On a
51
+ large tree that changes in bulk — a sync, a generated batch — whoever
52
+ queries next pays that re-parse; `sense watch` moves it into the
53
+ background instead. It changes latency, never answers, and the OS
54
+ supervises it rather than the CLI (WATCH.md ships launchd and systemd
55
+ units).
50
56
 
51
57
  ## Tree design decisions
52
58
 
@@ -56,7 +62,10 @@ do, and every consequence is listed so the choice can be made deliberately.
56
62
 
57
63
  - **Frontmatter fields.** Columns are discovered per tree — whatever keys notes
58
64
  declare become queryable. Consistent fields across notes make SQL filters
59
- and named queries possible (`WHERE status = 'active'`). Reserved keys
65
+ and named queries possible (`WHERE status = 'active'`). SQLite's compiled
66
+ column limit (2,000; sqlite.org/limits.html) bounds distinct keys per tree —
67
+ the crawl stops with an error naming the count and the levers, which in
68
+ practice only a generator writing unbounded keys ever hits. Reserved keys
60
69
  (dropped with a warning): `path`, `_mtime`, `_size`, `_rank`, `content`,
61
70
  `links`, `sections`. Values keep their YAML type: strings TEXT, whole numbers
62
71
  and booleans INTEGER (`true` is 1), fractions REAL, lists and maps JSON text;
@@ -83,6 +92,11 @@ do, and every consequence is listed so the choice can be made deliberately.
83
92
  cheap, more links to maintain. Fewer large notes: `sections` and `peek`
84
93
  carry the cost down to line-range reads. Both work; per-section token
85
94
  estimates exist either way.
95
+ - **Recurring questions.** A scenario an agent will run repeatedly can be saved
96
+ with its settings: a SQL string for filters and reports, or a saved find
97
+ (`"hot": { "find": "...", "k": 20, "semantic": true }`) for searches — either
98
+ runs as `sense <name>`, and `sense check` validates both kinds against the
99
+ real tree, so a broken saved scenario fails at check time, not mid-task.
86
100
  - **Where decisions live.** Choices that should outlive one conversation can
87
101
  be recorded in the agent's own instruction or skill files, or in a note in
88
102
  the tree itself; a one-off search over an existing corpus needs none of
@@ -1,143 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "default", {
6
- enumerable: true,
7
- get: function() {
8
- return _default;
9
- }
10
- });
11
- var _dbts = require("../db.js");
12
- var _outputts = require("../output.js");
13
- var _sharedts = require("./shared.js");
14
- function _array_like_to_array(arr, len) {
15
- if (len == null || len > arr.length) len = arr.length;
16
- for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
17
- return arr2;
18
- }
19
- function _array_with_holes(arr) {
20
- if (Array.isArray(arr)) return arr;
21
- }
22
- function _iterable_to_array_limit(arr, i) {
23
- var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
24
- if (_i == null) return;
25
- var _arr = [];
26
- var _n = true;
27
- var _d = false;
28
- var _s, _e;
29
- try {
30
- for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
31
- _arr.push(_s.value);
32
- if (i && _arr.length === i) break;
33
- }
34
- } catch (err) {
35
- _d = true;
36
- _e = err;
37
- } finally{
38
- try {
39
- if (!_n && _i["return"] != null) _i["return"]();
40
- } finally{
41
- if (_d) throw _e;
42
- }
43
- }
44
- return _arr;
45
- }
46
- function _non_iterable_rest() {
47
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
48
- }
49
- function _sliced_to_array(arr, i) {
50
- return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
51
- }
52
- function _unsupported_iterable_to_array(o, minLen) {
53
- if (!o) return;
54
- if (typeof o === "string") return _array_like_to_array(o, minLen);
55
- var n = Object.prototype.toString.call(o).slice(8, -1);
56
- if (n === "Object" && o.constructor) n = o.constructor.name;
57
- if (n === "Map" || n === "Set") return Array.from(n);
58
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
59
- }
60
- // A saved query that returns zero rows looks identical to a true empty result, so a broken
61
- // one can sit unnoticed -- one field report had `WHERE flag = 'true'` against a numeric
62
- // column reading as "nothing tagged yet" for hours. Preparing each query catches syntax and
63
- // unknown-column errors without executing it; queries that take no parameters are also run
64
- // for a row count. Parameterised queries are validated but not counted: inventing arguments
65
- // would report a count for a query nobody ran.
66
- var check = function check(ctx) {
67
- var _cfg_queries;
68
- var cfg = ctx.resolveConfig();
69
- var _open = (0, _dbts.open)(cfg), db = _open.db, warnings = _open.warnings;
70
- (0, _sharedts.printWarnings)(warnings);
71
- var rows = [];
72
- var failed = 0;
73
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
74
- try {
75
- for(var _iterator = Object.entries((_cfg_queries = cfg.queries) !== null && _cfg_queries !== void 0 ? _cfg_queries : {})[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
76
- var _step_value = _sliced_to_array(_step.value, 2), name = _step_value[0], sql = _step_value[1];
77
- var _sql_match;
78
- var _cfg_checks;
79
- var params = ((_sql_match = sql.match(/\?/g)) !== null && _sql_match !== void 0 ? _sql_match : []).length;
80
- var expectEmpty = ((_cfg_checks = cfg.checks) === null || _cfg_checks === void 0 ? void 0 : _cfg_checks[name]) === 'empty';
81
- try {
82
- var statement = db.prepare(sql);
83
- if (params > 0) {
84
- rows.push({
85
- query: name,
86
- params: params,
87
- rows: '—',
88
- status: 'ok (not run: needs parameters)'
89
- });
90
- continue;
91
- }
92
- var count = statement.all().length;
93
- if (expectEmpty) {
94
- // An invariant query: rows are violations, so 0 is the pass, anything else fails.
95
- if (count > 0) failed++;
96
- rows.push({
97
- query: name,
98
- params: params,
99
- rows: count,
100
- status: count === 0 ? 'ok (empty, as asserted)' : "FAILED: expected empty, returned ".concat(count, " row(s)")
101
- });
102
- } else {
103
- rows.push({
104
- query: name,
105
- params: params,
106
- rows: count,
107
- status: count === 0 ? 'ok, but returns 0 rows' : 'ok'
108
- });
109
- }
110
- } catch (err) {
111
- failed++;
112
- rows.push({
113
- query: name,
114
- params: params,
115
- rows: '—',
116
- status: "FAILED: ".concat(err.message)
117
- });
118
- }
119
- }
120
- } catch (err) {
121
- _didIteratorError = true;
122
- _iteratorError = err;
123
- } finally{
124
- try {
125
- if (!_iteratorNormalCompletion && _iterator.return != null) {
126
- _iterator.return();
127
- }
128
- } finally{
129
- if (_didIteratorError) {
130
- throw _iteratorError;
131
- }
132
- }
133
- }
134
- db.close();
135
- if (rows.length === 0) {
136
- console.log('no saved queries in config');
137
- return;
138
- }
139
- (0, _outputts.printRows)(rows, ctx.format);
140
- if (failed > 0) process.exit(1);
141
- };
142
- var _default = check;
143
- /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/check.ts"],"sourcesContent":["import { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// A saved query that returns zero rows looks identical to a true empty result, so a broken\n// one can sit unnoticed -- one field report had `WHERE flag = 'true'` against a numeric\n// column reading as \"nothing tagged yet\" for hours. Preparing each query catches syntax and\n// unknown-column errors without executing it; queries that take no parameters are also run\n// for a row count. Parameterised queries are validated but not counted: inventing arguments\n// would report a count for a query nobody ran.\nconst check: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n\n const rows: Row[] = [];\n let failed = 0;\n for (const [name, sql] of Object.entries(cfg.queries ?? {})) {\n const params = (sql.match(/\\?/g) ?? []).length;\n const expectEmpty = cfg.checks?.[name] === 'empty';\n try {\n const statement = db.prepare(sql);\n if (params > 0) {\n rows.push({ query: name, params, rows: '—', status: 'ok (not run: needs parameters)' });\n continue;\n }\n const count = (statement.all() as unknown[]).length;\n if (expectEmpty) {\n // An invariant query: rows are violations, so 0 is the pass, anything else fails.\n if (count > 0) failed++;\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok (empty, as asserted)' : `FAILED: expected empty, returned ${count} row(s)` });\n } else {\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok, but returns 0 rows' : 'ok' });\n }\n } catch (err) {\n failed++;\n rows.push({ query: name, params, rows: '—', status: `FAILED: ${(err as Error).message}` });\n }\n }\n\n db.close();\n if (rows.length === 0) {\n console.log('no saved queries in config');\n return;\n }\n printRows(rows, ctx.format);\n if (failed > 0) process.exit(1);\n};\nexport default check;\n"],"names":["check","ctx","cfg","resolveConfig","open","db","warnings","printWarnings","rows","failed","Object","entries","queries","name","sql","params","match","length","expectEmpty","checks","statement","prepare","push","query","status","count","all","err","message","close","console","log","printRows","format","process","exit"],"mappings":";;;;+BAkDA;;;eAAA;;;oBAlDqB;wBAEK;wBACI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG9B,2FAA2F;AAC3F,wFAAwF;AACxF,4FAA4F;AAC5F,2FAA2F;AAC3F,4FAA4F;AAC5F,+CAA+C;AAC/C,IAAMA,QAAiB,eAACC;QAOmBC;IANzC,IAAMA,MAAMD,IAAIE,aAAa;IAC7B,IAAyBC,QAAAA,IAAAA,UAAI,EAACF,MAAtBG,KAAiBD,MAAjBC,IAAIC,WAAaF,MAAbE;IACZC,IAAAA,uBAAa,EAACD;IAEd,IAAME,OAAc,EAAE;IACtB,IAAIC,SAAS;QACR,kCAAA,2BAAA;;QAAL,QAAK,YAAqBC,OAAOC,OAAO,EAACT,eAAAA,IAAIU,OAAO,cAAXV,0BAAAA,eAAe,CAAC,uBAApD,SAAA,6BAAA,QAAA,yBAAA,iCAAwD;YAAxD,mCAAA,iBAAOW,uBAAMC;gBACAA;gBACIZ;YADpB,IAAMa,SAAS,EAACD,aAAAA,IAAIE,KAAK,CAAC,oBAAVF,wBAAAA,aAAoB,EAAE,EAAEG,MAAM;YAC9C,IAAMC,cAAchB,EAAAA,cAAAA,IAAIiB,MAAM,cAAVjB,kCAAAA,WAAY,CAACW,KAAK,MAAK;YAC3C,IAAI;gBACF,IAAMO,YAAYf,GAAGgB,OAAO,CAACP;gBAC7B,IAAIC,SAAS,GAAG;oBACdP,KAAKc,IAAI,CAAC;wBAAEC,OAAOV;wBAAME,QAAAA;wBAAQP,MAAM;wBAAKgB,QAAQ;oBAAiC;oBACrF;gBACF;gBACA,IAAMC,QAAQ,AAACL,UAAUM,GAAG,GAAiBT,MAAM;gBACnD,IAAIC,aAAa;oBACf,kFAAkF;oBAClF,IAAIO,QAAQ,GAAGhB;oBACfD,KAAKc,IAAI,CAAC;wBAAEC,OAAOV;wBAAME,QAAAA;wBAAQP,MAAMiB;wBAAOD,QAAQC,UAAU,IAAI,4BAA4B,AAAC,oCAAyC,OAANA,OAAM;oBAAS;gBACrJ,OAAO;oBACLjB,KAAKc,IAAI,CAAC;wBAAEC,OAAOV;wBAAME,QAAAA;wBAAQP,MAAMiB;wBAAOD,QAAQC,UAAU,IAAI,2BAA2B;oBAAK;gBACtG;YACF,EAAE,OAAOE,KAAK;gBACZlB;gBACAD,KAAKc,IAAI,CAAC;oBAAEC,OAAOV;oBAAME,QAAAA;oBAAQP,MAAM;oBAAKgB,QAAQ,AAAC,WAAiC,OAAvB,AAACG,IAAcC,OAAO;gBAAG;YAC1F;QACF;;QArBK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAuBLvB,GAAGwB,KAAK;IACR,IAAIrB,KAAKS,MAAM,KAAK,GAAG;QACrBa,QAAQC,GAAG,CAAC;QACZ;IACF;IACAC,IAAAA,mBAAS,EAACxB,MAAMP,IAAIgC,MAAM;IAC1B,IAAIxB,SAAS,GAAGyB,QAAQC,IAAI,CAAC;AAC/B;IACA,WAAenC"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/find.ts"],"sourcesContent":["import { printRows } from '../output.ts';\nimport { find } from '../verbs.ts';\nimport { withDb } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst findCmd: Command = async (ctx) => {\n const [terms] = ctx.rest;\n if (!terms) ctx.usageError(`usage: ${ctx.name} find \"<terms>\" [--where \"<sql>\"] [--k n] [--semantic]`);\n const k = ctx.values.k === undefined ? undefined : Number.parseInt(ctx.values.k, 10);\n if (ctx.values.k !== undefined && !Number.isInteger(k)) ctx.usageError(`--k expects an integer, got \"${ctx.values.k}\"`);\n await withDb(ctx, async (db, cfg) => printRows(await find(db, cfg, terms, { k, where: ctx.values.where, semantic: ctx.values.semantic }), ctx.format));\n};\nexport default findCmd;\n"],"names":["findCmd","ctx","terms","k","rest","usageError","name","values","undefined","Number","parseInt","isInteger","withDb","db","cfg","find","where","semantic","printRows","format"],"mappings":";;;;+BAYA;;;eAAA;;;wBAZ0B;uBACL;wBACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGvB,IAAMA,UAAmB,iBAAOC;;YACdA,WAATC,OAEDC;;;;oBAFUF,6BAAAA,IAAIG,IAAI,MAAjBF,QAASD;oBAChB,IAAI,CAACC,OAAOD,IAAII,UAAU,CAAC,AAAC,UAAkB,OAATJ,IAAIK,IAAI,EAAC;oBACxCH,IAAIF,IAAIM,MAAM,CAACJ,CAAC,KAAKK,YAAYA,YAAYC,OAAOC,QAAQ,CAACT,IAAIM,MAAM,CAACJ,CAAC,EAAE;oBACjF,IAAIF,IAAIM,MAAM,CAACJ,CAAC,KAAKK,aAAa,CAACC,OAAOE,SAAS,CAACR,IAAIF,IAAII,UAAU,CAAC,AAAC,gCAA4C,OAAbJ,IAAIM,MAAM,CAACJ,CAAC,EAAC;oBACpH;;wBAAMS,IAAAA,gBAAM,EAACX,KAAK,SAAOY,IAAIC;;;;;4CAAkB;;gDAAMC,IAAAA,aAAI,EAACF,IAAIC,KAAKZ,OAAO;oDAAEC,GAAAA;oDAAGa,OAAOf,IAAIM,MAAM,CAACS,KAAK;oDAAEC,UAAUhB,IAAIM,MAAM,CAACU,QAAQ;gDAAC;;;;;gDAAjGC,mBAAS;oDAAC;oDAA2FjB,IAAIkB,MAAM;;;;;;;;;oBAApJ;;;;;;IACF;;IACA,WAAenB"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/index.ts"],"sourcesContent":["import type { Command } from './types.ts';\n\n// Lazy registry: a command's imports load only when it runs, so a heavy dependency in one\n// command never taxes the others (or --version/--help). These names are reserved: a named\n// query in config can never shadow them.\nexport const COMMANDS: Record<string, () => Promise<{ default: Command }>> = {\n init: () => import('./init.ts'),\n watch: () => import('./watch.ts'),\n status: () => import('./status.ts'),\n check: () => import('./check.ts'),\n rebuild: () => import('./rebuild.ts'),\n query: () => import('./query.ts'),\n find: () => import('./find.ts'),\n map: () => import('./map.ts'),\n peek: () => import('./peek.ts'),\n};\n\nexport type { Command, Ctx } from './types.ts';\n"],"names":["COMMANDS","init","watch","status","check","rebuild","query","find","map","peek"],"mappings":";;;;+BAKaA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAN,IAAMA,WAAgE;IAC3EC,MAAM,SAANA;eAAY;2DAAA,QAAO;;;IACnBC,OAAO,SAAPA;eAAa;2DAAA,QAAO;;;IACpBC,QAAQ,SAARA;eAAc;2DAAA,QAAO;;;IACrBC,OAAO,SAAPA;eAAa;2DAAA,QAAO;;;IACpBC,SAAS,SAATA;eAAe;2DAAA,QAAO;;;IACtBC,OAAO,SAAPA;eAAa;2DAAA,QAAO;;;IACpBC,MAAM,SAANA;eAAY;2DAAA,QAAO;;;IACnBC,KAAK,SAALA;eAAW;2DAAA,QAAO;;;IAClBC,MAAM,SAANA;eAAY;2DAAA,QAAO;;;AACrB"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/init.ts"],"sourcesContent":["import { initConfig } from '../config.ts';\nimport type { Command } from './types.ts';\n\nconst init: Command = (ctx) => {\n const configPath = initConfig(process.cwd());\n console.log(`created ${configPath}`);\n console.log(`query away: ${ctx.name} query \"SELECT path FROM frontmatter LIMIT 10\"`);\n};\nexport default init;\n"],"names":["init","ctx","configPath","initConfig","process","cwd","console","log","name"],"mappings":";;;;+BAQA;;;eAAA;;;wBAR2B;AAG3B,IAAMA,OAAgB,cAACC;IACrB,IAAMC,aAAaC,IAAAA,oBAAU,EAACC,QAAQC,GAAG;IACzCC,QAAQC,GAAG,CAAC,AAAC,WAAqB,OAAXL;IACvBI,QAAQC,GAAG,CAAC,AAAC,eAAuB,OAATN,IAAIO,IAAI,EAAC;AACtC;IACA,WAAeR"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/map.ts"],"sourcesContent":["import { renderMap } from '../output.ts';\nimport { mapTree } from '../verbs.ts';\nimport { withDb } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst map: Command = (ctx) =>\n withDb(ctx, (db, cfg) => {\n const result = mapTree(db, cfg);\n console.log(ctx.format === 'json' ? JSON.stringify(result, null, 2) : renderMap(result));\n });\nexport default map;\n"],"names":["map","ctx","withDb","db","cfg","result","mapTree","console","log","format","JSON","stringify","renderMap"],"mappings":";;;;+BAUA;;;eAAA;;;wBAV0B;uBACF;wBACD;AAGvB,IAAMA,MAAe,aAACC;WACpBC,IAAAA,gBAAM,EAACD,KAAK,SAACE,IAAIC;QACf,IAAMC,SAASC,IAAAA,gBAAO,EAACH,IAAIC;QAC3BG,QAAQC,GAAG,CAACP,IAAIQ,MAAM,KAAK,SAASC,KAAKC,SAAS,CAACN,QAAQ,MAAM,KAAKO,IAAAA,mBAAS,EAACP;IAClF;;IACF,WAAeL"}
@@ -1,23 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, // Fallback when the first positional is not a command: a named query from config.
6
- "default", {
7
- enumerable: true,
8
- get: function() {
9
- return named;
10
- }
11
- });
12
- var _sharedts = require("./shared.js");
13
- function named(ctx, queryName, params) {
14
- var cfg = ctx.resolveConfig();
15
- var sql = cfg.queries[queryName];
16
- if (!sql) {
17
- console.error('unknown query: "'.concat(queryName, '"'));
18
- console.error("valid queries: ".concat(Object.keys(cfg.queries).sort().join(', ')));
19
- process.exit(2);
20
- }
21
- (0, _sharedts.runSql)(cfg, sql, params, ctx.format, 'query "'.concat(queryName, '"'));
22
- }
23
- /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/named.ts"],"sourcesContent":["import { runSql } from './shared.ts';\nimport type { Ctx } from './types.ts';\n\n// Fallback when the first positional is not a command: a named query from config.\nexport default function named(ctx: Ctx, queryName: string, params: string[]): void {\n const cfg = ctx.resolveConfig();\n const sql = cfg.queries[queryName];\n if (!sql) {\n console.error(`unknown query: \"${queryName}\"`);\n console.error(`valid queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(2);\n }\n runSql(cfg, sql, params, ctx.format, `query \"${queryName}\"`);\n}\n"],"names":["named","ctx","queryName","params","cfg","resolveConfig","sql","queries","console","error","Object","keys","sort","join","process","exit","runSql","format"],"mappings":";;;;+BAGA,kFAAkF;AAClF;;;eAAwBA;;;wBAJD;AAIR,SAASA,MAAMC,GAAQ,EAAEC,SAAiB,EAAEC,MAAgB;IACzE,IAAMC,MAAMH,IAAII,aAAa;IAC7B,IAAMC,MAAMF,IAAIG,OAAO,CAACL,UAAU;IAClC,IAAI,CAACI,KAAK;QACRE,QAAQC,KAAK,CAAC,AAAC,mBAA4B,OAAVP,WAAU;QAC3CM,QAAQC,KAAK,CAAC,AAAC,kBAA4D,OAA3CC,OAAOC,IAAI,CAACP,IAAIG,OAAO,EAAEK,IAAI,GAAGC,IAAI,CAAC;QACrEC,QAAQC,IAAI,CAAC;IACf;IACAC,IAAAA,gBAAM,EAACZ,KAAKE,KAAKH,QAAQF,IAAIgB,MAAM,EAAE,AAAC,UAAmB,OAAVf,WAAU;AAC3D"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/peek.ts"],"sourcesContent":["import { renderPeek } from '../output.ts';\nimport { peek } from '../verbs.ts';\nimport { withDb } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst peekCmd: Command = (ctx) => {\n const [pathArg] = ctx.rest;\n if (!pathArg) ctx.usageError(`usage: ${ctx.name} peek <path>`);\n return withDb(ctx, (db, cfg) => {\n const result = peek(db, cfg, pathArg);\n console.log(ctx.format === 'json' ? JSON.stringify(result, null, 2) : renderPeek(result));\n });\n};\nexport default peekCmd;\n"],"names":["peekCmd","ctx","rest","pathArg","usageError","name","withDb","db","cfg","result","peek","console","log","format","JSON","stringify","renderPeek"],"mappings":";;;;+BAaA;;;eAAA;;;wBAb2B;uBACN;wBACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGvB,IAAMA,UAAmB,iBAACC;IACxB,IAAkBA,6BAAAA,IAAIC,IAAI,MAAnBC,UAAWF;IAClB,IAAI,CAACE,SAASF,IAAIG,UAAU,CAAC,AAAC,UAAkB,OAATH,IAAII,IAAI,EAAC;IAChD,OAAOC,IAAAA,gBAAM,EAACL,KAAK,SAACM,IAAIC;QACtB,IAAMC,SAASC,IAAAA,aAAI,EAACH,IAAIC,KAAKL;QAC7BQ,QAAQC,GAAG,CAACX,IAAIY,MAAM,KAAK,SAASC,KAAKC,SAAS,CAACN,QAAQ,MAAM,KAAKO,IAAAA,oBAAU,EAACP;IACnF;AACF;IACA,WAAeT"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/query.ts"],"sourcesContent":["import { runSql } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst query: Command = (ctx) => {\n const [sql, ...params] = ctx.rest;\n if (!sql) ctx.usageError(`usage: ${ctx.name} query \"<sql>\" [params...]`);\n runSql(ctx.resolveConfig(), sql, params, ctx.format, 'ad-hoc query');\n};\nexport default query;\n"],"names":["query","ctx","rest","sql","params","usageError","name","runSql","resolveConfig","format"],"mappings":";;;;+BAQA;;;eAAA;;;wBARuB;;;;;;;;;;;;;;;;;;;;;;;;;;AAGvB,IAAMA,QAAiB,eAACC;IACtB,IAAyBA,sBAAAA,IAAIC,IAAI,GAA1BC,MAAkBF,cAAb,AAAGG,SAAUH,gBAAb;IACZ,IAAI,CAACE,KAAKF,IAAII,UAAU,CAAC,AAAC,UAAkB,OAATJ,IAAIK,IAAI,EAAC;IAC5CC,IAAAA,gBAAM,EAACN,IAAIO,aAAa,IAAIL,KAAKC,QAAQH,IAAIQ,MAAM,EAAE;AACvD;IACA,WAAeT"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/rebuild.ts"],"sourcesContent":["import { docCount, rebuild } from '../db.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst rebuildCmd: Command = (ctx) => {\n const result = rebuild(ctx.resolveConfig());\n printWarnings(result.warnings);\n console.log(`rebuilt: ${docCount(result.db)} docs`);\n result.db.close();\n};\nexport default rebuildCmd;\n"],"names":["rebuildCmd","ctx","result","rebuild","resolveConfig","printWarnings","warnings","console","log","docCount","db","close"],"mappings":";;;;+BAUA;;;eAAA;;;oBAVkC;wBACJ;AAG9B,IAAMA,aAAsB,oBAACC;IAC3B,IAAMC,SAASC,IAAAA,aAAO,EAACF,IAAIG,aAAa;IACxCC,IAAAA,uBAAa,EAACH,OAAOI,QAAQ;IAC7BC,QAAQC,GAAG,CAAC,AAAC,YAA+B,OAApBC,IAAAA,cAAQ,EAACP,OAAOQ,EAAE,GAAE;IAC5CR,OAAOQ,EAAE,CAACC,KAAK;AACjB;IACA,WAAeX"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/shared.ts"],"sourcesContent":["import type { ResolvedConfig } from '../config.ts';\nimport type { OpenResult } from '../db.ts';\nimport { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { searchError } from '../search-error.ts';\nimport type { Ctx } from './types.ts';\n\n// Shared open-query-close envelope for commands that touch the tree.\n\nexport function printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\nexport async function withDb(ctx: Ctx, fn: (db: OpenResult['db'], cfg: ResolvedConfig) => void | Promise<void>): Promise<void> {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n try {\n await fn(db, cfg);\n } finally {\n db.close();\n }\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nexport function runSql(cfg: ResolvedConfig, sql: string, params: string[], format: 'table' | 'json', label: string): void {\n const placeholderCount = (sql.match(/\\?/g) ?? []).length;\n if (params.length !== placeholderCount) {\n console.error(`${label} expects ${placeholderCount} parameter(s), got ${params.length}`);\n process.exit(2);\n }\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n let rows: Row[];\n try {\n rows = db.prepare(sql).all(...params) as Row[];\n } catch (err) {\n db.close();\n // A saved query or ad-hoc SQL can carry `content MATCH ?` too, so the same FTS5\n // punctuation trap applies -- the bound parameters are the search terms there. SQL\n // without MATCH gets its error verbatim; search advice on a plain typo would mislead.\n if (/\\bMATCH\\b/i.test(sql)) throw searchError(err as Error, params.join(' '));\n throw err;\n }\n printRows(rows, format);\n db.close();\n}\n"],"names":["printWarnings","runSql","withDb","warnings","w","console","warn","ctx","fn","cfg","open","db","resolveConfig","close","sql","params","format","label","placeholderCount","match","length","error","process","exit","rows","prepare","all","err","test","searchError","join","printRows"],"mappings":";;;;;;;;;;;QAUgBA;eAAAA;;QAgBAC;eAAAA;;QAZMC;eAAAA;;;oBAZD;wBAEK;6BACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKrB,SAASF,cAAcG,QAAkB;QACzC,kCAAA,2BAAA;;QAAL,QAAK,YAAWA,6BAAX,SAAA,6BAAA,QAAA,yBAAA;YAAA,IAAMC,IAAN;YAAqBC,QAAQC,IAAI,CAACF;;;QAAlC;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;AACP;AAEO,SAAeF,OAAOK,GAAQ,EAAEC,EAAuE;;YACtGC,KACmBC,OAAjBC,IAAIR;;;;oBADNM,MAAMF,IAAIK,aAAa;oBACJF,QAAAA,IAAAA,UAAI,EAACD,MAAtBE,KAAiBD,MAAjBC,IAAIR,WAAaO,MAAbP;oBACZH,cAAcG;;;;;;;;;oBAEZ;;wBAAMK,GAAGG,IAAIF;;;oBAAb;;;;;;oBAEAE,GAAGE,KAAK;;;;;;;;;;IAEZ;;AAGO,SAASZ,OAAOQ,GAAmB,EAAEK,GAAW,EAAEC,MAAgB,EAAEC,MAAwB,EAAEC,KAAa;QACtFH;IAA1B,IAAMI,mBAAmB,EAACJ,aAAAA,IAAIK,KAAK,CAAC,oBAAVL,wBAAAA,aAAoB,EAAE,EAAEM,MAAM;IACxD,IAAIL,OAAOK,MAAM,KAAKF,kBAAkB;QACtCb,QAAQgB,KAAK,CAAC,AAAC,GAAmBH,OAAjBD,OAAM,aAAiDF,OAAtCG,kBAAiB,uBAAmC,OAAdH,OAAOK,MAAM;QACrFE,QAAQC,IAAI,CAAC;IACf;IACA,IAAyBb,QAAAA,IAAAA,UAAI,EAACD,MAAtBE,KAAiBD,MAAjBC,IAAIR,WAAaO,MAAbP;IACZH,cAAcG;IACd,IAAIqB;IACJ,IAAI;YACKb;QAAPa,OAAOb,CAAAA,cAAAA,GAAGc,OAAO,CAACX,MAAKY,GAAG,OAAnBf,aAAoB,qBAAGI;IAChC,EAAE,OAAOY,KAAK;QACZhB,GAAGE,KAAK;QACR,gFAAgF;QAChF,mFAAmF;QACnF,sFAAsF;QACtF,IAAI,aAAae,IAAI,CAACd,MAAM,MAAMe,IAAAA,0BAAW,EAACF,KAAcZ,OAAOe,IAAI,CAAC;QACxE,MAAMH;IACR;IACAI,IAAAA,mBAAS,EAACP,MAAMR;IAChBL,GAAGE,KAAK;AACV"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/status.ts"],"sourcesContent":["import { embedConfig, featureStates } from '../config.ts';\nimport { docCount, getMeta, open } from '../db.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// Vectors are computed on the first semantic query, not at reconcile, so a tree can be\n// embed-enabled with nothing embedded yet -- reported here because a fast `rebuild` on an\n// embed tree otherwise reads as \"the feature isn't on\".\nfunction vectorState(db: ReturnType<typeof open>['db']): { embedded: number; pending: number } | null {\n try {\n const row = db.prepare('SELECT COUNT(vector) AS embedded, COUNT(*) - COUNT(vector) AS pending FROM embeddings').get() as { embedded: number; pending: number };\n return row;\n } catch {\n return null;\n }\n}\n\nconst status: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, dbPath, warnings } = open(cfg);\n printWarnings(warnings);\n\n const features = featureStates(cfg);\n const e = embedConfig(cfg);\n const vectors = e ? vectorState(db) : null;\n const heartbeat = getMeta(db, 'watch_heartbeat');\n const result = {\n db: dbPath,\n docs: docCount(db),\n features: features.on,\n featuresOff: features.off,\n embed: e ? { type: e.type, model: e.model, ...(vectors ?? {}) } : null,\n findDefaultWhere: cfg.defaults?.find?.where ?? null,\n watcherHeartbeatSecondsAgo: heartbeat ? Math.round((Date.now() - Date.parse(heartbeat)) / 1000) : null,\n };\n\n if (ctx.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(`db: ${result.db}`);\n console.log(`docs: ${result.docs}`);\n const off = features.off.length > 0 ? ` · off: ${features.off.map((name) => `${name} (features.${name})`).join(', ')}` : '';\n console.log(`features: ${features.on.join(', ')}${off}`);\n if (e) console.log(`embed: ${e.type} ${e.model}${vectors ? ` (vectors: ${vectors.embedded} embedded, ${vectors.pending} pending — embedded on first --semantic query)` : ''}`);\n if (result.findDefaultWhere) console.log(`find default scope: ${result.findDefaultWhere} (--where replaces it)`);\n console.log(result.watcherHeartbeatSecondsAgo === null ? 'watcher: no watcher' : `watcher: last heartbeat ${result.watcherHeartbeatSecondsAgo}s ago`);\n }\n db.close();\n};\nexport default status;\n"],"names":["vectorState","db","row","prepare","get","status","ctx","cfg","resolveConfig","open","dbPath","warnings","printWarnings","features","featureStates","e","embedConfig","vectors","heartbeat","getMeta","result","docs","docCount","on","featuresOff","off","embed","type","model","findDefaultWhere","defaults","find","where","watcherHeartbeatSecondsAgo","Math","round","Date","now","parse","format","console","log","JSON","stringify","length","map","name","join","embedded","pending","close"],"mappings":";;;;+BAiDA;;;eAAA;;;wBAjD2C;oBACH;wBACV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG9B,uFAAuF;AACvF,0FAA0F;AAC1F,wDAAwD;AACxD,SAASA,YAAYC,EAAiC;IACpD,IAAI;QACF,IAAMC,MAAMD,GAAGE,OAAO,CAAC,yFAAyFC,GAAG;QACnH,OAAOF;IACT,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,IAAMG,SAAkB,gBAACC;;QAeHC,oBAAAA;IAdpB,IAAMA,MAAMD,IAAIE,aAAa;IAC7B,IAAiCC,QAAAA,IAAAA,UAAI,EAACF,MAA9BN,KAAyBQ,MAAzBR,IAAIS,SAAqBD,MAArBC,QAAQC,WAAaF,MAAbE;IACpBC,IAAAA,uBAAa,EAACD;IAEd,IAAME,WAAWC,IAAAA,uBAAa,EAACP;IAC/B,IAAMQ,IAAIC,IAAAA,qBAAW,EAACT;IACtB,IAAMU,UAAUF,IAAIf,YAAYC,MAAM;IACtC,IAAMiB,YAAYC,IAAAA,aAAO,EAAClB,IAAI;IAC9B,IAAMmB,SAAS;QACbnB,IAAIS;QACJW,MAAMC,IAAAA,cAAQ,EAACrB;QACfY,UAAUA,SAASU,EAAE;QACrBC,aAAaX,SAASY,GAAG;QACzBC,OAAOX,IAAI;YAAEY,MAAMZ,EAAEY,IAAI;YAAEC,OAAOb,EAAEa,KAAK;WAAMX,oBAAAA,qBAAAA,UAAW,CAAC,KAAO;QAClEY,gBAAgB,WAAEtB,gBAAAA,IAAIuB,QAAQ,cAAZvB,qCAAAA,qBAAAA,cAAcwB,IAAI,cAAlBxB,yCAAAA,mBAAoByB,KAAK,uCAAI;QAC/CC,4BAA4Bf,YAAYgB,KAAKC,KAAK,CAAC,AAACC,CAAAA,KAAKC,GAAG,KAAKD,KAAKE,KAAK,CAACpB,UAAS,IAAK,QAAQ;IACpG;IAEA,IAAIZ,IAAIiC,MAAM,KAAK,QAAQ;QACzBC,QAAQC,GAAG,CAACC,KAAKC,SAAS,CAACvB,QAAQ,MAAM;IAC3C,OAAO;QACLoB,QAAQC,GAAG,CAAC,AAAC,OAAgB,OAAVrB,OAAOnB,EAAE;QAC5BuC,QAAQC,GAAG,CAAC,AAAC,SAAoB,OAAZrB,OAAOC,IAAI;QAChC,IAAMI,MAAMZ,SAASY,GAAG,CAACmB,MAAM,GAAG,IAAI,AAAC,cAA8E,OAApE/B,SAASY,GAAG,CAACoB,GAAG,CAAC,SAACC;mBAAS,AAAC,GAAoBA,OAAlBA,MAAK,eAAkB,OAALA,MAAK;WAAIC,IAAI,CAAC,SAAU;QACzHP,QAAQC,GAAG,CAAC,AAAC,aAAqChB,OAAzBZ,SAASU,EAAE,CAACwB,IAAI,CAAC,OAAY,OAAJtB;QAClD,IAAIV,GAAGyB,QAAQC,GAAG,CAAC,AAAC,UAAmB1B,OAAVA,EAAEY,IAAI,EAAC,KAAaV,OAAVF,EAAEa,KAAK,EAA8H,OAA3HX,UAAU,AAAC,cAA2CA,OAA9BA,QAAQ+B,QAAQ,EAAC,eAA6B,OAAhB/B,QAAQgC,OAAO,EAAC,oDAAkD;QACzK,IAAI7B,OAAOS,gBAAgB,EAAEW,QAAQC,GAAG,CAAC,AAAC,uBAA8C,OAAxBrB,OAAOS,gBAAgB,EAAC;QACxFW,QAAQC,GAAG,CAACrB,OAAOa,0BAA0B,KAAK,OAAO,wBAAwB,AAAC,2BAA4D,OAAlCb,OAAOa,0BAA0B,EAAC;IAChJ;IACAhC,GAAGiD,KAAK;AACV;IACA,WAAe7C"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/watch.ts"],"sourcesContent":["import type { WatchEvent } from '../watch.ts';\nimport { runWatch } from '../watch.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\nconst watch: Command = async (ctx) => {\n await runWatch(ctx.resolveConfig(), {\n force: ctx.values.force,\n onEvent: (event: WatchEvent) => {\n if (event.type === 'started') {\n console.log(`${ctx.name} watch: watching ${event.baseDir}`);\n console.log(`${ctx.name} watch: db ${event.dbPath}`);\n } else if (event.type === 'reconciled') {\n printWarnings(event.warnings);\n if (event.parsed > 0) console.log(`${ctx.name} watch: reconciled, ${event.parsed} file(s) reparsed (${event.total} total)`);\n } else {\n console.error(`${ctx.name} watch: reconcile error: ${event.message}`);\n }\n },\n });\n};\nexport default watch;\n"],"names":["watch","ctx","runWatch","resolveConfig","force","values","onEvent","event","type","console","log","name","baseDir","dbPath","printWarnings","warnings","parsed","total","error","message"],"mappings":";;;;+BAqBA;;;eAAA;;;uBApByB;wBACK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG9B,IAAMA,QAAiB,eAAOC;;;;;oBAC5B;;wBAAMC,IAAAA,iBAAQ,EAACD,IAAIE,aAAa,IAAI;4BAClCC,OAAOH,IAAII,MAAM,CAACD,KAAK;4BACvBE,SAAS,SAATA,QAAUC;gCACR,IAAIA,MAAMC,IAAI,KAAK,WAAW;oCAC5BC,QAAQC,GAAG,CAAC,AAAC,GAA8BH,OAA5BN,IAAIU,IAAI,EAAC,qBAAiC,OAAdJ,MAAMK,OAAO;oCACxDH,QAAQC,GAAG,CAAC,AAAC,GAAwBH,OAAtBN,IAAIU,IAAI,EAAC,eAA0B,OAAbJ,MAAMM,MAAM;gCACnD,OAAO,IAAIN,MAAMC,IAAI,KAAK,cAAc;oCACtCM,IAAAA,uBAAa,EAACP,MAAMQ,QAAQ;oCAC5B,IAAIR,MAAMS,MAAM,GAAG,GAAGP,QAAQC,GAAG,CAAC,AAAC,GAAiCH,OAA/BN,IAAIU,IAAI,EAAC,wBAAwDJ,OAAlCA,MAAMS,MAAM,EAAC,uBAAiC,OAAZT,MAAMU,KAAK,EAAC;gCACpH,OAAO;oCACLR,QAAQS,KAAK,CAAC,AAAC,GAAsCX,OAApCN,IAAIU,IAAI,EAAC,6BAAyC,OAAdJ,MAAMY,OAAO;gCACpE;4BACF;wBACF;;;oBAbA;;;;;;IAcF;;IACA,WAAenB"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, FeatureName } from './config.ts';\nimport { featureEnabled, featureStates } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { semanticCandidates } from './features/embed.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\nimport { searchError } from './search-error.ts';\n\n// The three layer verbs: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n semantic?: boolean; // invoke vector expansion (requires features.embed); rows gain via 'vector' and a lines column\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\n// Semantic expansion is per-query opt-in: without opts.semantic the result is\n// byte-for-byte independent of the embed feature.\nexport async function find(db: DatabaseSync, cfg: Config, terms: string, opts: FindOptions = {}): Promise<Row[]> {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n // --where applies inside the candidate query (a post-filter over the top-N would drop\n // matches ranked past the pool) and again on the final select for link-derived rows.\n // An explicit --where replaces the tree's declared default rather than ANDing with it,\n // so a caller can always widen back to the whole tree.\n const scope = opts.where ?? cfg.defaults?.find?.where;\n const whereJoin = scope ? `JOIN frontmatter f ON f.\"path\" = content.path` : '';\n const whereCond = scope ? `AND (${scope})` : '';\n const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n let matchRows: Array<{ path: string; hit: string }>;\n try {\n matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\n } catch (err) {\n throw searchError(err as Error, terms, scope);\n }\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n const edges = featureEnabled(cfg, 'links') && matchRows.length > 0 ? linkEdges(db) : [];\n if (edges.length > 0) {\n // `linked` gates the label only, not the score: PPR restart mass gives every seed a\n // nonzero rank even without an incident edge, which is not link evidence — but dropping\n // that mass from the score list reweights fusion toward connectivity and measurably\n // wrecks ranking on link-dense corpora (FEVER hit@10 0.997 -> 0.907; fusion-tuning.md).\n const linked = new Set(edges.flat());\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, edges, seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n if (linked.has(path)) existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n // Vector expansion, invoked only: a third RRF list at the swept flat-region constants\n // (weight 1, pool = fetch). Each row carries its best chunk's line range.\n const chunkLines = new Map<string, string>();\n const chunkSimilarity = new Map<string, number>();\n if (opts.semantic) {\n const vec = await semanticCandidates(db, cfg, terms, fetch);\n vec.forEach(({ path, lines, similarity }, i) => {\n chunkLines.set(path, lines);\n chunkSimilarity.set(path, similarity);\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = `${existing.via}+vector`;\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'vector' });\n }\n });\n }\n\n db.exec('DROP TABLE IF EXISTS _find');\n db.exec('CREATE TEMP TABLE _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT, lines TEXT, similarity REAL)');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit, lines, similarity) VALUES (?, ?, ?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null, chunkLines.get(path) ?? null, chunkSimilarity.get(path) ?? null);\n\n const where = scope ? `WHERE ${scope}` : '';\n const linesCol = opts.semantic ? ', _find.lines, _find.similarity' : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score${linesCol}\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n features: { on: FeatureName[]; off: FeatureName[] };\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n const allFields = columns\n .map((name) => {\n const quoted = `\"${name.split('\"').join('\"\"')}\"`;\n // Observed storage class, not a declared one: these columns are added dynamically and\n // SQLite types per value, so a field can be text in most notes and numeric in a few.\n // Listing every distinct type makes both the type and any drift visible.\n const { n, types } = db.prepare(`SELECT COUNT(${quoted}) AS n, GROUP_CONCAT(DISTINCT typeof(${quoted})) AS types FROM frontmatter WHERE ${quoted} IS NOT NULL`).get() as { n: number; types: string | null };\n return { field: name, coverage: n, type: types ?? '' };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, features: featureStates(cfg), hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n off: FeatureName[]; // disabled features whose blocks are omitted (not empty)\n}\n\nconst PEEK_LINK_LIMIT = 20;\n\n// Layer 2: 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(db: DatabaseSync, cfg: Config, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\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 const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE 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 = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LINK_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n off: (['sections', 'links'] as FeatureName[]).filter((name) => !featureEnabled(cfg, name)),\n };\n}\n"],"names":["find","mapTree","peek","WEIGHTED_BM25","RRF_K","db","cfg","terms","opts","hits","chunkLines","chunkSimilarity","k","fetch","scope","whereJoin","whereCond","matchSql","matchRows","candidates","edges","linked","nodes","seeds","ranked","vec","insert","path","c","where","linesCol","Math","max","defaults","prepare","all","err","searchError","Map","map","r","hit","forEach","i","set","score","via","featureEnabled","length","linkEdges","Set","flat","personalizedRank","filter","sort","a","b","slice","existing","get","has","semantic","semanticCandidates","lines","similarity","exec","run","INTERNAL_COLUMNS","docs","columns","name","allFields","quoted","split","join","n","types","field","coverage","type","fields","hubs","recent","fieldsTotal","features","featureStates","PEEK_LINK_LIMIT","pathArg","row","paths","p","base","posix","basename","replace","toLowerCase","matches","SenseError","frontmatter","Object","entries","key","value","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal","off"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/commands/check.ts"],"sourcesContent":["import { open } from '../db.ts';\nimport type { Row } from '../output.ts';\nimport { printRows } from '../output.ts';\nimport { printWarnings } from './shared.ts';\nimport type { Command } from './types.ts';\n\n// A saved query that returns zero rows looks identical to a true empty result, so a broken\n// one can sit unnoticed -- one field report had `WHERE flag = 'true'` against a numeric\n// column reading as \"nothing tagged yet\" for hours. Preparing each query catches syntax and\n// unknown-column errors without executing it; queries that take no parameters are also run\n// for a row count. Parameterised queries are validated but not counted: inventing arguments\n// would report a count for a query nobody ran.\nconst check: Command = (ctx) => {\n const cfg = ctx.resolveConfig();\n const { db, warnings } = open(cfg);\n printWarnings(warnings);\n\n const rows: Row[] = [];\n let failed = 0;\n for (const [name, sql] of Object.entries(cfg.queries ?? {})) {\n const params = (sql.match(/\\?/g) ?? []).length;\n const expectEmpty = cfg.checks?.[name] === 'empty';\n try {\n const statement = db.prepare(sql);\n if (params > 0) {\n rows.push({ query: name, params, rows: '—', status: 'ok (not run: needs parameters)' });\n continue;\n }\n const count = (statement.all() as unknown[]).length;\n if (expectEmpty) {\n // An invariant query: rows are violations, so 0 is the pass, anything else fails.\n if (count > 0) failed++;\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok (empty, as asserted)' : `FAILED: expected empty, returned ${count} row(s)` });\n } else {\n rows.push({ query: name, params, rows: count, status: count === 0 ? 'ok, but returns 0 rows' : 'ok' });\n }\n } catch (err) {\n failed++;\n rows.push({ query: name, params, rows: '—', status: `FAILED: ${(err as Error).message}` });\n }\n }\n\n db.close();\n if (rows.length === 0) {\n console.log('no saved queries in config');\n return;\n }\n printRows(rows, ctx.format);\n if (failed > 0) process.exit(1);\n};\nexport default check;\n"],"names":["open","printRows","printWarnings","check","ctx","cfg","resolveConfig","db","warnings","rows","failed","name","sql","Object","entries","queries","params","match","length","expectEmpty","checks","statement","prepare","push","query","status","count","all","err","message","close","console","log","format","process","exit"],"mappings":"AAAA,SAASA,IAAI,QAAQ,WAAW;AAEhC,SAASC,SAAS,QAAQ,eAAe;AACzC,SAASC,aAAa,QAAQ,cAAc;AAG5C,2FAA2F;AAC3F,wFAAwF;AACxF,4FAA4F;AAC5F,2FAA2F;AAC3F,4FAA4F;AAC5F,+CAA+C;AAC/C,MAAMC,QAAiB,CAACC;QAOmBC;IANzC,MAAMA,MAAMD,IAAIE,aAAa;IAC7B,MAAM,EAAEC,EAAE,EAAEC,QAAQ,EAAE,GAAGR,KAAKK;IAC9BH,cAAcM;IAEd,MAAMC,OAAc,EAAE;IACtB,IAAIC,SAAS;IACb,KAAK,MAAM,CAACC,MAAMC,IAAI,IAAIC,OAAOC,OAAO,EAACT,eAAAA,IAAIU,OAAO,cAAXV,0BAAAA,eAAe,CAAC,GAAI;YAC3CO;YACIP;QADpB,MAAMW,SAAS,EAACJ,aAAAA,IAAIK,KAAK,CAAC,oBAAVL,wBAAAA,aAAoB,EAAE,EAAEM,MAAM;QAC9C,MAAMC,cAAcd,EAAAA,cAAAA,IAAIe,MAAM,cAAVf,kCAAAA,WAAY,CAACM,KAAK,MAAK;QAC3C,IAAI;YACF,MAAMU,YAAYd,GAAGe,OAAO,CAACV;YAC7B,IAAII,SAAS,GAAG;gBACdP,KAAKc,IAAI,CAAC;oBAAEC,OAAOb;oBAAMK;oBAAQP,MAAM;oBAAKgB,QAAQ;gBAAiC;gBACrF;YACF;YACA,MAAMC,QAAQ,AAACL,UAAUM,GAAG,GAAiBT,MAAM;YACnD,IAAIC,aAAa;gBACf,kFAAkF;gBAClF,IAAIO,QAAQ,GAAGhB;gBACfD,KAAKc,IAAI,CAAC;oBAAEC,OAAOb;oBAAMK;oBAAQP,MAAMiB;oBAAOD,QAAQC,UAAU,IAAI,4BAA4B,CAAC,iCAAiC,EAAEA,MAAM,OAAO,CAAC;gBAAC;YACrJ,OAAO;gBACLjB,KAAKc,IAAI,CAAC;oBAAEC,OAAOb;oBAAMK;oBAAQP,MAAMiB;oBAAOD,QAAQC,UAAU,IAAI,2BAA2B;gBAAK;YACtG;QACF,EAAE,OAAOE,KAAK;YACZlB;YACAD,KAAKc,IAAI,CAAC;gBAAEC,OAAOb;gBAAMK;gBAAQP,MAAM;gBAAKgB,QAAQ,CAAC,QAAQ,EAAE,AAACG,IAAcC,OAAO,EAAE;YAAC;QAC1F;IACF;IAEAtB,GAAGuB,KAAK;IACR,IAAIrB,KAAKS,MAAM,KAAK,GAAG;QACrBa,QAAQC,GAAG,CAAC;QACZ;IACF;IACA/B,UAAUQ,MAAML,IAAI6B,MAAM;IAC1B,IAAIvB,SAAS,GAAGwB,QAAQC,IAAI,CAAC;AAC/B;AACA,eAAehC,MAAM"}