@voidwire/lore 2.0.1 → 2.1.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/cli.ts +142 -9
- package/lib/age.ts +162 -0
- package/lib/config.ts +24 -0
- package/lib/contradiction.ts +18 -4
- package/lib/db.ts +2 -9
- package/lib/indexer.ts +70 -25
- package/lib/indexers/blogs.ts +14 -2
- package/lib/indexers/captures.ts +14 -2
- package/lib/indexers/commits.ts +14 -2
- package/lib/indexers/development.ts +14 -2
- package/lib/indexers/explorations.ts +14 -2
- package/lib/indexers/flux.ts +14 -2
- package/lib/indexers/index.ts +28 -28
- package/lib/indexers/observations.ts +14 -2
- package/lib/indexers/obsidian.ts +14 -2
- package/lib/indexers/personal.ts +14 -2
- package/lib/indexers/readmes.ts +14 -2
- package/lib/indexers/sessions.ts +14 -2
- package/lib/indexers/teachings.ts +14 -2
- package/lib/info.ts +2 -3
- package/lib/init.ts +24 -28
- package/lib/list.ts +3 -10
- package/lib/projects.ts +2 -3
- package/lib/realtime.ts +2 -7
- package/lib/search.ts +64 -17
- package/lib/semantic.ts +7 -1
- package/package.json +4 -2
package/lib/indexers/commits.ts
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
import { readdirSync, existsSync } from "fs";
|
|
14
14
|
import { join } from "path";
|
|
15
15
|
import { spawnSync } from "child_process";
|
|
16
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
16
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
async function indexCommits(ctx: IndexerContext): Promise<void> {
|
|
19
19
|
const projectsDir = ctx.config.paths.projects;
|
|
20
20
|
if (!checkPath("commits", "paths.projects", projectsDir)) return;
|
|
21
21
|
|
|
@@ -84,3 +84,15 @@ export async function indexCommits(ctx: IndexerContext): Promise<void> {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
export const commitsPlugin: IndexerPlugin = {
|
|
89
|
+
manifest: {
|
|
90
|
+
name: "commits",
|
|
91
|
+
description: "Indexes git commit history from project directories",
|
|
92
|
+
requiredConfig: ["paths.projects"],
|
|
93
|
+
optionalConfig: [],
|
|
94
|
+
rebuildExcluded: false,
|
|
95
|
+
},
|
|
96
|
+
canRun: (config) => !!config.paths?.projects,
|
|
97
|
+
run: indexCommits,
|
|
98
|
+
};
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import { readdirSync, readFileSync, statSync, existsSync } from "fs";
|
|
12
12
|
import { join } from "path";
|
|
13
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
13
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
async function indexDevelopment(ctx: IndexerContext): Promise<void> {
|
|
16
16
|
const projectsDir = ctx.config.paths.projects;
|
|
17
17
|
if (!checkPath("development", "paths.projects", projectsDir)) return;
|
|
18
18
|
|
|
@@ -62,3 +62,15 @@ export async function indexDevelopment(ctx: IndexerContext): Promise<void> {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
export const developmentPlugin: IndexerPlugin = {
|
|
67
|
+
manifest: {
|
|
68
|
+
name: "development",
|
|
69
|
+
description: "Indexes project summary files from development directories",
|
|
70
|
+
requiredConfig: ["paths.projects"],
|
|
71
|
+
optionalConfig: [],
|
|
72
|
+
rebuildExcluded: false,
|
|
73
|
+
},
|
|
74
|
+
canRun: (config) => !!config.paths?.projects,
|
|
75
|
+
run: indexDevelopment,
|
|
76
|
+
};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { readdirSync, readFileSync, statSync } from "fs";
|
|
14
14
|
import { join, basename, dirname } from "path";
|
|
15
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
15
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
16
16
|
|
|
17
17
|
function walkMarkdownFiles(dir: string, files: string[] = []): string[] {
|
|
18
18
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -30,7 +30,7 @@ function walkMarkdownFiles(dir: string, files: string[] = []): string[] {
|
|
|
30
30
|
return files;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
async function indexExplorations(ctx: IndexerContext): Promise<void> {
|
|
34
34
|
const explorationsDir = ctx.config.paths.explorations;
|
|
35
35
|
|
|
36
36
|
if (!checkPath("explorations", "paths.explorations", explorationsDir)) return;
|
|
@@ -84,3 +84,15 @@ export async function indexExplorations(ctx: IndexerContext): Promise<void> {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
export const explorationsPlugin: IndexerPlugin = {
|
|
89
|
+
manifest: {
|
|
90
|
+
name: "explorations",
|
|
91
|
+
description: "Indexes markdown files from the explorations directory",
|
|
92
|
+
requiredConfig: ["paths.explorations"],
|
|
93
|
+
optionalConfig: [],
|
|
94
|
+
rebuildExcluded: false,
|
|
95
|
+
},
|
|
96
|
+
canRun: (config) => !!config.paths?.explorations,
|
|
97
|
+
run: indexExplorations,
|
|
98
|
+
};
|
package/lib/indexers/flux.ts
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
import { readdirSync, readFileSync, existsSync, statSync } from "fs";
|
|
16
16
|
import { join, basename } from "path";
|
|
17
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
17
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
async function indexFlux(ctx: IndexerContext): Promise<void> {
|
|
20
20
|
const fluxDir = ctx.config.paths.flux;
|
|
21
21
|
const fluxProjectsDir = ctx.config.paths.flux_projects;
|
|
22
22
|
let found = false;
|
|
@@ -141,3 +141,15 @@ function parseFluxFile(
|
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
export const fluxPlugin: IndexerPlugin = {
|
|
146
|
+
manifest: {
|
|
147
|
+
name: "flux",
|
|
148
|
+
description: "Indexes todo and idea entries from flux markdown files",
|
|
149
|
+
requiredConfig: ["paths.flux"],
|
|
150
|
+
optionalConfig: [],
|
|
151
|
+
rebuildExcluded: false,
|
|
152
|
+
},
|
|
153
|
+
canRun: (config) => !!config.paths?.flux,
|
|
154
|
+
run: indexFlux,
|
|
155
|
+
};
|
package/lib/indexers/index.ts
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/indexers/index.ts - Indexer registry
|
|
3
3
|
*
|
|
4
|
-
* Maps source names to indexer
|
|
5
|
-
*
|
|
4
|
+
* Maps source names to indexer plugins.
|
|
5
|
+
* Each plugin provides manifest metadata, a canRun gate, and the run function.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
8
|
+
import type { IndexerPlugin } from "../indexer";
|
|
9
|
+
import { readmesPlugin } from "./readmes";
|
|
10
|
+
import { developmentPlugin } from "./development";
|
|
11
|
+
import { capturesPlugin } from "./captures";
|
|
12
|
+
import { teachingsPlugin } from "./teachings";
|
|
13
|
+
import { observationsPlugin } from "./observations";
|
|
14
|
+
import { explorationsPlugin } from "./explorations";
|
|
15
|
+
import { sessionsPlugin } from "./sessions";
|
|
16
|
+
import { fluxPlugin } from "./flux";
|
|
17
|
+
import { obsidianPlugin } from "./obsidian";
|
|
18
|
+
import { commitsPlugin } from "./commits";
|
|
19
|
+
import { blogsPlugin } from "./blogs";
|
|
20
|
+
import { personalPlugin } from "./personal";
|
|
21
21
|
|
|
22
|
-
export const indexers: Record<string,
|
|
23
|
-
readmes:
|
|
24
|
-
development:
|
|
25
|
-
captures:
|
|
26
|
-
teachings:
|
|
27
|
-
observations:
|
|
28
|
-
explorations:
|
|
29
|
-
sessions:
|
|
30
|
-
flux:
|
|
31
|
-
obsidian:
|
|
32
|
-
commits:
|
|
33
|
-
blogs:
|
|
34
|
-
personal:
|
|
22
|
+
export const indexers: Record<string, IndexerPlugin> = {
|
|
23
|
+
readmes: readmesPlugin,
|
|
24
|
+
development: developmentPlugin,
|
|
25
|
+
captures: capturesPlugin,
|
|
26
|
+
teachings: teachingsPlugin,
|
|
27
|
+
observations: observationsPlugin,
|
|
28
|
+
explorations: explorationsPlugin,
|
|
29
|
+
sessions: sessionsPlugin,
|
|
30
|
+
flux: fluxPlugin,
|
|
31
|
+
obsidian: obsidianPlugin,
|
|
32
|
+
commits: commitsPlugin,
|
|
33
|
+
blogs: blogsPlugin,
|
|
34
|
+
personal: personalPlugin,
|
|
35
35
|
};
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { readFileSync } from "fs";
|
|
14
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
14
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
async function indexObservations(ctx: IndexerContext): Promise<void> {
|
|
17
17
|
const logPath = `${ctx.config.paths.data}/log.jsonl`;
|
|
18
18
|
if (
|
|
19
19
|
!checkPath(
|
|
@@ -56,3 +56,15 @@ export async function indexObservations(ctx: IndexerContext): Promise<void> {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
export const observationsPlugin: IndexerPlugin = {
|
|
61
|
+
manifest: {
|
|
62
|
+
name: "observations",
|
|
63
|
+
description: "Indexes observation captures from log.jsonl",
|
|
64
|
+
requiredConfig: ["paths.data"],
|
|
65
|
+
optionalConfig: [],
|
|
66
|
+
rebuildExcluded: true,
|
|
67
|
+
},
|
|
68
|
+
canRun: (config) => !!config.paths?.data,
|
|
69
|
+
run: indexObservations,
|
|
70
|
+
};
|
package/lib/indexers/obsidian.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { readdirSync, readFileSync, statSync } from "fs";
|
|
15
15
|
import { join, basename, dirname } from "path";
|
|
16
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
16
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
17
17
|
|
|
18
18
|
function walkMarkdownFiles(
|
|
19
19
|
dir: string,
|
|
@@ -38,7 +38,7 @@ function walkMarkdownFiles(
|
|
|
38
38
|
return files;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
async function indexObsidian(ctx: IndexerContext): Promise<void> {
|
|
42
42
|
const obsidianDir = ctx.config.paths.obsidian;
|
|
43
43
|
|
|
44
44
|
if (!checkPath("obsidian", "paths.obsidian", obsidianDir)) return;
|
|
@@ -148,3 +148,15 @@ export async function indexObsidian(ctx: IndexerContext): Promise<void> {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
+
|
|
152
|
+
export const obsidianPlugin: IndexerPlugin = {
|
|
153
|
+
manifest: {
|
|
154
|
+
name: "obsidian",
|
|
155
|
+
description: "Indexes markdown files from the Obsidian vault",
|
|
156
|
+
requiredConfig: ["paths.obsidian"],
|
|
157
|
+
optionalConfig: [],
|
|
158
|
+
rebuildExcluded: false,
|
|
159
|
+
},
|
|
160
|
+
canRun: (config) => !!config.paths?.obsidian,
|
|
161
|
+
run: indexObsidian,
|
|
162
|
+
};
|
package/lib/indexers/personal.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { readFileSync, statSync, existsSync } from "fs";
|
|
14
14
|
import { join } from "path";
|
|
15
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
15
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
16
16
|
import { complete } from "@voidwire/llm-core";
|
|
17
17
|
|
|
18
18
|
function fileMtime(path: string): string {
|
|
@@ -91,7 +91,7 @@ async function enrich(type: string, input: string): Promise<string | null> {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
async function indexPersonal(ctx: IndexerContext): Promise<void> {
|
|
95
95
|
enrichmentFailures = 0;
|
|
96
96
|
const personalDir = ctx.config.paths.personal;
|
|
97
97
|
|
|
@@ -354,3 +354,15 @@ export async function indexPersonal(ctx: IndexerContext): Promise<void> {
|
|
|
354
354
|
}
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
|
+
|
|
358
|
+
export const personalPlugin: IndexerPlugin = {
|
|
359
|
+
manifest: {
|
|
360
|
+
name: "personal",
|
|
361
|
+
description: "Indexes personal data JSON files (books, contacts, movies, etc.)",
|
|
362
|
+
requiredConfig: ["paths.personal"],
|
|
363
|
+
optionalConfig: [],
|
|
364
|
+
rebuildExcluded: false,
|
|
365
|
+
},
|
|
366
|
+
canRun: (config) => !!config.paths?.personal,
|
|
367
|
+
run: indexPersonal,
|
|
368
|
+
};
|
package/lib/indexers/readmes.ts
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import { readdirSync, readFileSync, statSync, existsSync } from "fs";
|
|
12
12
|
import { join } from "path";
|
|
13
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
13
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
async function indexReadmes(ctx: IndexerContext): Promise<void> {
|
|
16
16
|
const projectsDir = ctx.config.paths.projects;
|
|
17
17
|
if (!checkPath("readmes", "paths.projects", projectsDir)) return;
|
|
18
18
|
|
|
@@ -43,3 +43,15 @@ export async function indexReadmes(ctx: IndexerContext): Promise<void> {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
export const readmesPlugin: IndexerPlugin = {
|
|
48
|
+
manifest: {
|
|
49
|
+
name: "readmes",
|
|
50
|
+
description: "Indexes README.md files from project directories",
|
|
51
|
+
requiredConfig: ["paths.projects"],
|
|
52
|
+
optionalConfig: [],
|
|
53
|
+
rebuildExcluded: false,
|
|
54
|
+
},
|
|
55
|
+
canRun: (config) => !!config.paths?.projects,
|
|
56
|
+
run: indexReadmes,
|
|
57
|
+
};
|
package/lib/indexers/sessions.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { readdirSync, readFileSync } from "fs";
|
|
14
14
|
import { join } from "path";
|
|
15
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
15
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
16
16
|
|
|
17
17
|
interface SessionData {
|
|
18
18
|
project: string;
|
|
@@ -26,7 +26,7 @@ interface SessionData {
|
|
|
26
26
|
lastTs: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
async function indexSessions(ctx: IndexerContext): Promise<void> {
|
|
30
30
|
// Check each event directory individually for clear diagnostics
|
|
31
31
|
const eventDirs: string[] = [];
|
|
32
32
|
for (const { name, path } of [
|
|
@@ -136,3 +136,15 @@ export async function indexSessions(ctx: IndexerContext): Promise<void> {
|
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
export const sessionsPlugin: IndexerPlugin = {
|
|
141
|
+
manifest: {
|
|
142
|
+
name: "sessions",
|
|
143
|
+
description: "Indexes session event telemetry aggregated by session ID",
|
|
144
|
+
requiredConfig: ["paths.session_events", "paths.sable_events"],
|
|
145
|
+
optionalConfig: [],
|
|
146
|
+
rebuildExcluded: false,
|
|
147
|
+
},
|
|
148
|
+
canRun: (config) => !!config.paths?.session_events && !!config.paths?.sable_events,
|
|
149
|
+
run: indexSessions,
|
|
150
|
+
};
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { readFileSync } from "fs";
|
|
14
|
-
import { checkPath, type IndexerContext } from "../indexer";
|
|
14
|
+
import { checkPath, type IndexerContext, type IndexerPlugin } from "../indexer";
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
async function indexTeachings(ctx: IndexerContext): Promise<void> {
|
|
17
17
|
const logPath = `${ctx.config.paths.data}/log.jsonl`;
|
|
18
18
|
if (
|
|
19
19
|
!checkPath(
|
|
@@ -55,3 +55,15 @@ export async function indexTeachings(ctx: IndexerContext): Promise<void> {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
export const teachingsPlugin: IndexerPlugin = {
|
|
60
|
+
manifest: {
|
|
61
|
+
name: "teachings",
|
|
62
|
+
description: "Indexes teaching captures from log.jsonl",
|
|
63
|
+
requiredConfig: ["paths.data"],
|
|
64
|
+
optionalConfig: [],
|
|
65
|
+
rebuildExcluded: true,
|
|
66
|
+
},
|
|
67
|
+
canRun: (config) => !!config.paths?.data,
|
|
68
|
+
run: indexTeachings,
|
|
69
|
+
};
|
package/lib/info.ts
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* Uses Bun's built-in SQLite for zero external dependencies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Database } from "bun:sqlite";
|
|
9
8
|
import { existsSync } from "fs";
|
|
10
|
-
import { getDatabasePath } from "./db.js";
|
|
9
|
+
import { getDatabasePath, openDatabaseBasic } from "./db.js";
|
|
11
10
|
import { projects as getProjects } from "./projects.js";
|
|
12
11
|
|
|
13
12
|
export interface SourceInfo {
|
|
@@ -41,7 +40,7 @@ export function info(): InfoOutput {
|
|
|
41
40
|
};
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
const db =
|
|
43
|
+
const db = openDatabaseBasic(true);
|
|
45
44
|
|
|
46
45
|
try {
|
|
47
46
|
// Get distinct sources with counts
|
package/lib/init.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
13
13
|
import { homedir } from "os";
|
|
14
14
|
import { Database } from "bun:sqlite";
|
|
15
|
+
import { getLoadablePath, load } from "sqlite-vec";
|
|
15
16
|
|
|
16
17
|
interface DetectedPaths {
|
|
17
18
|
obsidian?: string;
|
|
@@ -76,7 +77,7 @@ export async function runInit(homeOverride?: string): Promise<void> {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// 5. Initialize or verify database
|
|
79
|
-
initDatabase(dbPath, detected.customSqlite
|
|
80
|
+
initDatabase(dbPath, detected.customSqlite);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
async function detectPaths(home: string): Promise<DetectedPaths> {
|
|
@@ -122,7 +123,15 @@ async function detectPaths(home: string): Promise<DetectedPaths> {
|
|
|
122
123
|
function detectSqliteVec(): string | undefined {
|
|
123
124
|
const ext = process.platform === "darwin" ? "dylib" : "so";
|
|
124
125
|
|
|
125
|
-
// Strategy 1:
|
|
126
|
+
// Strategy 1: npm package (preferred — self-resolves platform binary)
|
|
127
|
+
try {
|
|
128
|
+
const npmPath = getLoadablePath();
|
|
129
|
+
if (existsSync(npmPath)) return npmPath;
|
|
130
|
+
} catch {
|
|
131
|
+
// npm package not installed or getLoadablePath() failed — try fallbacks
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Strategy 2: brew --prefix sqlite-vec
|
|
126
135
|
const result = Bun.spawnSync(["brew", "--prefix", "sqlite-vec"]);
|
|
127
136
|
if (result.exitCode === 0) {
|
|
128
137
|
const prefix = new TextDecoder().decode(result.stdout).trim();
|
|
@@ -130,11 +139,6 @@ function detectSqliteVec(): string | undefined {
|
|
|
130
139
|
if (existsSync(candidate)) return candidate;
|
|
131
140
|
}
|
|
132
141
|
|
|
133
|
-
// Strategy 2: npm-installed sqlite-vec (node_modules)
|
|
134
|
-
const platformPkg = `sqlite-vec-${process.platform}-${process.arch}`;
|
|
135
|
-
const nmCandidate = `${import.meta.dir}/../node_modules/${platformPkg}/vec0.${ext}`;
|
|
136
|
-
if (existsSync(nmCandidate)) return nmCandidate;
|
|
137
|
-
|
|
138
142
|
// Strategy 3: common macOS/Linux system paths
|
|
139
143
|
for (const p of [
|
|
140
144
|
"/opt/homebrew/lib/sqlite-vec/vec0.dylib",
|
|
@@ -146,7 +150,7 @@ function detectSqliteVec(): string | undefined {
|
|
|
146
150
|
if (existsSync(p)) return p;
|
|
147
151
|
}
|
|
148
152
|
|
|
149
|
-
// Strategy 4: pip-installed sqlite-vec (Python site-packages)
|
|
153
|
+
// Strategy 4: pip-installed sqlite-vec (Python site-packages — last resort)
|
|
150
154
|
const pipGlob =
|
|
151
155
|
process.platform === "darwin"
|
|
152
156
|
? "/opt/homebrew/lib/python3.*/site-packages/sqlite_vec/vec0.dylib"
|
|
@@ -189,7 +193,7 @@ ${pathLine("sable_events", detected.sableEvents ? "~/.local/share/sable/events"
|
|
|
189
193
|
[database]
|
|
190
194
|
sqlite = "${dbPath}"
|
|
191
195
|
${pathLine("custom_sqlite", detected.customSqlite)}
|
|
192
|
-
|
|
196
|
+
# sqlite_vec — no longer needed; loaded from npm package via load(db)
|
|
193
197
|
|
|
194
198
|
[embedding]
|
|
195
199
|
model = "nomic-ai/nomic-embed-text-v1.5"
|
|
@@ -197,11 +201,7 @@ dimensions = 768
|
|
|
197
201
|
`;
|
|
198
202
|
}
|
|
199
203
|
|
|
200
|
-
function initDatabase(
|
|
201
|
-
dbPath: string,
|
|
202
|
-
customSqlite: string | undefined,
|
|
203
|
-
vecPath: string | undefined,
|
|
204
|
-
): void {
|
|
204
|
+
function initDatabase(dbPath: string, customSqlite: string | undefined): void {
|
|
205
205
|
if (customSqlite && existsSync(customSqlite)) {
|
|
206
206
|
try {
|
|
207
207
|
Database.setCustomSQLite(customSqlite);
|
|
@@ -215,21 +215,17 @@ function initDatabase(
|
|
|
215
215
|
const db = new Database(dbPath);
|
|
216
216
|
db.exec("PRAGMA journal_mode=WAL");
|
|
217
217
|
|
|
218
|
-
// Load sqlite-vec for vec0 table creation
|
|
218
|
+
// Load sqlite-vec from npm package for vec0 table creation
|
|
219
219
|
let vecLoaded = false;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
console.warn("sqlite-vec not found — embeddings table not created");
|
|
220
|
+
try {
|
|
221
|
+
load(db);
|
|
222
|
+
vecLoaded = true;
|
|
223
|
+
} catch (e) {
|
|
224
|
+
// Extension loading fails if custom_sqlite wasn't set (Bun's built-in
|
|
225
|
+
// sqlite doesn't support extensions). Warn instead of crashing.
|
|
226
|
+
console.warn(
|
|
227
|
+
`sqlite-vec load failed — extension loading may not be supported without custom_sqlite`,
|
|
228
|
+
);
|
|
233
229
|
}
|
|
234
230
|
|
|
235
231
|
// Create tables (IF NOT EXISTS = idempotent)
|
package/lib/list.ts
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* Uses Bun's built-in SQLite for zero external dependencies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Database } from "bun:sqlite";
|
|
9
|
-
import {
|
|
10
|
-
import { getDatabasePath } from "./db.js";
|
|
8
|
+
import type { Database } from "bun:sqlite";
|
|
9
|
+
import { openDatabaseBasic } from "./db.js";
|
|
11
10
|
|
|
12
11
|
// Source types - data sources that can be listed
|
|
13
12
|
export type Source =
|
|
@@ -194,13 +193,7 @@ export function list(source: Source, options: ListOptions = {}): ListResult {
|
|
|
194
193
|
);
|
|
195
194
|
}
|
|
196
195
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
if (!existsSync(dbPath)) {
|
|
200
|
-
throw new Error(`Database not found: ${dbPath}. Run lore-db-init first.`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const db = new Database(dbPath, { readonly: true });
|
|
196
|
+
const db = openDatabaseBasic(true);
|
|
204
197
|
|
|
205
198
|
try {
|
|
206
199
|
let entries: ListEntry[];
|
package/lib/projects.ts
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* project-scoped sources.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Database } from "bun:sqlite";
|
|
9
8
|
import { existsSync } from "fs";
|
|
10
|
-
import { getDatabasePath } from "./db.js";
|
|
9
|
+
import { getDatabasePath, openDatabaseBasic } from "./db.js";
|
|
11
10
|
|
|
12
11
|
const PROJECT_SOURCES = [
|
|
13
12
|
"commits",
|
|
@@ -30,7 +29,7 @@ export function projects(): string[] {
|
|
|
30
29
|
return [];
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
const db =
|
|
32
|
+
const db = openDatabaseBasic(true);
|
|
34
33
|
|
|
35
34
|
try {
|
|
36
35
|
const placeholders = PROJECT_SOURCES.map(() => "?").join(", ");
|
package/lib/realtime.ts
CHANGED
|
@@ -15,12 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { Database } from "bun:sqlite";
|
|
18
|
-
import {
|
|
19
|
-
embedDocuments,
|
|
20
|
-
MODEL_NAME,
|
|
21
|
-
EMBEDDING_DIM,
|
|
22
|
-
serializeEmbedding,
|
|
23
|
-
} from "./semantic.js";
|
|
18
|
+
import { embedDocuments, MODEL_NAME, serializeEmbedding } from "./semantic.js";
|
|
24
19
|
import { openDatabase } from "./db.js";
|
|
25
20
|
import { hashContent, getCachedEmbedding, cacheEmbedding } from "./cache.js";
|
|
26
21
|
import type { CaptureEvent } from "./capture.js";
|
|
@@ -262,7 +257,7 @@ async function embedWithCache(
|
|
|
262
257
|
const embedding = embeddings[i];
|
|
263
258
|
|
|
264
259
|
results[idx] = embedding;
|
|
265
|
-
cacheEmbedding(db, hashContent(content), embedding, MODEL_NAME);
|
|
260
|
+
cacheEmbedding(db, hashContent(content), embedding, MODEL_NAME());
|
|
266
261
|
}
|
|
267
262
|
}
|
|
268
263
|
|
package/lib/search.ts
CHANGED
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
* Uses Bun's built-in SQLite for zero external dependencies.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import { existsSync } from "fs";
|
|
10
|
-
import { getDatabasePath } from "./db.js";
|
|
8
|
+
import { openDatabaseBasic } from "./db.js";
|
|
11
9
|
|
|
12
10
|
export interface SearchResult {
|
|
13
11
|
rowid: number;
|
|
@@ -53,13 +51,7 @@ export function search(
|
|
|
53
51
|
query: string,
|
|
54
52
|
options: SearchOptions = {},
|
|
55
53
|
): SearchResult[] {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
if (!existsSync(dbPath)) {
|
|
59
|
-
throw new Error(`Database not found: ${dbPath}. Run lore-db-init first.`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const db = new Database(dbPath, { readonly: true });
|
|
54
|
+
const db = openDatabaseBasic(true);
|
|
63
55
|
|
|
64
56
|
try {
|
|
65
57
|
const limit = options.limit ?? 20;
|
|
@@ -117,18 +109,73 @@ export function search(
|
|
|
117
109
|
}
|
|
118
110
|
|
|
119
111
|
/**
|
|
120
|
-
*
|
|
112
|
+
* Raw content result from FTS5 query (no snippet formatting).
|
|
113
|
+
* Used by contradiction detection where clean content is needed for LLM comparison.
|
|
114
|
+
*/
|
|
115
|
+
export interface RawSearchResult {
|
|
116
|
+
rowid: number;
|
|
117
|
+
source: string;
|
|
118
|
+
title: string;
|
|
119
|
+
content: string;
|
|
120
|
+
metadata: string;
|
|
121
|
+
topic: string;
|
|
122
|
+
type: string;
|
|
123
|
+
timestamp: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Search the FTS5 database returning raw content (no snippet markers).
|
|
121
128
|
*
|
|
122
|
-
*
|
|
129
|
+
* Unlike search(), this returns the raw content column without FTS5 snippet()
|
|
130
|
+
* formatting. Used by contradiction detection where snippet markers (arrows)
|
|
131
|
+
* would corrupt LLM text comparison.
|
|
132
|
+
*
|
|
133
|
+
* @param query - Text to match against FTS5 index
|
|
134
|
+
* @param options - Optional source filter and result limit
|
|
135
|
+
* @returns Array of raw search results ranked by relevance
|
|
136
|
+
* @throws Error if database doesn't exist or query fails
|
|
123
137
|
*/
|
|
124
|
-
export function
|
|
125
|
-
|
|
138
|
+
export function rawSearch(
|
|
139
|
+
query: string,
|
|
140
|
+
options: { source?: string; limit?: number } = {},
|
|
141
|
+
): RawSearchResult[] {
|
|
142
|
+
const db = openDatabaseBasic(true);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const limit = options.limit ?? 5;
|
|
146
|
+
|
|
147
|
+
const conditions: string[] = ["search MATCH ?"];
|
|
148
|
+
const params: (string | number)[] = [escapeFts5Query(query)];
|
|
149
|
+
|
|
150
|
+
if (options.source) {
|
|
151
|
+
conditions.push("source = ?");
|
|
152
|
+
params.push(options.source);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
params.push(limit);
|
|
156
|
+
|
|
157
|
+
const sql = `
|
|
158
|
+
SELECT rowid, source, title, content, metadata, topic, type, timestamp
|
|
159
|
+
FROM search
|
|
160
|
+
WHERE ${conditions.join(" AND ")}
|
|
161
|
+
ORDER BY rank
|
|
162
|
+
LIMIT ?
|
|
163
|
+
`;
|
|
126
164
|
|
|
127
|
-
|
|
128
|
-
|
|
165
|
+
const stmt = db.prepare(sql);
|
|
166
|
+
return stmt.all(...params) as RawSearchResult[];
|
|
167
|
+
} finally {
|
|
168
|
+
db.close();
|
|
129
169
|
}
|
|
170
|
+
}
|
|
130
171
|
|
|
131
|
-
|
|
172
|
+
/**
|
|
173
|
+
* List all available sources in the database
|
|
174
|
+
*
|
|
175
|
+
* @returns Array of source names with entry counts
|
|
176
|
+
*/
|
|
177
|
+
export function listSources(): { source: string; count: number }[] {
|
|
178
|
+
const db = openDatabaseBasic(true);
|
|
132
179
|
|
|
133
180
|
try {
|
|
134
181
|
const stmt = db.prepare(`
|