@voidwire/lore 0.9.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.ts +66 -1
- package/lib/config.ts +134 -0
- package/lib/db.ts +2 -2
- package/lib/indexer.ts +213 -0
- package/lib/indexers/blogs.ts +146 -0
- package/lib/indexers/captures.ts +105 -0
- package/lib/indexers/commits.ts +90 -0
- package/lib/indexers/development.ts +68 -0
- package/lib/indexers/events.ts +61 -0
- package/lib/indexers/explorations.ts +89 -0
- package/lib/indexers/flux.ts +142 -0
- package/lib/indexers/index.ts +41 -0
- package/lib/indexers/insights.ts +53 -0
- package/lib/indexers/learnings.ts +53 -0
- package/lib/indexers/observations.ts +53 -0
- package/lib/indexers/obsidian.ts +151 -0
- package/lib/indexers/personal.ts +262 -0
- package/lib/indexers/readmes.ts +49 -0
- package/lib/indexers/sessions.ts +127 -0
- package/lib/indexers/teachings.ts +52 -0
- package/lib/info.ts +4 -8
- package/lib/list.ts +25 -39
- package/lib/projects.ts +28 -37
- package/lib/realtime.ts +16 -23
- package/lib/search.ts +6 -12
- package/lib/semantic.ts +6 -31
- package/package.json +3 -2
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/indexers/teachings.ts - Teachings indexer
|
|
3
|
+
*
|
|
4
|
+
* Reads log.jsonl and indexes teaching captures.
|
|
5
|
+
* Filters for event=captured AND type=teaching.
|
|
6
|
+
*
|
|
7
|
+
* Source: teachings
|
|
8
|
+
* Topic: data.topic (AI-written)
|
|
9
|
+
* Type: teaching (fixed)
|
|
10
|
+
* Timestamp: event timestamp
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, existsSync } from "fs";
|
|
14
|
+
import type { IndexerContext } from "../indexer";
|
|
15
|
+
|
|
16
|
+
export async function indexTeachings(ctx: IndexerContext): Promise<void> {
|
|
17
|
+
const logPath = `${ctx.config.paths.data}/log.jsonl`;
|
|
18
|
+
if (!existsSync(logPath)) {
|
|
19
|
+
console.log("No log.jsonl found, skipping teachings");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const lines = readFileSync(logPath, "utf-8").split("\n").filter(Boolean);
|
|
24
|
+
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
try {
|
|
27
|
+
const event = JSON.parse(line);
|
|
28
|
+
if (event.event !== "captured" || event.type !== "teaching") continue;
|
|
29
|
+
|
|
30
|
+
const topic = event.data?.topic || "general";
|
|
31
|
+
const content = event.data?.content || "";
|
|
32
|
+
const confidence = event.data?.confidence;
|
|
33
|
+
|
|
34
|
+
if (!content) continue;
|
|
35
|
+
|
|
36
|
+
const metadata: Record<string, unknown> = {};
|
|
37
|
+
if (confidence) metadata.confidence = confidence;
|
|
38
|
+
|
|
39
|
+
ctx.insert({
|
|
40
|
+
source: "teachings",
|
|
41
|
+
title: `[teaching] ${topic}`,
|
|
42
|
+
content,
|
|
43
|
+
topic,
|
|
44
|
+
type: "teaching",
|
|
45
|
+
timestamp: event.timestamp,
|
|
46
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
|
|
47
|
+
});
|
|
48
|
+
} catch (e) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/lib/info.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Database } from "bun:sqlite";
|
|
9
|
-
import { homedir } from "os";
|
|
10
9
|
import { existsSync } from "fs";
|
|
10
|
+
import { getDatabasePath } from "./db.js";
|
|
11
11
|
import { projects as getProjects } from "./projects.js";
|
|
12
12
|
|
|
13
13
|
export interface SourceInfo {
|
|
@@ -22,10 +22,6 @@ export interface InfoOutput {
|
|
|
22
22
|
total_entries: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function getDatabasePath(): string {
|
|
26
|
-
return `${homedir()}/.local/share/lore/lore.db`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
/**
|
|
30
26
|
* Get info about indexed sources
|
|
31
27
|
*
|
|
@@ -62,11 +58,11 @@ export function info(): InfoOutput {
|
|
|
62
58
|
const totalResult = totalStmt.get() as { total: number };
|
|
63
59
|
const total_entries = totalResult?.total ?? 0;
|
|
64
60
|
|
|
65
|
-
// Get last indexed timestamp from
|
|
61
|
+
// Get last indexed timestamp from column
|
|
66
62
|
const tsStmt = db.prepare(`
|
|
67
|
-
SELECT MAX(
|
|
63
|
+
SELECT MAX(timestamp) as ts
|
|
68
64
|
FROM search
|
|
69
|
-
WHERE
|
|
65
|
+
WHERE timestamp IS NOT NULL AND timestamp != ''
|
|
70
66
|
`);
|
|
71
67
|
const tsResult = tsStmt.get() as { ts: string | null };
|
|
72
68
|
const last_indexed = tsResult?.ts ?? new Date().toISOString();
|
package/lib/list.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Database } from "bun:sqlite";
|
|
9
|
-
import { homedir } from "os";
|
|
10
9
|
import { existsSync } from "fs";
|
|
10
|
+
import { getDatabasePath } from "./db.js";
|
|
11
11
|
|
|
12
12
|
// Source types - data sources that can be listed
|
|
13
13
|
export type Source =
|
|
@@ -65,19 +65,6 @@ const PERSONAL_SUBTYPES: Partial<Record<Source, string>> = {
|
|
|
65
65
|
habits: "habit",
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
// Maps source to metadata field for --project filter
|
|
69
|
-
// Project-based domains use "project", topic-based domains use "topic"
|
|
70
|
-
const PROJECT_FIELD: Record<string, string> = {
|
|
71
|
-
commits: "project",
|
|
72
|
-
sessions: "project",
|
|
73
|
-
flux: "project",
|
|
74
|
-
insights: "topic",
|
|
75
|
-
captures: "topic",
|
|
76
|
-
teachings: "topic",
|
|
77
|
-
learnings: "topic",
|
|
78
|
-
observations: "topic",
|
|
79
|
-
};
|
|
80
|
-
|
|
81
68
|
export interface ListOptions {
|
|
82
69
|
limit?: number;
|
|
83
70
|
project?: string;
|
|
@@ -87,6 +74,8 @@ export interface ListOptions {
|
|
|
87
74
|
export interface ListEntry {
|
|
88
75
|
title: string;
|
|
89
76
|
content: string;
|
|
77
|
+
topic: string;
|
|
78
|
+
type: string;
|
|
90
79
|
metadata: Record<string, unknown>;
|
|
91
80
|
}
|
|
92
81
|
|
|
@@ -96,14 +85,11 @@ export interface ListResult {
|
|
|
96
85
|
count: number;
|
|
97
86
|
}
|
|
98
87
|
|
|
99
|
-
// Database path following XDG spec
|
|
100
|
-
function getDatabasePath(): string {
|
|
101
|
-
return `${homedir()}/.local/share/lore/lore.db`;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
88
|
interface RawRow {
|
|
105
89
|
title: string;
|
|
106
90
|
content: string;
|
|
91
|
+
topic: string;
|
|
92
|
+
type: string;
|
|
107
93
|
metadata: string;
|
|
108
94
|
}
|
|
109
95
|
|
|
@@ -117,26 +103,24 @@ function queryBySource(
|
|
|
117
103
|
project?: string,
|
|
118
104
|
type?: string,
|
|
119
105
|
): ListEntry[] {
|
|
120
|
-
let sql =
|
|
106
|
+
let sql =
|
|
107
|
+
"SELECT title, content, topic, type, metadata FROM search WHERE source = ?";
|
|
121
108
|
const params: (string | number)[] = [source];
|
|
122
109
|
|
|
123
|
-
// Add project filter if provided
|
|
110
|
+
// Add project filter if provided — uses topic column directly
|
|
124
111
|
if (project) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
sql += ` AND json_extract(metadata, '$.${field}') = ?`;
|
|
128
|
-
params.push(project);
|
|
129
|
-
}
|
|
112
|
+
sql += " AND topic = ?";
|
|
113
|
+
params.push(project);
|
|
130
114
|
}
|
|
131
115
|
|
|
132
|
-
// Add type filter if provided
|
|
133
|
-
if (type
|
|
134
|
-
sql +=
|
|
116
|
+
// Add type filter if provided — uses type column directly
|
|
117
|
+
if (type) {
|
|
118
|
+
sql += " AND type = ?";
|
|
135
119
|
params.push(type);
|
|
136
120
|
}
|
|
137
121
|
|
|
138
122
|
// Order by timestamp descending (most recent first)
|
|
139
|
-
sql += " ORDER BY
|
|
123
|
+
sql += " ORDER BY timestamp DESC";
|
|
140
124
|
|
|
141
125
|
if (limit) {
|
|
142
126
|
sql += " LIMIT ?";
|
|
@@ -149,6 +133,8 @@ function queryBySource(
|
|
|
149
133
|
return rows.map((row) => ({
|
|
150
134
|
title: row.title,
|
|
151
135
|
content: row.content,
|
|
136
|
+
topic: row.topic,
|
|
137
|
+
type: row.type,
|
|
152
138
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
153
139
|
}));
|
|
154
140
|
}
|
|
@@ -163,10 +149,10 @@ function queryPersonalType(
|
|
|
163
149
|
): ListEntry[] {
|
|
164
150
|
// Filter by type in SQL, not JS - avoids LIMIT truncation bug
|
|
165
151
|
let sql = `
|
|
166
|
-
SELECT title, content, metadata FROM search
|
|
152
|
+
SELECT title, content, topic, type, metadata FROM search
|
|
167
153
|
WHERE source = 'personal'
|
|
168
|
-
AND
|
|
169
|
-
ORDER BY
|
|
154
|
+
AND type = ?
|
|
155
|
+
ORDER BY timestamp DESC
|
|
170
156
|
`;
|
|
171
157
|
const params: (string | number)[] = [type];
|
|
172
158
|
|
|
@@ -181,6 +167,8 @@ function queryPersonalType(
|
|
|
181
167
|
return rows.map((row) => ({
|
|
182
168
|
title: row.title,
|
|
183
169
|
content: row.content,
|
|
170
|
+
topic: row.topic,
|
|
171
|
+
type: row.type,
|
|
184
172
|
metadata: JSON.parse(row.metadata || "{}"),
|
|
185
173
|
}));
|
|
186
174
|
}
|
|
@@ -203,7 +191,7 @@ export function list(source: Source, options: ListOptions = {}): ListResult {
|
|
|
203
191
|
const dbPath = getDatabasePath();
|
|
204
192
|
|
|
205
193
|
if (!existsSync(dbPath)) {
|
|
206
|
-
throw new Error(`Database not found: ${dbPath}. Run lore-
|
|
194
|
+
throw new Error(`Database not found: ${dbPath}. Run lore-db-init first.`);
|
|
207
195
|
}
|
|
208
196
|
|
|
209
197
|
const db = new Database(dbPath, { readonly: true });
|
|
@@ -243,12 +231,10 @@ export function listSources(): Source[] {
|
|
|
243
231
|
}
|
|
244
232
|
|
|
245
233
|
/**
|
|
246
|
-
* Extract project name from entry
|
|
234
|
+
* Extract project name from entry
|
|
247
235
|
*/
|
|
248
|
-
function extractProjectFromEntry(entry: ListEntry,
|
|
249
|
-
|
|
250
|
-
if (!field) return "unknown";
|
|
251
|
-
return (entry.metadata[field] as string) || "unknown";
|
|
236
|
+
function extractProjectFromEntry(entry: ListEntry, _source: string): string {
|
|
237
|
+
return entry.topic || "unknown";
|
|
252
238
|
}
|
|
253
239
|
|
|
254
240
|
/**
|
package/lib/projects.ts
CHANGED
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/projects.ts - List all known projects across sources
|
|
3
3
|
*
|
|
4
|
-
* Queries distinct
|
|
5
|
-
*
|
|
4
|
+
* Queries distinct topic values from the topic column across
|
|
5
|
+
* project-scoped sources.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Database } from "bun:sqlite";
|
|
9
|
-
import { homedir } from "os";
|
|
10
9
|
import { existsSync } from "fs";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
commits
|
|
15
|
-
sessions
|
|
16
|
-
|
|
17
|
-
insights
|
|
18
|
-
captures
|
|
19
|
-
teachings
|
|
20
|
-
learnings
|
|
21
|
-
observations
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function getDatabasePath(): string {
|
|
25
|
-
return `${homedir()}/.local/share/lore/lore.db`;
|
|
26
|
-
}
|
|
10
|
+
import { getDatabasePath } from "./db.js";
|
|
11
|
+
|
|
12
|
+
const PROJECT_SOURCES = [
|
|
13
|
+
"commits",
|
|
14
|
+
"sessions",
|
|
15
|
+
"flux",
|
|
16
|
+
"insights",
|
|
17
|
+
"captures",
|
|
18
|
+
"teachings",
|
|
19
|
+
"learnings",
|
|
20
|
+
"observations",
|
|
21
|
+
];
|
|
27
22
|
|
|
28
23
|
/**
|
|
29
24
|
* Get all unique project names across sources
|
|
@@ -40,24 +35,20 @@ export function projects(): string[] {
|
|
|
40
35
|
const db = new Database(dbPath, { readonly: true });
|
|
41
36
|
|
|
42
37
|
try {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return Array.from(allProjects).sort();
|
|
38
|
+
const placeholders = PROJECT_SOURCES.map(() => "?").join(", ");
|
|
39
|
+
const stmt = db.prepare(`
|
|
40
|
+
SELECT DISTINCT topic
|
|
41
|
+
FROM search
|
|
42
|
+
WHERE source IN (${placeholders})
|
|
43
|
+
AND topic IS NOT NULL
|
|
44
|
+
AND topic != ''
|
|
45
|
+
`);
|
|
46
|
+
const results = stmt.all(...PROJECT_SOURCES) as { topic: string }[];
|
|
47
|
+
|
|
48
|
+
return results
|
|
49
|
+
.map((r) => r.topic)
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.sort();
|
|
61
52
|
} finally {
|
|
62
53
|
db.close();
|
|
63
54
|
}
|
package/lib/realtime.ts
CHANGED
|
@@ -23,12 +23,7 @@ import {
|
|
|
23
23
|
EMBEDDING_DIM,
|
|
24
24
|
serializeEmbedding,
|
|
25
25
|
} from "./semantic.js";
|
|
26
|
-
import {
|
|
27
|
-
hashContent,
|
|
28
|
-
getCachedEmbedding,
|
|
29
|
-
cacheEmbedding,
|
|
30
|
-
getMissingHashes,
|
|
31
|
-
} from "./cache.js";
|
|
26
|
+
import { hashContent, getCachedEmbedding, cacheEmbedding } from "./cache.js";
|
|
32
27
|
import type { CaptureEvent } from "./capture.js";
|
|
33
28
|
|
|
34
29
|
/**
|
|
@@ -89,13 +84,23 @@ function insertSearchEntry(db: Database, event: CaptureEvent): number {
|
|
|
89
84
|
const metadata = buildMetadata(event);
|
|
90
85
|
const data = event.data as Record<string, unknown>;
|
|
91
86
|
const topic = String(data.topic || "");
|
|
87
|
+
const type = extractType(event);
|
|
88
|
+
const timestamp = event.timestamp || new Date().toISOString();
|
|
92
89
|
|
|
93
90
|
const stmt = db.prepare(`
|
|
94
|
-
INSERT INTO search (source, title, content, metadata, topic)
|
|
95
|
-
VALUES (?, ?, ?, ?, ?)
|
|
91
|
+
INSERT INTO search (source, title, content, metadata, topic, type, timestamp)
|
|
92
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
96
93
|
`);
|
|
97
94
|
|
|
98
|
-
const result = stmt.run(
|
|
95
|
+
const result = stmt.run(
|
|
96
|
+
source,
|
|
97
|
+
title,
|
|
98
|
+
content,
|
|
99
|
+
metadata,
|
|
100
|
+
topic,
|
|
101
|
+
type,
|
|
102
|
+
timestamp,
|
|
103
|
+
);
|
|
99
104
|
return Number(result.lastInsertRowid);
|
|
100
105
|
}
|
|
101
106
|
|
|
@@ -163,31 +168,19 @@ function getContentForEmbedding(event: CaptureEvent): string {
|
|
|
163
168
|
*/
|
|
164
169
|
function buildMetadata(event: CaptureEvent): string {
|
|
165
170
|
const data = event.data as Record<string, unknown>;
|
|
166
|
-
const
|
|
167
|
-
const date = timestamp ? timestamp.substring(0, 10) : "";
|
|
168
|
-
|
|
169
|
-
const content = getContentForEmbedding(event);
|
|
170
|
-
const metadata: Record<string, unknown> = {
|
|
171
|
-
topic: data.topic || "general",
|
|
172
|
-
timestamp,
|
|
173
|
-
date,
|
|
174
|
-
content,
|
|
175
|
-
content_hash: hashContent(content),
|
|
176
|
-
};
|
|
171
|
+
const metadata: Record<string, unknown> = {};
|
|
177
172
|
|
|
178
|
-
// Add type-specific fields
|
|
173
|
+
// Add type-specific fields only (no topic, content, content_hash, date, timestamp)
|
|
179
174
|
switch (event.type) {
|
|
180
175
|
case "knowledge":
|
|
181
176
|
metadata.subtype = data.subtype;
|
|
182
177
|
break;
|
|
183
178
|
case "teaching":
|
|
184
179
|
metadata.confidence = data.confidence;
|
|
185
|
-
metadata.capture_source = data.source || "manual";
|
|
186
180
|
break;
|
|
187
181
|
case "observation":
|
|
188
182
|
metadata.subtype = data.subtype;
|
|
189
183
|
metadata.confidence = data.confidence;
|
|
190
|
-
metadata.capture_source = data.source || "auto";
|
|
191
184
|
break;
|
|
192
185
|
case "insight":
|
|
193
186
|
metadata.subtype = data.subtype;
|
package/lib/search.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { Database } from "bun:sqlite";
|
|
9
|
-
import { homedir } from "os";
|
|
10
9
|
import { existsSync } from "fs";
|
|
10
|
+
import { getDatabasePath } from "./db.js";
|
|
11
11
|
|
|
12
12
|
export interface SearchResult {
|
|
13
13
|
rowid: number;
|
|
@@ -15,6 +15,7 @@ export interface SearchResult {
|
|
|
15
15
|
title: string;
|
|
16
16
|
content: string;
|
|
17
17
|
metadata: string;
|
|
18
|
+
topic: string;
|
|
18
19
|
rank: number;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -25,10 +26,6 @@ export interface SearchOptions {
|
|
|
25
26
|
type?: string | string[];
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
function getDatabasePath(): string {
|
|
29
|
-
return `${homedir()}/.local/share/lore/lore.db`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
29
|
/**
|
|
33
30
|
* Escape a query for safe FTS5 MATCH
|
|
34
31
|
* Wraps terms in double quotes to prevent FTS5 syntax interpretation
|
|
@@ -85,19 +82,16 @@ export function search(
|
|
|
85
82
|
|
|
86
83
|
if (options.type) {
|
|
87
84
|
const types = Array.isArray(options.type) ? options.type : [options.type];
|
|
88
|
-
const typeClauses = types.map(
|
|
89
|
-
() =>
|
|
90
|
-
"(json_extract(metadata, '$.type') = ? OR json_extract(metadata, '$.subtype') = ?)",
|
|
91
|
-
);
|
|
85
|
+
const typeClauses = types.map(() => "type = ?");
|
|
92
86
|
conditions.push(`(${typeClauses.join(" OR ")})`);
|
|
93
87
|
types.forEach((t) => {
|
|
94
|
-
params.push(t
|
|
88
|
+
params.push(t);
|
|
95
89
|
});
|
|
96
90
|
}
|
|
97
91
|
|
|
98
92
|
if (options.since) {
|
|
99
93
|
conditions.push(
|
|
100
|
-
"
|
|
94
|
+
"timestamp IS NOT NULL AND timestamp != '' AND timestamp >= ?",
|
|
101
95
|
);
|
|
102
96
|
params.push(options.since);
|
|
103
97
|
}
|
|
@@ -105,7 +99,7 @@ export function search(
|
|
|
105
99
|
params.push(limit);
|
|
106
100
|
|
|
107
101
|
const sql = `
|
|
108
|
-
SELECT rowid, source, title, snippet(search, 2, '→', '←', '...', 32) as content, metadata, rank
|
|
102
|
+
SELECT rowid, source, title, snippet(search, 2, '→', '←', '...', 32) as content, metadata, topic, rank
|
|
109
103
|
FROM search
|
|
110
104
|
WHERE ${conditions.join(" AND ")}
|
|
111
105
|
ORDER BY rank
|
package/lib/semantic.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface SemanticResult {
|
|
|
18
18
|
title: string;
|
|
19
19
|
content: string;
|
|
20
20
|
metadata: string;
|
|
21
|
+
topic: string;
|
|
21
22
|
distance: number;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -28,21 +29,6 @@ export interface SemanticSearchOptions {
|
|
|
28
29
|
type?: string | string[];
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
/**
|
|
32
|
-
* Maps source types to their project/topic field name in metadata JSON.
|
|
33
|
-
* Project-based domains use "project", topic-based domains use "topic".
|
|
34
|
-
*/
|
|
35
|
-
const PROJECT_FIELD: Record<string, string> = {
|
|
36
|
-
commits: "project",
|
|
37
|
-
sessions: "project",
|
|
38
|
-
tasks: "project",
|
|
39
|
-
insights: "topic",
|
|
40
|
-
captures: "topic",
|
|
41
|
-
teachings: "topic",
|
|
42
|
-
learnings: "topic",
|
|
43
|
-
observations: "topic",
|
|
44
|
-
};
|
|
45
|
-
|
|
46
32
|
const MODEL_NAME = "nomic-ai/nomic-embed-text-v1.5";
|
|
47
33
|
const EMBEDDING_DIM = 768;
|
|
48
34
|
|
|
@@ -254,6 +240,7 @@ export async function semanticSearch(
|
|
|
254
240
|
s.title,
|
|
255
241
|
s.content,
|
|
256
242
|
s.metadata,
|
|
243
|
+
s.topic,
|
|
257
244
|
e.distance
|
|
258
245
|
FROM embeddings e
|
|
259
246
|
JOIN search s ON e.doc_id = s.rowid
|
|
@@ -281,6 +268,7 @@ export interface HybridResult {
|
|
|
281
268
|
title: string;
|
|
282
269
|
content: string;
|
|
283
270
|
metadata: string;
|
|
271
|
+
topic: string;
|
|
284
272
|
score: number;
|
|
285
273
|
vectorScore: number;
|
|
286
274
|
textScore: number;
|
|
@@ -367,6 +355,7 @@ export async function hybridSearch(
|
|
|
367
355
|
title: r.title,
|
|
368
356
|
content: r.content,
|
|
369
357
|
metadata: r.metadata,
|
|
358
|
+
topic: r.topic,
|
|
370
359
|
vectorScore,
|
|
371
360
|
textScore: 0,
|
|
372
361
|
score: vectorWeight * vectorScore,
|
|
@@ -393,6 +382,7 @@ export async function hybridSearch(
|
|
|
393
382
|
title: r.title,
|
|
394
383
|
content: r.content,
|
|
395
384
|
metadata: r.metadata,
|
|
385
|
+
topic: r.topic,
|
|
396
386
|
vectorScore: 0,
|
|
397
387
|
textScore,
|
|
398
388
|
score: textWeight * textScore,
|
|
@@ -408,21 +398,6 @@ export async function hybridSearch(
|
|
|
408
398
|
return results;
|
|
409
399
|
}
|
|
410
400
|
|
|
411
|
-
/**
|
|
412
|
-
* Extract project from result metadata
|
|
413
|
-
*/
|
|
414
|
-
function extractProjectFromMetadata(metadata: string, source: string): string {
|
|
415
|
-
const field = PROJECT_FIELD[source];
|
|
416
|
-
if (!field) return "unknown";
|
|
417
|
-
|
|
418
|
-
try {
|
|
419
|
-
const parsed = JSON.parse(metadata);
|
|
420
|
-
return parsed[field] || "unknown";
|
|
421
|
-
} catch {
|
|
422
|
-
return "unknown";
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
401
|
/**
|
|
427
402
|
* Extract identifier from semantic result
|
|
428
403
|
*/
|
|
@@ -478,7 +453,7 @@ export function formatBriefSearch(results: SemanticResult[]): string {
|
|
|
478
453
|
const lines = [`${source} (${sourceResults.length}):`];
|
|
479
454
|
|
|
480
455
|
sourceResults.forEach((r) => {
|
|
481
|
-
const project =
|
|
456
|
+
const project = r.topic || "unknown";
|
|
482
457
|
const identifier = extractIdentifierFromResult(r);
|
|
483
458
|
const displayText = getDisplayText(r);
|
|
484
459
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidwire/lore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Unified knowledge CLI - Search, list, and capture your indexed knowledge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"bun": ">=1.0.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@huggingface/transformers": "^3.2.6"
|
|
46
|
+
"@huggingface/transformers": "^3.2.6",
|
|
47
|
+
"@iarna/toml": "^2.2.5"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"bun-types": "1.3.5"
|