@lotargo/memory_plugin 1.3.2 → 1.4.5
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 +334 -324
- package/mcp-server/admin/auth.js +385 -0
- package/mcp-server/admin/snapshot.js +34 -32
- package/mcp-server/cli.js +108 -12
- package/mcp-server/config/auth_store.js +137 -0
- package/mcp-server/config/config_manager.js +5 -0
- package/mcp-server/db/database.js +198 -14
- package/mcp-server/db/migrations.js +62 -33
- package/mcp-server/db/sync_queue.js +211 -0
- package/mcp-server/graph/graph_extractor.js +40 -17
- package/mcp-server/graph/knowledge_linker.js +14 -14
- package/mcp-server/index.js +34 -23
- package/mcp-server/ingest/exporter.js +12 -12
- package/mcp-server/ingest/normalizer.js +102 -5
- package/mcp-server/ingest/pipeline.js +53 -29
- package/mcp-server/memory.js +73 -0
- package/mcp-server/retrieval/retriever.js +24 -15
- package/package.json +6 -2
|
@@ -2,9 +2,9 @@ const MIGRATIONS = [
|
|
|
2
2
|
{
|
|
3
3
|
version: 1,
|
|
4
4
|
name: "001_initial_rag_schema",
|
|
5
|
-
up: (db) => {
|
|
5
|
+
up: async (db) => {
|
|
6
6
|
// 1. Documents Table
|
|
7
|
-
db.exec(`
|
|
7
|
+
await db.exec(`
|
|
8
8
|
CREATE TABLE IF NOT EXISTS documents (
|
|
9
9
|
id TEXT PRIMARY KEY,
|
|
10
10
|
path TEXT UNIQUE NOT NULL,
|
|
@@ -19,7 +19,7 @@ const MIGRATIONS = [
|
|
|
19
19
|
`);
|
|
20
20
|
|
|
21
21
|
// 2. Sections Table
|
|
22
|
-
db.exec(`
|
|
22
|
+
await db.exec(`
|
|
23
23
|
CREATE TABLE IF NOT EXISTS sections (
|
|
24
24
|
id TEXT PRIMARY KEY,
|
|
25
25
|
doc_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
@@ -31,7 +31,7 @@ const MIGRATIONS = [
|
|
|
31
31
|
`);
|
|
32
32
|
|
|
33
33
|
// 3. Micro-Chunks Table
|
|
34
|
-
db.exec(`
|
|
34
|
+
await db.exec(`
|
|
35
35
|
CREATE TABLE IF NOT EXISTS micro_chunks (
|
|
36
36
|
id TEXT PRIMARY KEY,
|
|
37
37
|
section_id TEXT NOT NULL REFERENCES sections(id) ON DELETE CASCADE,
|
|
@@ -43,7 +43,7 @@ const MIGRATIONS = [
|
|
|
43
43
|
`);
|
|
44
44
|
|
|
45
45
|
// 4. Full-Text Search (BM25 Index via SQLite FTS5)
|
|
46
|
-
db.exec(`
|
|
46
|
+
await db.exec(`
|
|
47
47
|
CREATE VIRTUAL TABLE IF NOT EXISTS micro_chunks_fts USING fts5(
|
|
48
48
|
id UNINDEXED,
|
|
49
49
|
content,
|
|
@@ -52,7 +52,7 @@ const MIGRATIONS = [
|
|
|
52
52
|
`);
|
|
53
53
|
|
|
54
54
|
// 5. GraphRAG Lite Edges Table
|
|
55
|
-
db.exec(`
|
|
55
|
+
await db.exec(`
|
|
56
56
|
CREATE TABLE IF NOT EXISTS graph_edges (
|
|
57
57
|
source_id TEXT NOT NULL,
|
|
58
58
|
target_id TEXT NOT NULL,
|
|
@@ -65,15 +65,15 @@ const MIGRATIONS = [
|
|
|
65
65
|
{
|
|
66
66
|
version: 2,
|
|
67
67
|
name: "002_agent_knowledge_graph",
|
|
68
|
-
up: (db) => {
|
|
68
|
+
up: async (db) => {
|
|
69
69
|
try {
|
|
70
|
-
db.exec(`ALTER TABLE graph_edges ADD COLUMN metadata_json TEXT;`);
|
|
70
|
+
await db.exec(`ALTER TABLE graph_edges ADD COLUMN metadata_json TEXT;`);
|
|
71
71
|
} catch (e) {}
|
|
72
72
|
try {
|
|
73
|
-
db.exec(`ALTER TABLE graph_edges ADD COLUMN created_at INTEGER;`);
|
|
73
|
+
await db.exec(`ALTER TABLE graph_edges ADD COLUMN created_at INTEGER;`);
|
|
74
74
|
} catch (e) {}
|
|
75
75
|
|
|
76
|
-
db.exec(`
|
|
76
|
+
await db.exec(`
|
|
77
77
|
CREATE TABLE IF NOT EXISTS knowledge_links (
|
|
78
78
|
id TEXT PRIMARY KEY,
|
|
79
79
|
fact_key TEXT NOT NULL,
|
|
@@ -92,8 +92,8 @@ const MIGRATIONS = [
|
|
|
92
92
|
{
|
|
93
93
|
version: 3,
|
|
94
94
|
name: "003_medium_chunks_hierarchy",
|
|
95
|
-
up: (db) => {
|
|
96
|
-
db.exec(`
|
|
95
|
+
up: async (db) => {
|
|
96
|
+
await db.exec(`
|
|
97
97
|
CREATE TABLE IF NOT EXISTS medium_chunks (
|
|
98
98
|
id TEXT PRIMARY KEY,
|
|
99
99
|
section_id TEXT NOT NULL REFERENCES sections(id) ON DELETE CASCADE,
|
|
@@ -106,43 +106,72 @@ const MIGRATIONS = [
|
|
|
106
106
|
`);
|
|
107
107
|
|
|
108
108
|
try {
|
|
109
|
-
db.exec(`ALTER TABLE micro_chunks ADD COLUMN medium_id TEXT REFERENCES medium_chunks(id) ON DELETE CASCADE;`);
|
|
109
|
+
await db.exec(`ALTER TABLE micro_chunks ADD COLUMN medium_id TEXT REFERENCES medium_chunks(id) ON DELETE CASCADE;`);
|
|
110
110
|
} catch (e) {}
|
|
111
111
|
},
|
|
112
112
|
},
|
|
113
113
|
];
|
|
114
114
|
|
|
115
|
-
export function runMigrations(db) {
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
export async function runMigrations(db) {
|
|
116
|
+
let currentVersion = 0;
|
|
117
|
+
try {
|
|
118
|
+
const row = await db.prepare("SELECT MAX(version) as v FROM schema_migrations;").get();
|
|
119
|
+
currentVersion = row ? row.v || 0 : 0;
|
|
120
|
+
} catch (e) {
|
|
121
|
+
try {
|
|
122
|
+
const versionRow = await db.prepare("PRAGMA user_version;").get();
|
|
123
|
+
currentVersion = versionRow ? (versionRow.user_version || 0) : 0;
|
|
124
|
+
} catch (e2) {}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Ensure schema_migrations table exists for future
|
|
128
|
+
try {
|
|
129
|
+
await db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY);`);
|
|
130
|
+
} catch (e) {}
|
|
131
|
+
|
|
132
|
+
// Create notebooks table if not exists
|
|
133
|
+
try {
|
|
134
|
+
await db.exec(`
|
|
135
|
+
CREATE TABLE IF NOT EXISTS notebooks (
|
|
136
|
+
key TEXT PRIMARY KEY,
|
|
137
|
+
content TEXT NOT NULL,
|
|
138
|
+
updated_at INTEGER NOT NULL
|
|
139
|
+
);
|
|
140
|
+
`);
|
|
141
|
+
} catch (e) {}
|
|
118
142
|
|
|
119
143
|
for (const migration of MIGRATIONS) {
|
|
120
144
|
if (migration.version > currentVersion) {
|
|
121
|
-
db.exec("BEGIN
|
|
145
|
+
await db.exec("BEGIN;");
|
|
122
146
|
try {
|
|
123
|
-
migration.up(db);
|
|
124
|
-
db.
|
|
125
|
-
db.exec(
|
|
147
|
+
await migration.up(db);
|
|
148
|
+
await db.prepare("INSERT INTO schema_migrations (version) VALUES (?);").run(migration.version);
|
|
149
|
+
await db.exec("COMMIT;");
|
|
150
|
+
try {
|
|
151
|
+
await db.exec(`PRAGMA user_version = ${migration.version};`);
|
|
152
|
+
} catch (e) {}
|
|
126
153
|
} catch (err) {
|
|
127
|
-
|
|
154
|
+
try {
|
|
155
|
+
await db.exec("ROLLBACK;");
|
|
156
|
+
} catch (e) {}
|
|
128
157
|
throw new Error(`Migration ${migration.name} failed: ${err.message}`);
|
|
129
158
|
}
|
|
130
159
|
}
|
|
131
160
|
}
|
|
132
161
|
|
|
133
162
|
// Defensive table & column check for medium_chunks hierarchy
|
|
134
|
-
db.exec(`
|
|
135
|
-
CREATE TABLE IF NOT EXISTS medium_chunks (
|
|
136
|
-
id TEXT PRIMARY KEY,
|
|
137
|
-
section_id TEXT NOT NULL REFERENCES sections(id) ON DELETE CASCADE,
|
|
138
|
-
doc_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
139
|
-
content TEXT NOT NULL,
|
|
140
|
-
block_type TEXT NOT NULL,
|
|
141
|
-
token_count INTEGER NOT NULL,
|
|
142
|
-
created_at INTEGER
|
|
143
|
-
);
|
|
144
|
-
`);
|
|
145
163
|
try {
|
|
146
|
-
db.exec(`
|
|
164
|
+
await db.exec(`
|
|
165
|
+
CREATE TABLE IF NOT EXISTS medium_chunks (
|
|
166
|
+
id TEXT PRIMARY KEY,
|
|
167
|
+
section_id TEXT NOT NULL REFERENCES sections(id) ON DELETE CASCADE,
|
|
168
|
+
doc_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
169
|
+
content TEXT NOT NULL,
|
|
170
|
+
block_type TEXT NOT NULL,
|
|
171
|
+
token_count INTEGER NOT NULL,
|
|
172
|
+
created_at INTEGER
|
|
173
|
+
);
|
|
174
|
+
`);
|
|
175
|
+
await db.exec(`ALTER TABLE micro_chunks ADD COLUMN medium_id TEXT REFERENCES medium_chunks(id) ON DELETE CASCADE;`);
|
|
147
176
|
} catch (e) {}
|
|
148
177
|
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
let isSyncing = false;
|
|
2
|
+
|
|
3
|
+
async function processSyncTask(db, task) {
|
|
4
|
+
if (task.action === "write_memory") {
|
|
5
|
+
await db.cloudClient.execute({
|
|
6
|
+
sql: `
|
|
7
|
+
INSERT INTO notebooks (key, content, updated_at)
|
|
8
|
+
VALUES (?, ?, ?)
|
|
9
|
+
ON CONFLICT(key) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at;
|
|
10
|
+
`,
|
|
11
|
+
args: [task.key_or_id, task.payload, task.created_at],
|
|
12
|
+
});
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (task.action === "delete_document") {
|
|
17
|
+
const docId = task.key_or_id;
|
|
18
|
+
const docRow = await db.cloudClient.execute({
|
|
19
|
+
sql: "SELECT id FROM documents WHERE id = ? OR path = ?;",
|
|
20
|
+
args: [docId, docId],
|
|
21
|
+
});
|
|
22
|
+
if (docRow.rows.length > 0) {
|
|
23
|
+
const realDocId = docRow.rows[0].id;
|
|
24
|
+
await db.cloudClient.execute({ sql: "DELETE FROM micro_chunks_fts WHERE id IN (SELECT id FROM micro_chunks WHERE doc_id = ?);", args: [realDocId] });
|
|
25
|
+
await db.cloudClient.execute({ sql: "DELETE FROM micro_chunks WHERE doc_id = ?;", args: [realDocId] });
|
|
26
|
+
await db.cloudClient.execute({ sql: "DELETE FROM medium_chunks WHERE doc_id = ?;", args: [realDocId] });
|
|
27
|
+
await db.cloudClient.execute({ sql: "DELETE FROM sections WHERE doc_id = ?;", args: [realDocId] });
|
|
28
|
+
await db.cloudClient.execute({ sql: "DELETE FROM graph_edges WHERE source_id = ? OR target_id = ?;", args: [realDocId, realDocId] });
|
|
29
|
+
await db.cloudClient.execute({ sql: "DELETE FROM knowledge_links WHERE doc_id = ?;", args: [realDocId] });
|
|
30
|
+
await db.cloudClient.execute({ sql: "DELETE FROM documents WHERE id = ?;", args: [realDocId] });
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (task.action === "ingest_document") {
|
|
36
|
+
const data = JSON.parse(task.payload);
|
|
37
|
+
const doc = data.document;
|
|
38
|
+
|
|
39
|
+
// 1. Delete existing doc from cloud if any
|
|
40
|
+
const existingDocRow = await db.cloudClient.execute({
|
|
41
|
+
sql: "SELECT id FROM documents WHERE id = ? OR path = ?;",
|
|
42
|
+
args: [doc.id, doc.path],
|
|
43
|
+
});
|
|
44
|
+
if (existingDocRow.rows.length > 0) {
|
|
45
|
+
const realDocId = existingDocRow.rows[0].id;
|
|
46
|
+
await db.cloudClient.execute({ sql: "DELETE FROM micro_chunks_fts WHERE id IN (SELECT id FROM micro_chunks WHERE doc_id = ?);", args: [realDocId] });
|
|
47
|
+
await db.cloudClient.execute({ sql: "DELETE FROM micro_chunks WHERE doc_id = ?;", args: [realDocId] });
|
|
48
|
+
await db.cloudClient.execute({ sql: "DELETE FROM medium_chunks WHERE doc_id = ?;", args: [realDocId] });
|
|
49
|
+
await db.cloudClient.execute({ sql: "DELETE FROM sections WHERE doc_id = ?;", args: [realDocId] });
|
|
50
|
+
await db.cloudClient.execute({ sql: "DELETE FROM graph_edges WHERE source_id = ? OR target_id = ?;", args: [realDocId, realDocId] });
|
|
51
|
+
await db.cloudClient.execute({ sql: "DELETE FROM knowledge_links WHERE doc_id = ?;", args: [realDocId] });
|
|
52
|
+
await db.cloudClient.execute({ sql: "DELETE FROM documents WHERE id = ?;", args: [realDocId] });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Insert document
|
|
56
|
+
await db.cloudClient.execute({
|
|
57
|
+
sql: `
|
|
58
|
+
INSERT INTO documents (id, path, blob_hash, title, checksum, toc_json, metadata_json, created_at, updated_at)
|
|
59
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
|
60
|
+
`,
|
|
61
|
+
args: [
|
|
62
|
+
doc.id,
|
|
63
|
+
doc.path,
|
|
64
|
+
doc.blob_hash,
|
|
65
|
+
doc.title,
|
|
66
|
+
doc.checksum,
|
|
67
|
+
doc.toc_json ? (typeof doc.toc_json === "string" ? doc.toc_json : JSON.stringify(doc.toc_json)) : null,
|
|
68
|
+
doc.metadata_json ? (typeof doc.metadata_json === "string" ? doc.metadata_json : JSON.stringify(doc.metadata_json)) : null,
|
|
69
|
+
doc.created_at,
|
|
70
|
+
doc.updated_at,
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// 3. Insert sections
|
|
75
|
+
if (Array.isArray(data.sections)) {
|
|
76
|
+
for (const s of data.sections) {
|
|
77
|
+
await db.cloudClient.execute({
|
|
78
|
+
sql: "INSERT INTO sections (id, doc_id, heading, breadcrumbs, content, token_count) VALUES (?, ?, ?, ?, ?, ?);",
|
|
79
|
+
args: [s.id, doc.id, s.heading, s.breadcrumbs, s.content, s.token_count],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 4. Insert medium_chunks
|
|
85
|
+
if (Array.isArray(data.medium_chunks)) {
|
|
86
|
+
for (const m of data.medium_chunks) {
|
|
87
|
+
await db.cloudClient.execute({
|
|
88
|
+
sql: "INSERT INTO medium_chunks (id, section_id, doc_id, content, block_type, token_count, created_at) VALUES (?, ?, ?, ?, ?, ?, ?);",
|
|
89
|
+
args: [m.id, m.section_id, doc.id, m.content, m.block_type, m.token_count, m.created_at || Date.now()],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 5. Insert micro_chunks & FTS
|
|
95
|
+
if (Array.isArray(data.micro_chunks)) {
|
|
96
|
+
for (const mc of data.micro_chunks) {
|
|
97
|
+
let vecBuf = Buffer.alloc(0);
|
|
98
|
+
if (mc.vector) {
|
|
99
|
+
if (Buffer.isBuffer(mc.vector)) {
|
|
100
|
+
vecBuf = mc.vector;
|
|
101
|
+
} else if (typeof mc.vector === "string") {
|
|
102
|
+
vecBuf = Buffer.from(mc.vector, "base64");
|
|
103
|
+
} else if (mc.vector.type === "Buffer" && Array.isArray(mc.vector.data)) {
|
|
104
|
+
vecBuf = Buffer.from(mc.vector.data);
|
|
105
|
+
} else if (Array.isArray(mc.vector)) {
|
|
106
|
+
vecBuf = Buffer.from(mc.vector);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
await db.cloudClient.execute({
|
|
110
|
+
sql: "INSERT INTO micro_chunks (id, section_id, doc_id, content, vector, token_count, medium_id) VALUES (?, ?, ?, ?, ?, ?, ?);",
|
|
111
|
+
args: [mc.id, mc.section_id, doc.id, mc.content, vecBuf, mc.token_count, mc.medium_id || null],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Insert into remote FTS
|
|
115
|
+
try {
|
|
116
|
+
await db.cloudClient.execute({
|
|
117
|
+
sql: "INSERT INTO micro_chunks_fts (id, content, breadcrumbs) VALUES (?, ?, ?);",
|
|
118
|
+
args: [mc.id, mc.content, mc.breadcrumbs || ""],
|
|
119
|
+
});
|
|
120
|
+
} catch (ftsErr) {
|
|
121
|
+
console.warn("FTS insertion failed on cloud:", ftsErr.message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 6. Insert graph_edges
|
|
127
|
+
if (Array.isArray(data.graph_edges)) {
|
|
128
|
+
for (const e of data.graph_edges) {
|
|
129
|
+
await db.cloudClient.execute({
|
|
130
|
+
sql: "INSERT OR IGNORE INTO graph_edges (source_id, target_id, relation_type, metadata_json, created_at) VALUES (?, ?, ?, ?, ?);",
|
|
131
|
+
args: [e.source_id, e.target_id, e.relation_type, e.metadata_json ? (typeof e.metadata_json === "string" ? e.metadata_json : JSON.stringify(e.metadata_json)) : null, e.created_at || Date.now()],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function enqueueSyncTask(action, keyOrId, payload = null) {
|
|
139
|
+
const { getDatabase } = await import("./database.js");
|
|
140
|
+
const db = await getDatabase();
|
|
141
|
+
if (db.mode === "only-cloud") return; // No need to queue in only-cloud mode
|
|
142
|
+
|
|
143
|
+
// Ensure queue table exists
|
|
144
|
+
await db.exec(`
|
|
145
|
+
CREATE TABLE IF NOT EXISTS sync_queue (
|
|
146
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
147
|
+
action TEXT NOT NULL,
|
|
148
|
+
key_or_id TEXT NOT NULL,
|
|
149
|
+
payload TEXT,
|
|
150
|
+
created_at INTEGER NOT NULL
|
|
151
|
+
);
|
|
152
|
+
`);
|
|
153
|
+
|
|
154
|
+
await db.prepare(`
|
|
155
|
+
INSERT INTO sync_queue (action, key_or_id, payload, created_at)
|
|
156
|
+
VALUES (?, ?, ?, ?);
|
|
157
|
+
`).run(action, keyOrId, payload ? (typeof payload === "string" ? payload : JSON.stringify(payload)) : null, Date.now());
|
|
158
|
+
|
|
159
|
+
// Trigger background sync worker asynchronously
|
|
160
|
+
triggerBackgroundSync().catch((err) => {
|
|
161
|
+
console.error("Background sync trigger error:", err.message);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function triggerBackgroundSync() {
|
|
166
|
+
if (isSyncing) return;
|
|
167
|
+
isSyncing = true;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const { getDatabase } = await import("./database.js");
|
|
171
|
+
const db = await getDatabase();
|
|
172
|
+
if (db.mode !== "hybrid-sync" || !db.cloudClient) {
|
|
173
|
+
isSyncing = false;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Ensure table exists
|
|
178
|
+
await db.exec(`
|
|
179
|
+
CREATE TABLE IF NOT EXISTS sync_queue (
|
|
180
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
181
|
+
action TEXT NOT NULL,
|
|
182
|
+
key_or_id TEXT NOT NULL,
|
|
183
|
+
payload TEXT,
|
|
184
|
+
created_at INTEGER NOT NULL
|
|
185
|
+
);
|
|
186
|
+
`);
|
|
187
|
+
|
|
188
|
+
// Fetch tasks ordered by id
|
|
189
|
+
const tasks = await db.prepare("SELECT * FROM sync_queue ORDER BY id ASC LIMIT 50;").all();
|
|
190
|
+
if (tasks.length === 0) {
|
|
191
|
+
isSyncing = false;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const task of tasks) {
|
|
196
|
+
try {
|
|
197
|
+
await processSyncTask(db, task);
|
|
198
|
+
// On success, delete from queue
|
|
199
|
+
await db.prepare("DELETE FROM sync_queue WHERE id = ?;").run(task.id);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
console.error(`Failed to process sync task ${task.id} (${task.action}):`, err.message, err.stack);
|
|
202
|
+
// Stop processing this batch to preserve order on error, retry next time
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.error("Error during background sync execution:", err.message);
|
|
208
|
+
} finally {
|
|
209
|
+
isSyncing = false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -1,20 +1,43 @@
|
|
|
1
|
+
const ignoredKeywords = new Set([
|
|
2
|
+
"const", "let", "var", "function", "class", "import", "export", "from", "return", "if", "for", "while", "switch", "case", "default",
|
|
3
|
+
"def", "self", "lambda", "pass", "yield", "async", "await", "with", "except", "try", "catch", "finally",
|
|
4
|
+
"func", "type", "struct", "interface", "chan", "map", "go", "defer", "package", "range",
|
|
5
|
+
"fn", "enum", "trait", "impl", "pub", "mut", "ref", "self", "Self", "match", "use", "mod", "crate",
|
|
6
|
+
"namespace", "template", "typename", "public", "private", "protected", "virtual", "override", "using", "inline", "static", "constexpr", "extern", "explicit", "friend", "operator", "throw",
|
|
7
|
+
"record", "synchronized", "final", "void", "throws", "new", "this", "super", "fun", "val", "null", "true", "false",
|
|
8
|
+
"internal", "readonly", "base", "get", "set", "echo", "exit", "die", "require", "include",
|
|
9
|
+
"module", "end", "extend", "attr_accessor", "attr_reader", "attr_writer", "nil", "puts", "raise"
|
|
10
|
+
]);
|
|
11
|
+
|
|
1
12
|
export function extractSymbolsFromContent(content) {
|
|
13
|
+
if (!content) return [];
|
|
2
14
|
const symbols = new Set();
|
|
3
15
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
const patterns = [
|
|
17
|
+
// JS/TS
|
|
18
|
+
/(?:function|class|interface|type|enum|const|let|var)\s+([a-zA-Z0-9_$]+)/g,
|
|
19
|
+
// Python / PHP / Ruby
|
|
20
|
+
/(?:def|class|function|module|trait)\s+([a-zA-Z0-9_!?=]+)/g,
|
|
21
|
+
// Go / Rust / C++ / Java / Kotlin / C# / PHP (Types)
|
|
22
|
+
/\b(?:class|struct|interface|record|enum|trait|type|namespace)\s+([a-zA-Z0-9_]+)/g,
|
|
23
|
+
// Go (Functions)
|
|
24
|
+
/\bfunc\s+(?:\([^)]+\)\s+)?([a-zA-Z0-9_]+)\s*\(/g,
|
|
25
|
+
// Rust (Functions)
|
|
26
|
+
/\bfn\s+([a-zA-Z0-9_]+)/g,
|
|
27
|
+
// Kotlin (Functions)
|
|
28
|
+
/\bfun\s+([a-zA-Z0-9_]+)/g,
|
|
29
|
+
// Java / C# / C++ (Methods/Functions)
|
|
30
|
+
/\b(?:public|protected|private|static|synchronized|final|async|virtual|override|readonly)*\s*[\w<>\[\]]+\s+([a-zA-Z0-9_]+)\s*\([^)]*\)\s*(?:const|override|noexcept|throws\s+[\w,\s]+|\s)*\s*[{;]/g
|
|
31
|
+
];
|
|
12
32
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
33
|
+
for (const pattern of patterns) {
|
|
34
|
+
let match;
|
|
35
|
+
pattern.lastIndex = 0;
|
|
36
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
37
|
+
const symbol = match[1];
|
|
38
|
+
if (symbol && symbol.length > 2 && !ignoredKeywords.has(symbol)) {
|
|
39
|
+
symbols.add(symbol);
|
|
40
|
+
}
|
|
18
41
|
}
|
|
19
42
|
}
|
|
20
43
|
|
|
@@ -52,21 +75,21 @@ export function buildGraphEdges(docId, hierarchy) {
|
|
|
52
75
|
return edges;
|
|
53
76
|
}
|
|
54
77
|
|
|
55
|
-
export function saveGraphEdges(db, edges) {
|
|
78
|
+
export async function saveGraphEdges(db, edges) {
|
|
56
79
|
const stmt = db.prepare(`
|
|
57
80
|
INSERT OR IGNORE INTO graph_edges (source_id, target_id, relation_type)
|
|
58
81
|
VALUES (?, ?, ?);
|
|
59
82
|
`);
|
|
60
83
|
for (const edge of edges) {
|
|
61
|
-
stmt.run(edge.source_id, edge.target_id, edge.relation_type);
|
|
84
|
+
await stmt.run(edge.source_id, edge.target_id, edge.relation_type);
|
|
62
85
|
}
|
|
63
86
|
}
|
|
64
87
|
|
|
65
|
-
export function getRelatedSymbols(db, sectionId) {
|
|
88
|
+
export async function getRelatedSymbols(db, sectionId) {
|
|
66
89
|
const stmt = db.prepare(`
|
|
67
90
|
SELECT target_id, relation_type FROM graph_edges
|
|
68
91
|
WHERE source_id = ? AND relation_type = 'DEFINES_SYMBOL';
|
|
69
92
|
`);
|
|
70
|
-
const rows = stmt.all(sectionId);
|
|
93
|
+
const rows = await stmt.all(sectionId);
|
|
71
94
|
return rows.map((r) => r.target_id.replace("symbol:", ""));
|
|
72
95
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getDatabase } from "../db/database.js";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
|
|
4
|
-
export function linkFactToDocument({
|
|
4
|
+
export async function linkFactToDocument({
|
|
5
5
|
factKey,
|
|
6
6
|
factText,
|
|
7
7
|
docId,
|
|
@@ -11,11 +11,11 @@ export function linkFactToDocument({
|
|
|
11
11
|
relationType = "LINKS_TO",
|
|
12
12
|
metadata = null,
|
|
13
13
|
}) {
|
|
14
|
-
const db = getDatabase();
|
|
14
|
+
const db = await getDatabase();
|
|
15
15
|
const id = `link_${randomUUID().substring(0, 12)}`;
|
|
16
16
|
const now = Date.now();
|
|
17
17
|
|
|
18
|
-
const doc = db
|
|
18
|
+
const doc = await db
|
|
19
19
|
.prepare("SELECT id, title, path FROM documents WHERE id = ? OR path = ? OR title = ?")
|
|
20
20
|
.get(docId, docId, docId);
|
|
21
21
|
|
|
@@ -28,7 +28,7 @@ export function linkFactToDocument({
|
|
|
28
28
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
29
29
|
`);
|
|
30
30
|
|
|
31
|
-
stmt.run(
|
|
31
|
+
await stmt.run(
|
|
32
32
|
id,
|
|
33
33
|
factKey,
|
|
34
34
|
factText,
|
|
@@ -46,7 +46,7 @@ export function linkFactToDocument({
|
|
|
46
46
|
VALUES (?, ?, ?, ?, ?)
|
|
47
47
|
`);
|
|
48
48
|
const targetSpec = startLine ? `${doc.id}:L${startLine}-${endLine || startLine}` : doc.id;
|
|
49
|
-
edgeStmt.run(`fact:${factKey}:${factText.substring(0, 30)}`, targetSpec, relationType, JSON.stringify({ linkId: id }), now);
|
|
49
|
+
await edgeStmt.run(`fact:${factKey}:${factText.substring(0, 30)}`, targetSpec, relationType, JSON.stringify({ linkId: id }), now);
|
|
50
50
|
|
|
51
51
|
return {
|
|
52
52
|
linkId: id,
|
|
@@ -60,8 +60,8 @@ export function linkFactToDocument({
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export function getLinksForFact(factKey, factText) {
|
|
64
|
-
const db = getDatabase();
|
|
63
|
+
export async function getLinksForFact(factKey, factText) {
|
|
64
|
+
const db = await getDatabase();
|
|
65
65
|
const stmt = db.prepare(`
|
|
66
66
|
SELECT k.*, d.title as doc_title, d.path as doc_path
|
|
67
67
|
FROM knowledge_links k
|
|
@@ -70,11 +70,11 @@ export function getLinksForFact(factKey, factText) {
|
|
|
70
70
|
ORDER BY k.created_at DESC
|
|
71
71
|
`);
|
|
72
72
|
const queryPattern = `%${factText.substring(0, 20)}%`;
|
|
73
|
-
return stmt.all(factKey, queryPattern, factText);
|
|
73
|
+
return await stmt.all(factKey, queryPattern, factText);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export function getLinksForDoc(docId) {
|
|
77
|
-
const db = getDatabase();
|
|
76
|
+
export async function getLinksForDoc(docId) {
|
|
77
|
+
const db = await getDatabase();
|
|
78
78
|
const stmt = db.prepare(`
|
|
79
79
|
SELECT k.*, d.title as doc_title, d.path as doc_path
|
|
80
80
|
FROM knowledge_links k
|
|
@@ -82,11 +82,11 @@ export function getLinksForDoc(docId) {
|
|
|
82
82
|
WHERE k.doc_id = ? OR d.path = ? OR d.title = ?
|
|
83
83
|
ORDER BY k.created_at DESC
|
|
84
84
|
`);
|
|
85
|
-
return stmt.all(docId, docId, docId);
|
|
85
|
+
return await stmt.all(docId, docId, docId);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export function listAllLinks(factKey = null) {
|
|
89
|
-
const db = getDatabase();
|
|
88
|
+
export async function listAllLinks(factKey = null) {
|
|
89
|
+
const db = await getDatabase();
|
|
90
90
|
let sql = `
|
|
91
91
|
SELECT k.*, d.title as doc_title, d.path as doc_path
|
|
92
92
|
FROM knowledge_links k
|
|
@@ -98,5 +98,5 @@ export function listAllLinks(factKey = null) {
|
|
|
98
98
|
params.push(factKey);
|
|
99
99
|
}
|
|
100
100
|
sql += " ORDER BY k.created_at DESC";
|
|
101
|
-
return db.prepare(sql).all(...params);
|
|
101
|
+
return await db.prepare(sql).all(...params);
|
|
102
102
|
}
|