sensemaking 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -36
- package/dist/cjs/cli.js +3 -16
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/config.js +1 -7
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/db.d.cts +1 -1
- package/dist/cjs/db.d.ts +1 -1
- package/dist/cjs/db.js +29 -22
- package/dist/cjs/db.js.map +1 -1
- package/dist/cjs/errors.js +1 -3
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/index.js +1 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/output.js +29 -40
- package/dist/cjs/output.js.map +1 -1
- package/dist/cjs/scan.d.cts +5 -0
- package/dist/cjs/scan.d.ts +5 -0
- package/dist/cjs/scan.js +26 -14
- package/dist/cjs/scan.js.map +1 -1
- package/dist/cjs/watch.js +2 -9
- package/dist/cjs/watch.js.map +1 -1
- package/dist/esm/cli.js +3 -16
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/config.js +3 -20
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/db.d.ts +1 -1
- package/dist/esm/db.js +35 -39
- package/dist/esm/db.js.map +1 -1
- package/dist/esm/errors.js +1 -3
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +1 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/output.js +21 -19
- package/dist/esm/output.js.map +1 -1
- package/dist/esm/scan.d.ts +5 -0
- package/dist/esm/scan.js +26 -19
- package/dist/esm/scan.js.map +1 -1
- package/dist/esm/watch.js +3 -13
- package/dist/esm/watch.js.map +1 -1
- package/package.json +3 -2
- package/schema.json +2 -2
- package/skills/sense/EXAMPLES.md +115 -0
- package/skills/sense/SKILL.md +72 -55
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# sense: worked examples
|
|
2
|
+
|
|
3
|
+
Every query returns *references with evidence* — enough to decide which files to open, never the
|
|
4
|
+
files themselves. Reading happens afterward, through the filesystem, on the paths that earned it.
|
|
5
|
+
|
|
6
|
+
Token counts below are from a real 26-note vault (~62 KB of markdown): a search costs ~1–2% of
|
|
7
|
+
reading the files it points at.
|
|
8
|
+
|
|
9
|
+
## A. "Does the vault say anything about X?" (discovery search)
|
|
10
|
+
|
|
11
|
+
The question an agent should ask *before* reading anything — cheap enough to run speculatively.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
sense query "SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit
|
|
15
|
+
FROM frontmatter f JOIN content ON content.path = f.path
|
|
16
|
+
WHERE content MATCH ?
|
|
17
|
+
ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10" "compensation OR salary" --format json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
[
|
|
22
|
+
{
|
|
23
|
+
"path": "knowledge/compensation-floor.md",
|
|
24
|
+
"title": "Compensation floor",
|
|
25
|
+
"summary": "The comp floor and how to apply it when screening",
|
|
26
|
+
"hit": "«Compensation» floor Kevin's «compensation» preference for the later job…"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "methodology/jobs-table-schema.md",
|
|
30
|
+
"title": "Jobs table schema",
|
|
31
|
+
"summary": "",
|
|
32
|
+
"hit": "…B Company employer C «Salary» as posted; blank if not…"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
~55 tokens per row. Decide from `title`/`summary`/`hit`; often the row itself answers the
|
|
38
|
+
question. If not, `Read knowledge/compensation-floor.md` (~800 tokens) — one file, not the vault
|
|
39
|
+
(~16,000 tokens).
|
|
40
|
+
|
|
41
|
+
Note the `content.title`/`content.summary` spelling: those columns always exist on `content`
|
|
42
|
+
(empty string when a note lacks the key), so this query works on any vault. `f.title`/`f.summary`
|
|
43
|
+
only work once some note actually declares the key — `frontmatter` columns are discovered, not fixed.
|
|
44
|
+
|
|
45
|
+
## B. Filtering on frontmatter you already know (no content search)
|
|
46
|
+
|
|
47
|
+
When the fields are known — typically because an agent wrote the notes to a schema — plain SQL on
|
|
48
|
+
`frontmatter` is the whole query. No join needed when you aren't searching prose:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
sense query "SELECT path, title, summary FROM frontmatter
|
|
52
|
+
WHERE status = 'active' AND has(track, ?) ORDER BY updated DESC" within-tech --format json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`has()` does array membership on JSON-array fields (`track: [a, b]`), substring on strings, false
|
|
56
|
+
on missing keys. A note missing `summary` yields NULL — costs nothing, breaks nothing. (Only if
|
|
57
|
+
*no* note in the vault declares a key does its column not exist at all — pattern D's pragma shows
|
|
58
|
+
what's there.)
|
|
59
|
+
|
|
60
|
+
## C. Filter + search + rank in one query
|
|
61
|
+
|
|
62
|
+
The case neither grep nor a frontmatter-only tool can do: a hard constraint AND a relevance
|
|
63
|
+
ranking, composed.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
sense query "SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit
|
|
67
|
+
FROM frontmatter f JOIN content ON content.path = f.path
|
|
68
|
+
WHERE f.status = 'active' AND has(f.track, 'within-tech') AND content MATCH ?
|
|
69
|
+
ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10" remote --format json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The frontmatter conditions are hard filters (a non-active note can never appear); the `MATCH` +
|
|
73
|
+
`bm25()` ranks whatever survives. The join exists **only** for `MATCH`/`bm25()`/`snippet()` —
|
|
74
|
+
never use it to fetch text.
|
|
75
|
+
|
|
76
|
+
## D. Cold start: a vault you didn't build
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
sense --list # named queries, if any
|
|
80
|
+
sense query "SELECT name FROM pragma_table_info('frontmatter') ORDER BY name" # discover the fields
|
|
81
|
+
sense query "SELECT DISTINCT type FROM frontmatter" # discover a field's values
|
|
82
|
+
sense query "SELECT count(*) AS n FROM frontmatter" # corpus size
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Each costs a few dozen tokens and turns an unknown corpus into a queryable schema. From there,
|
|
86
|
+
patterns A–C apply.
|
|
87
|
+
|
|
88
|
+
## E. Search syntax worth knowing
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
"exact phrase" # phrase match
|
|
92
|
+
compensat* # prefix
|
|
93
|
+
summary: onboarding # only match in the summary field
|
|
94
|
+
title: retro OR text: retrospective
|
|
95
|
+
NEAR(salary negotiate, 8)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Stemming is on: `negotiate` finds "negotiating". Markdown is stripped from the index, so search
|
|
99
|
+
for the words, not the syntax around them.
|
|
100
|
+
|
|
101
|
+
## F. Anti-patterns
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
sense query "SELECT text FROM content" # dumps every note into context
|
|
105
|
+
```
|
|
106
|
+
This is the one query shape that defeats the tool's purpose. `sense` warns on stderr when a
|
|
107
|
+
result exceeds 50 KB (stdout stays clean for --format json), but the fix is upstream: select
|
|
108
|
+
`path` + `snippet()`, keep a `LIMIT`, and `Read` the files that deserve it.
|
|
109
|
+
|
|
110
|
+
Other traps:
|
|
111
|
+
- **No `LIMIT`** on a search — fine on a tiny vault, a context bomb on a big one. Habit: always.
|
|
112
|
+
- **Fetching content through SQL** because the join is there. The join ranks; the filesystem
|
|
113
|
+
retrieves. An agent already has `Read` and `grep` for the path the query returned.
|
|
114
|
+
- **Adding a named query for a one-off question.** `sense query "<sql>"` exists so the config
|
|
115
|
+
only accumulates queries that are genuinely reused.
|
package/skills/sense/SKILL.md
CHANGED
|
@@ -1,85 +1,104 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sense
|
|
3
|
-
description: Query a markdown tree
|
|
3
|
+
description: Query a markdown tree with SQL via the sense CLI — filter notes by YAML frontmatter, then full-text search inside them. Use when the user wants to query, filter, count, search, or report on a folder of markdown notes, when you need to find which notes discuss a topic before reading them, when a directory has a sense.config.json, when asked what named queries exist, or when asked to add a new query to one.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# sense
|
|
7
7
|
|
|
8
|
-
`sense` runs SQL over
|
|
9
|
-
`docs` table (one column per frontmatter key); the file's own content is never queried, only its
|
|
10
|
-
frontmatter. A running app is **never** required — files on disk are the only source of truth.
|
|
8
|
+
`sense` runs SQL over a tree of markdown files. Two tables, two facets of the same document:
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
| table | one row per file holds… | for… |
|
|
11
|
+
|---------------|------------------------------------------------------------------------|-------------------|
|
|
12
|
+
| `frontmatter` | one column per frontmatter key (+ reserved `path`, `_mtime`, `_size`) | filtering |
|
|
13
|
+
| `content` | FTS5 index: `title`, `summary`, `text` (+ `path` to join on) | searching/ranking |
|
|
14
|
+
|
|
15
|
+
No running app required — every query re-checks the filesystem first, so results are never stale.
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
and that a config exists. Discovery walks **up** from cwd looking for `sense.config.json`, same as
|
|
16
|
-
git looks for `.git` — you don't need to `cd` to the project root, and `--config <path>` bypasses
|
|
17
|
-
discovery entirely. If no config exists yet in the tree you're being asked to query, run
|
|
18
|
-
`sense init` at its root to write a starter config, then tailor the queries.
|
|
17
|
+
## The flow: query → decide → Read
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
A query result is *references with evidence*, never file contents: `path`, `title`, `summary` (if
|
|
20
|
+
present), `hit` (matching excerpt) — enough to decide whether a file is worth opening at ~2% of
|
|
21
|
+
the token cost of reading it. Then `Read` the one or two paths that matter.
|
|
21
22
|
|
|
22
23
|
```
|
|
23
|
-
sense
|
|
24
|
+
sense query "SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit
|
|
25
|
+
FROM frontmatter f JOIN content ON content.path = f.path
|
|
26
|
+
WHERE content MATCH ?
|
|
27
|
+
ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10" "compensation OR equity" --format json
|
|
24
28
|
```
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
Select `title`/`summary` from `content`, where they always exist (empty when a note lacks the
|
|
31
|
+
key) — on `frontmatter` they're discovered columns, present only if some note declares them, so
|
|
32
|
+
`f.summary` errors on a vault with no summaries yet. Add frontmatter conditions
|
|
33
|
+
(`f.status = 'active' AND has(f.track, ?)`) to the same WHERE — the filter and the search compose
|
|
34
|
+
in one query. Worked traces for the common cases (discovery,
|
|
35
|
+
known-field filtering, cold start, anti-patterns): [EXAMPLES.md](EXAMPLES.md).
|
|
28
36
|
|
|
29
|
-
##
|
|
37
|
+
## Before anything else
|
|
38
|
+
|
|
39
|
+
Confirm `sense` is installed (`sense --list` or `which sense`; if missing,
|
|
40
|
+
`npm install -g sensemaking`) and that a config exists. Discovery walks **up** from cwd looking for
|
|
41
|
+
`sense.config.json`, same as git looks for `.git` — you don't need to `cd` to the project root, and
|
|
42
|
+
`--config <path>` bypasses discovery entirely. If no config exists yet, `sense init` at the tree's
|
|
43
|
+
root writes a minimal one (globs only, no queries — ad-hoc `sense query` works immediately).
|
|
44
|
+
|
|
45
|
+
## Cold start: discover what's queryable
|
|
30
46
|
|
|
31
47
|
```
|
|
32
|
-
sense
|
|
48
|
+
sense --list # named queries, if any
|
|
49
|
+
sense query "SELECT name FROM pragma_table_info('frontmatter') ORDER BY name" # what frontmatter fields exist
|
|
50
|
+
sense query "SELECT DISTINCT status FROM frontmatter" # what values a field takes
|
|
33
51
|
```
|
|
34
52
|
|
|
35
|
-
|
|
36
|
-
a padded text table. `--format table` (the default) is for humans at a terminal.
|
|
53
|
+
If you wrote the notes, you already know the fields; the pragma is for vaults you didn't build.
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
order. The parameter count is checked strictly — passing the wrong number of arguments is a usage
|
|
40
|
-
error (exit 2), never a silent empty result.
|
|
55
|
+
## Run a query
|
|
41
56
|
|
|
42
57
|
```
|
|
43
|
-
sense
|
|
58
|
+
sense <name> [params...] --format json # named query from sense.config.json
|
|
59
|
+
sense query "<sql>" [params...] # ad-hoc SQL, no config edit
|
|
44
60
|
```
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
**Prefer `--format json` when consuming output as an agent.** Positional args bind to `?`
|
|
63
|
+
placeholders in order, count-checked strictly (wrong count = exit 2, never a silent empty result).
|
|
64
|
+
Save a query into `sense.config.json` (plain JSON, edit directly) only when it's meant to be
|
|
65
|
+
reused as a named view — never add-then-remove one for a one-off question.
|
|
47
66
|
|
|
48
|
-
|
|
49
|
-
`sense.config.json` and remove it afterward:
|
|
67
|
+
## Search syntax and ranking
|
|
50
68
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
- `content MATCH ?` takes FTS5 syntax: `a OR b`, `a AND b`, `"exact phrase"`, `pref*`,
|
|
70
|
+
`NEAR(a b, 5)`, column-scoped `summary: onboarding`.
|
|
71
|
+
- `bm25(content, 10.0, 5.0, 1.0)` — weights follow column order (title, summary, text), so a
|
|
72
|
+
title hit outranks a passing mention. Lower is better; plain `ORDER BY bm25(…)` sorts best-first.
|
|
73
|
+
- `snippet(content, -1, '«', '»', '…', 10)` — bounded excerpt; `-1` picks whichever column
|
|
74
|
+
matched; the last argument is the excerpt budget in tokens.
|
|
75
|
+
- Stemming is on (`negotiate` matches "negotiating"); markdown syntax is stripped at index time,
|
|
76
|
+
so snippets are clean prose and `**bold**` matches `bold`.
|
|
55
77
|
|
|
56
|
-
|
|
78
|
+
## Keep results small
|
|
57
79
|
|
|
58
|
-
|
|
80
|
+
- Select `path, title, summary` + a `snippet()` — never `SELECT text FROM content`, which dumps
|
|
81
|
+
the whole tree into context (`sense` warns on stderr past 50 KB).
|
|
82
|
+
- Always `LIMIT`. Ten rows is plenty; widen only if they all look wrong.
|
|
83
|
+
- `SELECT * FROM frontmatter` is safe — prose is deliberately not a `frontmatter` column.
|
|
59
84
|
|
|
60
|
-
|
|
85
|
+
## When writing notes, not just querying them
|
|
61
86
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
| string | substring match |
|
|
66
|
-
| NULL (key absent) | always false |
|
|
87
|
+
Give every note a one-line `summary:` in its frontmatter — what's on the page and when it's worth
|
|
88
|
+
opening, like a skill's `description:`. It pays twice: it appears in result rows (often answering
|
|
89
|
+
the question with no file read at all), and it's a weighted search field. Keep it to one line.
|
|
67
90
|
|
|
68
|
-
|
|
91
|
+
Reserved frontmatter key names (dropped with a warning): `path`, `_mtime`, `_size`, `content`.
|
|
69
92
|
|
|
70
|
-
|
|
93
|
+
## `has()` semantics
|
|
71
94
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
`scan.include` globs and query results are both relative to the config file's own directory,
|
|
81
|
-
regardless of your cwd. Reserved columns (don't use as frontmatter key names): `path`, `_mtime`,
|
|
82
|
-
`_size`.
|
|
95
|
+
The one custom SQL function, for frontmatter fields that are arrays or free text:
|
|
96
|
+
|
|
97
|
+
| field type | `has(field, value)` means |
|
|
98
|
+
|--------------------------|---------------------------|
|
|
99
|
+
| JSON array (e.g. `tags`) | array membership |
|
|
100
|
+
| string | substring match |
|
|
101
|
+
| NULL (key absent) | always false |
|
|
83
102
|
|
|
84
103
|
## Exit codes
|
|
85
104
|
|
|
@@ -88,8 +107,6 @@ regardless of your cwd. Reserved columns (don't use as frontmatter key names): `
|
|
|
88
107
|
|
|
89
108
|
## When results look stale
|
|
90
109
|
|
|
91
|
-
`sense rebuild` deletes the local `.sense/` cache and re-crawls
|
|
92
|
-
query
|
|
93
|
-
|
|
94
|
-
path, and whether a background `sense watch` process is running (it's an optional pre-warmer, not
|
|
95
|
-
a correctness requirement).
|
|
110
|
+
`sense rebuild` deletes the local `.sense/` cache and re-crawls from scratch. Rarely needed since
|
|
111
|
+
every query reconciles on open — use it if you doubt the cache. `sense status` shows doc count, db
|
|
112
|
+
path, and whether a background `sense watch` (optional pre-warmer) is running.
|