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
package/README.md
CHANGED
|
@@ -18,12 +18,13 @@ step, or you re-explaining anything.
|
|
|
18
18
|
|
|
19
19
|
## How it works
|
|
20
20
|
|
|
21
|
-
Every file becomes a row; every frontmatter key becomes a column
|
|
22
|
-
and run them by name.
|
|
21
|
+
Every file becomes a row; every frontmatter key becomes a column; the prose becomes a full-text
|
|
22
|
+
index you can join against. You write named SQL queries once and run them by name.
|
|
23
23
|
|
|
24
24
|
```markdown
|
|
25
25
|
---
|
|
26
26
|
title: Ship the Q3 report
|
|
27
|
+
summary: where the Q3 numbers came from and who signed off
|
|
27
28
|
status: active
|
|
28
29
|
tags: [urgent, reports]
|
|
29
30
|
---
|
|
@@ -31,6 +32,20 @@ tags: [urgent, reports]
|
|
|
31
32
|
Notes about the report…
|
|
32
33
|
```
|
|
33
34
|
|
|
35
|
+
```sql
|
|
36
|
+
-- active notes that actually discuss revenue, best match first
|
|
37
|
+
SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit
|
|
38
|
+
FROM frontmatter f JOIN content ON content.path = f.path
|
|
39
|
+
WHERE f.status = 'active' AND content MATCH 'revenue'
|
|
40
|
+
ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The filter shrinks the haystack, the search finds the needle, and each row that comes back —
|
|
44
|
+
path, title, summary, matching excerpt — is enough to decide whether to open the file, without
|
|
45
|
+
carrying the file. On a real 26-note vault that's a few hundred tokens, against ~6,000 to read the
|
|
46
|
+
three files it points at. Cheap enough to run before deciding what to open; reading afterward is
|
|
47
|
+
the expensive step, and it happens through the filesystem, not SQL.
|
|
48
|
+
|
|
34
49
|
Two properties make it trustworthy:
|
|
35
50
|
|
|
36
51
|
- **Never stale.** Every query re-checks the filesystem first, re-reading only what changed. The
|
|
@@ -43,11 +58,20 @@ Two properties make it trustworthy:
|
|
|
43
58
|
|
|
44
59
|
```bash
|
|
45
60
|
npm install -g sensemaking
|
|
46
|
-
cd your-notes && sense init # writes a
|
|
61
|
+
cd your-notes && sense init # writes a minimal sense.config.json (globs only, no queries)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Query immediately — ad-hoc SQL needs no config beyond the globs:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
sense query "SELECT … FROM frontmatter" # ad-hoc SQL; positional args bind to ? placeholders
|
|
68
|
+
sense query "…" --format json # structured output (the default is a table)
|
|
69
|
+
sense --list # what named queries exist
|
|
70
|
+
sense status | rebuild # cache info / delete .sense/ and re-crawl
|
|
47
71
|
```
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
and validation)
|
|
73
|
+
When a query proves worth reusing, name it in `sense.config.json` (plain JSON; the `$schema` line
|
|
74
|
+
gives your editor autocomplete and validation) and run it as `sense <name> [params...]`:
|
|
51
75
|
|
|
52
76
|
```json
|
|
53
77
|
{
|
|
@@ -55,28 +79,37 @@ and validation):
|
|
|
55
79
|
"version": 1,
|
|
56
80
|
"scan": { "include": ["**/*.md"] },
|
|
57
81
|
"queries": {
|
|
58
|
-
"
|
|
59
|
-
"by-tag": "SELECT path, title FROM docs WHERE has(tags, ?) ORDER BY path"
|
|
82
|
+
"by-tag": "SELECT path, title, summary FROM frontmatter WHERE has(tags, ?) ORDER BY path"
|
|
60
83
|
}
|
|
61
84
|
}
|
|
62
85
|
```
|
|
63
86
|
|
|
64
|
-
```
|
|
65
|
-
sense all # run a named query
|
|
66
|
-
sense by-tag urgent # positional args bind to ? placeholders (count-checked)
|
|
67
|
-
sense query "SELECT … FROM docs" # ad-hoc SQL for one-off questions, no config edit
|
|
68
|
-
sense --list # what queries exist
|
|
69
|
-
sense <name> --format json # structured output (the default is a table)
|
|
70
|
-
sense status | rebuild # cache info / delete .sense/ and re-crawl
|
|
71
|
-
```
|
|
72
|
-
|
|
73
87
|
Discovery walks up from your cwd git-style, so you can run `sense` from anywhere in the tree
|
|
74
88
|
(`--config <path>` overrides). Exit codes: `0` ok, `1` real error (SQLite's message verbatim),
|
|
75
89
|
`2` usage error.
|
|
76
90
|
|
|
77
91
|
The one custom SQL function is `has(field, value)`: array membership on a JSON-array field (like
|
|
78
|
-
`tags`), substring match on a string, always false on a missing key. Reserved
|
|
79
|
-
`_mtime`, `_size`.
|
|
92
|
+
`tags`), substring match on a string, always false on a missing key. Reserved names: `path`,
|
|
93
|
+
`_mtime`, `_size`, `content`.
|
|
94
|
+
|
|
95
|
+
### Content search
|
|
96
|
+
|
|
97
|
+
`content` is an FTS5 table with columns `title`, `summary`, `text`, and `path` (for the join).
|
|
98
|
+
`content MATCH ?` takes FTS5 syntax (`a OR b`, `"exact phrase"`, `pref*`, `NEAR(a b, 5)`,
|
|
99
|
+
`summary: term`), with stemming on so `negotiate` matches "negotiating". Markdown syntax is
|
|
100
|
+
stripped at index time — search for the words, not the formatting around them, and excerpts come
|
|
101
|
+
back as clean prose. `bm25()` weights follow column order, so `bm25(content, 10.0, 5.0, 1.0)`
|
|
102
|
+
ranks a title hit above a passing mention; `snippet(content, -1, …)` excerpts whichever column
|
|
103
|
+
matched.
|
|
104
|
+
|
|
105
|
+
Prose is deliberately **not** a column on `frontmatter`, so `SELECT * FROM frontmatter` can never
|
|
106
|
+
dump your notes into an agent's context — reaching it takes an explicit join. Select `path`,
|
|
107
|
+
`title`, `summary`, and a `snippet()`, keep a `LIMIT`, and read the files worth reading. `sense`
|
|
108
|
+
warns on stderr when a result grows past 50 KB.
|
|
109
|
+
|
|
110
|
+
A one-line `summary:` in frontmatter is worth adding as you write — what's on the page and when
|
|
111
|
+
it's worth opening, like a skill's `description:`. It's both a column you can select (a search
|
|
112
|
+
result row often answers the question with no file read at all) and a weighted search field.
|
|
80
113
|
|
|
81
114
|
### For AI agents
|
|
82
115
|
|
|
@@ -98,31 +131,36 @@ launchd and systemd examples: [WATCH.md](WATCH.md).
|
|
|
98
131
|
|
|
99
132
|
## Why not …
|
|
100
133
|
|
|
101
|
-
- **Obsidian (Bases/Dataview):** filters this well
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
- **Obsidian (Bases/Dataview):** filters this well, but only inside the running Electron app —
|
|
135
|
+
scripts and agents can't query it headless. Your vault works unmodified either way; Obsidian
|
|
136
|
+
stays a fine viewer for the same files.
|
|
137
|
+
- **RAG / semantic search:** ranks by similarity, not hard constraints like "only active notes from
|
|
138
|
+
this project" — that needs a `WHERE` clause. The two compose: filter first, rank later; vector
|
|
139
|
+
similarity, if added later, would join the same way.
|
|
140
|
+
- **An LLM-written index file** (à la Karpathy's llm-wiki `index.md`): a good instinct, but a
|
|
141
|
+
second artifact that drifts out of sync. `frontmatter` is that catalog, derived from the notes
|
|
142
|
+
themselves on every query.
|
|
143
|
+
- **Index-on-build tools (MarkdownDB and similar):** query a snapshot that's stale the moment a
|
|
144
|
+
file changes, instead of reconciling live.
|
|
145
|
+
- **Note CLIs (zk and similar):** good at their own model — tags, links, full text — but can't
|
|
146
|
+
filter on arbitrary frontmatter fields, which is the whole point here.
|
|
113
147
|
- **grep / one-off scripts:** fine until you want named, reusable, parameterized queries with real
|
|
114
148
|
AND/OR/ORDER BY — at which point you've started writing a worse query engine.
|
|
115
149
|
|
|
116
150
|
`sensemaking` is deliberately thin glue: [gray-matter](https://github.com/jonschlinkert/gray-matter)
|
|
117
|
-
parses, [
|
|
118
|
-
(
|
|
119
|
-
required.
|
|
151
|
+
parses, [remove-markdown](https://github.com/zuchka/remove-markdown) cleans the prose for indexing,
|
|
152
|
+
[fast-glob](https://github.com/mrmlnc/fast-glob) walks, and Node's built-in SQLite (`node:sqlite`)
|
|
153
|
+
does all the querying. Three small dependencies, no native builds, no background services required.
|
|
120
154
|
|
|
121
155
|
## Roadmap
|
|
122
156
|
|
|
123
|
-
|
|
124
|
-
so
|
|
125
|
-
|
|
157
|
+
Vector similarity is the natural next facet — a `doc_vec` table joined the same way `content` is,
|
|
158
|
+
so semantic recall composes with the frontmatter filter instead of living in a separate tool
|
|
159
|
+
(`SELECT path, distance`, never the embedding). It's deferred, not planned: today it would require
|
|
160
|
+
a native SQLite extension (sqlite-vec is pre-v1 and ships platform binaries), which breaks the
|
|
161
|
+
no-native-builds line above. It becomes worth revisiting when Node can do it dependency-free — and
|
|
162
|
+
only if BM25 demonstrably misses things; on a curated vault of a few hundred notes it often
|
|
163
|
+
doesn't.
|
|
126
164
|
|
|
127
165
|
Beyond that, the corpus model isn't tied to markdown — anything carrying structured metadata
|
|
128
166
|
(document properties, sidecar JSON) can join it without changing the query surface.
|
package/dist/cjs/cli.js
CHANGED
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
Object.defineProperty(exports, //
|
|
6
|
-
// this build supports, another watcher's fresh heartbeat, a SQLite error)
|
|
7
|
-
// is caught by the top-level handler below and reported as exit 1 with the
|
|
8
|
-
// error's message verbatim. Usage errors (missing/unknown query name, wrong
|
|
9
|
-
// parameter count) exit(2) directly instead of throwing.
|
|
5
|
+
Object.defineProperty(exports, // Thrown errors -> exit 1 with the message verbatim; usage errors exit(2) directly.
|
|
10
6
|
"default", {
|
|
11
7
|
enumerable: true,
|
|
12
8
|
get: function() {
|
|
@@ -239,10 +235,7 @@ function printWarnings(warnings) {
|
|
|
239
235
|
}
|
|
240
236
|
}
|
|
241
237
|
}
|
|
242
|
-
//
|
|
243
|
-
// `?` silently binds NULL and returns misleading empty results — fail
|
|
244
|
-
// loudly instead. (Naive count; queries putting '?' in string literals
|
|
245
|
-
// would miscount, which none of ours do.)
|
|
238
|
+
// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.
|
|
246
239
|
function runSql(cfg, sql, params, format, label) {
|
|
247
240
|
var _db_prepare;
|
|
248
241
|
var _sql_match;
|
|
@@ -283,8 +276,6 @@ function cli(argv, name) {
|
|
|
283
276
|
console.log(usage(name));
|
|
284
277
|
process.exit(0);
|
|
285
278
|
}
|
|
286
|
-
// cli.ts's only config knowledge is the --config flag; discovery, parsing,
|
|
287
|
-
// and version-gating all live in config.ts.
|
|
288
279
|
resolveConfig = function resolveConfig() {
|
|
289
280
|
return (0, _configts.loadConfig)(values.config);
|
|
290
281
|
};
|
|
@@ -300,7 +291,7 @@ function cli(argv, name) {
|
|
|
300
291
|
if (first === 'init') {
|
|
301
292
|
configPath = (0, _configts.initConfig)(process.cwd());
|
|
302
293
|
console.log("created ".concat(configPath));
|
|
303
|
-
console.log('
|
|
294
|
+
console.log('query away: sense query "SELECT path FROM frontmatter LIMIT 10"');
|
|
304
295
|
process.exit(0);
|
|
305
296
|
}
|
|
306
297
|
if (!(first === 'watch')) return [
|
|
@@ -369,8 +360,6 @@ function cli(argv, name) {
|
|
|
369
360
|
process.exit(0);
|
|
370
361
|
}
|
|
371
362
|
format = values.format === 'json' ? 'json' : 'table';
|
|
372
|
-
// Ad-hoc SQL without touching the config -- for one-off questions;
|
|
373
|
-
// save a query into sense.config.json only when it'll be reused.
|
|
374
363
|
if (first === 'query') {
|
|
375
364
|
_rest = _to_array(rest), sql = _rest[0], params = _rest.slice(1);
|
|
376
365
|
if (!sql) {
|
|
@@ -403,8 +392,6 @@ function cli(argv, name) {
|
|
|
403
392
|
];
|
|
404
393
|
case 4:
|
|
405
394
|
err1 = _state.sent();
|
|
406
|
-
// SQLite's own error message, a bad config, an unsupported config
|
|
407
|
-
// version, or an already-active watcher — printed verbatim.
|
|
408
395
|
console.error(err1.message);
|
|
409
396
|
process.exit(1);
|
|
410
397
|
return [
|
package/dist/cjs/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport type { ResolvedConfig } from './config.ts';\nimport { initConfig, loadConfig } from './config.ts';\nimport { docCount, getMeta, open, rebuild } from './db.ts';\nimport type { Row } from './output.ts';\nimport { printRows } from './output.ts';\nimport type { WatchEvent } from './watch.ts';\nimport { runWatch } from './watch.ts';\n\nfunction usage(name: string): string {\n return `usage: ${name} <name> [params...] [--format table|json] [--config path]\\n` + ` ${name} query \"<sql>\" [params...]\\n` + ` ${name} --list\\n` + ` ${name} init\\n` + ` ${name} watch [--force]\\n` + ` ${name} status\\n` + ` ${name} rebuild`;\n}\n\nfunction parseCliArgs(argv: string[], name: string) {\n try {\n return parseArgs({\n args: argv,\n options: {\n format: { type: 'string', default: 'table' },\n config: { type: 'string' },\n list: { type: 'boolean', default: false },\n force: { type: 'boolean', default: false },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n allowPositionals: true,\n });\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n}\n\nfunction printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\n// Shared tail of both the named-query and ad-hoc `query` paths. An unbound\n// `?` silently binds NULL and returns misleading empty results — fail\n// loudly instead. (Naive count; queries putting '?' in string literals\n// would miscount, which none of ours do.)\nfunction 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 const rows = db.prepare(sql).all(...params) as Row[];\n printRows(rows, format);\n db.close();\n}\n\nfunction logWatchEvent(event: WatchEvent): void {\n if (event.type === 'started') {\n console.log(`sense watch: watching ${event.baseDir}`);\n console.log(`sense watch: db ${event.dbPath}`);\n return;\n }\n if (event.type === 'reconciled') {\n printWarnings(event.warnings);\n if (event.parsed > 0) {\n console.log(`sense watch: reconciled, ${event.parsed} file(s) reparsed (${event.total} total)`);\n }\n return;\n }\n console.error(`sense watch: reconcile error: ${event.message}`);\n}\n\n// Everything below that can throw (bad JSON, a config `version` newer than\n// this build supports, another watcher's fresh heartbeat, a SQLite error)\n// is caught by the top-level handler below and reported as exit 1 with the\n// error's message verbatim. Usage errors (missing/unknown query name, wrong\n// parameter count) exit(2) directly instead of throwing.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n const { values, positionals } = parseCliArgs(argv, name);\n\n if (values.help) {\n console.log(usage(name));\n process.exit(0);\n }\n\n // cli.ts's only config knowledge is the --config flag; discovery, parsing,\n // and version-gating all live in config.ts.\n const resolveConfig = () => loadConfig(values.config);\n\n try {\n const [first, ...rest] = positionals;\n\n if (first === 'init') {\n const configPath = initConfig(process.cwd());\n console.log(`created ${configPath}`);\n console.log('edit the queries to fit your tree, then: sense --list');\n process.exit(0);\n }\n\n if (first === 'watch') {\n const cfg = resolveConfig();\n await runWatch(cfg, { force: values.force, onEvent: logWatchEvent });\n process.exit(0);\n }\n\n if (first === 'status') {\n const cfg = resolveConfig();\n const { db, dbPath, warnings } = open(cfg);\n printWarnings(warnings);\n console.log(`db: ${dbPath}`);\n console.log(`docs: ${docCount(db)}`);\n const heartbeat = getMeta(db, 'watch_heartbeat');\n if (heartbeat) {\n const ageSec = Math.round((Date.now() - Date.parse(heartbeat)) / 1000);\n console.log(`watcher: last heartbeat ${ageSec}s ago`);\n } else {\n console.log('watcher: no watcher');\n }\n db.close();\n process.exit(0);\n }\n\n if (first === 'rebuild') {\n const cfg = resolveConfig();\n const result = rebuild(cfg);\n printWarnings(result.warnings);\n console.log(`rebuilt: ${docCount(result.db)} docs`);\n result.db.close();\n process.exit(0);\n }\n\n if (values.list) {\n const cfg = resolveConfig();\n for (const queryName of Object.keys(cfg.queries).sort()) console.log(queryName);\n process.exit(0);\n }\n\n const format = values.format === 'json' ? 'json' : 'table';\n\n // Ad-hoc SQL without touching the config -- for one-off questions;\n // save a query into sense.config.json only when it'll be reused.\n if (first === 'query') {\n const [sql, ...params] = rest;\n if (!sql) {\n console.error(`usage: ${name} query \"<sql>\" [params...]`);\n process.exit(2);\n }\n runSql(resolveConfig(), sql, params, format, 'ad-hoc query');\n return;\n }\n\n const [name_, ...params] = [first, ...rest];\n\n if (!name_) {\n console.error(usage(name));\n process.exit(2);\n }\n\n const cfg = resolveConfig();\n\n const sql = cfg.queries[name_];\n if (!sql) {\n console.error(`unknown query: \"${name_}\"`);\n console.error(`valid queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(2);\n }\n\n runSql(cfg, sql, params, format, `query \"${name_}\"`);\n } catch (err) {\n // SQLite's own error message, a bad config, an unsupported config\n // version, or an already-active watcher — printed verbatim.\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["cli","usage","name","parseCliArgs","argv","parseArgs","args","options","format","type","default","config","list","force","help","short","allowPositionals","err","console","error","message","process","exit","printWarnings","warnings","w","warn","runSql","cfg","sql","params","label","db","placeholderCount","match","length","open","rows","prepare","all","printRows","close","logWatchEvent","event","log","baseDir","dbPath","parsed","total","values","positionals","resolveConfig","first","rest","configPath","heartbeat","ageSec","result","queryName","name_","loadConfig","initConfig","cwd","runWatch","onEvent","docCount","getMeta","Math","round","Date","now","parse","rebuild","Object","keys","queries","sort","join"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/cli.ts"],"sourcesContent":["import { parseArgs } from 'node:util';\nimport type { ResolvedConfig } from './config.ts';\nimport { initConfig, loadConfig } from './config.ts';\nimport { docCount, getMeta, open, rebuild } from './db.ts';\nimport type { Row } from './output.ts';\nimport { printRows } from './output.ts';\nimport type { WatchEvent } from './watch.ts';\nimport { runWatch } from './watch.ts';\n\nfunction usage(name: string): string {\n return `usage: ${name} <name> [params...] [--format table|json] [--config path]\\n` + ` ${name} query \"<sql>\" [params...]\\n` + ` ${name} --list\\n` + ` ${name} init\\n` + ` ${name} watch [--force]\\n` + ` ${name} status\\n` + ` ${name} rebuild`;\n}\n\nfunction parseCliArgs(argv: string[], name: string) {\n try {\n return parseArgs({\n args: argv,\n options: {\n format: { type: 'string', default: 'table' },\n config: { type: 'string' },\n list: { type: 'boolean', default: false },\n force: { type: 'boolean', default: false },\n help: { type: 'boolean', default: false, short: 'h' },\n },\n allowPositionals: true,\n });\n } catch (err) {\n console.error((err as Error).message);\n console.error(usage(name));\n process.exit(2);\n }\n}\n\nfunction printWarnings(warnings: string[]): void {\n for (const w of warnings) console.warn(w);\n}\n\n// An unbound `?` silently binds NULL, so mismatched param counts fail loudly instead.\nfunction 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 const rows = db.prepare(sql).all(...params) as Row[];\n printRows(rows, format);\n db.close();\n}\n\nfunction logWatchEvent(event: WatchEvent): void {\n if (event.type === 'started') {\n console.log(`sense watch: watching ${event.baseDir}`);\n console.log(`sense watch: db ${event.dbPath}`);\n return;\n }\n if (event.type === 'reconciled') {\n printWarnings(event.warnings);\n if (event.parsed > 0) {\n console.log(`sense watch: reconciled, ${event.parsed} file(s) reparsed (${event.total} total)`);\n }\n return;\n }\n console.error(`sense watch: reconcile error: ${event.message}`);\n}\n\n// Thrown errors -> exit 1 with the message verbatim; usage errors exit(2) directly.\nexport default async function cli(argv: string[], name: string): Promise<void> {\n const { values, positionals } = parseCliArgs(argv, name);\n\n if (values.help) {\n console.log(usage(name));\n process.exit(0);\n }\n\n const resolveConfig = () => loadConfig(values.config);\n\n try {\n const [first, ...rest] = positionals;\n\n if (first === 'init') {\n const configPath = initConfig(process.cwd());\n console.log(`created ${configPath}`);\n console.log('query away: sense query \"SELECT path FROM frontmatter LIMIT 10\"');\n process.exit(0);\n }\n\n if (first === 'watch') {\n const cfg = resolveConfig();\n await runWatch(cfg, { force: values.force, onEvent: logWatchEvent });\n process.exit(0);\n }\n\n if (first === 'status') {\n const cfg = resolveConfig();\n const { db, dbPath, warnings } = open(cfg);\n printWarnings(warnings);\n console.log(`db: ${dbPath}`);\n console.log(`docs: ${docCount(db)}`);\n const heartbeat = getMeta(db, 'watch_heartbeat');\n if (heartbeat) {\n const ageSec = Math.round((Date.now() - Date.parse(heartbeat)) / 1000);\n console.log(`watcher: last heartbeat ${ageSec}s ago`);\n } else {\n console.log('watcher: no watcher');\n }\n db.close();\n process.exit(0);\n }\n\n if (first === 'rebuild') {\n const cfg = resolveConfig();\n const result = rebuild(cfg);\n printWarnings(result.warnings);\n console.log(`rebuilt: ${docCount(result.db)} docs`);\n result.db.close();\n process.exit(0);\n }\n\n if (values.list) {\n const cfg = resolveConfig();\n for (const queryName of Object.keys(cfg.queries).sort()) console.log(queryName);\n process.exit(0);\n }\n\n const format = values.format === 'json' ? 'json' : 'table';\n\n if (first === 'query') {\n const [sql, ...params] = rest;\n if (!sql) {\n console.error(`usage: ${name} query \"<sql>\" [params...]`);\n process.exit(2);\n }\n runSql(resolveConfig(), sql, params, format, 'ad-hoc query');\n return;\n }\n\n const [name_, ...params] = [first, ...rest];\n\n if (!name_) {\n console.error(usage(name));\n process.exit(2);\n }\n\n const cfg = resolveConfig();\n\n const sql = cfg.queries[name_];\n if (!sql) {\n console.error(`unknown query: \"${name_}\"`);\n console.error(`valid queries: ${Object.keys(cfg.queries).sort().join(', ')}`);\n process.exit(2);\n }\n\n runSql(cfg, sql, params, format, `query \"${name_}\"`);\n } catch (err) {\n console.error((err as Error).message);\n process.exit(1);\n }\n}\n"],"names":["cli","usage","name","parseCliArgs","argv","parseArgs","args","options","format","type","default","config","list","force","help","short","allowPositionals","err","console","error","message","process","exit","printWarnings","warnings","w","warn","runSql","cfg","sql","params","label","db","placeholderCount","match","length","open","rows","prepare","all","printRows","close","logWatchEvent","event","log","baseDir","dbPath","parsed","total","values","positionals","resolveConfig","first","rest","configPath","heartbeat","ageSec","result","queryName","name_","loadConfig","initConfig","cwd","runWatch","onEvent","docCount","getMeta","Math","round","Date","now","parse","rebuild","Object","keys","queries","sort","join"],"mappings":"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"}
|
package/dist/cjs/config.js
CHANGED
|
@@ -99,10 +99,7 @@ function initConfig(dir) {
|
|
|
99
99
|
'**/*.md'
|
|
100
100
|
]
|
|
101
101
|
},
|
|
102
|
-
queries: {
|
|
103
|
-
all: 'SELECT path, title FROM docs ORDER BY path',
|
|
104
|
-
'by-tag': 'SELECT path, title FROM docs WHERE has(tags, ?) ORDER BY path'
|
|
105
|
-
}
|
|
102
|
+
queries: {}
|
|
106
103
|
};
|
|
107
104
|
(0, _nodefs.writeFileSync)(configPath, "".concat(JSON.stringify(starter, null, 2), "\n"));
|
|
108
105
|
return configPath;
|
|
@@ -132,9 +129,6 @@ function loadConfig(explicitPath) {
|
|
|
132
129
|
}
|
|
133
130
|
var raw = (0, _nodefs.readFileSync)(configPath, 'utf8');
|
|
134
131
|
var cfg = JSON.parse(raw);
|
|
135
|
-
// Missing `version` is treated as 1. A version newer than this build
|
|
136
|
-
// supports is a hard error -- misinterpreting a future config format
|
|
137
|
-
// silently would be worse than refusing to run.
|
|
138
132
|
var version = (_cfg_version = cfg.version) !== null && _cfg_version !== void 0 ? _cfg_version : 1;
|
|
139
133
|
if (version > SUPPORTED_CONFIG_VERSION) {
|
|
140
134
|
throw new _errorsts.SenseError('CONFIG_VERSION_UNSUPPORTED', "config version ".concat(version, " requires a newer sense"));
|
package/dist/cjs/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/config.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { SenseError } from './errors.ts';\n\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands.\nexport const SUPPORTED_CONFIG_VERSION = 1;\n\nexport interface Config {\n // Editor-only pointer to schema.json; never read by sense.\n $schema?: string;\n version?: number;\n scan: { include: string[] };\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n}\n\n// Refuses to overwrite an existing config.\nexport function initConfig(dir: string): string {\n const configPath = join(dir, CONFIG_FILENAME);\n if (existsSync(configPath)) {\n throw new SenseError('CONFIG_EXISTS', `${CONFIG_FILENAME} already exists in ${dir}`);\n }\n const starter: Config = {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: 1,\n scan: { include: ['**/*.md'] },\n queries: {},\n };\n writeFileSync(configPath, `${JSON.stringify(starter, null, 2)}\\n`);\n return configPath;\n}\n\nexport function findConfigPath(startDir: string): string | null {\n let dir = resolve(startDir);\n for (;;) {\n const candidate = join(dir, CONFIG_FILENAME);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\nexport function loadConfig(explicitPath?: string): ResolvedConfig {\n let configPath: string;\n if (explicitPath) {\n configPath = resolve(process.cwd(), explicitPath);\n if (!existsSync(configPath)) throw new SenseError('CONFIG_NOT_FOUND', `config not found: ${configPath}`);\n } else {\n const found = findConfigPath(process.cwd());\n if (!found) {\n throw new SenseError('CONFIG_NOT_FOUND', `could not find ${CONFIG_FILENAME} in ${process.cwd()} or any parent directory`);\n }\n configPath = found;\n }\n\n const raw = readFileSync(configPath, 'utf8');\n const cfg = JSON.parse(raw) as Config;\n\n const version = cfg.version ?? 1;\n if (version > SUPPORTED_CONFIG_VERSION) {\n throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath };\n}\n"],"names":["CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","findConfigPath","initConfig","loadConfig","dir","configPath","join","existsSync","SenseError","starter","$schema","version","scan","include","queries","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","explicitPath","cfg","process","cwd","found","raw","readFileSync","parse","baseDir"],"mappings":";;;;;;;;;;;QAIaA;eAAAA;;QACAC;eAAAA;;QAGAC;eAAAA;;QA+BGC;eAAAA;;QAfAC;eAAAA;;QA0BAC;eAAAA;;;sBAlDwC;wBACjB;wBACZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEpB,IAAML,kBAAkB;AACxB,IAAMC,YAAY;AAGlB,IAAMC,2BAA2B;AAgBjC,SAASE,WAAWE,GAAW;IACpC,IAAMC,aAAaC,IAAAA,cAAI,EAACF,KAAKN;IAC7B,IAAIS,IAAAA,kBAAU,EAACF,aAAa;QAC1B,MAAM,IAAIG,oBAAU,CAAC,iBAAiB,AAAC,GAAuCJ,OAArCN,iBAAgB,uBAAyB,OAAJM;IAChF;IACA,IAAMK,UAAkB;QACtBC,SAAS;QACTC,SAAS;QACTC,MAAM;YAAEC,SAAS;gBAAC;aAAU;QAAC;QAC7BC,SAAS,CAAC;IACZ;IACAC,IAAAA,qBAAa,EAACV,YAAY,AAAC,GAAmC,OAAjCW,KAAKC,SAAS,CAACR,SAAS,MAAM,IAAG;IAC9D,OAAOJ;AACT;AAEO,SAASJ,eAAeiB,QAAgB;IAC7C,IAAId,MAAMe,IAAAA,iBAAO,EAACD;IAClB,OAAS;QACP,IAAME,YAAYd,IAAAA,cAAI,EAACF,KAAKN;QAC5B,IAAIS,IAAAA,kBAAU,EAACa,YAAY,OAAOA;QAClC,IAAMC,SAASC,IAAAA,iBAAO,EAAClB;QACvB,IAAIiB,WAAWjB,KAAK,OAAO;QAC3BA,MAAMiB;IACR;AACF;AAEO,SAASlB,WAAWoB,YAAqB;QAgB9BC;IAfhB,IAAInB;IACJ,IAAIkB,cAAc;QAChBlB,aAAac,IAAAA,iBAAO,EAACM,QAAQC,GAAG,IAAIH;QACpC,IAAI,CAAChB,IAAAA,kBAAU,EAACF,aAAa,MAAM,IAAIG,oBAAU,CAAC,oBAAoB,AAAC,qBAA+B,OAAXH;IAC7F,OAAO;QACL,IAAMsB,QAAQ1B,eAAewB,QAAQC,GAAG;QACxC,IAAI,CAACC,OAAO;YACV,MAAM,IAAInB,oBAAU,CAAC,oBAAoB,AAAC,kBAAuCiB,OAAtB3B,iBAAgB,QAAoB,OAAd2B,QAAQC,GAAG,IAAG;QACjG;QACArB,aAAasB;IACf;IAEA,IAAMC,MAAMC,IAAAA,oBAAY,EAACxB,YAAY;IACrC,IAAMmB,MAAMR,KAAKc,KAAK,CAACF;IAEvB,IAAMjB,WAAUa,eAAAA,IAAIb,OAAO,cAAXa,0BAAAA,eAAe;IAC/B,IAAIb,UAAUX,0BAA0B;QACtC,MAAM,IAAIQ,oBAAU,CAAC,8BAA8B,AAAC,kBAAyB,OAARG,SAAQ;IAC/E;IAEA,OAAO,wCAAKa;QAAKO,SAAST,IAAAA,iBAAO,EAACjB;QAAaA,YAAAA;;AACjD"}
|
package/dist/cjs/db.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { Config, ResolvedConfig } from './config.js';
|
|
3
3
|
export declare const DB_FILENAME = "cache.db";
|
|
4
|
-
export declare const SCHEMA_VERSION = "
|
|
4
|
+
export declare const SCHEMA_VERSION = "2";
|
|
5
5
|
export interface OpenResult {
|
|
6
6
|
db: DatabaseSync;
|
|
7
7
|
cfg: ResolvedConfig;
|
package/dist/cjs/db.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { Config, ResolvedConfig } from './config.js';
|
|
3
3
|
export declare const DB_FILENAME = "cache.db";
|
|
4
|
-
export declare const SCHEMA_VERSION = "
|
|
4
|
+
export declare const SCHEMA_VERSION = "2";
|
|
5
5
|
export interface OpenResult {
|
|
6
6
|
db: DatabaseSync;
|
|
7
7
|
cfg: ResolvedConfig;
|
package/dist/cjs/db.js
CHANGED
|
@@ -65,16 +65,11 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
65
65
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
66
66
|
}
|
|
67
67
|
var DB_FILENAME = 'cache.db';
|
|
68
|
-
var SCHEMA_VERSION = '
|
|
69
|
-
// Quote a SQL identifier, escaping embedded double quotes so an unusual
|
|
70
|
-
// frontmatter key (however unlikely) can't break out of the identifier.
|
|
68
|
+
var SCHEMA_VERSION = '2';
|
|
71
69
|
function quoteIdent(name) {
|
|
72
70
|
return '"'.concat(name.split('"').join('""'), '"');
|
|
73
71
|
}
|
|
74
|
-
//
|
|
75
|
-
// - JSON-array field (stored as JSON text, e.g. `["a","b"]`) -> membership
|
|
76
|
-
// - string field -> substring
|
|
77
|
-
// - NULL -> false
|
|
72
|
+
// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.
|
|
78
73
|
function registerFunctions(db) {
|
|
79
74
|
db.function('has', {
|
|
80
75
|
deterministic: true,
|
|
@@ -83,7 +78,6 @@ function registerFunctions(db) {
|
|
|
83
78
|
if (field === null || field === undefined) return 0;
|
|
84
79
|
var needle = String(value);
|
|
85
80
|
if (typeof field === 'string') {
|
|
86
|
-
// Try JSON array first (arrays are stored as JSON text).
|
|
87
81
|
if (field.startsWith('[')) {
|
|
88
82
|
try {
|
|
89
83
|
var parsed = JSON.parse(field);
|
|
@@ -92,25 +86,23 @@ function registerFunctions(db) {
|
|
|
92
86
|
return String(item) === needle;
|
|
93
87
|
}) ? 1 : 0;
|
|
94
88
|
}
|
|
95
|
-
} catch (unused) {
|
|
96
|
-
// fall through to substring match
|
|
97
|
-
}
|
|
89
|
+
} catch (unused) {}
|
|
98
90
|
}
|
|
99
91
|
return field.includes(needle) ? 1 : 0;
|
|
100
92
|
}
|
|
101
|
-
// Numbers, etc: coerce to string and substring-match.
|
|
102
93
|
return String(field).includes(needle) ? 1 : 0;
|
|
103
94
|
});
|
|
104
95
|
}
|
|
105
96
|
function getColumns(db) {
|
|
106
|
-
var rows = db.prepare('PRAGMA table_info(
|
|
97
|
+
var rows = db.prepare('PRAGMA table_info(frontmatter)').all();
|
|
107
98
|
return new Set(rows.map(function(r) {
|
|
108
99
|
return r.name;
|
|
109
100
|
}));
|
|
110
101
|
}
|
|
102
|
+
// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter` can't dump file text into context.
|
|
111
103
|
function ensureSchema(db) {
|
|
112
|
-
db.exec('CREATE TABLE IF NOT EXISTS
|
|
113
|
-
db.exec(
|
|
104
|
+
db.exec('CREATE TABLE IF NOT EXISTS frontmatter ("path" TEXT PRIMARY KEY, "_mtime" REAL, "_size" INTEGER)');
|
|
105
|
+
db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')");
|
|
114
106
|
if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);
|
|
115
107
|
}
|
|
116
108
|
function getMeta(db, key) {
|
|
@@ -125,7 +117,7 @@ function setMeta(db, key, value) {
|
|
|
125
117
|
db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);
|
|
126
118
|
}
|
|
127
119
|
function docCount(db) {
|
|
128
|
-
var row = db.prepare('SELECT COUNT(*) AS n FROM
|
|
120
|
+
var row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get();
|
|
129
121
|
return row.n;
|
|
130
122
|
}
|
|
131
123
|
function reconcile(db, cfg, baseDir) {
|
|
@@ -133,7 +125,7 @@ function reconcile(db, cfg, baseDir) {
|
|
|
133
125
|
var currentSet = new Set(files.map(function(f) {
|
|
134
126
|
return f.relPath;
|
|
135
127
|
}));
|
|
136
|
-
var existingRows = db.prepare('SELECT "path", "_mtime", "_size" FROM
|
|
128
|
+
var existingRows = db.prepare('SELECT "path", "_mtime", "_size" FROM frontmatter').all();
|
|
137
129
|
var existing = new Map(existingRows.map(function(r) {
|
|
138
130
|
return [
|
|
139
131
|
r.path,
|
|
@@ -203,10 +195,8 @@ function reconcile(db, cfg, baseDir) {
|
|
|
203
195
|
}
|
|
204
196
|
}
|
|
205
197
|
}
|
|
206
|
-
// seenColumns already contains the reserved columns (they're real columns
|
|
207
|
-
// of `docs`), so spreading it alone avoids duplicate names in the INSERT.
|
|
208
198
|
var allColumns = _to_consumable_array(seenColumns);
|
|
209
|
-
var insertSql = "INSERT OR REPLACE INTO
|
|
199
|
+
var insertSql = "INSERT OR REPLACE INTO frontmatter (".concat(allColumns.map(quoteIdent).join(', '), ") VALUES (").concat(allColumns.map(function() {
|
|
210
200
|
return '?';
|
|
211
201
|
}).join(', '), ")");
|
|
212
202
|
db.exec('BEGIN');
|
|
@@ -215,7 +205,7 @@ function reconcile(db, cfg, baseDir) {
|
|
|
215
205
|
try {
|
|
216
206
|
for(var _iterator2 = newColumns[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true){
|
|
217
207
|
var col = _step2.value;
|
|
218
|
-
db.exec("ALTER TABLE
|
|
208
|
+
db.exec("ALTER TABLE frontmatter ADD COLUMN ".concat(quoteIdent(col)));
|
|
219
209
|
}
|
|
220
210
|
} catch (err) {
|
|
221
211
|
_didIteratorError2 = true;
|
|
@@ -231,13 +221,16 @@ function reconcile(db, cfg, baseDir) {
|
|
|
231
221
|
}
|
|
232
222
|
}
|
|
233
223
|
}
|
|
224
|
+
// FTS5 has no upsert, so delete-before-insert into `content`.
|
|
225
|
+
var delBody = db.prepare('DELETE FROM content WHERE "path" = ?');
|
|
234
226
|
if (vanished.length > 0) {
|
|
235
|
-
var del = db.prepare('DELETE FROM
|
|
227
|
+
var del = db.prepare('DELETE FROM frontmatter WHERE "path" = ?');
|
|
236
228
|
var _iteratorNormalCompletion3 = true, _didIteratorError3 = false, _iteratorError3 = undefined;
|
|
237
229
|
try {
|
|
238
230
|
for(var _iterator3 = vanished[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true){
|
|
239
231
|
var path = _step3.value;
|
|
240
232
|
del.run(path);
|
|
233
|
+
delBody.run(path);
|
|
241
234
|
}
|
|
242
235
|
} catch (err) {
|
|
243
236
|
_didIteratorError3 = true;
|
|
@@ -256,6 +249,7 @@ function reconcile(db, cfg, baseDir) {
|
|
|
256
249
|
}
|
|
257
250
|
if (parsedDocs.length > 0) {
|
|
258
251
|
var insert = db.prepare(insertSql);
|
|
252
|
+
var insertBody = db.prepare('INSERT INTO content (title, summary, text, "path") VALUES (?, ?, ?, ?)');
|
|
259
253
|
var _iteratorNormalCompletion4 = true, _didIteratorError4 = false, _iteratorError4 = undefined;
|
|
260
254
|
try {
|
|
261
255
|
var _loop = function() {
|
|
@@ -269,6 +263,8 @@ function reconcile(db, cfg, baseDir) {
|
|
|
269
263
|
return (_doc_data_col = doc.data[col]) !== null && _doc_data_col !== void 0 ? _doc_data_col : null;
|
|
270
264
|
});
|
|
271
265
|
(_insert = insert).run.apply(_insert, _to_consumable_array(values));
|
|
266
|
+
delBody.run(doc.relPath);
|
|
267
|
+
insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);
|
|
272
268
|
};
|
|
273
269
|
for(var _iterator4 = parsedDocs[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true)_loop();
|
|
274
270
|
} catch (err) {
|
|
@@ -306,6 +302,17 @@ function open(cfg) {
|
|
|
306
302
|
db.exec('PRAGMA journal_mode = WAL');
|
|
307
303
|
db.exec('PRAGMA busy_timeout = 5000');
|
|
308
304
|
registerFunctions(db);
|
|
305
|
+
db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');
|
|
306
|
+
// Schema version mismatch: reconcile only reparses changed files, so an old cache can't be patched up incrementally -- rebuild instead.
|
|
307
|
+
var version = getMeta(db, 'schema_version');
|
|
308
|
+
if (version !== null && version !== SCHEMA_VERSION) {
|
|
309
|
+
db.close();
|
|
310
|
+
(0, _nodefs.rmSync)(stateDir, {
|
|
311
|
+
recursive: true,
|
|
312
|
+
force: true
|
|
313
|
+
});
|
|
314
|
+
return open(cfg);
|
|
315
|
+
}
|
|
309
316
|
ensureSchema(db);
|
|
310
317
|
var _reconcile = reconcile(db, cfg, cfg.baseDir), parsed = _reconcile.parsed, warnings = _reconcile.warnings;
|
|
311
318
|
return {
|
package/dist/cjs/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.ts"],"sourcesContent":["import { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from './config.ts';\nimport { STATE_DIR } from './config.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile } from './scan.ts';\n\n// Rows -> SQLite only: schema/ALTER, the reconcile diff + transaction,\n// pragmas, the meta table, has() registration. Filesystem reading and\n// frontmatter parsing live in scan.ts; this module never touches\n// node:fs/gray-matter directly and never prints (throws are for callers).\n\nexport const DB_FILENAME = 'cache.db';\nexport const SCHEMA_VERSION = '1';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n // Number of files (re)parsed by this open's reconcile -- 0 on a fully\n // warm cache. Tests use this as reconcile instrumentation.\n parsed: number;\n warnings: string[];\n}\n\n// Quote a SQL identifier, escaping embedded double quotes so an unusual\n// frontmatter key (however unlikely) can't break out of the identifier.\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// The one custom SQL function in the whole tool: has(field, value).\n// - JSON-array field (stored as JSON text, e.g. `[\"a\",\"b\"]`) -> membership\n// - string field -> substring\n// - NULL -> false\nfunction registerFunctions(db: DatabaseSync): void {\n db.function('has', { deterministic: true, varargs: false }, (field: unknown, value: unknown): number => {\n if (field === null || field === undefined) return 0;\n\n const needle = String(value);\n\n if (typeof field === 'string') {\n // Try JSON array first (arrays are stored as JSON text).\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) {\n return parsed.some((item) => String(item) === needle) ? 1 : 0;\n }\n } catch {\n // fall through to substring match\n }\n }\n return field.includes(needle) ? 1 : 0;\n }\n\n // Numbers, etc: coerce to string and substring-match.\n return String(field).includes(needle) ? 1 : 0;\n });\n}\n\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(docs)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\n}\n\nfunction ensureSchema(db: DatabaseSync): void {\n db.exec(`CREATE TABLE IF NOT EXISTS docs (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER)`);\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n}\n\nexport function getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM docs').get() as { n: number };\n return row.n;\n}\n\n// Reconcile the `docs` table against the filesystem: glob current files via\n// scan.ts, reparse stale (mtime/size changed) + new files in one\n// transaction, DELETE vanished paths, ALTER TABLE ADD COLUMN for newly\n// discovered frontmatter keys. Returns the number of files (re)parsed and\n// any warnings scan.ts collected (e.g. reserved-key collisions).\nexport function reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM docs`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n for (const file of toReparse) {\n const { doc, warnings: fileWarnings } = parseFile(file);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n\n // seenColumns already contains the reserved columns (they're real columns\n // of `docs`), so spreading it alone avoids duplicate names in the INSERT.\n const allColumns = [...seenColumns];\n const insertSql = `INSERT OR REPLACE INTO docs (${allColumns.map(quoteIdent).join(', ')}) VALUES (${allColumns.map(() => '?').join(', ')})`;\n\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE docs ADD COLUMN ${quoteIdent(col)}`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM docs WHERE \"path\" = ?`);\n for (const path of vanished) del.run(path);\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n for (const doc of parsedDocs) {\n const values = allColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n return doc.data[col] ?? null;\n });\n insert.run(...values);\n }\n }\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n return { parsed: parsedDocs.length, warnings };\n}\n\n// open(resolved config): open (or create) the on-disk SQLite cache at\n// `<baseDir>/.sense/cache.db`, reconcile against the filesystem, and return\n// a live handle. The DB is a warm start, never a source of truth -- the\n// `.md` files remain truth. Takes only an already-resolved config; discovery\n// and version-gating are config.ts's job, not this one's.\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n db.exec('PRAGMA busy_timeout = 5000');\n registerFunctions(db);\n\n ensureSchema(db);\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings };\n}\n\n// Delete the `.sense/` state dir entirely and reconcile fresh -- the manual\n// reset for lingering columns or a doubted cache.\nexport function rebuild(cfg: ResolvedConfig): OpenResult {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n return open(cfg);\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","docCount","getMeta","open","rebuild","reconcile","setMeta","quoteIdent","name","split","join","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","Set","map","r","ensureSchema","exec","key","row","get","run","n","cfg","baseDir","files","listFiles","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","seenColumns","newColumns","parsedDocs","file","parseFile","doc","fileWarnings","push","Object","keys","data","add","allColumns","insertSql","col","del","insert","values","err","stateDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","rmSync","force"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/db.ts"],"sourcesContent":["import { mkdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { Config, ResolvedConfig } from './config.ts';\nimport { STATE_DIR } from './config.ts';\nimport type { ParsedDoc } from './scan.ts';\nimport { listFiles, parseFile } from './scan.ts';\n\n// Rows -> SQLite: schema, reconcile, has(). Filesystem/frontmatter parsing lives in scan.ts.\n\nexport const DB_FILENAME = 'cache.db';\n// Cache shape version, independent of the config's own `version`.\nexport const SCHEMA_VERSION = '2';\n\nexport interface OpenResult {\n db: DatabaseSync;\n cfg: ResolvedConfig;\n dbPath: string;\n parsed: number;\n warnings: string[];\n}\n\nfunction quoteIdent(name: string): string {\n return `\"${name.split('\"').join('\"\"')}\"`;\n}\n\n// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.\nfunction registerFunctions(db: DatabaseSync): void {\n db.function('has', { deterministic: true, varargs: false }, (field: unknown, value: unknown): number => {\n if (field === null || field === undefined) return 0;\n\n const needle = String(value);\n\n if (typeof field === 'string') {\n if (field.startsWith('[')) {\n try {\n const parsed = JSON.parse(field);\n if (Array.isArray(parsed)) {\n return parsed.some((item) => String(item) === needle) ? 1 : 0;\n }\n } catch {}\n }\n return field.includes(needle) ? 1 : 0;\n }\n\n return String(field).includes(needle) ? 1 : 0;\n });\n}\n\nfunction getColumns(db: DatabaseSync): Set<string> {\n const rows = db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>;\n return new Set(rows.map((r) => r.name));\n}\n\n// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter` can't dump file text into context.\nfunction ensureSchema(db: DatabaseSync): void {\n db.exec(`CREATE TABLE IF NOT EXISTS frontmatter (\"path\" TEXT PRIMARY KEY, \"_mtime\" REAL, \"_size\" INTEGER)`);\n db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);\n if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);\n}\n\nexport function getMeta(db: DatabaseSync, key: string): string | null {\n const row = db.prepare('SELECT value FROM meta WHERE key = ?').get(key) as { value: string } | undefined;\n return row ? row.value : null;\n}\n\nexport function setMeta(db: DatabaseSync, key: string, value: string | null): void {\n if (value === null) {\n db.prepare('DELETE FROM meta WHERE key = ?').run(key);\n return;\n }\n db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);\n}\n\nexport function docCount(db: DatabaseSync): number {\n const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get() as { n: number };\n return row.n;\n}\n\nexport function reconcile(db: DatabaseSync, cfg: Config, baseDir: string): { parsed: number; warnings: string[] } {\n const files = listFiles(cfg, baseDir);\n const currentSet = new Set(files.map((f) => f.relPath));\n\n const existingRows = db.prepare(`SELECT \"path\", \"_mtime\", \"_size\" FROM frontmatter`).all() as Array<{\n path: string;\n _mtime: number;\n _size: number;\n }>;\n const existing = new Map(existingRows.map((r) => [r.path, r]));\n const vanished = existingRows.filter((r) => !currentSet.has(r.path)).map((r) => r.path);\n\n const toReparse = files.filter((f) => {\n const row = existing.get(f.relPath);\n return !row || row._mtime !== f.mtimeMs || row._size !== f.size;\n });\n\n if (vanished.length === 0 && toReparse.length === 0) return { parsed: 0, warnings: [] };\n\n const seenColumns = getColumns(db);\n const newColumns: string[] = [];\n const parsedDocs: ParsedDoc[] = [];\n const warnings: string[] = [];\n\n for (const file of toReparse) {\n const { doc, warnings: fileWarnings } = parseFile(file);\n warnings.push(...fileWarnings);\n for (const key of Object.keys(doc.data)) {\n if (!seenColumns.has(key)) {\n seenColumns.add(key);\n newColumns.push(key);\n }\n }\n parsedDocs.push(doc);\n }\n\n const allColumns = [...seenColumns];\n const insertSql = `INSERT OR REPLACE INTO frontmatter (${allColumns.map(quoteIdent).join(', ')}) VALUES (${allColumns.map(() => '?').join(', ')})`;\n\n db.exec('BEGIN');\n try {\n for (const col of newColumns) db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);\n // FTS5 has no upsert, so delete-before-insert into `content`.\n const delBody = db.prepare(`DELETE FROM content WHERE \"path\" = ?`);\n if (vanished.length > 0) {\n const del = db.prepare(`DELETE FROM frontmatter WHERE \"path\" = ?`);\n for (const path of vanished) {\n del.run(path);\n delBody.run(path);\n }\n }\n if (parsedDocs.length > 0) {\n const insert = db.prepare(insertSql);\n const insertBody = db.prepare(`INSERT INTO content (title, summary, text, \"path\") VALUES (?, ?, ?, ?)`);\n for (const doc of parsedDocs) {\n const values = allColumns.map((col) => {\n if (col === 'path') return doc.relPath;\n if (col === '_mtime') return doc.mtimeMs;\n if (col === '_size') return doc.size;\n return doc.data[col] ?? null;\n });\n insert.run(...values);\n delBody.run(doc.relPath);\n insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);\n }\n }\n db.exec('COMMIT');\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n\n return { parsed: parsedDocs.length, warnings };\n}\n\nexport function open(cfg: ResolvedConfig): OpenResult {\n const stateDir = join(cfg.baseDir, STATE_DIR);\n mkdirSync(stateDir, { recursive: true });\n const dbPath = join(stateDir, DB_FILENAME);\n\n const db = new DatabaseSync(dbPath);\n db.exec('PRAGMA journal_mode = WAL');\n db.exec('PRAGMA busy_timeout = 5000');\n registerFunctions(db);\n\n db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');\n\n // Schema version mismatch: reconcile only reparses changed files, so an old cache can't be patched up incrementally -- rebuild instead.\n const version = getMeta(db, 'schema_version');\n if (version !== null && version !== SCHEMA_VERSION) {\n db.close();\n rmSync(stateDir, { recursive: true, force: true });\n return open(cfg);\n }\n\n ensureSchema(db);\n const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);\n\n return { db, cfg, dbPath, parsed, warnings };\n}\n\n// Manual reset for a doubted cache.\nexport function rebuild(cfg: ResolvedConfig): OpenResult {\n rmSync(join(cfg.baseDir, STATE_DIR), { recursive: true, force: true });\n return open(cfg);\n}\n"],"names":["DB_FILENAME","SCHEMA_VERSION","docCount","getMeta","open","rebuild","reconcile","setMeta","quoteIdent","name","split","join","registerFunctions","db","function","deterministic","varargs","field","value","undefined","needle","String","startsWith","parsed","JSON","parse","Array","isArray","some","item","includes","getColumns","rows","prepare","all","Set","map","r","ensureSchema","exec","key","row","get","run","n","cfg","baseDir","files","listFiles","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","seenColumns","newColumns","parsedDocs","file","parseFile","doc","fileWarnings","push","Object","keys","data","add","allColumns","insertSql","col","delBody","del","insert","insertBody","values","search","title","summary","text","err","stateDir","STATE_DIR","mkdirSync","recursive","dbPath","DatabaseSync","version","close","rmSync","force"],"mappings":"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"}
|
package/dist/cjs/errors.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
// db.ts, watch.ts) never calls process.exit and never prints -- it throws.
|
|
3
|
-
// Only cli.ts catches, prints the message, and maps it to an exit code.
|
|
1
|
+
// Library code throws; only cli.ts prints and exits.
|
|
4
2
|
"use strict";
|
|
5
3
|
Object.defineProperty(exports, "__esModule", {
|
|
6
4
|
value: true
|
package/dist/cjs/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["//
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/errors.ts"],"sourcesContent":["// Library code throws; only cli.ts prints and exits.\nexport type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE';\n\nexport class SenseError extends Error {\n code: SenseErrorCode;\n\n constructor(code: SenseErrorCode, message: string) {\n super(message);\n this.name = 'SenseError';\n this.code = code;\n }\n}\n"],"names":["SenseError","code","message","name","Error"],"mappings":"AAAA,qDAAqD;;;;;+BAGxCA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAN,IAAA,AAAMA,2BAAN;;cAAMA;aAAAA,WAGCC,IAAoB,EAAEC,OAAe;gCAHtCF;;gBAIT,kBAJSA;YAIHE;;QACN,MAAKC,IAAI,GAAG;QACZ,MAAKF,IAAI,GAAGA;;;WANHD;qBAAmBI"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// Public library API.
|
|
2
|
-
// truth for it -- anything a test or another tool needs from sense should be
|
|
3
|
-
// exported here rather than reached into src/ by relative path.
|
|
1
|
+
// Public library API.
|
|
4
2
|
"use strict";
|
|
5
3
|
Object.defineProperty(exports, "__esModule", {
|
|
6
4
|
value: true
|