sensemaking 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -55
- 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/dist/esm/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":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","initConfig","dir","configPath","starter","$schema","version","scan","include","queries","JSON","stringify","findConfigPath","startDir","candidate","parent","loadConfig","explicitPath","cfg","process","cwd","found","raw","parse","baseDir"],"mappings":"AAAA,SAASA,UAAU,EAAEC,YAAY,EAAEC,aAAa,QAAQ,UAAU;AAClE,SAASC,OAAO,EAAEC,IAAI,EAAEC,OAAO,QAAQ,YAAY;AACnD,SAASC,UAAU,QAAQ,cAAc;AAEzC,OAAO,MAAMC,kBAAkB,oBAAoB;AACnD,OAAO,MAAMC,YAAY,SAAS;AAElC,8DAA8D;AAC9D,OAAO,MAAMC,2BAA2B,EAAE;AAe1C,2CAA2C;AAC3C,OAAO,SAASC,WAAWC,GAAW;IACpC,MAAMC,aAAaR,KAAKO,KAAKJ;IAC7B,IAAIP,WAAWY,aAAa;QAC1B,MAAM,IAAIN,WAAW,iBAAiB,GAAGC,gBAAgB,mBAAmB,EAAEI,KAAK;IACrF;IACA,MAAME,UAAkB;QACtBC,SAAS;QACTC,SAAS;QACTC,MAAM;YAAEC,SAAS;gBAAC;aAAU;QAAC;QAC7BC,SAAS,CAAC;IACZ;IACAhB,cAAcU,YAAY,GAAGO,KAAKC,SAAS,CAACP,SAAS,MAAM,GAAG,EAAE,CAAC;IACjE,OAAOD;AACT;AAEA,OAAO,SAASS,eAAeC,QAAgB;IAC7C,IAAIX,MAAMN,QAAQiB;IAClB,OAAS;QACP,MAAMC,YAAYnB,KAAKO,KAAKJ;QAC5B,IAAIP,WAAWuB,YAAY,OAAOA;QAClC,MAAMC,SAASrB,QAAQQ;QACvB,IAAIa,WAAWb,KAAK,OAAO;QAC3BA,MAAMa;IACR;AACF;AAEA,OAAO,SAASC,WAAWC,YAAqB;QAgB9BC;IAfhB,IAAIf;IACJ,IAAIc,cAAc;QAChBd,aAAaP,QAAQuB,QAAQC,GAAG,IAAIH;QACpC,IAAI,CAAC1B,WAAWY,aAAa,MAAM,IAAIN,WAAW,oBAAoB,CAAC,kBAAkB,EAAEM,YAAY;IACzG,OAAO;QACL,MAAMkB,QAAQT,eAAeO,QAAQC,GAAG;QACxC,IAAI,CAACC,OAAO;YACV,MAAM,IAAIxB,WAAW,oBAAoB,CAAC,eAAe,EAAEC,gBAAgB,IAAI,EAAEqB,QAAQC,GAAG,GAAG,wBAAwB,CAAC;QAC1H;QACAjB,aAAakB;IACf;IAEA,MAAMC,MAAM9B,aAAaW,YAAY;IACrC,MAAMe,MAAMR,KAAKa,KAAK,CAACD;IAEvB,MAAMhB,WAAUY,eAAAA,IAAIZ,OAAO,cAAXY,0BAAAA,eAAe;IAC/B,IAAIZ,UAAUN,0BAA0B;QACtC,MAAM,IAAIH,WAAW,8BAA8B,CAAC,eAAe,EAAES,QAAQ,uBAAuB,CAAC;IACvG;IAEA,OAAO;QAAE,GAAGY,GAAG;QAAEM,SAAS9B,QAAQS;QAAaA;IAAW;AAC5D"}
|
package/dist/esm/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/esm/db.js
CHANGED
|
@@ -3,21 +3,14 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
4
4
|
import { STATE_DIR } from './config.js';
|
|
5
5
|
import { listFiles, parseFile } from './scan.js';
|
|
6
|
-
// Rows -> SQLite
|
|
7
|
-
// pragmas, the meta table, has() registration. Filesystem reading and
|
|
8
|
-
// frontmatter parsing live in scan.ts; this module never touches
|
|
9
|
-
// node:fs/gray-matter directly and never prints (throws are for callers).
|
|
6
|
+
// Rows -> SQLite: schema, reconcile, has(). Filesystem/frontmatter parsing lives in scan.ts.
|
|
10
7
|
export const DB_FILENAME = 'cache.db';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// frontmatter key (however unlikely) can't break out of the identifier.
|
|
8
|
+
// Cache shape version, independent of the config's own `version`.
|
|
9
|
+
export const SCHEMA_VERSION = '2';
|
|
14
10
|
function quoteIdent(name) {
|
|
15
11
|
return `"${name.split('"').join('""')}"`;
|
|
16
12
|
}
|
|
17
|
-
//
|
|
18
|
-
// - JSON-array field (stored as JSON text, e.g. `["a","b"]`) -> membership
|
|
19
|
-
// - string field -> substring
|
|
20
|
-
// - NULL -> false
|
|
13
|
+
// has(field, value): JSON-array field -> membership, string field -> substring, NULL -> false.
|
|
21
14
|
function registerFunctions(db) {
|
|
22
15
|
db.function('has', {
|
|
23
16
|
deterministic: true,
|
|
@@ -26,30 +19,27 @@ function registerFunctions(db) {
|
|
|
26
19
|
if (field === null || field === undefined) return 0;
|
|
27
20
|
const needle = String(value);
|
|
28
21
|
if (typeof field === 'string') {
|
|
29
|
-
// Try JSON array first (arrays are stored as JSON text).
|
|
30
22
|
if (field.startsWith('[')) {
|
|
31
23
|
try {
|
|
32
24
|
const parsed = JSON.parse(field);
|
|
33
25
|
if (Array.isArray(parsed)) {
|
|
34
26
|
return parsed.some((item)=>String(item) === needle) ? 1 : 0;
|
|
35
27
|
}
|
|
36
|
-
} catch {
|
|
37
|
-
// fall through to substring match
|
|
38
|
-
}
|
|
28
|
+
} catch {}
|
|
39
29
|
}
|
|
40
30
|
return field.includes(needle) ? 1 : 0;
|
|
41
31
|
}
|
|
42
|
-
// Numbers, etc: coerce to string and substring-match.
|
|
43
32
|
return String(field).includes(needle) ? 1 : 0;
|
|
44
33
|
});
|
|
45
34
|
}
|
|
46
35
|
function getColumns(db) {
|
|
47
|
-
const rows = db.prepare('PRAGMA table_info(
|
|
36
|
+
const rows = db.prepare('PRAGMA table_info(frontmatter)').all();
|
|
48
37
|
return new Set(rows.map((r)=>r.name));
|
|
49
38
|
}
|
|
39
|
+
// Content is a separate table (not a column on frontmatter) so `SELECT * FROM frontmatter` can't dump file text into context.
|
|
50
40
|
function ensureSchema(db) {
|
|
51
|
-
db.exec(`CREATE TABLE IF NOT EXISTS
|
|
52
|
-
db.exec(
|
|
41
|
+
db.exec(`CREATE TABLE IF NOT EXISTS frontmatter ("path" TEXT PRIMARY KEY, "_mtime" REAL, "_size" INTEGER)`);
|
|
42
|
+
db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS content USING fts5(title, summary, text, path UNINDEXED, tokenize = 'porter unicode61')`);
|
|
53
43
|
if (getMeta(db, 'schema_version') === null) setMeta(db, 'schema_version', SCHEMA_VERSION);
|
|
54
44
|
}
|
|
55
45
|
export function getMeta(db, key) {
|
|
@@ -64,18 +54,13 @@ export function setMeta(db, key, value) {
|
|
|
64
54
|
db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value').run(key, value);
|
|
65
55
|
}
|
|
66
56
|
export function docCount(db) {
|
|
67
|
-
const row = db.prepare('SELECT COUNT(*) AS n FROM
|
|
57
|
+
const row = db.prepare('SELECT COUNT(*) AS n FROM frontmatter').get();
|
|
68
58
|
return row.n;
|
|
69
59
|
}
|
|
70
|
-
// Reconcile the `docs` table against the filesystem: glob current files via
|
|
71
|
-
// scan.ts, reparse stale (mtime/size changed) + new files in one
|
|
72
|
-
// transaction, DELETE vanished paths, ALTER TABLE ADD COLUMN for newly
|
|
73
|
-
// discovered frontmatter keys. Returns the number of files (re)parsed and
|
|
74
|
-
// any warnings scan.ts collected (e.g. reserved-key collisions).
|
|
75
60
|
export function reconcile(db, cfg, baseDir) {
|
|
76
61
|
const files = listFiles(cfg, baseDir);
|
|
77
62
|
const currentSet = new Set(files.map((f)=>f.relPath));
|
|
78
|
-
const existingRows = db.prepare(`SELECT "path", "_mtime", "_size" FROM
|
|
63
|
+
const existingRows = db.prepare(`SELECT "path", "_mtime", "_size" FROM frontmatter`).all();
|
|
79
64
|
const existing = new Map(existingRows.map((r)=>[
|
|
80
65
|
r.path,
|
|
81
66
|
r
|
|
@@ -104,21 +89,25 @@ export function reconcile(db, cfg, baseDir) {
|
|
|
104
89
|
}
|
|
105
90
|
parsedDocs.push(doc);
|
|
106
91
|
}
|
|
107
|
-
// seenColumns already contains the reserved columns (they're real columns
|
|
108
|
-
// of `docs`), so spreading it alone avoids duplicate names in the INSERT.
|
|
109
92
|
const allColumns = [
|
|
110
93
|
...seenColumns
|
|
111
94
|
];
|
|
112
|
-
const insertSql = `INSERT OR REPLACE INTO
|
|
95
|
+
const insertSql = `INSERT OR REPLACE INTO frontmatter (${allColumns.map(quoteIdent).join(', ')}) VALUES (${allColumns.map(()=>'?').join(', ')})`;
|
|
113
96
|
db.exec('BEGIN');
|
|
114
97
|
try {
|
|
115
|
-
for (const col of newColumns)db.exec(`ALTER TABLE
|
|
98
|
+
for (const col of newColumns)db.exec(`ALTER TABLE frontmatter ADD COLUMN ${quoteIdent(col)}`);
|
|
99
|
+
// FTS5 has no upsert, so delete-before-insert into `content`.
|
|
100
|
+
const delBody = db.prepare(`DELETE FROM content WHERE "path" = ?`);
|
|
116
101
|
if (vanished.length > 0) {
|
|
117
|
-
const del = db.prepare(`DELETE FROM
|
|
118
|
-
for (const path of vanished)
|
|
102
|
+
const del = db.prepare(`DELETE FROM frontmatter WHERE "path" = ?`);
|
|
103
|
+
for (const path of vanished){
|
|
104
|
+
del.run(path);
|
|
105
|
+
delBody.run(path);
|
|
106
|
+
}
|
|
119
107
|
}
|
|
120
108
|
if (parsedDocs.length > 0) {
|
|
121
109
|
const insert = db.prepare(insertSql);
|
|
110
|
+
const insertBody = db.prepare(`INSERT INTO content (title, summary, text, "path") VALUES (?, ?, ?, ?)`);
|
|
122
111
|
for (const doc of parsedDocs){
|
|
123
112
|
const values = allColumns.map((col)=>{
|
|
124
113
|
var _doc_data_col;
|
|
@@ -128,6 +117,8 @@ export function reconcile(db, cfg, baseDir) {
|
|
|
128
117
|
return (_doc_data_col = doc.data[col]) !== null && _doc_data_col !== void 0 ? _doc_data_col : null;
|
|
129
118
|
});
|
|
130
119
|
insert.run(...values);
|
|
120
|
+
delBody.run(doc.relPath);
|
|
121
|
+
insertBody.run(doc.search.title, doc.search.summary, doc.search.text, doc.relPath);
|
|
131
122
|
}
|
|
132
123
|
}
|
|
133
124
|
db.exec('COMMIT');
|
|
@@ -140,11 +131,6 @@ export function reconcile(db, cfg, baseDir) {
|
|
|
140
131
|
warnings
|
|
141
132
|
};
|
|
142
133
|
}
|
|
143
|
-
// open(resolved config): open (or create) the on-disk SQLite cache at
|
|
144
|
-
// `<baseDir>/.sense/cache.db`, reconcile against the filesystem, and return
|
|
145
|
-
// a live handle. The DB is a warm start, never a source of truth -- the
|
|
146
|
-
// `.md` files remain truth. Takes only an already-resolved config; discovery
|
|
147
|
-
// and version-gating are config.ts's job, not this one's.
|
|
148
134
|
export function open(cfg) {
|
|
149
135
|
const stateDir = join(cfg.baseDir, STATE_DIR);
|
|
150
136
|
mkdirSync(stateDir, {
|
|
@@ -155,6 +141,17 @@ export function open(cfg) {
|
|
|
155
141
|
db.exec('PRAGMA journal_mode = WAL');
|
|
156
142
|
db.exec('PRAGMA busy_timeout = 5000');
|
|
157
143
|
registerFunctions(db);
|
|
144
|
+
db.exec('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)');
|
|
145
|
+
// Schema version mismatch: reconcile only reparses changed files, so an old cache can't be patched up incrementally -- rebuild instead.
|
|
146
|
+
const version = getMeta(db, 'schema_version');
|
|
147
|
+
if (version !== null && version !== SCHEMA_VERSION) {
|
|
148
|
+
db.close();
|
|
149
|
+
rmSync(stateDir, {
|
|
150
|
+
recursive: true,
|
|
151
|
+
force: true
|
|
152
|
+
});
|
|
153
|
+
return open(cfg);
|
|
154
|
+
}
|
|
158
155
|
ensureSchema(db);
|
|
159
156
|
const { parsed, warnings } = reconcile(db, cfg, cfg.baseDir);
|
|
160
157
|
return {
|
|
@@ -165,8 +162,7 @@ export function open(cfg) {
|
|
|
165
162
|
warnings
|
|
166
163
|
};
|
|
167
164
|
}
|
|
168
|
-
//
|
|
169
|
-
// reset for lingering columns or a doubted cache.
|
|
165
|
+
// Manual reset for a doubted cache.
|
|
170
166
|
export function rebuild(cfg) {
|
|
171
167
|
rmSync(join(cfg.baseDir, STATE_DIR), {
|
|
172
168
|
recursive: true,
|
package/dist/esm/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":["mkdirSync","rmSync","join","DatabaseSync","STATE_DIR","listFiles","parseFile","DB_FILENAME","SCHEMA_VERSION","quoteIdent","name","split","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","getMeta","setMeta","key","row","get","run","docCount","n","reconcile","cfg","baseDir","files","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","seenColumns","newColumns","parsedDocs","file","doc","fileWarnings","push","Object","keys","data","add","allColumns","insertSql","col","del","insert","values","err","open","stateDir","recursive","dbPath","rebuild","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":["mkdirSync","rmSync","join","DatabaseSync","STATE_DIR","listFiles","parseFile","DB_FILENAME","SCHEMA_VERSION","quoteIdent","name","split","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","getMeta","setMeta","key","row","get","run","docCount","n","reconcile","cfg","baseDir","files","currentSet","f","relPath","existingRows","existing","Map","path","vanished","filter","has","toReparse","_mtime","mtimeMs","_size","size","length","warnings","seenColumns","newColumns","parsedDocs","file","doc","fileWarnings","push","Object","keys","data","add","allColumns","insertSql","col","delBody","del","insert","insertBody","values","search","title","summary","text","err","open","stateDir","recursive","dbPath","version","close","force","rebuild"],"mappings":"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"}
|
package/dist/esm/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
|
export class SenseError extends Error {
|
|
5
3
|
constructor(code, message){
|
|
6
4
|
super(message);
|
package/dist/esm/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","Error","code","message","name"],"mappings":"AAAA,qDAAqD;AAGrD,OAAO,MAAMA,mBAAmBC;IAG9B,YAAYC,IAAoB,EAAEC,OAAe,CAAE;QACjD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;QACZ,IAAI,CAACF,IAAI,GAAGA;IACd;AACF"}
|
package/dist/esm/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
|
export { CONFIG_FILENAME, findConfigPath, initConfig, loadConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
|
|
5
3
|
export { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.js';
|
|
6
4
|
export { SenseError } from './errors.js';
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API.\n\nexport type { Config, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME, findConfigPath, initConfig, loadConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\nexport type { OpenResult } from './db.ts';\nexport { DB_FILENAME, docCount, getMeta, open, rebuild, reconcile, setMeta } from './db.ts';\n\nexport type { SenseErrorCode } from './errors.ts';\nexport { SenseError } from './errors.ts';\n\nexport type { Row } from './output.ts';\nexport { printRows } from './output.ts';\n\nexport type { FileStat, ParsedDoc } from './scan.ts';\nexport { listFiles, parseFile } from './scan.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","findConfigPath","initConfig","loadConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","DB_FILENAME","docCount","getMeta","open","rebuild","reconcile","setMeta","SenseError","printRows","listFiles","parseFile","runWatch"],"mappings":"AAAA,sBAAsB;AAGtB,SAASA,eAAe,EAAEC,cAAc,EAAEC,UAAU,EAAEC,UAAU,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAG3H,SAASC,WAAW,EAAEC,QAAQ,EAAEC,OAAO,EAAEC,IAAI,EAAEC,OAAO,EAAEC,SAAS,EAAEC,OAAO,QAAQ,UAAU;AAG5F,SAASC,UAAU,QAAQ,cAAc;AAGzC,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,SAAS,EAAEC,SAAS,QAAQ,YAAY;AAGjD,SAASC,QAAQ,QAAQ,aAAa"}
|
package/dist/esm/output.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
// rows -> table (default) | json
|
|
2
|
+
// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.
|
|
3
|
+
const OVERSIZE_BYTES = 50000;
|
|
4
|
+
// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.
|
|
5
|
+
function cell(value) {
|
|
6
|
+
return String(value !== null && value !== void 0 ? value : '').replace(/\s*\n\s*/g, ' ');
|
|
7
|
+
}
|
|
2
8
|
export function printRows(rows, format) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (rows.length === 0) {
|
|
8
|
-
console.log('(0 rows)');
|
|
9
|
-
return;
|
|
9
|
+
const rendered = render(rows, format);
|
|
10
|
+
console.log(rendered);
|
|
11
|
+
if (rendered.length > OVERSIZE_BYTES) {
|
|
12
|
+
console.warn(`warning: result is ${Math.round(rendered.length / 1000)} KB across ${rows.length} row(s); consider a LIMIT, fewer columns, or snippet() instead of whole values`);
|
|
10
13
|
}
|
|
14
|
+
}
|
|
15
|
+
function render(rows, format) {
|
|
16
|
+
if (format === 'json') return JSON.stringify(rows, null, 2);
|
|
17
|
+
if (rows.length === 0) return '(0 rows)';
|
|
11
18
|
const columns = Object.keys(rows[0]);
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
return String((_row_col = row[col]) !== null && _row_col !== void 0 ? _row_col : '').length;
|
|
15
|
-
})));
|
|
19
|
+
const cells = rows.map((row)=>columns.map((col)=>cell(row[col])));
|
|
20
|
+
const widths = columns.map((col, i)=>Math.max(col.length, ...cells.map((row)=>row[i].length)));
|
|
16
21
|
const formatRow = (values)=>values.map((value, i)=>value.padEnd(widths[i])).join(' ');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return String((_row_col = row[col]) !== null && _row_col !== void 0 ? _row_col : '');
|
|
23
|
-
})));
|
|
24
|
-
}
|
|
22
|
+
return [
|
|
23
|
+
formatRow(columns),
|
|
24
|
+
widths.map((w)=>'-'.repeat(w)).join(' '),
|
|
25
|
+
...cells.map(formatRow)
|
|
26
|
+
].join('\n');
|
|
25
27
|
}
|
package/dist/esm/output.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json\n\nexport type Row = Record<string, unknown>;\n\nexport function printRows(rows: Row[], format: 'table' | 'json'): void {\n
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/output.ts"],"sourcesContent":["// rows -> table (default) | json\n\nexport type Row = Record<string, unknown>;\n\n// Warned about on stderr (keeps --format json stdout machine-readable), never truncated.\nconst OVERSIZE_BYTES = 50_000;\n\n// Flatten embedded newlines so they can't break table column alignment; JSON output is left alone.\nfunction cell(value: unknown): string {\n return String(value ?? '').replace(/\\s*\\n\\s*/g, ' ');\n}\n\nexport function printRows(rows: Row[], format: 'table' | 'json'): void {\n const rendered = render(rows, format);\n console.log(rendered);\n if (rendered.length > OVERSIZE_BYTES) {\n console.warn(`warning: result is ${Math.round(rendered.length / 1000)} KB across ${rows.length} row(s); consider a LIMIT, fewer columns, or snippet() instead of whole values`);\n }\n}\n\nfunction render(rows: Row[], format: 'table' | 'json'): string {\n if (format === 'json') return JSON.stringify(rows, null, 2);\n if (rows.length === 0) return '(0 rows)';\n\n const columns = Object.keys(rows[0]);\n const cells = rows.map((row) => columns.map((col) => cell(row[col])));\n const widths = columns.map((col, i) => Math.max(col.length, ...cells.map((row) => row[i].length)));\n\n const formatRow = (values: string[]) => values.map((value, i) => value.padEnd(widths[i])).join(' ');\n\n return [formatRow(columns), widths.map((w) => '-'.repeat(w)).join(' '), ...cells.map(formatRow)].join('\\n');\n}\n"],"names":["OVERSIZE_BYTES","cell","value","String","replace","printRows","rows","format","rendered","render","console","log","length","warn","Math","round","JSON","stringify","columns","Object","keys","cells","map","row","col","widths","i","max","formatRow","values","padEnd","join","w","repeat"],"mappings":"AAAA,iCAAiC;AAIjC,yFAAyF;AACzF,MAAMA,iBAAiB;AAEvB,mGAAmG;AACnG,SAASC,KAAKC,KAAc;IAC1B,OAAOC,OAAOD,kBAAAA,mBAAAA,QAAS,IAAIE,OAAO,CAAC,aAAa;AAClD;AAEA,OAAO,SAASC,UAAUC,IAAW,EAAEC,MAAwB;IAC7D,MAAMC,WAAWC,OAAOH,MAAMC;IAC9BG,QAAQC,GAAG,CAACH;IACZ,IAAIA,SAASI,MAAM,GAAGZ,gBAAgB;QACpCU,QAAQG,IAAI,CAAC,CAAC,mBAAmB,EAAEC,KAAKC,KAAK,CAACP,SAASI,MAAM,GAAG,MAAM,WAAW,EAAEN,KAAKM,MAAM,CAAC,8EAA8E,CAAC;IAChL;AACF;AAEA,SAASH,OAAOH,IAAW,EAAEC,MAAwB;IACnD,IAAIA,WAAW,QAAQ,OAAOS,KAAKC,SAAS,CAACX,MAAM,MAAM;IACzD,IAAIA,KAAKM,MAAM,KAAK,GAAG,OAAO;IAE9B,MAAMM,UAAUC,OAAOC,IAAI,CAACd,IAAI,CAAC,EAAE;IACnC,MAAMe,QAAQf,KAAKgB,GAAG,CAAC,CAACC,MAAQL,QAAQI,GAAG,CAAC,CAACE,MAAQvB,KAAKsB,GAAG,CAACC,IAAI;IAClE,MAAMC,SAASP,QAAQI,GAAG,CAAC,CAACE,KAAKE,IAAMZ,KAAKa,GAAG,CAACH,IAAIZ,MAAM,KAAKS,MAAMC,GAAG,CAAC,CAACC,MAAQA,GAAG,CAACG,EAAE,CAACd,MAAM;IAE/F,MAAMgB,YAAY,CAACC,SAAqBA,OAAOP,GAAG,CAAC,CAACpB,OAAOwB,IAAMxB,MAAM4B,MAAM,CAACL,MAAM,CAACC,EAAE,GAAGK,IAAI,CAAC;IAE/F,OAAO;QAACH,UAAUV;QAAUO,OAAOH,GAAG,CAAC,CAACU,IAAM,IAAIC,MAAM,CAACD,IAAID,IAAI,CAAC;WAAUV,MAAMC,GAAG,CAACM;KAAW,CAACG,IAAI,CAAC;AACzG"}
|
package/dist/esm/scan.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface ParsedDoc {
|
|
|
11
11
|
mtimeMs: number;
|
|
12
12
|
size: number;
|
|
13
13
|
data: Record<string, string | number | null>;
|
|
14
|
+
search: {
|
|
15
|
+
title: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
text: string;
|
|
18
|
+
};
|
|
14
19
|
}
|
|
15
20
|
export declare function parseFile(file: FileStat): {
|
|
16
21
|
doc: ParsedDoc;
|
package/dist/esm/scan.js
CHANGED
|
@@ -2,20 +2,27 @@ import { readFileSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import fastGlob from 'fast-glob';
|
|
4
4
|
import matter from 'gray-matter';
|
|
5
|
-
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// Reserved `docs` columns: the real file path plus the mtime/size pair used
|
|
9
|
-
// to detect staleness. A frontmatter key literally named one of these would
|
|
10
|
-
// collide, so it's dropped (with a warning returned to the caller) rather
|
|
11
|
-
// than clobbering the column.
|
|
5
|
+
import removeMarkdown from 'remove-markdown';
|
|
6
|
+
// Filesystem -> rows. Pure data in, data + warnings out; db.ts does the SQL.
|
|
7
|
+
// Reserved: colliding with these would clash with the `frontmatter` table's own columns or the `content` FTS5 table.
|
|
12
8
|
const RESERVED_COLUMNS = new Set([
|
|
13
9
|
'path',
|
|
14
10
|
'_mtime',
|
|
15
|
-
'_size'
|
|
11
|
+
'_size',
|
|
12
|
+
'content'
|
|
16
13
|
]);
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
function normalizeText(value) {
|
|
15
|
+
if (value === null || value === undefined) return '';
|
|
16
|
+
return String(value).replace(/\s+/g, ' ').trim();
|
|
17
|
+
}
|
|
18
|
+
// Markdown is stripped at index time so snippets read as clean prose (also improves matching, e.g. `**bold**` indexes as `bold`).
|
|
19
|
+
// remove-markdown doesn't cover Obsidian wikilinks or table scaffolding, so those are handled first/after.
|
|
20
|
+
function stripText(value) {
|
|
21
|
+
const withoutWikilinks = value.replace(/\[\[([^\]|]+)\|([^\]]+)\]\]/g, '$2').replace(/\[\[([^\]]+)\]\]/g, '$1');
|
|
22
|
+
const withoutMarkdown = removeMarkdown(withoutWikilinks);
|
|
23
|
+
const withoutTables = withoutMarkdown.replace(/^\s*\|?[-\s|:]+\|\s*$/gm, '').replace(/\|/g, ' ');
|
|
24
|
+
return normalizeText(withoutTables);
|
|
25
|
+
}
|
|
19
26
|
export function listFiles(cfg, baseDir) {
|
|
20
27
|
const relPaths = fastGlob.sync(cfg.scan.include, {
|
|
21
28
|
cwd: baseDir
|
|
@@ -31,23 +38,16 @@ export function listFiles(cfg, baseDir) {
|
|
|
31
38
|
};
|
|
32
39
|
});
|
|
33
40
|
}
|
|
34
|
-
// Value mapping: strings/numbers as-is; booleans -> 0/1; dates -> ISO
|
|
35
|
-
// strings (lexicographic = chronological, no date lib); arrays/objects ->
|
|
36
|
-
// JSON text. `null`/`undefined` map to SQL NULL.
|
|
37
41
|
function mapValue(value) {
|
|
38
42
|
if (value === null || value === undefined) return null;
|
|
39
43
|
if (value instanceof Date) return value.toISOString();
|
|
40
44
|
if (typeof value === 'boolean') return value ? 1 : 0;
|
|
41
45
|
if (typeof value === 'string' || typeof value === 'number') return value;
|
|
42
|
-
// arrays and plain objects
|
|
43
46
|
return JSON.stringify(value);
|
|
44
47
|
}
|
|
45
|
-
// Read + parse one file's frontmatter, mapping values and dropping reserved
|
|
46
|
-
// keys. Returns the parsed doc plus any warnings (e.g. a reserved key
|
|
47
|
-
// collision) for the caller to surface.
|
|
48
48
|
export function parseFile(file) {
|
|
49
49
|
const raw = readFileSync(file.absPath, 'utf8');
|
|
50
|
-
const { data } = matter(raw);
|
|
50
|
+
const { data, content } = matter(raw);
|
|
51
51
|
const warnings = [];
|
|
52
52
|
const mapped = {};
|
|
53
53
|
for (const key of Object.keys(data)){
|
|
@@ -62,7 +62,14 @@ export function parseFile(file) {
|
|
|
62
62
|
relPath: file.relPath,
|
|
63
63
|
mtimeMs: file.mtimeMs,
|
|
64
64
|
size: file.size,
|
|
65
|
-
data: mapped
|
|
65
|
+
data: mapped,
|
|
66
|
+
search: {
|
|
67
|
+
// title/summary are plain YAML strings -- whitespace-collapse only;
|
|
68
|
+
// the prose gets the full markdown strip.
|
|
69
|
+
title: normalizeText(data.title),
|
|
70
|
+
summary: normalizeText(data.summary),
|
|
71
|
+
text: stripText(content)
|
|
72
|
+
}
|
|
66
73
|
},
|
|
67
74
|
warnings
|
|
68
75
|
};
|
package/dist/esm/scan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan.ts"],"sourcesContent":["import { readFileSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport fastGlob from 'fast-glob';\nimport matter from 'gray-matter';\nimport type { Config } from './config.ts';\n\n// Filesystem -> rows. Pure data in, data + warnings out
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/scan.ts"],"sourcesContent":["import { readFileSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport fastGlob from 'fast-glob';\nimport matter from 'gray-matter';\nimport removeMarkdown from 'remove-markdown';\nimport type { Config } from './config.ts';\n\n// Filesystem -> rows. Pure data in, data + warnings out; db.ts does the SQL.\n\n// Reserved: colliding with these would clash with the `frontmatter` table's own columns or the `content` FTS5 table.\nconst RESERVED_COLUMNS = new Set(['path', '_mtime', '_size', 'content']);\n\nfunction normalizeText(value: unknown): string {\n if (value === null || value === undefined) return '';\n return String(value).replace(/\\s+/g, ' ').trim();\n}\n\n// Markdown is stripped at index time so snippets read as clean prose (also improves matching, e.g. `**bold**` indexes as `bold`).\n// remove-markdown doesn't cover Obsidian wikilinks or table scaffolding, so those are handled first/after.\nfunction stripText(value: string): string {\n const withoutWikilinks = value.replace(/\\[\\[([^\\]|]+)\\|([^\\]]+)\\]\\]/g, '$2').replace(/\\[\\[([^\\]]+)\\]\\]/g, '$1');\n const withoutMarkdown = removeMarkdown(withoutWikilinks);\n const withoutTables = withoutMarkdown.replace(/^\\s*\\|?[-\\s|:]+\\|\\s*$/gm, '').replace(/\\|/g, ' ');\n return normalizeText(withoutTables);\n}\n\nexport interface FileStat {\n relPath: string;\n absPath: string;\n mtimeMs: number;\n size: number;\n}\n\nexport function listFiles(cfg: Config, baseDir: string): FileStat[] {\n const relPaths = fastGlob.sync(cfg.scan.include, { cwd: baseDir }).sort();\n return relPaths.map((relPath) => {\n const absPath = join(baseDir, relPath);\n const st = statSync(absPath);\n return { relPath, absPath, mtimeMs: st.mtimeMs, size: st.size };\n });\n}\n\nexport interface ParsedDoc {\n relPath: string;\n mtimeMs: number;\n size: number;\n data: Record<string, string | number | null>;\n // title/summary are duplicated from frontmatter so bm25() can weight them above the body text.\n search: { title: string; summary: string; text: string };\n}\n\nfunction mapValue(value: unknown): string | number | null {\n if (value === null || value === undefined) return null;\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'boolean') return value ? 1 : 0;\n if (typeof value === 'string' || typeof value === 'number') return value;\n return JSON.stringify(value);\n}\n\nexport function parseFile(file: FileStat): { doc: ParsedDoc; warnings: string[] } {\n const raw = readFileSync(file.absPath, 'utf8');\n const { data, content } = matter(raw);\n\n const warnings: string[] = [];\n const mapped: Record<string, string | number | null> = {};\n\n for (const key of Object.keys(data)) {\n if (RESERVED_COLUMNS.has(key)) {\n warnings.push(`warning: ${file.relPath} has a frontmatter key named \"${key}\", which is reserved; ignoring it`);\n continue;\n }\n mapped[key] = mapValue(data[key]);\n }\n\n return {\n doc: {\n relPath: file.relPath,\n mtimeMs: file.mtimeMs,\n size: file.size,\n data: mapped,\n search: {\n // title/summary are plain YAML strings -- whitespace-collapse only;\n // the prose gets the full markdown strip.\n title: normalizeText(data.title),\n summary: normalizeText(data.summary),\n text: stripText(content),\n },\n },\n warnings,\n };\n}\n"],"names":["readFileSync","statSync","join","fastGlob","matter","removeMarkdown","RESERVED_COLUMNS","Set","normalizeText","value","undefined","String","replace","trim","stripText","withoutWikilinks","withoutMarkdown","withoutTables","listFiles","cfg","baseDir","relPaths","sync","scan","include","cwd","sort","map","relPath","absPath","st","mtimeMs","size","mapValue","Date","toISOString","JSON","stringify","parseFile","file","raw","data","content","warnings","mapped","key","Object","keys","has","push","doc","search","title","summary","text"],"mappings":"AAAA,SAASA,YAAY,EAAEC,QAAQ,QAAQ,UAAU;AACjD,SAASC,IAAI,QAAQ,YAAY;AACjC,OAAOC,cAAc,YAAY;AACjC,OAAOC,YAAY,cAAc;AACjC,OAAOC,oBAAoB,kBAAkB;AAG7C,6EAA6E;AAE7E,qHAAqH;AACrH,MAAMC,mBAAmB,IAAIC,IAAI;IAAC;IAAQ;IAAU;IAAS;CAAU;AAEvE,SAASC,cAAcC,KAAc;IACnC,IAAIA,UAAU,QAAQA,UAAUC,WAAW,OAAO;IAClD,OAAOC,OAAOF,OAAOG,OAAO,CAAC,QAAQ,KAAKC,IAAI;AAChD;AAEA,kIAAkI;AAClI,2GAA2G;AAC3G,SAASC,UAAUL,KAAa;IAC9B,MAAMM,mBAAmBN,MAAMG,OAAO,CAAC,gCAAgC,MAAMA,OAAO,CAAC,qBAAqB;IAC1G,MAAMI,kBAAkBX,eAAeU;IACvC,MAAME,gBAAgBD,gBAAgBJ,OAAO,CAAC,2BAA2B,IAAIA,OAAO,CAAC,OAAO;IAC5F,OAAOJ,cAAcS;AACvB;AASA,OAAO,SAASC,UAAUC,GAAW,EAAEC,OAAe;IACpD,MAAMC,WAAWlB,SAASmB,IAAI,CAACH,IAAII,IAAI,CAACC,OAAO,EAAE;QAAEC,KAAKL;IAAQ,GAAGM,IAAI;IACvE,OAAOL,SAASM,GAAG,CAAC,CAACC;QACnB,MAAMC,UAAU3B,KAAKkB,SAASQ;QAC9B,MAAME,KAAK7B,SAAS4B;QACpB,OAAO;YAAED;YAASC;YAASE,SAASD,GAAGC,OAAO;YAAEC,MAAMF,GAAGE,IAAI;QAAC;IAChE;AACF;AAWA,SAASC,SAASxB,KAAc;IAC9B,IAAIA,UAAU,QAAQA,UAAUC,WAAW,OAAO;IAClD,IAAID,iBAAiByB,MAAM,OAAOzB,MAAM0B,WAAW;IACnD,IAAI,OAAO1B,UAAU,WAAW,OAAOA,QAAQ,IAAI;IACnD,IAAI,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAAU,OAAOA;IACnE,OAAO2B,KAAKC,SAAS,CAAC5B;AACxB;AAEA,OAAO,SAAS6B,UAAUC,IAAc;IACtC,MAAMC,MAAMxC,aAAauC,KAAKV,OAAO,EAAE;IACvC,MAAM,EAAEY,IAAI,EAAEC,OAAO,EAAE,GAAGtC,OAAOoC;IAEjC,MAAMG,WAAqB,EAAE;IAC7B,MAAMC,SAAiD,CAAC;IAExD,KAAK,MAAMC,OAAOC,OAAOC,IAAI,CAACN,MAAO;QACnC,IAAInC,iBAAiB0C,GAAG,CAACH,MAAM;YAC7BF,SAASM,IAAI,CAAC,CAAC,SAAS,EAAEV,KAAKX,OAAO,CAAC,8BAA8B,EAAEiB,IAAI,iCAAiC,CAAC;YAC7G;QACF;QACAD,MAAM,CAACC,IAAI,GAAGZ,SAASQ,IAAI,CAACI,IAAI;IAClC;IAEA,OAAO;QACLK,KAAK;YACHtB,SAASW,KAAKX,OAAO;YACrBG,SAASQ,KAAKR,OAAO;YACrBC,MAAMO,KAAKP,IAAI;YACfS,MAAMG;YACNO,QAAQ;gBACN,oEAAoE;gBACpE,0CAA0C;gBAC1CC,OAAO5C,cAAciC,KAAKW,KAAK;gBAC/BC,SAAS7C,cAAciC,KAAKY,OAAO;gBACnCC,MAAMxC,UAAU4B;YAClB;QACF;QACAC;IACF;AACF"}
|
package/dist/esm/watch.js
CHANGED
|
@@ -2,19 +2,11 @@ import { watch as fsWatch } from 'node:fs';
|
|
|
2
2
|
import { STATE_DIR } from './config.js';
|
|
3
3
|
import { docCount, getMeta, open, reconcile, setMeta } from './db.js';
|
|
4
4
|
import { SenseError } from './errors.js';
|
|
5
|
-
// Watch is a cache pre-warmer, not a correctness mechanism:
|
|
6
|
-
// `open()` reconciles against the filesystem regardless, so a missed or
|
|
7
|
-
// coalesced fs event can never change a query result -- it only moves one
|
|
8
|
-
// file's parse from ahead-of-time to query-time. That's why any event, of
|
|
9
|
-
// any kind, triggers the exact same response: a debounced full reconcile.
|
|
10
|
-
// Per-file event handling would be complexity with no correctness payoff.
|
|
5
|
+
// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.
|
|
11
6
|
const DEBOUNCE_MS = 200;
|
|
12
7
|
const HEARTBEAT_INTERVAL_MS = 5000;
|
|
13
8
|
const STALE_HEARTBEAT_MS = 15000;
|
|
14
|
-
// Runs in the foreground until SIGINT/SIGTERM
|
|
15
|
-
// process lifecycle belongs to the OS (launchd/systemd/terminal). Resolves
|
|
16
|
-
// on clean shutdown; throws SenseError("WATCH_ACTIVE", ...) if another
|
|
17
|
-
// watcher's heartbeat is still fresh and `--force` wasn't given.
|
|
9
|
+
// Runs in the foreground until SIGINT/SIGTERM. Throws WATCH_ACTIVE if another watcher's heartbeat is still fresh and --force wasn't given.
|
|
18
10
|
export async function runWatch(cfg, opts = {}) {
|
|
19
11
|
var _opts_onEvent;
|
|
20
12
|
const onEvent = (_opts_onEvent = opts.onEvent) !== null && _opts_onEvent !== void 0 ? _opts_onEvent : ()=>{};
|
|
@@ -67,9 +59,7 @@ export async function runWatch(cfg, opts = {}) {
|
|
|
67
59
|
}
|
|
68
60
|
}, DEBOUNCE_MS);
|
|
69
61
|
};
|
|
70
|
-
// Ignore
|
|
71
|
-
// every few seconds, and without this filter each write would schedule a
|
|
72
|
-
// (harmless but pointless) reconcile forever — a perpetual self-trigger.
|
|
62
|
+
// Ignore our own state dir, or the heartbeat write would retrigger itself forever.
|
|
73
63
|
const watcher = fsWatch(baseDir, {
|
|
74
64
|
recursive: true
|
|
75
65
|
}, (_event, filename)=>{
|
package/dist/esm/watch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config.ts';\nimport { STATE_DIR } from './config.ts';\nimport { docCount, getMeta, open, reconcile, setMeta } from './db.ts';\nimport { SenseError } from './errors.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism:
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/watch.ts"],"sourcesContent":["import { watch as fsWatch } from 'node:fs';\nimport type { ResolvedConfig } from './config.ts';\nimport { STATE_DIR } from './config.ts';\nimport { docCount, getMeta, open, reconcile, setMeta } from './db.ts';\nimport { SenseError } from './errors.ts';\n\n// Watch is a cache pre-warmer, not a correctness mechanism: open() always reconciles anyway, so any fs event just triggers a debounced full reconcile.\nconst DEBOUNCE_MS = 200;\nconst HEARTBEAT_INTERVAL_MS = 5000;\nconst STALE_HEARTBEAT_MS = 15000;\n\nexport type WatchEvent = { type: 'started'; baseDir: string; dbPath: string } | { type: 'reconciled'; parsed: number; total: number; warnings: string[] } | { type: 'reconcile-error'; message: string };\n\nexport interface WatchOptions {\n force?: boolean;\n onEvent?: (event: WatchEvent) => void;\n}\n\n// Runs in the foreground until SIGINT/SIGTERM. Throws WATCH_ACTIVE if another watcher's heartbeat is still fresh and --force wasn't given.\nexport async function runWatch(cfg: ResolvedConfig, opts: WatchOptions = {}): Promise<void> {\n const onEvent = opts.onEvent ?? (() => {});\n const { db, dbPath, warnings: initialWarnings, parsed: initialParsed } = open(cfg);\n const baseDir = cfg.baseDir;\n\n const existingHeartbeat = getMeta(db, 'watch_heartbeat');\n if (existingHeartbeat && !opts.force) {\n const age = Date.now() - Date.parse(existingHeartbeat);\n if (age >= 0 && age < STALE_HEARTBEAT_MS) {\n db.close();\n throw new SenseError('WATCH_ACTIVE', `another watcher appears active (heartbeat ${Math.round(age / 1000)}s ago); use --force to override`);\n }\n }\n\n onEvent({ type: 'started', baseDir, dbPath });\n if (initialWarnings.length > 0 || initialParsed > 0) {\n onEvent({ type: 'reconciled', parsed: initialParsed, total: docCount(db), warnings: initialWarnings });\n }\n\n const touchHeartbeat = () => {\n setMeta(db, 'watch_heartbeat', new Date().toISOString());\n setMeta(db, 'watch_pid', String(process.pid));\n };\n touchHeartbeat();\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const scheduleReconcile = () => {\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n debounceTimer = null;\n try {\n const { parsed, warnings } = reconcile(db, cfg, baseDir);\n onEvent({ type: 'reconciled', parsed, total: docCount(db), warnings });\n } catch (err) {\n onEvent({ type: 'reconcile-error', message: (err as Error).message });\n }\n }, DEBOUNCE_MS);\n };\n\n // Ignore our own state dir, or the heartbeat write would retrigger itself forever.\n const watcher = fsWatch(baseDir, { recursive: true }, (_event, filename) => {\n if (typeof filename === 'string' && filename.startsWith(STATE_DIR)) return;\n scheduleReconcile();\n });\n const heartbeatTimer = setInterval(touchHeartbeat, HEARTBEAT_INTERVAL_MS);\n\n return new Promise<void>((resolveShutdown) => {\n const cleanExit = () => {\n clearInterval(heartbeatTimer);\n if (debounceTimer) clearTimeout(debounceTimer);\n watcher.close();\n setMeta(db, 'watch_heartbeat', null);\n setMeta(db, 'watch_pid', null);\n db.close();\n resolveShutdown();\n };\n process.once('SIGINT', cleanExit);\n process.once('SIGTERM', cleanExit);\n });\n}\n"],"names":["watch","fsWatch","STATE_DIR","docCount","getMeta","open","reconcile","setMeta","SenseError","DEBOUNCE_MS","HEARTBEAT_INTERVAL_MS","STALE_HEARTBEAT_MS","runWatch","cfg","opts","onEvent","db","dbPath","warnings","initialWarnings","parsed","initialParsed","baseDir","existingHeartbeat","force","age","Date","now","parse","close","Math","round","type","length","total","touchHeartbeat","toISOString","String","process","pid","debounceTimer","scheduleReconcile","clearTimeout","setTimeout","err","message","watcher","recursive","_event","filename","startsWith","heartbeatTimer","setInterval","Promise","resolveShutdown","cleanExit","clearInterval","once"],"mappings":"AAAA,SAASA,SAASC,OAAO,QAAQ,UAAU;AAE3C,SAASC,SAAS,QAAQ,cAAc;AACxC,SAASC,QAAQ,EAAEC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,OAAO,QAAQ,UAAU;AACtE,SAASC,UAAU,QAAQ,cAAc;AAEzC,uJAAuJ;AACvJ,MAAMC,cAAc;AACpB,MAAMC,wBAAwB;AAC9B,MAAMC,qBAAqB;AAS3B,2IAA2I;AAC3I,OAAO,eAAeC,SAASC,GAAmB,EAAEC,OAAqB,CAAC,CAAC;QACzDA;IAAhB,MAAMC,WAAUD,gBAAAA,KAAKC,OAAO,cAAZD,2BAAAA,gBAAiB,KAAO;IACxC,MAAM,EAAEE,EAAE,EAAEC,MAAM,EAAEC,UAAUC,eAAe,EAAEC,QAAQC,aAAa,EAAE,GAAGhB,KAAKQ;IAC9E,MAAMS,UAAUT,IAAIS,OAAO;IAE3B,MAAMC,oBAAoBnB,QAAQY,IAAI;IACtC,IAAIO,qBAAqB,CAACT,KAAKU,KAAK,EAAE;QACpC,MAAMC,MAAMC,KAAKC,GAAG,KAAKD,KAAKE,KAAK,CAACL;QACpC,IAAIE,OAAO,KAAKA,MAAMd,oBAAoB;YACxCK,GAAGa,KAAK;YACR,MAAM,IAAIrB,WAAW,gBAAgB,CAAC,0CAA0C,EAAEsB,KAAKC,KAAK,CAACN,MAAM,MAAM,+BAA+B,CAAC;QAC3I;IACF;IAEAV,QAAQ;QAAEiB,MAAM;QAAWV;QAASL;IAAO;IAC3C,IAAIE,gBAAgBc,MAAM,GAAG,KAAKZ,gBAAgB,GAAG;QACnDN,QAAQ;YAAEiB,MAAM;YAAcZ,QAAQC;YAAea,OAAO/B,SAASa;YAAKE,UAAUC;QAAgB;IACtG;IAEA,MAAMgB,iBAAiB;QACrB5B,QAAQS,IAAI,mBAAmB,IAAIU,OAAOU,WAAW;QACrD7B,QAAQS,IAAI,aAAaqB,OAAOC,QAAQC,GAAG;IAC7C;IACAJ;IAEA,IAAIK,gBAAuC;IAC3C,MAAMC,oBAAoB;QACxB,IAAID,eAAeE,aAAaF;QAChCA,gBAAgBG,WAAW;YACzBH,gBAAgB;YAChB,IAAI;gBACF,MAAM,EAAEpB,MAAM,EAAEF,QAAQ,EAAE,GAAGZ,UAAUU,IAAIH,KAAKS;gBAChDP,QAAQ;oBAAEiB,MAAM;oBAAcZ;oBAAQc,OAAO/B,SAASa;oBAAKE;gBAAS;YACtE,EAAE,OAAO0B,KAAK;gBACZ7B,QAAQ;oBAAEiB,MAAM;oBAAmBa,SAAS,AAACD,IAAcC,OAAO;gBAAC;YACrE;QACF,GAAGpC;IACL;IAEA,mFAAmF;IACnF,MAAMqC,UAAU7C,QAAQqB,SAAS;QAAEyB,WAAW;IAAK,GAAG,CAACC,QAAQC;QAC7D,IAAI,OAAOA,aAAa,YAAYA,SAASC,UAAU,CAAChD,YAAY;QACpEuC;IACF;IACA,MAAMU,iBAAiBC,YAAYjB,gBAAgBzB;IAEnD,OAAO,IAAI2C,QAAc,CAACC;QACxB,MAAMC,YAAY;YAChBC,cAAcL;YACd,IAAIX,eAAeE,aAAaF;YAChCM,QAAQjB,KAAK;YACbtB,QAAQS,IAAI,mBAAmB;YAC/BT,QAAQS,IAAI,aAAa;YACzBA,GAAGa,KAAK;YACRyB;QACF;QACAhB,QAAQmB,IAAI,CAAC,UAAUF;QACvBjB,QAAQmB,IAAI,CAAC,WAAWF;IAC1B;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sensemaking",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Query a knowledge base you build with an agent: filter notes by frontmatter, then search inside them",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"fast-glob": "^3.3.3",
|
|
51
|
-
"gray-matter": "^4.0.3"
|
|
51
|
+
"gray-matter": "^4.0.3",
|
|
52
|
+
"remove-markdown": "^0.6.4"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"@types/mocha": "*",
|