sensemaking 0.4.0 → 0.5.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 +3 -1
- package/dist/cjs/config.js +36 -3
- package/dist/cjs/config.js.map +1 -1
- package/dist/cjs/errors.d.cts +1 -1
- package/dist/cjs/errors.d.ts +1 -1
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/index.d.cts +2 -9
- package/dist/cjs/index.d.ts +2 -9
- package/dist/cjs/index.js +2 -52
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/verbs.js +5 -1
- package/dist/cjs/verbs.js.map +1 -1
- package/dist/esm/config.js +26 -3
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/errors.d.ts +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +2 -9
- package/dist/esm/index.js +4 -6
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/verbs.js +5 -1
- package/dist/esm/verbs.js.map +1 -1
- package/package.json +1 -1
- package/schema.json +1 -1
- package/skills/sense/SKILL.md +4 -1
package/README.md
CHANGED
|
@@ -94,7 +94,9 @@ noted on stderr.
|
|
|
94
94
|
## Reference
|
|
95
95
|
|
|
96
96
|
- `has(field, value)` — the one custom SQL function: array membership on JSON-array fields,
|
|
97
|
-
substring on strings, false on missing
|
|
97
|
+
substring on strings (so `has(f.status, 'active')` also matches `inactive`), false on missing
|
|
98
|
+
keys. Exact matches: `=` for scalars, `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)`
|
|
99
|
+
for array members.
|
|
98
100
|
- Reserved frontmatter keys (dropped with a warning): `path`, `_mtime`, `_size`, `_rank`,
|
|
99
101
|
`content`, `links`, `sections`.
|
|
100
102
|
- FTS5 syntax in `MATCH`: `a OR b`, `"phrase"`, `pref*`, `NEAR(a b, 5)`, `summary: term`.
|
package/dist/cjs/config.js
CHANGED
|
@@ -95,6 +95,10 @@ function _object_spread_props(target, source) {
|
|
|
95
95
|
}
|
|
96
96
|
return target;
|
|
97
97
|
}
|
|
98
|
+
function _type_of(obj) {
|
|
99
|
+
"@swc/helpers - typeof";
|
|
100
|
+
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
101
|
+
}
|
|
98
102
|
var CONFIG_FILENAME = 'sense.config.json';
|
|
99
103
|
var STATE_DIR = '.sense';
|
|
100
104
|
var SUPPORTED_CONFIG_VERSION = 2;
|
|
@@ -179,8 +183,34 @@ function findConfigPath(startDir) {
|
|
|
179
183
|
dir = parent;
|
|
180
184
|
}
|
|
181
185
|
}
|
|
186
|
+
// Shape check for hand-edited files: a typo'd config fails with a named error, not a
|
|
187
|
+
// TypeError from whatever code touched the missing field first. `queries` is optional
|
|
188
|
+
// on disk (absent = none); `scan.include` has no usable default.
|
|
189
|
+
function validateConfig(parsed, configPath) {
|
|
190
|
+
if ((typeof parsed === "undefined" ? "undefined" : _type_of(parsed)) !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
191
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": config must be a JSON object"));
|
|
192
|
+
}
|
|
193
|
+
var cfg = parsed;
|
|
194
|
+
var scan = cfg.scan;
|
|
195
|
+
if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every(function(g) {
|
|
196
|
+
return typeof g === 'string';
|
|
197
|
+
})) {
|
|
198
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": scan.include must be a non-empty array of glob strings"));
|
|
199
|
+
}
|
|
200
|
+
if (cfg.queries === undefined) cfg.queries = {};
|
|
201
|
+
if (_type_of(cfg.queries) !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every(function(q) {
|
|
202
|
+
return typeof q === 'string';
|
|
203
|
+
})) {
|
|
204
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": queries must be an object of name -> SQL string"));
|
|
205
|
+
}
|
|
206
|
+
if (cfg.features !== undefined && (_type_of(cfg.features) !== 'object' || cfg.features === null || Array.isArray(cfg.features) || !Object.values(cfg.features).every(function(v) {
|
|
207
|
+
return typeof v === 'boolean';
|
|
208
|
+
}))) {
|
|
209
|
+
throw new _errorsts.SenseError('CONFIG_INVALID', "".concat(configPath, ": features must be an object of name -> boolean"));
|
|
210
|
+
}
|
|
211
|
+
return cfg;
|
|
212
|
+
}
|
|
182
213
|
function loadConfig(explicitPath) {
|
|
183
|
-
var _cfg_version;
|
|
184
214
|
var configPath;
|
|
185
215
|
if (explicitPath) {
|
|
186
216
|
configPath = (0, _nodepath.resolve)(process.cwd(), explicitPath);
|
|
@@ -193,11 +223,14 @@ function loadConfig(explicitPath) {
|
|
|
193
223
|
configPath = found;
|
|
194
224
|
}
|
|
195
225
|
var raw = (0, _nodefs.readFileSync)(configPath, 'utf8');
|
|
196
|
-
var
|
|
197
|
-
|
|
226
|
+
var parsed = JSON.parse(raw);
|
|
227
|
+
// Version gate before shape validation: a config written by a newer sense should fail
|
|
228
|
+
// with "requires a newer sense", not with shape errors its own version may not have.
|
|
229
|
+
var version = (typeof parsed === "undefined" ? "undefined" : _type_of(parsed)) === 'object' && parsed !== null && typeof parsed.version === 'number' ? parsed.version : 1;
|
|
198
230
|
if (version > SUPPORTED_CONFIG_VERSION) {
|
|
199
231
|
throw new _errorsts.SenseError('CONFIG_VERSION_UNSUPPORTED', "config version ".concat(version, " requires a newer sense"));
|
|
200
232
|
}
|
|
233
|
+
var cfg = validateConfig(parsed, configPath);
|
|
201
234
|
var migratedFrom;
|
|
202
235
|
if (version < SUPPORTED_CONFIG_VERSION) {
|
|
203
236
|
var result = migrateConfig(cfg);
|
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\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\nexport const FEATURE_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\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 features?: Partial<Record<FeatureName, boolean>>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Absent block or key means enabled -- features are opt-out. `rank` additionally requires `links`.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, everything enabled (matches the old implicit behavior of `links` etc. not existing).\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\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 writeFileSync(configPath, `${JSON.stringify(starterConfig(), 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 let 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 let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["CONFIG_FILENAME","FEATURE_NAMES","STATE_DIR","SUPPORTED_CONFIG_VERSION","enabledFeatures","featureEnabled","findConfigPath","initConfig","loadConfig","migrateConfig","cfg","name","enabled","features","filter","MIGRATIONS","version","Object","fromEntries","map","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","join","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","baseDir"],"mappings":"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"}
|
|
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. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\nexport const FEATURE_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\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 features?: Partial<Record<FeatureName, boolean>>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Absent block or key means enabled -- features are opt-out. `rank` additionally requires `links`.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, everything enabled (matches the old implicit behavior of `links` etc. not existing).\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\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 writeFileSync(configPath, `${JSON.stringify(starterConfig(), 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\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features) || !Object.values(cfg.features).every((v) => typeof v === 'boolean'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n return cfg as unknown as Config;\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 parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).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 let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["CONFIG_FILENAME","FEATURE_NAMES","STATE_DIR","SUPPORTED_CONFIG_VERSION","enabledFeatures","featureEnabled","findConfigPath","initConfig","loadConfig","migrateConfig","cfg","name","enabled","features","filter","MIGRATIONS","version","Object","fromEntries","map","from","current","v","step","SenseError","starterConfig","$schema","scan","include","queries","dir","configPath","join","existsSync","writeFileSync","JSON","stringify","startDir","resolve","candidate","parent","dirname","validateConfig","parsed","Array","isArray","length","every","g","undefined","values","q","explicitPath","process","cwd","found","raw","readFileSync","parse","migratedFrom","result","baseDir"],"mappings":"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"}
|
package/dist/cjs/errors.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
1
|
+
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
2
2
|
export declare class SenseError extends Error {
|
|
3
3
|
code: SenseErrorCode;
|
|
4
4
|
constructor(code: SenseErrorCode, message: string);
|
package/dist/cjs/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
1
|
+
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
2
2
|
export declare class SenseError extends Error {
|
|
3
3
|
code: SenseErrorCode;
|
|
4
4
|
constructor(code: SenseErrorCode, message: string);
|
package/dist/cjs/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';\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"}
|
|
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_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';\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.d.cts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
export type { Config, FeatureName, ResolvedConfig } from './config.js';
|
|
2
|
-
export { CONFIG_FILENAME,
|
|
2
|
+
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
|
|
3
3
|
export type { OpenResult } from './db.js';
|
|
4
|
-
export {
|
|
4
|
+
export { open, rebuild } from './db.js';
|
|
5
5
|
export type { SenseErrorCode } from './errors.js';
|
|
6
6
|
export { SenseError } from './errors.js';
|
|
7
|
-
export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
|
|
8
|
-
export type { Section } from './features/sections.js';
|
|
9
|
-
export type { Feature } from './features/types.js';
|
|
10
|
-
export type { Edge } from './graph.js';
|
|
11
|
-
export { pagerank, personalizedRank } from './graph.js';
|
|
12
7
|
export type { Row } from './output.js';
|
|
13
8
|
export { printRows } from './output.js';
|
|
14
|
-
export type { FileStat, ParsedDoc } from './scan.js';
|
|
15
|
-
export { listFiles, parseFile } from './scan.js';
|
|
16
9
|
export type { FindOptions, Peek, TreeMap } from './verbs.js';
|
|
17
10
|
export { find, mapTree, peek } from './verbs.js';
|
|
18
11
|
export type { WatchEvent, WatchOptions } from './watch.js';
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
export type { Config, FeatureName, ResolvedConfig } from './config.js';
|
|
2
|
-
export { CONFIG_FILENAME,
|
|
2
|
+
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
|
|
3
3
|
export type { OpenResult } from './db.js';
|
|
4
|
-
export {
|
|
4
|
+
export { open, rebuild } from './db.js';
|
|
5
5
|
export type { SenseErrorCode } from './errors.js';
|
|
6
6
|
export { SenseError } from './errors.js';
|
|
7
|
-
export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
|
|
8
|
-
export type { Section } from './features/sections.js';
|
|
9
|
-
export type { Feature } from './features/types.js';
|
|
10
|
-
export type { Edge } from './graph.js';
|
|
11
|
-
export { pagerank, personalizedRank } from './graph.js';
|
|
12
7
|
export type { Row } from './output.js';
|
|
13
8
|
export { printRows } from './output.js';
|
|
14
|
-
export type { FileStat, ParsedDoc } from './scan.js';
|
|
15
|
-
export { listFiles, parseFile } from './scan.js';
|
|
16
9
|
export type { FindOptions, Peek, TreeMap } from './verbs.js';
|
|
17
10
|
export { find, mapTree, peek } from './verbs.js';
|
|
18
11
|
export type { WatchEvent, WatchOptions } from './watch.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
// Public library API.
|
|
1
|
+
// Public library API. Deliberately small: every export is a stability promise;
|
|
2
|
+
// internals (feature registry, graph, scan, meta) stay module-private.
|
|
2
3
|
"use strict";
|
|
3
4
|
Object.defineProperty(exports, "__esModule", {
|
|
4
5
|
value: true
|
|
@@ -13,15 +14,6 @@ _export(exports, {
|
|
|
13
14
|
get CONFIG_FILENAME () {
|
|
14
15
|
return _configts.CONFIG_FILENAME;
|
|
15
16
|
},
|
|
16
|
-
get DB_FILENAME () {
|
|
17
|
-
return _dbts.DB_FILENAME;
|
|
18
|
-
},
|
|
19
|
-
get FEATURES () {
|
|
20
|
-
return _indexts.FEATURES;
|
|
21
|
-
},
|
|
22
|
-
get FEATURE_NAMES () {
|
|
23
|
-
return _configts.FEATURE_NAMES;
|
|
24
|
-
},
|
|
25
17
|
get STATE_DIR () {
|
|
26
18
|
return _configts.STATE_DIR;
|
|
27
19
|
},
|
|
@@ -31,36 +23,12 @@ _export(exports, {
|
|
|
31
23
|
get SenseError () {
|
|
32
24
|
return _errorsts.SenseError;
|
|
33
25
|
},
|
|
34
|
-
get activeFeatures () {
|
|
35
|
-
return _indexts.activeFeatures;
|
|
36
|
-
},
|
|
37
|
-
get docCount () {
|
|
38
|
-
return _dbts.docCount;
|
|
39
|
-
},
|
|
40
|
-
get enabledFeatures () {
|
|
41
|
-
return _configts.enabledFeatures;
|
|
42
|
-
},
|
|
43
|
-
get featureEnabled () {
|
|
44
|
-
return _configts.featureEnabled;
|
|
45
|
-
},
|
|
46
26
|
get find () {
|
|
47
27
|
return _verbsts.find;
|
|
48
28
|
},
|
|
49
|
-
get findConfigPath () {
|
|
50
|
-
return _configts.findConfigPath;
|
|
51
|
-
},
|
|
52
|
-
get getMeta () {
|
|
53
|
-
return _dbts.getMeta;
|
|
54
|
-
},
|
|
55
29
|
get initConfig () {
|
|
56
30
|
return _configts.initConfig;
|
|
57
31
|
},
|
|
58
|
-
get linkEdges () {
|
|
59
|
-
return _indexts.linkEdges;
|
|
60
|
-
},
|
|
61
|
-
get listFiles () {
|
|
62
|
-
return _scants.listFiles;
|
|
63
|
-
},
|
|
64
32
|
get loadConfig () {
|
|
65
33
|
return _configts.loadConfig;
|
|
66
34
|
},
|
|
@@ -73,41 +41,23 @@ _export(exports, {
|
|
|
73
41
|
get open () {
|
|
74
42
|
return _dbts.open;
|
|
75
43
|
},
|
|
76
|
-
get pagerank () {
|
|
77
|
-
return _graphts.pagerank;
|
|
78
|
-
},
|
|
79
|
-
get parseFile () {
|
|
80
|
-
return _scants.parseFile;
|
|
81
|
-
},
|
|
82
44
|
get peek () {
|
|
83
45
|
return _verbsts.peek;
|
|
84
46
|
},
|
|
85
|
-
get personalizedRank () {
|
|
86
|
-
return _graphts.personalizedRank;
|
|
87
|
-
},
|
|
88
47
|
get printRows () {
|
|
89
48
|
return _outputts.printRows;
|
|
90
49
|
},
|
|
91
50
|
get rebuild () {
|
|
92
51
|
return _dbts.rebuild;
|
|
93
52
|
},
|
|
94
|
-
get reconcile () {
|
|
95
|
-
return _dbts.reconcile;
|
|
96
|
-
},
|
|
97
53
|
get runWatch () {
|
|
98
54
|
return _watchts.runWatch;
|
|
99
|
-
},
|
|
100
|
-
get setMeta () {
|
|
101
|
-
return _dbts.setMeta;
|
|
102
55
|
}
|
|
103
56
|
});
|
|
104
57
|
var _configts = require("./config.js");
|
|
105
58
|
var _dbts = require("./db.js");
|
|
106
59
|
var _errorsts = require("./errors.js");
|
|
107
|
-
var _indexts = require("./features/index.js");
|
|
108
|
-
var _graphts = require("./graph.js");
|
|
109
60
|
var _outputts = require("./output.js");
|
|
110
|
-
var _scants = require("./scan.js");
|
|
111
61
|
var _verbsts = require("./verbs.js");
|
|
112
62
|
var _watchts = require("./watch.js");
|
|
113
63
|
/* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME,
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\nexport type { OpenResult } from './db.ts';\nexport { open, rebuild } 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 { FindOptions, Peek, TreeMap } from './verbs.ts';\nexport { find, mapTree, peek } from './verbs.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","SenseError","find","initConfig","loadConfig","mapTree","migrateConfig","open","peek","printRows","rebuild","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;;;;;;;;;;;;QAG9DA;eAAAA,yBAAe;;QAAyCC;eAAAA,mBAAS;;QAAEC;eAAAA,kCAAwB;;QAM3FC;eAAAA,oBAAU;;QAMVC;eAAAA,aAAI;;QAZaC;eAAAA,oBAAU;;QAAEC;eAAAA,oBAAU;;QAYjCC;eAAAA,gBAAO;;QAZ4BC;eAAAA,uBAAa;;QAGtDC;eAAAA,UAAI;;QASWC;eAAAA,aAAI;;QAHnBC;eAAAA,mBAAS;;QANHC;eAAAA,aAAO;;QAYbC;eAAAA,iBAAQ;;;wBAf2F;oBAG9E;wBAGH;wBAGD;uBAGU;uBAGX"}
|
package/dist/cjs/verbs.js
CHANGED
|
@@ -98,7 +98,11 @@ function find(db, cfg, terms) {
|
|
|
98
98
|
var fetch = Math.max(k * 3, 30);
|
|
99
99
|
// Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.
|
|
100
100
|
// Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.
|
|
101
|
-
|
|
101
|
+
// --where applies inside the candidate query (a post-filter over the top-N would drop
|
|
102
|
+
// matches ranked past the pool) and again on the final select for link-derived rows.
|
|
103
|
+
var whereJoin = opts.where ? 'JOIN frontmatter f ON f."path" = content.path' : '';
|
|
104
|
+
var whereCond = opts.where ? "AND (".concat(opts.where, ")") : '';
|
|
105
|
+
var matchSql = "SELECT content.path AS path, snippet(content, -1, '\xab', '\xbb', '…', 10) AS hit FROM content ".concat(whereJoin, " WHERE content MATCH ? ").concat(whereCond, " ORDER BY ").concat(WEIGHTED_BM25, " LIMIT ").concat(fetch);
|
|
102
106
|
var matchRows = db.prepare(matchSql).all(terms);
|
|
103
107
|
var hits = new Map(matchRows.map(function(r) {
|
|
104
108
|
return [
|
package/dist/cjs/verbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config } from './config.ts';\nimport { featureEnabled } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\n\n// The three layer verbs: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\nexport function find(db: DatabaseSync, cfg: Config, terms: string, opts: FindOptions = {}): Row[] {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content WHERE content MATCH ? ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n const matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n if (featureEnabled(cfg, 'links') && matchRows.length > 0) {\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, linkEdges(db), seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n db.exec('CREATE TEMP TABLE IF NOT EXISTS _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT)');\n db.exec('DELETE FROM _find');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit) VALUES (?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null);\n\n const where = opts.where ? `WHERE ${opts.where}` : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n const allFields = columns\n .map((name) => {\n const { n } = db.prepare(`SELECT COUNT(\"${name.split('\"').join('\"\"')}\") AS n FROM frontmatter`).get() as { n: number };\n return { field: name, coverage: n };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n}\n\nconst PEEK_LINK_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: Config, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LINK_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n };\n}\n"],"names":["find","mapTree","peek","WEIGHTED_BM25","RRF_K","db","cfg","terms","opts","hits","k","fetch","Math","max","matchSql","matchRows","prepare","all","Map","map","r","path","hit","candidates","forEach","i","set","score","via","featureEnabled","length","nodes","seeds","ranked","personalizedRank","linkEdges","filter","sort","a","b","slice","existing","get","exec","insert","c","run","where","INTERNAL_COLUMNS","Set","docs","columns","name","has","allFields","n","split","join","field","coverage","fields","hubs","recent","fieldsTotal","PEEK_LINK_LIMIT","pathArg","row","paths","p","base","posix","basename","replace","toLowerCase","matches","SenseError","frontmatter","Object","entries","key","value","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal"],"mappings":";;;;;;;;;;;QAsBgBA;eAAAA;;QA2DAC;eAAAA;;QAsCAC;eAAAA;;;4DAvHE;wBAGa;wBACJ;uBACD;uBACO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGjC,4EAA4E;AAC5E,gFAAgF;AAEhF,IAAMC,gBAAgB;AACtB,IAAMC,QAAQ;AASP,SAASJ,KAAKK,EAAgB,EAAEC,GAAW,EAAEC,KAAa;QAAEC,OAAAA,iEAAoB,CAAC;QAC5EA,SAmC2DC;IAnCrE,IAAMC,KAAIF,UAAAA,KAAKE,CAAC,cAANF,qBAAAA,UAAU;IACpB,IAAMG,QAAQC,KAAKC,GAAG,CAACH,IAAI,GAAG;IAE9B,sFAAsF;IACtF,yFAAyF;IACzF,IAAMI,WAAW,AAAC,iIAAiJH,OAAvBR,eAAc,WAAe,OAANQ;IACnK,IAAMI,YAAYV,GAAGW,OAAO,CAACF,UAAUG,GAAG,CAACV;IAE3C,IAAME,OAAO,IAAIS,IAAIH,UAAUI,GAAG,CAAC,SAACC;eAAM;YAACA,EAAEC,IAAI;YAAED,EAAEE,GAAG;SAAC;;IACzD,IAAMC,aAAa,IAAIL;IACvBH,UAAUS,OAAO,CAAC,SAACJ,GAAGK;QACpBF,WAAWG,GAAG,CAACN,EAAEC,IAAI,EAAE;YAAEM,OAAO,IAAKvB,CAAAA,QAAQqB,CAAAA;YAAIG,KAAK;QAAQ;IAChE;IAEA,IAAIC,IAAAA,wBAAc,EAACvB,KAAK,YAAYS,UAAUe,MAAM,GAAG,GAAG;QACxD,IAAMC,QAAQ,AAAC1B,GAAGW,OAAO,CAAC,kCAAkCC,GAAG,GAA+BE,GAAG,CAAC,SAACC;mBAAMA,EAAEC,IAAI;;QAC/G,IAAMW,QAAQ,IAAId,IAAIH,UAAUI,GAAG,CAAC,SAACC,GAAGK;mBAAM;gBAACL,EAAEC,IAAI;gBAAE,IAAKI,CAAAA,IAAI,CAAA;aAAG;;QACnE,IAAMQ,SAAS,AAAC,qBAAGC,IAAAA,yBAAgB,EAACH,OAAOI,IAAAA,kBAAS,EAAC9B,KAAK2B,QACvDI,MAAM,CAAC;qDAAIT;mBAAWA,QAAQ;WAC9BU,IAAI,CAAC,SAACC,GAAGC;mBAAMA,CAAC,CAAC,EAAE,GAAGD,CAAC,CAAC,EAAE;WAC1BE,KAAK,CAAC,GAAG7B;QACZsB,OAAOT,OAAO,CAAC,gBAASC;qDAAPJ;YACf,IAAMoB,WAAWlB,WAAWmB,GAAG,CAACrB;YAChC,IAAIoB,UAAU;gBACZA,SAASd,KAAK,IAAI,IAAKvB,CAAAA,QAAQqB,CAAAA;gBAC/BgB,SAASb,GAAG,GAAG;YACjB,OAAO;gBACLL,WAAWG,GAAG,CAACL,MAAM;oBAAEM,OAAO,IAAKvB,CAAAA,QAAQqB,CAAAA;oBAAIG,KAAK;gBAAO;YAC7D;QACF;IACF;IAEAvB,GAAGsC,IAAI,CAAC;IACRtC,GAAGsC,IAAI,CAAC;IACR,IAAMC,SAASvC,GAAGW,OAAO,CAAC;QACrB,kCAAA,2BAAA;;QAAL,QAAK,YAAmBO,+BAAnB,SAAA,6BAAA,QAAA,yBAAA;YAAA,mCAAA,iBAAOF,uBAAMwB;YAAkBD,OAAOE,GAAG,CAACzB,MAAMwB,EAAElB,KAAK,EAAEkB,EAAEjB,GAAG,GAAEnB,YAAAA,KAAKiC,GAAG,CAACrB,mBAATZ,uBAAAA,YAAkB;;;QAAlF;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAEL,IAAMsC,QAAQvC,KAAKuC,KAAK,GAAG,AAAC,SAAmB,OAAXvC,KAAKuC,KAAK,IAAK;IACnD,OAAO1C,GACJW,OAAO,CACN,AAAC,sOAEQ,OAAN+B,OAAM,uCAEV9B,GAAG,CAACP;AACT;AAUA,IAAMsC,mBAAmB,IAAIC,IAAI;IAAC;IAAQ;IAAU;IAAS;CAAQ;AAG9D,SAAShD,QAAQI,EAAgB,EAAEC,GAAW;IACnD,IAAM4C,OAAO7C,GAAGW,OAAO,CAAC,iFAAiF0B,GAAG;IAE5G,IAAMS,UAAU,AAAC9C,GAAGW,OAAO,CAAC,kCAAkCC,GAAG,GAA+BE,GAAG,CAAC,SAACC;eAAMA,EAAEgC,IAAI;OAAEhB,MAAM,CAAC,SAACgB;eAAS,CAACJ,iBAAiBK,GAAG,CAACD;;IAC1J,IAAME,YAAYH,QACfhC,GAAG,CAAC,SAACiC;QACJ,IAAM,AAAEG,IAAMlD,GAAGW,OAAO,CAAC,AAAC,iBAA2C,OAA3BoC,KAAKI,KAAK,CAAC,KAAKC,IAAI,CAAC,OAAM,6BAA2Bf,GAAG,GAA3Fa;QACR,OAAO;YAAEG,OAAON;YAAMO,UAAUJ;QAAE;IACpC,GACClB,IAAI,CAAC,SAACC,GAAGC;eAAM,AAACA,EAAEoB,QAAQ,GAAerB,EAAEqB,QAAQ;;IACtD,IAAMC,SAASN,UAAUd,KAAK,CAAC,GAAG;IAElC,IAAMqB,OAAOhC,IAAAA,wBAAc,EAACvB,KAAK,UAAWD,GAAGW,OAAO,CAAC,oMAAoMC,GAAG,KAAe,EAAE;IAE/Q,IAAM6C,SAASzD,GAAGW,OAAO,CAAC,uHAAqHC,GAAG;IAElJ,OAAO;QAAEiC,MAAAA;QAAMU,QAAAA;QAAQG,aAAaT,UAAUxB,MAAM;QAAE+B,MAAAA;QAAMC,QAAAA;IAAO;AACrE;AAiBA,IAAME,kBAAkB;AAIjB,SAAS9D,KAAKG,EAAgB,EAAEC,GAAW,EAAE2D,OAAe;QAiC3CC;IAhCtB,IAAMC,QAAQ,AAAC9D,GAAGW,OAAO,CAAC,kCAAkCC,GAAG,GAA+BE,GAAG,CAAC,SAACC;eAAMA,EAAEC,IAAI;;IAC/G,IAAIA,OAAO8C,MAAMnE,IAAI,CAAC,SAACoE;eAAMA,MAAMH;;IACnC,IAAI,CAAC5C,MAAM;QACT,IAAMgD,OAAOC,cAAK,CAACC,QAAQ,CAACN,SAASO,OAAO,CAAC,UAAU,IAAIC,WAAW;QACtE,IAAMC,UAAUP,MAAM/B,MAAM,CAAC,SAACgC;mBAAME,cAAK,CAACC,QAAQ,CAACH,GAAGI,OAAO,CAAC,UAAU,IAAIC,WAAW,OAAOJ;;QAC9F,IAAIK,QAAQ5C,MAAM,KAAK,GAAGT,OAAOqD,OAAO,CAAC,EAAE;aACtC,IAAIA,QAAQ5C,MAAM,GAAG,GAAG,MAAM,IAAI6C,oBAAU,CAAC,kBAAkB,AAAC,IAA6BD,OAA1BT,SAAQ,oBAAqC,OAAnBS,QAAQjB,IAAI,CAAC;aAC1G,MAAM,IAAIkB,oBAAU,CAAC,kBAAkB,AAAC,oBAA2B,OAARV,SAAQ;IAC1E;IAEA,IAAMC,MAAM7D,GAAGW,OAAO,CAAC,8CAA8C0B,GAAG,CAACrB;IACzE,IAAMuD,cAAmB,CAAC;QACrB,kCAAA,2BAAA;;QAAL,QAAK,YAAsBC,OAAOC,OAAO,CAACZ,yBAArC,SAAA,6BAAA,QAAA,yBAAA,iCAA2C;YAA3C,mCAAA,iBAAOa,sBAAKC;YACf,IAAI,CAAChC,iBAAiBK,GAAG,CAAC0B,QAAQC,UAAU,MAAMJ,WAAW,CAACG,IAAI,GAAGC;QACvE;;QAFK;QAAA;;;iBAAA,6BAAA;gBAAA;;;gBAAA;sBAAA;;;;IAIL,IAAMC,WAAWpD,IAAAA,wBAAc,EAACvB,KAAK,cAAeD,GAAGW,OAAO,CAAC,mGAAmGC,GAAG,CAACI,QAAkB,EAAE;IAE1L,IAAI6D,WAAqB,EAAE;IAC3B,IAAIC,YAAsB,EAAE;IAC5B,IAAIC,aAAuB,EAAE;IAC7B,IAAIC,iBAAiB;IACrB,IAAIxD,IAAAA,wBAAc,EAACvB,KAAK,UAAU;QAChC,IAAMgF,MAAMjF,GAAGW,OAAO,CAAC,+DAA+DC,GAAG,CAACI;QAC1F6D,WAAY,qBAAG,IAAIjC,IAAIqC,IAAIlD,MAAM,CAAC,SAACmD;mBAAMA,EAAEC,GAAG,KAAK;WAAMrE,GAAG,CAAC,SAACoE;mBAAMA,EAAEC,GAAG;;QACzEJ,aAAaE,IAAIlD,MAAM,CAAC,SAACmD;mBAAMA,EAAEC,GAAG,KAAK;WAAMrE,GAAG,CAAC,SAACoE;mBAAMA,EAAEE,MAAM;;QAClEJ,iBAAiB,AAAChF,GAAGW,OAAO,CAAC,4DAA4D0B,GAAG,CAACrB,MAAwBkC,CAAC;QACtH4B,YAAY,AAAC9E,GAAGW,OAAO,CAAC,qEAAqEC,GAAG,CAACI,MAAM2C,iBAA4C7C,GAAG,CAAC,SAACC;mBAAMA,EAAEsE,GAAG;;IACrK;IAEA,OAAO;QACLrE,MAAAA;QACAsE,QAAQ/E,KAAKgF,IAAI,CAAC,EAAE1B,aAAAA,IAAI2B,KAAK,cAAT3B,wBAAAA,aAAwB,KAAK;QACjDU,aAAAA;QACAK,UAAAA;QACAC,UAAUA,SAAS1C,KAAK,CAAC,GAAGwB;QAC5BmB,WAAAA;QACAC,YAAYA,WAAW5C,KAAK,CAAC,GAAGwB;QAChC8B,eAAeZ,SAASpD,MAAM;QAC9BuD,gBAAAA;QACAU,iBAAiBX,WAAWtD,MAAM;IACpC;AACF"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config } from './config.ts';\nimport { featureEnabled } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\n\n// The three layer verbs: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\nexport function find(db: DatabaseSync, cfg: Config, terms: string, opts: FindOptions = {}): Row[] {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n // --where applies inside the candidate query (a post-filter over the top-N would drop\n // matches ranked past the pool) and again on the final select for link-derived rows.\n const whereJoin = opts.where ? `JOIN frontmatter f ON f.\"path\" = content.path` : '';\n const whereCond = opts.where ? `AND (${opts.where})` : '';\n const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n const matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n if (featureEnabled(cfg, 'links') && matchRows.length > 0) {\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, linkEdges(db), seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n db.exec('CREATE TEMP TABLE IF NOT EXISTS _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT)');\n db.exec('DELETE FROM _find');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit) VALUES (?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null);\n\n const where = opts.where ? `WHERE ${opts.where}` : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n const allFields = columns\n .map((name) => {\n const { n } = db.prepare(`SELECT COUNT(\"${name.split('\"').join('\"\"')}\") AS n FROM frontmatter`).get() as { n: number };\n return { field: name, coverage: n };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n}\n\nconst PEEK_LINK_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: Config, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LINK_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n };\n}\n"],"names":["find","mapTree","peek","WEIGHTED_BM25","RRF_K","db","cfg","terms","opts","hits","k","fetch","Math","max","whereJoin","where","whereCond","matchSql","matchRows","prepare","all","Map","map","r","path","hit","candidates","forEach","i","set","score","via","featureEnabled","length","nodes","seeds","ranked","personalizedRank","linkEdges","filter","sort","a","b","slice","existing","get","exec","insert","c","run","INTERNAL_COLUMNS","Set","docs","columns","name","has","allFields","n","split","join","field","coverage","fields","hubs","recent","fieldsTotal","PEEK_LINK_LIMIT","pathArg","row","paths","p","base","posix","basename","replace","toLowerCase","matches","SenseError","frontmatter","Object","entries","key","value","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal"],"mappings":"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"}
|
package/dist/esm/config.js
CHANGED
|
@@ -82,8 +82,28 @@ export function findConfigPath(startDir) {
|
|
|
82
82
|
dir = parent;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
// Shape check for hand-edited files: a typo'd config fails with a named error, not a
|
|
86
|
+
// TypeError from whatever code touched the missing field first. `queries` is optional
|
|
87
|
+
// on disk (absent = none); `scan.include` has no usable default.
|
|
88
|
+
function validateConfig(parsed, configPath) {
|
|
89
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
90
|
+
throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);
|
|
91
|
+
}
|
|
92
|
+
const cfg = parsed;
|
|
93
|
+
const scan = cfg.scan;
|
|
94
|
+
if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g)=>typeof g === 'string')) {
|
|
95
|
+
throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);
|
|
96
|
+
}
|
|
97
|
+
if (cfg.queries === undefined) cfg.queries = {};
|
|
98
|
+
if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q)=>typeof q === 'string')) {
|
|
99
|
+
throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);
|
|
100
|
+
}
|
|
101
|
+
if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features) || !Object.values(cfg.features).every((v)=>typeof v === 'boolean'))) {
|
|
102
|
+
throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);
|
|
103
|
+
}
|
|
104
|
+
return cfg;
|
|
105
|
+
}
|
|
85
106
|
export function loadConfig(explicitPath) {
|
|
86
|
-
var _cfg_version;
|
|
87
107
|
let configPath;
|
|
88
108
|
if (explicitPath) {
|
|
89
109
|
configPath = resolve(process.cwd(), explicitPath);
|
|
@@ -96,11 +116,14 @@ export function loadConfig(explicitPath) {
|
|
|
96
116
|
configPath = found;
|
|
97
117
|
}
|
|
98
118
|
const raw = readFileSync(configPath, 'utf8');
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
const parsed = JSON.parse(raw);
|
|
120
|
+
// Version gate before shape validation: a config written by a newer sense should fail
|
|
121
|
+
// with "requires a newer sense", not with shape errors its own version may not have.
|
|
122
|
+
const version = typeof parsed === 'object' && parsed !== null && typeof parsed.version === 'number' ? parsed.version : 1;
|
|
101
123
|
if (version > SUPPORTED_CONFIG_VERSION) {
|
|
102
124
|
throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `config version ${version} requires a newer sense`);
|
|
103
125
|
}
|
|
126
|
+
let cfg = validateConfig(parsed, configPath);
|
|
104
127
|
let migratedFrom;
|
|
105
128
|
if (version < SUPPORTED_CONFIG_VERSION) {
|
|
106
129
|
const result = migrateConfig(cfg);
|
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\nexport const CONFIG_FILENAME = 'sense.config.json';\nexport const STATE_DIR = '.sense';\n\n// Highest sense.config.json `version` this build understands. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\nexport const FEATURE_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\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 features?: Partial<Record<FeatureName, boolean>>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Absent block or key means enabled -- features are opt-out. `rank` additionally requires `links`.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, everything enabled (matches the old implicit behavior of `links` etc. not existing).\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\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 writeFileSync(configPath, `${JSON.stringify(starterConfig(), 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 let 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 let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","FEATURE_NAMES","featureEnabled","cfg","name","enabled","features","enabledFeatures","filter","MIGRATIONS","version","Object","fromEntries","map","migrateConfig","from","current","v","step","starterConfig","$schema","scan","include","queries","initConfig","dir","configPath","JSON","stringify","findConfigPath","startDir","candidate","parent","loadConfig","explicitPath","process","cwd","found","raw","parse","migratedFrom","result","baseDir"],"mappings":"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"}
|
|
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. Older versions auto-migrate on load.\nexport const SUPPORTED_CONFIG_VERSION = 2;\n\n// Each feature owns its tables, parse-time extraction, and reconcile step; verbs degrade when one is off.\nexport const FEATURE_NAMES = ['links', 'sections', 'rank'] as const;\nexport type FeatureName = (typeof FEATURE_NAMES)[number];\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 features?: Partial<Record<FeatureName, boolean>>;\n queries: Record<string, string>;\n}\n\nexport interface ResolvedConfig extends Config {\n baseDir: string;\n configPath: string | null;\n // Set when loadConfig auto-migrated the file on disk; cli reports it.\n migratedFrom?: number;\n}\n\n// Absent block or key means enabled -- features are opt-out. `rank` additionally requires `links`.\nexport function featureEnabled(cfg: Config, name: FeatureName): boolean {\n const enabled = cfg.features?.[name] !== false;\n if (name === 'rank') return enabled && featureEnabled(cfg, 'links');\n return enabled;\n}\n\nexport function enabledFeatures(cfg: Config): FeatureName[] {\n return FEATURE_NAMES.filter((name) => featureEnabled(cfg, name));\n}\n\n// Pure per-version steps; loadConfig chains them from the file's version up to SUPPORTED_CONFIG_VERSION.\nconst MIGRATIONS: Record<number, (cfg: Config) => Config> = {\n // v1 -> v2: features block introduced, everything enabled (matches the old implicit behavior of `links` etc. not existing).\n 1: (cfg) => ({ ...cfg, version: 2, features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'] }),\n};\n\nexport function migrateConfig(cfg: Config): { cfg: Config; from: number } {\n const from = cfg.version ?? 1;\n let current = cfg;\n for (let v = from; v < SUPPORTED_CONFIG_VERSION; v++) {\n const step = MIGRATIONS[v];\n if (!step) throw new SenseError('CONFIG_VERSION_UNSUPPORTED', `no migration from config version ${v}`);\n current = step(current);\n }\n return { cfg: current, from };\n}\n\nfunction starterConfig(): Config {\n return {\n $schema: 'https://unpkg.com/sensemaking/schema.json',\n version: SUPPORTED_CONFIG_VERSION,\n scan: { include: ['**/*.md'] },\n features: Object.fromEntries(FEATURE_NAMES.map((name) => [name, true])) as Config['features'],\n queries: {},\n };\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 writeFileSync(configPath, `${JSON.stringify(starterConfig(), 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\n// Shape check for hand-edited files: a typo'd config fails with a named error, not a\n// TypeError from whatever code touched the missing field first. `queries` is optional\n// on disk (absent = none); `scan.include` has no usable default.\nfunction validateConfig(parsed: unknown, configPath: string): Config {\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: config must be a JSON object`);\n }\n const cfg = parsed as Record<string, unknown>;\n const scan = cfg.scan as { include?: unknown } | undefined;\n if (!scan || !Array.isArray(scan.include) || scan.include.length === 0 || !scan.include.every((g) => typeof g === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: scan.include must be a non-empty array of glob strings`);\n }\n if (cfg.queries === undefined) cfg.queries = {};\n if (typeof cfg.queries !== 'object' || cfg.queries === null || Array.isArray(cfg.queries) || !Object.values(cfg.queries).every((q) => typeof q === 'string')) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: queries must be an object of name -> SQL string`);\n }\n if (cfg.features !== undefined && (typeof cfg.features !== 'object' || cfg.features === null || Array.isArray(cfg.features) || !Object.values(cfg.features).every((v) => typeof v === 'boolean'))) {\n throw new SenseError('CONFIG_INVALID', `${configPath}: features must be an object of name -> boolean`);\n }\n return cfg as unknown as Config;\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 parsed: unknown = JSON.parse(raw);\n\n // Version gate before shape validation: a config written by a newer sense should fail\n // with \"requires a newer sense\", not with shape errors its own version may not have.\n const version = typeof parsed === 'object' && parsed !== null && typeof (parsed as { version?: unknown }).version === 'number' ? (parsed as { version: number }).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 let cfg = validateConfig(parsed, configPath);\n\n let migratedFrom: number | undefined;\n if (version < SUPPORTED_CONFIG_VERSION) {\n const result = migrateConfig(cfg);\n cfg = result.cfg;\n migratedFrom = result.from;\n writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}\\n`);\n }\n\n return { ...cfg, baseDir: dirname(configPath), configPath, migratedFrom };\n}\n"],"names":["existsSync","readFileSync","writeFileSync","dirname","join","resolve","SenseError","CONFIG_FILENAME","STATE_DIR","SUPPORTED_CONFIG_VERSION","FEATURE_NAMES","featureEnabled","cfg","name","enabled","features","enabledFeatures","filter","MIGRATIONS","version","Object","fromEntries","map","migrateConfig","from","current","v","step","starterConfig","$schema","scan","include","queries","initConfig","dir","configPath","JSON","stringify","findConfigPath","startDir","candidate","parent","validateConfig","parsed","Array","isArray","length","every","g","undefined","values","q","loadConfig","explicitPath","process","cwd","found","raw","parse","migratedFrom","result","baseDir"],"mappings":"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"}
|
package/dist/esm/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
1
|
+
export type SenseErrorCode = 'CONFIG_NOT_FOUND' | 'CONFIG_EXISTS' | 'CONFIG_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';
|
|
2
2
|
export declare class SenseError extends Error {
|
|
3
3
|
code: SenseErrorCode;
|
|
4
4
|
constructor(code: SenseErrorCode, message: string);
|
package/dist/esm/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';\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"}
|
|
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_INVALID' | 'CONFIG_VERSION_UNSUPPORTED' | 'WATCH_ACTIVE' | 'NOTE_NOT_FOUND' | 'NOTE_AMBIGUOUS';\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.d.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
export type { Config, FeatureName, ResolvedConfig } from './config.js';
|
|
2
|
-
export { CONFIG_FILENAME,
|
|
2
|
+
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
|
|
3
3
|
export type { OpenResult } from './db.js';
|
|
4
|
-
export {
|
|
4
|
+
export { open, rebuild } from './db.js';
|
|
5
5
|
export type { SenseErrorCode } from './errors.js';
|
|
6
6
|
export { SenseError } from './errors.js';
|
|
7
|
-
export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
|
|
8
|
-
export type { Section } from './features/sections.js';
|
|
9
|
-
export type { Feature } from './features/types.js';
|
|
10
|
-
export type { Edge } from './graph.js';
|
|
11
|
-
export { pagerank, personalizedRank } from './graph.js';
|
|
12
7
|
export type { Row } from './output.js';
|
|
13
8
|
export { printRows } from './output.js';
|
|
14
|
-
export type { FileStat, ParsedDoc } from './scan.js';
|
|
15
|
-
export { listFiles, parseFile } from './scan.js';
|
|
16
9
|
export type { FindOptions, Peek, TreeMap } from './verbs.js';
|
|
17
10
|
export { find, mapTree, peek } from './verbs.js';
|
|
18
11
|
export type { WatchEvent, WatchOptions } from './watch.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// Public library API.
|
|
2
|
-
|
|
3
|
-
export {
|
|
1
|
+
// Public library API. Deliberately small: every export is a stability promise;
|
|
2
|
+
// internals (feature registry, graph, scan, meta) stay module-private.
|
|
3
|
+
export { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.js';
|
|
4
|
+
export { open, rebuild } from './db.js';
|
|
4
5
|
export { SenseError } from './errors.js';
|
|
5
|
-
export { activeFeatures, FEATURES, linkEdges } from './features/index.js';
|
|
6
|
-
export { pagerank, personalizedRank } from './graph.js';
|
|
7
6
|
export { printRows } from './output.js';
|
|
8
|
-
export { listFiles, parseFile } from './scan.js';
|
|
9
7
|
export { find, mapTree, peek } from './verbs.js';
|
|
10
8
|
export { runWatch } from './watch.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.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME,
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/index.ts"],"sourcesContent":["// Public library API. Deliberately small: every export is a stability promise;\n// internals (feature registry, graph, scan, meta) stay module-private.\n\nexport type { Config, FeatureName, ResolvedConfig } from './config.ts';\nexport { CONFIG_FILENAME, initConfig, loadConfig, migrateConfig, STATE_DIR, SUPPORTED_CONFIG_VERSION } from './config.ts';\n\nexport type { OpenResult } from './db.ts';\nexport { open, rebuild } 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 { FindOptions, Peek, TreeMap } from './verbs.ts';\nexport { find, mapTree, peek } from './verbs.ts';\n\nexport type { WatchEvent, WatchOptions } from './watch.ts';\nexport { runWatch } from './watch.ts';\n"],"names":["CONFIG_FILENAME","initConfig","loadConfig","migrateConfig","STATE_DIR","SUPPORTED_CONFIG_VERSION","open","rebuild","SenseError","printRows","find","mapTree","peek","runWatch"],"mappings":"AAAA,+EAA+E;AAC/E,uEAAuE;AAGvE,SAASA,eAAe,EAAEC,UAAU,EAAEC,UAAU,EAAEC,aAAa,EAAEC,SAAS,EAAEC,wBAAwB,QAAQ,cAAc;AAG1H,SAASC,IAAI,EAAEC,OAAO,QAAQ,UAAU;AAGxC,SAASC,UAAU,QAAQ,cAAc;AAGzC,SAASC,SAAS,QAAQ,cAAc;AAGxC,SAASC,IAAI,EAAEC,OAAO,EAAEC,IAAI,QAAQ,aAAa;AAGjD,SAASC,QAAQ,QAAQ,aAAa"}
|
package/dist/esm/verbs.js
CHANGED
|
@@ -15,7 +15,11 @@ export function find(db, cfg, terms, opts = {}) {
|
|
|
15
15
|
const fetch = Math.max(k * 3, 30);
|
|
16
16
|
// Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.
|
|
17
17
|
// Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.
|
|
18
|
-
|
|
18
|
+
// --where applies inside the candidate query (a post-filter over the top-N would drop
|
|
19
|
+
// matches ranked past the pool) and again on the final select for link-derived rows.
|
|
20
|
+
const whereJoin = opts.where ? `JOIN frontmatter f ON f."path" = content.path` : '';
|
|
21
|
+
const whereCond = opts.where ? `AND (${opts.where})` : '';
|
|
22
|
+
const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;
|
|
19
23
|
const matchRows = db.prepare(matchSql).all(terms);
|
|
20
24
|
const hits = new Map(matchRows.map((r)=>[
|
|
21
25
|
r.path,
|
package/dist/esm/verbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config } from './config.ts';\nimport { featureEnabled } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\n\n// The three layer verbs: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\nexport function find(db: DatabaseSync, cfg: Config, terms: string, opts: FindOptions = {}): Row[] {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content WHERE content MATCH ? ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n const matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n if (featureEnabled(cfg, 'links') && matchRows.length > 0) {\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, linkEdges(db), seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n db.exec('CREATE TEMP TABLE IF NOT EXISTS _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT)');\n db.exec('DELETE FROM _find');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit) VALUES (?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null);\n\n const where = opts.where ? `WHERE ${opts.where}` : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n const allFields = columns\n .map((name) => {\n const { n } = db.prepare(`SELECT COUNT(\"${name.split('\"').join('\"\"')}\") AS n FROM frontmatter`).get() as { n: number };\n return { field: name, coverage: n };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n}\n\nconst PEEK_LINK_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: Config, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LINK_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n };\n}\n"],"names":["posix","featureEnabled","SenseError","linkEdges","personalizedRank","WEIGHTED_BM25","RRF_K","find","db","cfg","terms","opts","hits","k","fetch","Math","max","matchSql","matchRows","prepare","all","Map","map","r","path","hit","candidates","forEach","i","set","score","via","length","nodes","seeds","ranked","filter","sort","a","b","slice","existing","get","exec","insert","c","run","where","INTERNAL_COLUMNS","Set","mapTree","docs","columns","name","has","allFields","n","split","join","field","coverage","fields","hubs","recent","fieldsTotal","PEEK_LINK_LIMIT","peek","pathArg","row","paths","p","base","basename","replace","toLowerCase","matches","frontmatter","key","value","Object","entries","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/sensemaking/src/verbs.ts"],"sourcesContent":["import posix from 'node:path/posix';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config } from './config.ts';\nimport { featureEnabled } from './config.ts';\nimport { SenseError } from './errors.ts';\nimport { linkEdges } from './features/index.ts';\nimport { personalizedRank } from './graph.ts';\nimport type { Row } from './output.ts';\n\n// The three layer verbs: mapTree (orient), find (locate), peek (structure).\n// Each returns data; cli.ts renders. All of them degrade when a feature is off.\n\nconst WEIGHTED_BM25 = 'bm25(content, 10.0, 5.0, 1.0)';\nconst RRF_K = 60;\n\nexport interface FindOptions {\n k?: number;\n where?: string; // SQL fragment against frontmatter alias `f`, e.g. \"f.status = 'active'\"\n}\n\n// Layer 1: BM25 + link-graph expansion, fused by reciprocal rank. `via` says which\n// signal produced each row so the agent knows what evidence it is trusting.\nexport function find(db: DatabaseSync, cfg: Config, terms: string, opts: FindOptions = {}): Row[] {\n const k = opts.k ?? 10;\n const fetch = Math.max(k * 3, 30);\n\n // Terms pass verbatim to FTS5 MATCH: bare words AND-join, operators are the caller's.\n // Invalid syntax propagates as an error, zero matches return zero -- no silent rewrites.\n // --where applies inside the candidate query (a post-filter over the top-N would drop\n // matches ranked past the pool) and again on the final select for link-derived rows.\n const whereJoin = opts.where ? `JOIN frontmatter f ON f.\"path\" = content.path` : '';\n const whereCond = opts.where ? `AND (${opts.where})` : '';\n const matchSql = `SELECT content.path AS path, snippet(content, -1, '«', '»', '…', 10) AS hit FROM content ${whereJoin} WHERE content MATCH ? ${whereCond} ORDER BY ${WEIGHTED_BM25} LIMIT ${fetch}`;\n const matchRows = db.prepare(matchSql).all(terms) as Array<{ path: string; hit: string }>;\n\n const hits = new Map(matchRows.map((r) => [r.path, r.hit]));\n const candidates = new Map<string, { score: number; via: string }>();\n matchRows.forEach((r, i) => {\n candidates.set(r.path, { score: 1 / (RRF_K + i), via: 'match' });\n });\n\n if (featureEnabled(cfg, 'links') && matchRows.length > 0) {\n const nodes = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n const seeds = new Map(matchRows.map((r, i) => [r.path, 1 / (i + 1)]));\n const ranked = [...personalizedRank(nodes, linkEdges(db), seeds)]\n .filter(([, score]) => score > 1e-9)\n .sort((a, b) => b[1] - a[1])\n .slice(0, fetch);\n ranked.forEach(([path], i) => {\n const existing = candidates.get(path);\n if (existing) {\n existing.score += 1 / (RRF_K + i);\n existing.via = 'match+link';\n } else {\n candidates.set(path, { score: 1 / (RRF_K + i), via: 'link' });\n }\n });\n }\n\n db.exec('CREATE TEMP TABLE IF NOT EXISTS _find (\"path\" TEXT PRIMARY KEY, score REAL, via TEXT, hit TEXT)');\n db.exec('DELETE FROM _find');\n const insert = db.prepare('INSERT INTO _find (\"path\", score, via, hit) VALUES (?, ?, ?, ?)');\n for (const [path, c] of candidates) insert.run(path, c.score, c.via, hits.get(path) ?? null);\n\n const where = opts.where ? `WHERE ${opts.where}` : '';\n return db\n .prepare(\n `SELECT f.\"path\" AS path, content.title, content.summary, _find.hit, _find.via, round(_find.score, 4) AS score\n FROM _find JOIN frontmatter f ON f.\"path\" = _find.\"path\" JOIN content ON content.path = _find.\"path\"\n ${where} ORDER BY _find.score DESC LIMIT ?`\n )\n .all(k) as Row[];\n}\n\nexport interface TreeMap {\n docs: { count: number; bytes: number };\n fields: Row[]; // top 20 by coverage; fieldsTotal carries the real count\n fieldsTotal: number;\n hubs: Row[];\n recent: Row[];\n}\n\nconst INTERNAL_COLUMNS = new Set(['path', '_mtime', '_size', '_rank']);\n\n// Layer 0: what is this tree. Fixed-size output regardless of tree size.\nexport function mapTree(db: DatabaseSync, cfg: Config): TreeMap {\n const docs = db.prepare('SELECT COUNT(*) AS count, COALESCE(SUM(\"_size\"), 0) AS bytes FROM frontmatter').get() as { count: number; bytes: number };\n\n const columns = (db.prepare('PRAGMA table_info(frontmatter)').all() as Array<{ name: string }>).map((r) => r.name).filter((name) => !INTERNAL_COLUMNS.has(name));\n const allFields = columns\n .map((name) => {\n const { n } = db.prepare(`SELECT COUNT(\"${name.split('\"').join('\"\"')}\") AS n FROM frontmatter`).get() as { n: number };\n return { field: name, coverage: n };\n })\n .sort((a, b) => (b.coverage as number) - (a.coverage as number)) as Row[];\n const fields = allFields.slice(0, 20);\n\n const hubs = featureEnabled(cfg, 'rank') ? (db.prepare(`SELECT f.\"path\" AS path, round(f.\"_rank\" * 100, 2) AS rank, content.title FROM frontmatter f JOIN content ON content.path = f.\"path\" WHERE f.\"_rank\" IS NOT NULL ORDER BY f.\"_rank\" DESC LIMIT 8`).all() as Row[]) : [];\n\n const recent = db.prepare(`SELECT \"path\", datetime(\"_mtime\" / 1000, 'unixepoch') AS modified FROM frontmatter ORDER BY \"_mtime\" DESC LIMIT 5`).all() as Row[];\n\n return { docs, fields, fieldsTotal: allFields.length, hubs, recent };\n}\n\nexport interface Peek {\n path: string;\n tokens: number;\n frontmatter: Row;\n sections: Row[];\n outbound: string[];\n backlinks: string[];\n unresolved: string[];\n // Totals before truncation: a hub can have thousands of backlinks, and peek's whole\n // point is bounded output. Query the links table directly for the full list.\n outboundTotal: number;\n backlinksTotal: number;\n unresolvedTotal: number;\n}\n\nconst PEEK_LINK_LIMIT = 20;\n\n// Layer 2: everything about one note except its prose -- frontmatter, outline with line\n// ranges + token estimates (so the follow-up Read is a range, not the file), links both ways.\nexport function peek(db: DatabaseSync, cfg: Config, pathArg: string): Peek {\n const paths = (db.prepare('SELECT \"path\" FROM frontmatter').all() as Array<{ path: string }>).map((r) => r.path);\n let path = paths.find((p) => p === pathArg);\n if (!path) {\n const base = posix.basename(pathArg).replace(/\\.md$/i, '').toLowerCase();\n const matches = paths.filter((p) => posix.basename(p).replace(/\\.md$/i, '').toLowerCase() === base);\n if (matches.length === 1) path = matches[0];\n else if (matches.length > 1) throw new SenseError('NOTE_AMBIGUOUS', `\"${pathArg}\" is ambiguous: ${matches.join(', ')}`);\n else throw new SenseError('NOTE_NOT_FOUND', `no note matches \"${pathArg}\"`);\n }\n\n const row = db.prepare('SELECT * FROM frontmatter WHERE \"path\" = ?').get(path) as Row;\n const frontmatter: Row = {};\n for (const [key, value] of Object.entries(row)) {\n if (!INTERNAL_COLUMNS.has(key) && value !== null) frontmatter[key] = value;\n }\n\n const sections = featureEnabled(cfg, 'sections') ? (db.prepare('SELECT level, heading, start_line, end_line, tokens FROM sections WHERE \"path\" = ? ORDER BY idx').all(path) as Row[]) : [];\n\n let outbound: string[] = [];\n let backlinks: string[] = [];\n let unresolved: string[] = [];\n let backlinksTotal = 0;\n if (featureEnabled(cfg, 'links')) {\n const out = db.prepare('SELECT target, dst FROM links WHERE src = ? ORDER BY target').all(path) as Array<{ target: string; dst: string | null }>;\n outbound = [...new Set(out.filter((l) => l.dst !== null).map((l) => l.dst as string))];\n unresolved = out.filter((l) => l.dst === null).map((l) => l.target);\n backlinksTotal = (db.prepare('SELECT COUNT(DISTINCT src) AS n FROM links WHERE dst = ?').get(path) as { n: number }).n;\n backlinks = (db.prepare('SELECT DISTINCT src FROM links WHERE dst = ? ORDER BY src LIMIT ?').all(path, PEEK_LINK_LIMIT) as Array<{ src: string }>).map((r) => r.src);\n }\n\n return {\n path,\n tokens: Math.ceil(((row._size as number) ?? 0) / 4),\n frontmatter,\n sections,\n outbound: outbound.slice(0, PEEK_LINK_LIMIT),\n backlinks,\n unresolved: unresolved.slice(0, PEEK_LINK_LIMIT),\n outboundTotal: outbound.length,\n backlinksTotal,\n unresolvedTotal: unresolved.length,\n };\n}\n"],"names":["posix","featureEnabled","SenseError","linkEdges","personalizedRank","WEIGHTED_BM25","RRF_K","find","db","cfg","terms","opts","hits","k","fetch","Math","max","whereJoin","where","whereCond","matchSql","matchRows","prepare","all","Map","map","r","path","hit","candidates","forEach","i","set","score","via","length","nodes","seeds","ranked","filter","sort","a","b","slice","existing","get","exec","insert","c","run","INTERNAL_COLUMNS","Set","mapTree","docs","columns","name","has","allFields","n","split","join","field","coverage","fields","hubs","recent","fieldsTotal","PEEK_LINK_LIMIT","peek","pathArg","row","paths","p","base","basename","replace","toLowerCase","matches","frontmatter","key","value","Object","entries","sections","outbound","backlinks","unresolved","backlinksTotal","out","l","dst","target","src","tokens","ceil","_size","outboundTotal","unresolvedTotal"],"mappings":"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"}
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"queries": {
|
|
52
52
|
"type": "object",
|
|
53
|
-
"description": "Named SQL queries runnable as `sense <name> [params...]`. Tables: `frontmatter` (one row per file, one column per discovered frontmatter key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5: `title`, `summary`, `text`, `path`), `links` (`src`, `target`, `dst`), and `sections` (`path`, `idx`, `level`, `heading`, `start_line`, `end_line`, `tokens`). `?` placeholders bind to CLI positional args in order. `has(field, value)`: array membership on a JSON-array field, substring match on a string, false on NULL. Canonical query: `SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit FROM frontmatter f JOIN content ON content.path = f.path WHERE content MATCH ? ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10`. Reserved frontmatter keys: `path`, `_mtime`, `_size`, `_rank`, `content`, `links`, `sections`. Reserved query names (unreachable as subcommands): `init`, `query`, `find`, `map`, `peek`, `watch`, `status`, `rebuild`.",
|
|
53
|
+
"description": "Named SQL queries runnable as `sense <name> [params...]`. Tables: `frontmatter` (one row per file, one column per discovered frontmatter key, plus `path`/`_mtime`/`_size`/`_rank`), `content` (FTS5: `title`, `summary`, `text`, `path`), `links` (`src`, `target`, `dst`), and `sections` (`path`, `idx`, `level`, `heading`, `start_line`, `end_line`, `tokens`). `?` placeholders bind to CLI positional args in order. `has(field, value)`: array membership on a JSON-array field, substring match on a string (so has(f.status, 'active') also matches 'inactive'), false on NULL. Exact matches: `=` for scalars, `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)` for array members. Canonical query: `SELECT f.path, content.title, content.summary, snippet(content, -1, '«', '»', '…', 10) AS hit FROM frontmatter f JOIN content ON content.path = f.path WHERE content MATCH ? ORDER BY bm25(content, 10.0, 5.0, 1.0) LIMIT 10`. Reserved frontmatter keys: `path`, `_mtime`, `_size`, `_rank`, `content`, `links`, `sections`. Reserved query names (unreachable as subcommands): `init`, `query`, `find`, `map`, `peek`, `watch`, `status`, `rebuild`.",
|
|
54
54
|
"additionalProperties": { "type": "string" }
|
|
55
55
|
}
|
|
56
56
|
}
|
package/skills/sense/SKILL.md
CHANGED
|
@@ -72,7 +72,10 @@ sense query "SELECT j.value, COUNT(*) n FROM frontmatter, json_each(frontmatter.
|
|
|
72
72
|
`snippet(content, -1, '«', '»', '…', 10)`.
|
|
73
73
|
- Select `content.title`/`content.summary` (always exist, empty when absent) rather than
|
|
74
74
|
`f.title`/`f.summary` (discovered columns — error on trees that never declare them).
|
|
75
|
-
- `has(field, value)`: array membership on JSON-array fields, substring on strings, false on NULL
|
|
75
|
+
- `has(field, value)`: array membership on JSON-array fields, substring on strings, false on NULL
|
|
76
|
+
— the `includes()` convention. Substring means `has(f.status, 'active')` also matches
|
|
77
|
+
`inactive`; exact scalar match is `f.status = ?`, deliberate substring is `LIKE`, exact array
|
|
78
|
+
membership is `EXISTS (SELECT 1 FROM json_each(f.tags) WHERE value = ?)`.
|
|
76
79
|
To aggregate per member instead, use `json_each(frontmatter.<field>)` (above) -- GROUP BY on the
|
|
77
80
|
raw column splits `["a","b"]` and `["b","a"]` into separate buckets.
|
|
78
81
|
- Date fields are stored as written. Compare through `datetime()`, which normalizes ISO 8601
|