@vheins/local-memory-mcp 0.6.0 → 0.6.2
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/bin/mcp-memory-server.js +5 -3
- package/dist/{chunk-XIJO63UU.js → chunk-W3VX7ZD3.js} +528 -532
- package/dist/dashboard/public/assets/{index-Df97JpLg.js → index-D7VqgJ-Q.js} +5 -5
- package/dist/dashboard/public/index.html +1 -1
- package/dist/dashboard/server.js +6 -4
- package/dist/mcp/server.js +6 -5
- package/package.json +2 -3
|
@@ -82,6 +82,9 @@ function deriveLoggerName(message) {
|
|
|
82
82
|
return "app";
|
|
83
83
|
}
|
|
84
84
|
function emitToStderr(level, message, context) {
|
|
85
|
+
if (process.env.MCP_SERVER === "true") {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
85
88
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
86
89
|
if (message.startsWith("[MCP]")) {
|
|
87
90
|
const icon = getMcpIcon(message);
|
|
@@ -169,19 +172,43 @@ function addLogSink(sink) {
|
|
|
169
172
|
var LOG_LEVEL_VALUES = Object.keys(LEVELS);
|
|
170
173
|
|
|
171
174
|
// src/mcp/storage/sqlite.ts
|
|
172
|
-
import
|
|
175
|
+
import initSqlJs from "sql.js";
|
|
173
176
|
import path2 from "path";
|
|
174
177
|
import fs2 from "fs";
|
|
175
178
|
import os from "os";
|
|
176
179
|
|
|
177
180
|
// src/mcp/storage/migrations.ts
|
|
178
181
|
var MigrationManager = class {
|
|
179
|
-
constructor(db) {
|
|
182
|
+
constructor(db, saveDb) {
|
|
180
183
|
this.db = db;
|
|
184
|
+
this.saveDb = saveDb;
|
|
181
185
|
}
|
|
182
186
|
db;
|
|
187
|
+
saveDb;
|
|
188
|
+
run(sql) {
|
|
189
|
+
this.db.run(sql);
|
|
190
|
+
}
|
|
191
|
+
exec(sql) {
|
|
192
|
+
this.db.exec(sql);
|
|
193
|
+
}
|
|
194
|
+
all(sql) {
|
|
195
|
+
const result = this.db.exec(sql);
|
|
196
|
+
if (result.length === 0) return [];
|
|
197
|
+
const { columns, values } = result[0];
|
|
198
|
+
return values.map((row) => {
|
|
199
|
+
const obj = {};
|
|
200
|
+
columns.forEach((col, i) => {
|
|
201
|
+
obj[col] = row[i];
|
|
202
|
+
});
|
|
203
|
+
return obj;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
get(sql) {
|
|
207
|
+
const rows = this.all(sql);
|
|
208
|
+
return rows[0];
|
|
209
|
+
}
|
|
183
210
|
migrate() {
|
|
184
|
-
this.
|
|
211
|
+
this.exec(`
|
|
185
212
|
CREATE TABLE IF NOT EXISTS memories (
|
|
186
213
|
id TEXT PRIMARY KEY,
|
|
187
214
|
repo TEXT NOT NULL,
|
|
@@ -311,35 +338,6 @@ var MigrationManager = class {
|
|
|
311
338
|
|
|
312
339
|
CREATE INDEX IF NOT EXISTS idx_action_log_repo ON action_log(repo);
|
|
313
340
|
CREATE INDEX IF NOT EXISTS idx_action_log_created_at ON action_log(created_at);
|
|
314
|
-
|
|
315
|
-
-- FTS5 Virtual Table for Memories
|
|
316
|
-
-- Note: Only using id, title, and content for search indexing
|
|
317
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
318
|
-
id UNINDEXED,
|
|
319
|
-
repo,
|
|
320
|
-
type,
|
|
321
|
-
title,
|
|
322
|
-
content,
|
|
323
|
-
metadata UNINDEXED,
|
|
324
|
-
content='memories',
|
|
325
|
-
content_rowid='id'
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
-- Triggers to keep FTS index in sync with base table
|
|
329
|
-
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
330
|
-
INSERT INTO memories_fts(id, repo, type, title, content, metadata)
|
|
331
|
-
VALUES (new.id, new.repo, new.type, new.title, new.content, new.metadata);
|
|
332
|
-
END;
|
|
333
|
-
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
334
|
-
INSERT INTO memories_fts(memories_fts, id, repo, type, title, content, metadata)
|
|
335
|
-
VALUES ('delete', old.id, old.repo, old.type, old.title, old.content, old.metadata);
|
|
336
|
-
END;
|
|
337
|
-
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
338
|
-
INSERT INTO memories_fts(memories_fts, id, repo, type, title, content, metadata)
|
|
339
|
-
VALUES ('delete', old.id, old.repo, old.type, old.title, old.content, old.metadata);
|
|
340
|
-
INSERT INTO memories_fts(id, repo, type, title, content, metadata)
|
|
341
|
-
VALUES (new.id, new.repo, new.type, new.title, new.content, new.metadata);
|
|
342
|
-
END;
|
|
343
341
|
`);
|
|
344
342
|
const columnsToAdd = [
|
|
345
343
|
{ name: "title", table: "memories", definition: "ALTER TABLE memories ADD COLUMN title TEXT" },
|
|
@@ -409,37 +407,34 @@ var MigrationManager = class {
|
|
|
409
407
|
];
|
|
410
408
|
for (const col of columnsToAdd) {
|
|
411
409
|
try {
|
|
412
|
-
const tableInfo = this.
|
|
410
|
+
const tableInfo = this.all(`PRAGMA table_info(${col.table})`);
|
|
413
411
|
const existingTableColumns = tableInfo.map((c) => c.name);
|
|
414
412
|
if (tableInfo.length > 0 && !existingTableColumns.includes(col.name)) {
|
|
415
|
-
this.
|
|
413
|
+
this.exec(col.definition);
|
|
416
414
|
}
|
|
417
|
-
} catch
|
|
418
|
-
logger.error(
|
|
419
|
-
`Migration step failed for ${col.table}.${col.name}: ${e instanceof Error ? e.message : String(e)}`
|
|
420
|
-
);
|
|
415
|
+
} catch {
|
|
421
416
|
}
|
|
422
417
|
}
|
|
423
418
|
this.ensureMemoryTypeConstraint();
|
|
424
419
|
this.ensureTaskStatusConstraintRemoved();
|
|
425
420
|
this.ensureMemoryStatusConstraintRemoved();
|
|
426
|
-
this.
|
|
421
|
+
this.exec(`
|
|
427
422
|
CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
|
|
428
423
|
CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes);
|
|
429
424
|
CREATE INDEX IF NOT EXISTS idx_memories_is_global ON memories(is_global);
|
|
430
425
|
`);
|
|
431
426
|
try {
|
|
432
|
-
this.
|
|
433
|
-
} catch
|
|
434
|
-
logger.error(`Legacy backfill task_code failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
427
|
+
this.run("UPDATE tasks SET task_code = substr(id, 1, 8) WHERE task_code IS NULL");
|
|
428
|
+
} catch {
|
|
435
429
|
}
|
|
430
|
+
if (this.saveDb) this.saveDb();
|
|
436
431
|
}
|
|
437
432
|
ensureMemoryTypeConstraint() {
|
|
438
|
-
const tableSql = this.
|
|
439
|
-
if (!tableSql?.sql || !tableSql.sql.includes("CHECK (type IN")) {
|
|
433
|
+
const tableSql = this.get("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memories'");
|
|
434
|
+
if (!tableSql?.sql || !String(tableSql.sql).includes("CHECK (type IN")) {
|
|
440
435
|
return;
|
|
441
436
|
}
|
|
442
|
-
this.
|
|
437
|
+
this.exec(`
|
|
443
438
|
BEGIN TRANSACTION;
|
|
444
439
|
|
|
445
440
|
CREATE TABLE memories__migrated (
|
|
@@ -486,11 +481,11 @@ var MigrationManager = class {
|
|
|
486
481
|
`);
|
|
487
482
|
}
|
|
488
483
|
ensureTaskStatusConstraintRemoved() {
|
|
489
|
-
const tableSql = this.
|
|
490
|
-
if (!tableSql?.sql || !tableSql.sql.includes("CHECK (status IN") && !tableSql.sql.includes("DEFAULT 'pending'")) {
|
|
484
|
+
const tableSql = this.get("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'tasks'");
|
|
485
|
+
if (!tableSql?.sql || !String(tableSql.sql).includes("CHECK (status IN") && !String(tableSql.sql).includes("DEFAULT 'pending'")) {
|
|
491
486
|
return;
|
|
492
487
|
}
|
|
493
|
-
this.
|
|
488
|
+
this.exec(`
|
|
494
489
|
BEGIN TRANSACTION;
|
|
495
490
|
|
|
496
491
|
CREATE TABLE tasks__migrated (
|
|
@@ -535,7 +530,7 @@ var MigrationManager = class {
|
|
|
535
530
|
`);
|
|
536
531
|
}
|
|
537
532
|
ensureMemoryStatusConstraintRemoved() {
|
|
538
|
-
const tableSql = this.
|
|
533
|
+
const tableSql = this.get("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memories'");
|
|
539
534
|
if (tableSql?.sql?.includes("status TEXT NOT NULL DEFAULT 'active' CHECK")) {
|
|
540
535
|
this.ensureMemoryTypeConstraint();
|
|
541
536
|
}
|
|
@@ -788,13 +783,37 @@ function tokenize(text) {
|
|
|
788
783
|
|
|
789
784
|
// src/mcp/storage/base.ts
|
|
790
785
|
var BaseEntity = class {
|
|
791
|
-
constructor(db) {
|
|
786
|
+
constructor(db, saveDb) {
|
|
792
787
|
this.db = db;
|
|
788
|
+
this.saveDb = saveDb;
|
|
793
789
|
}
|
|
794
790
|
db;
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
791
|
+
saveDb;
|
|
792
|
+
run(sql, params = []) {
|
|
793
|
+
this.db.run(sql, params);
|
|
794
|
+
if (this.saveDb) this.saveDb();
|
|
795
|
+
return { changes: this.db.getRowsModified() };
|
|
796
|
+
}
|
|
797
|
+
exec(sql) {
|
|
798
|
+
this.db.exec(sql);
|
|
799
|
+
if (this.saveDb) this.saveDb();
|
|
800
|
+
}
|
|
801
|
+
all(sql, params = []) {
|
|
802
|
+
const result = this.db.exec(sql, params);
|
|
803
|
+
if (result.length === 0) return [];
|
|
804
|
+
const { columns, values } = result[0];
|
|
805
|
+
return values.map((row) => {
|
|
806
|
+
const obj = {};
|
|
807
|
+
columns.forEach((col, i) => {
|
|
808
|
+
obj[col] = row[i];
|
|
809
|
+
});
|
|
810
|
+
return obj;
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
get(sql, params = []) {
|
|
814
|
+
const rows = this.all(sql, params);
|
|
815
|
+
return rows[0];
|
|
816
|
+
}
|
|
798
817
|
safeJSONParse(json, defaultValue) {
|
|
799
818
|
if (!json) return defaultValue;
|
|
800
819
|
try {
|
|
@@ -803,9 +822,6 @@ var BaseEntity = class {
|
|
|
803
822
|
return defaultValue;
|
|
804
823
|
}
|
|
805
824
|
}
|
|
806
|
-
/**
|
|
807
|
-
* Mapping helper for MemoryEntry
|
|
808
|
-
*/
|
|
809
825
|
rowToMemoryEntry(row) {
|
|
810
826
|
const r = row;
|
|
811
827
|
return {
|
|
@@ -836,9 +852,6 @@ var BaseEntity = class {
|
|
|
836
852
|
metadata: this.safeJSONParse(r.metadata, {})
|
|
837
853
|
};
|
|
838
854
|
}
|
|
839
|
-
/**
|
|
840
|
-
* Mapping helper for Task
|
|
841
|
-
*/
|
|
842
855
|
rowToTask(row) {
|
|
843
856
|
const r = row;
|
|
844
857
|
return {
|
|
@@ -866,9 +879,6 @@ var BaseEntity = class {
|
|
|
866
879
|
comments_count: r.comments_count || 0
|
|
867
880
|
};
|
|
868
881
|
}
|
|
869
|
-
/**
|
|
870
|
-
* Vector math utilities (simple bag-of-words implementation)
|
|
871
|
-
*/
|
|
872
882
|
computeVector(text) {
|
|
873
883
|
const tokens = tokenize(text);
|
|
874
884
|
const vector = {};
|
|
@@ -897,34 +907,34 @@ var BaseEntity = class {
|
|
|
897
907
|
// src/mcp/entities/memory.ts
|
|
898
908
|
var MemoryEntity = class extends BaseEntity {
|
|
899
909
|
insert(entry) {
|
|
900
|
-
|
|
901
|
-
INSERT INTO memories (
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
910
|
+
this.run(
|
|
911
|
+
`INSERT INTO memories (
|
|
912
|
+
id, repo, type, title, content, importance, folder, language,
|
|
913
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
914
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
915
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
916
|
+
[
|
|
917
|
+
entry.id,
|
|
918
|
+
entry.scope.repo,
|
|
919
|
+
entry.type,
|
|
920
|
+
entry.title || null,
|
|
921
|
+
entry.content,
|
|
922
|
+
entry.importance,
|
|
923
|
+
entry.scope.folder || null,
|
|
924
|
+
entry.scope.language || null,
|
|
925
|
+
entry.created_at,
|
|
926
|
+
entry.updated_at,
|
|
927
|
+
entry.expires_at ?? null,
|
|
928
|
+
entry.supersedes ?? null,
|
|
929
|
+
entry.status || "active",
|
|
930
|
+
entry.is_global ? 1 : 0,
|
|
931
|
+
entry.tags ? JSON.stringify(entry.tags) : null,
|
|
932
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null,
|
|
933
|
+
entry.agent || "unknown",
|
|
934
|
+
entry.role || "unknown",
|
|
935
|
+
entry.model || "unknown",
|
|
936
|
+
entry.completed_at || null
|
|
937
|
+
]
|
|
928
938
|
);
|
|
929
939
|
}
|
|
930
940
|
update(id, updates) {
|
|
@@ -964,23 +974,20 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
964
974
|
fields.push("updated_at = ?");
|
|
965
975
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
966
976
|
values.push(id);
|
|
967
|
-
|
|
968
|
-
stmt.run(...values);
|
|
977
|
+
this.run(`UPDATE memories SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
969
978
|
}
|
|
970
979
|
delete(id) {
|
|
971
|
-
this.
|
|
980
|
+
this.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
972
981
|
}
|
|
973
982
|
getById(id) {
|
|
974
|
-
const row = this.
|
|
983
|
+
const row = this.get("SELECT * FROM memories WHERE id = ?", [id]);
|
|
975
984
|
return row ? this.rowToMemoryEntry(row) : null;
|
|
976
985
|
}
|
|
977
986
|
getByIdWithStats(id) {
|
|
978
|
-
const row = this.
|
|
979
|
-
`
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
`
|
|
983
|
-
).get(id);
|
|
987
|
+
const row = this.get(
|
|
988
|
+
`SELECT *, CASE WHEN hit_count > 0 THEN CAST(recall_count AS REAL) / hit_count ELSE 0 END AS recall_rate FROM memories WHERE id = ?`,
|
|
989
|
+
[id]
|
|
990
|
+
);
|
|
984
991
|
if (!row) return null;
|
|
985
992
|
return {
|
|
986
993
|
...this.rowToMemoryEntry(row),
|
|
@@ -999,7 +1006,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
999
1006
|
sql += " AND status = ?";
|
|
1000
1007
|
params.push(options.status);
|
|
1001
1008
|
}
|
|
1002
|
-
const rows = this.
|
|
1009
|
+
const rows = this.all(sql, params);
|
|
1003
1010
|
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1004
1011
|
}
|
|
1005
1012
|
getStats(repo) {
|
|
@@ -1010,7 +1017,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1010
1017
|
params.push(repo);
|
|
1011
1018
|
}
|
|
1012
1019
|
sql += " GROUP BY type";
|
|
1013
|
-
const rows = this.
|
|
1020
|
+
const rows = this.all(sql, params);
|
|
1014
1021
|
const byType = {};
|
|
1015
1022
|
let total = 0;
|
|
1016
1023
|
rows.forEach((row) => {
|
|
@@ -1021,32 +1028,6 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1021
1028
|
}
|
|
1022
1029
|
searchByRepo(repo, query = "", type, limit = 5) {
|
|
1023
1030
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1024
|
-
if (query && query.length >= 3) {
|
|
1025
|
-
try {
|
|
1026
|
-
let sql2 = `
|
|
1027
|
-
SELECT m.*
|
|
1028
|
-
FROM memories m
|
|
1029
|
-
JOIN memories_fts f ON m.id = f.id
|
|
1030
|
-
WHERE m.repo = ? AND memories_fts MATCH ? AND m.status = 'active'
|
|
1031
|
-
AND (m.expires_at IS NULL OR m.expires_at > ?)
|
|
1032
|
-
`;
|
|
1033
|
-
const params2 = [repo, query, now];
|
|
1034
|
-
if (type) {
|
|
1035
|
-
sql2 += " AND m.type = ?";
|
|
1036
|
-
params2.push(type);
|
|
1037
|
-
}
|
|
1038
|
-
sql2 += " ORDER BY rank, m.importance DESC, m.created_at DESC LIMIT ?";
|
|
1039
|
-
params2.push(limit);
|
|
1040
|
-
const rows2 = this.db.prepare(sql2).all(...params2);
|
|
1041
|
-
return rows2.map((row) => this.rowToMemoryEntry(row));
|
|
1042
|
-
} catch (e) {
|
|
1043
|
-
logger.warn("FTS5 similarity search failed, falling back to LIKE", {
|
|
1044
|
-
error: e instanceof Error ? e.message : String(e),
|
|
1045
|
-
repo,
|
|
1046
|
-
query
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
1031
|
let sql = "SELECT * FROM memories WHERE repo = ? AND (content LIKE ? OR title LIKE ? OR tags LIKE ?) AND status = 'active' AND (expires_at IS NULL OR expires_at > ?)";
|
|
1051
1032
|
const params = [repo, `%${query}%`, `%${query}%`, `%${query}%`, now];
|
|
1052
1033
|
if (type) {
|
|
@@ -1055,21 +1036,19 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1055
1036
|
}
|
|
1056
1037
|
sql += " ORDER BY importance DESC, created_at DESC LIMIT ?";
|
|
1057
1038
|
params.push(limit);
|
|
1058
|
-
const rows = this.
|
|
1039
|
+
const rows = this.all(sql, params);
|
|
1059
1040
|
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1060
1041
|
}
|
|
1061
1042
|
bulkInsertMemories(entries) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
for (const entry of entries2) {
|
|
1072
|
-
insert.run(
|
|
1043
|
+
let count = 0;
|
|
1044
|
+
for (const entry of entries) {
|
|
1045
|
+
this.run(
|
|
1046
|
+
`INSERT INTO memories (
|
|
1047
|
+
id, repo, type, title, content, importance, folder, language,
|
|
1048
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
1049
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
1050
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1051
|
+
[
|
|
1073
1052
|
entry.id,
|
|
1074
1053
|
entry.scope.repo,
|
|
1075
1054
|
entry.type,
|
|
@@ -1090,12 +1069,11 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1090
1069
|
entry.role || "unknown",
|
|
1091
1070
|
entry.model || "unknown",
|
|
1092
1071
|
entry.completed_at || null
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
return insertMany(entries);
|
|
1072
|
+
]
|
|
1073
|
+
);
|
|
1074
|
+
count++;
|
|
1075
|
+
}
|
|
1076
|
+
return count;
|
|
1099
1077
|
}
|
|
1100
1078
|
bulkUpdateMemories(ids, updates) {
|
|
1101
1079
|
if (ids.length === 0) return 0;
|
|
@@ -1119,35 +1097,28 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1119
1097
|
if (fields.length === 0) return 0;
|
|
1120
1098
|
fields.push("updated_at = ?");
|
|
1121
1099
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
return count;
|
|
1134
|
-
});
|
|
1135
|
-
return updateMany(ids, fields, values);
|
|
1100
|
+
let count = 0;
|
|
1101
|
+
const chunkSize = 500;
|
|
1102
|
+
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
1103
|
+
const chunk = ids.slice(i, i + chunkSize);
|
|
1104
|
+
const result = this.run(
|
|
1105
|
+
`UPDATE memories SET ${fields.join(", ")} WHERE id IN (${chunk.map(() => "?").join(",")})`,
|
|
1106
|
+
[...values, ...chunk]
|
|
1107
|
+
);
|
|
1108
|
+
count += result.changes;
|
|
1109
|
+
}
|
|
1110
|
+
return count;
|
|
1136
1111
|
}
|
|
1137
1112
|
bulkDeleteMemories(ids) {
|
|
1138
1113
|
if (ids.length === 0) return 0;
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
return count;
|
|
1149
|
-
});
|
|
1150
|
-
return deleteMany(ids);
|
|
1114
|
+
let count = 0;
|
|
1115
|
+
const chunkSize = 500;
|
|
1116
|
+
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
1117
|
+
const chunk = ids.slice(i, i + chunkSize);
|
|
1118
|
+
const result = this.run(`DELETE FROM memories WHERE id IN (${chunk.map(() => "?").join(",")})`, chunk);
|
|
1119
|
+
count += result.changes;
|
|
1120
|
+
}
|
|
1121
|
+
return count;
|
|
1151
1122
|
}
|
|
1152
1123
|
getRecentMemories(repo, limit, offset = 0, includeArchived = false, excludeTypes = []) {
|
|
1153
1124
|
let query = "SELECT * FROM memories WHERE repo = ?";
|
|
@@ -1161,7 +1132,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1161
1132
|
}
|
|
1162
1133
|
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
|
|
1163
1134
|
params.push(limit, offset);
|
|
1164
|
-
const rows = this.
|
|
1135
|
+
const rows = this.all(query, params);
|
|
1165
1136
|
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1166
1137
|
}
|
|
1167
1138
|
getTotalCount(repo, includeArchived = false, excludeTypes = []) {
|
|
@@ -1172,25 +1143,27 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1172
1143
|
sql += ` AND type NOT IN (${excludeTypes.map(() => "?").join(",")})`;
|
|
1173
1144
|
params.push(...excludeTypes);
|
|
1174
1145
|
}
|
|
1175
|
-
const row = this.
|
|
1176
|
-
return row
|
|
1146
|
+
const row = this.get(sql, params);
|
|
1147
|
+
return row?.count ?? 0;
|
|
1177
1148
|
}
|
|
1178
1149
|
incrementHitCount(id) {
|
|
1179
|
-
this.
|
|
1150
|
+
this.run("UPDATE memories SET hit_count = hit_count + 1, last_used_at = ? WHERE id = ?", [
|
|
1151
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
1152
|
+
id
|
|
1153
|
+
]);
|
|
1180
1154
|
}
|
|
1181
1155
|
incrementHitCounts(ids) {
|
|
1182
1156
|
if (!ids || ids.length === 0) return;
|
|
1183
|
-
const stmt = this.db.prepare("UPDATE memories SET hit_count = hit_count + 1, last_used_at = ? WHERE id = ?");
|
|
1184
1157
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
transaction(ids);
|
|
1158
|
+
for (const id of ids) {
|
|
1159
|
+
this.run("UPDATE memories SET hit_count = hit_count + 1, last_used_at = ? WHERE id = ?", [now, id]);
|
|
1160
|
+
}
|
|
1191
1161
|
}
|
|
1192
1162
|
incrementRecallCount(id) {
|
|
1193
|
-
this.
|
|
1163
|
+
this.run("UPDATE memories SET recall_count = recall_count + 1, last_used_at = ? WHERE id = ?", [
|
|
1164
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
1165
|
+
id
|
|
1166
|
+
]);
|
|
1194
1167
|
}
|
|
1195
1168
|
getVectorCandidates(repo, limit = 100) {
|
|
1196
1169
|
let sql = `SELECT mv.memory_id, mv.vector FROM memory_vectors mv JOIN memories m ON mv.memory_id = m.id`;
|
|
@@ -1201,41 +1174,38 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1201
1174
|
}
|
|
1202
1175
|
sql += " LIMIT ?";
|
|
1203
1176
|
params.push(limit);
|
|
1204
|
-
return this.
|
|
1177
|
+
return this.all(sql, params);
|
|
1205
1178
|
}
|
|
1206
1179
|
upsertVectorEmbedding(memoryId, vector) {
|
|
1207
|
-
this.
|
|
1208
|
-
`
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
).run(memoryId, JSON.stringify(vector), (/* @__PURE__ */ new Date()).toISOString());
|
|
1180
|
+
this.run(
|
|
1181
|
+
`INSERT INTO memory_vectors (memory_id, vector, updated_at) VALUES (?, ?, ?)
|
|
1182
|
+
ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, updated_at = excluded.updated_at`,
|
|
1183
|
+
[memoryId, JSON.stringify(vector), (/* @__PURE__ */ new Date()).toISOString()]
|
|
1184
|
+
);
|
|
1213
1185
|
}
|
|
1214
1186
|
getSummary(repo) {
|
|
1215
|
-
const row = this.
|
|
1187
|
+
const row = this.get(
|
|
1188
|
+
"SELECT summary, updated_at FROM memory_summary WHERE repo = ?",
|
|
1189
|
+
[repo]
|
|
1190
|
+
);
|
|
1216
1191
|
return row;
|
|
1217
1192
|
}
|
|
1218
1193
|
getAllMemoriesWithStats(repo) {
|
|
1219
|
-
const rows = this.
|
|
1220
|
-
`
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
WHERE repo = ?
|
|
1224
|
-
ORDER BY created_at DESC
|
|
1225
|
-
`
|
|
1226
|
-
).all(repo);
|
|
1194
|
+
const rows = this.all(
|
|
1195
|
+
`SELECT *, CASE WHEN hit_count > 0 THEN CAST(recall_count AS REAL) / hit_count ELSE 0 END AS recall_rate FROM memories WHERE repo = ? ORDER BY created_at DESC`,
|
|
1196
|
+
[repo]
|
|
1197
|
+
);
|
|
1227
1198
|
return rows.map((row) => ({
|
|
1228
1199
|
...this.rowToMemoryEntry(row),
|
|
1229
1200
|
recall_rate: row.recall_rate || 0
|
|
1230
1201
|
}));
|
|
1231
1202
|
}
|
|
1232
1203
|
upsertSummary(repo, summary) {
|
|
1233
|
-
this.
|
|
1234
|
-
`
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
).run(repo, summary, (/* @__PURE__ */ new Date()).toISOString());
|
|
1204
|
+
this.run(
|
|
1205
|
+
`INSERT INTO memory_summary (repo, summary, updated_at) VALUES (?, ?, ?)
|
|
1206
|
+
ON CONFLICT(repo) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at`,
|
|
1207
|
+
[repo, summary, (/* @__PURE__ */ new Date()).toISOString()]
|
|
1208
|
+
);
|
|
1239
1209
|
}
|
|
1240
1210
|
listMemoriesForDashboard(options) {
|
|
1241
1211
|
const {
|
|
@@ -1282,9 +1252,10 @@ ORDER BY created_at DESC
|
|
|
1282
1252
|
params.push(`%${search}%`, `%${search}%`);
|
|
1283
1253
|
}
|
|
1284
1254
|
const countSql = `SELECT COUNT(*) as count FROM memories WHERE ${where.join(" AND ")}`;
|
|
1285
|
-
const
|
|
1255
|
+
const totalRow = this.get(countSql, params);
|
|
1256
|
+
const total = totalRow?.count ?? 0;
|
|
1286
1257
|
const dataSql = `SELECT *, CASE WHEN hit_count > 0 THEN CAST(recall_count AS REAL) / hit_count ELSE 0 END AS recall_rate FROM memories WHERE ${where.join(" AND ")} ORDER BY ${sortBy} ${sortOrder} LIMIT ? OFFSET ?`;
|
|
1287
|
-
const rows = this.
|
|
1258
|
+
const rows = this.all(dataSql, [...params, limit, offset]);
|
|
1288
1259
|
const items = rows.map((row) => ({
|
|
1289
1260
|
...this.rowToMemoryEntry(row),
|
|
1290
1261
|
recall_rate: row.recall_rate || 0
|
|
@@ -1304,10 +1275,10 @@ ORDER BY created_at DESC
|
|
|
1304
1275
|
let sql = `SELECT * FROM memories WHERE (${where.join(" AND ")}) AND (expires_at IS NULL OR expires_at > ?)`;
|
|
1305
1276
|
if (!includeArchived) sql += " AND status = 'active'";
|
|
1306
1277
|
sql += ` ORDER BY CASE WHEN repo = ? THEN 0 ELSE 1 END, importance DESC, created_at DESC LIMIT 100`;
|
|
1307
|
-
const candidates = this.
|
|
1278
|
+
const candidates = this.all(sql, [...params, now.toISOString(), repo]);
|
|
1308
1279
|
if (candidates.length < 5) {
|
|
1309
1280
|
const recentSql = `SELECT * FROM memories WHERE (${where.join(" OR ")}) AND status = 'active' AND (expires_at IS NULL OR expires_at > ?) ORDER BY created_at DESC LIMIT 10`;
|
|
1310
|
-
const recent = this.
|
|
1281
|
+
const recent = this.all(recentSql, [...params, now.toISOString()]);
|
|
1311
1282
|
for (const r of recent) {
|
|
1312
1283
|
if (!candidates.find((c) => c.id === r.id)) candidates.push(r);
|
|
1313
1284
|
}
|
|
@@ -1338,25 +1309,21 @@ ORDER BY created_at DESC
|
|
|
1338
1309
|
archiveExpiredMemories(force = false) {
|
|
1339
1310
|
if (process.env.ENABLE_AUTO_ARCHIVE !== "true" && !force) return 0;
|
|
1340
1311
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1341
|
-
const result = this.
|
|
1342
|
-
`
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
`
|
|
1346
|
-
).run(now, now);
|
|
1312
|
+
const result = this.run(
|
|
1313
|
+
`UPDATE memories SET status = 'archived', updated_at = ? WHERE expires_at IS NOT NULL AND expires_at <= ? AND status = 'active'`,
|
|
1314
|
+
[now, now]
|
|
1315
|
+
);
|
|
1347
1316
|
return result.changes;
|
|
1348
1317
|
}
|
|
1349
1318
|
archiveLowScoreMemories(force = false) {
|
|
1350
1319
|
if (process.env.ENABLE_AUTO_ARCHIVE !== "true" && !force) return 0;
|
|
1351
|
-
const result = this.
|
|
1352
|
-
`
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
`
|
|
1359
|
-
).run((/* @__PURE__ */ new Date()).toISOString());
|
|
1320
|
+
const result = this.run(
|
|
1321
|
+
`UPDATE memories SET status = 'archived', updated_at = ? WHERE status = 'active' AND (
|
|
1322
|
+
(julianday('now') - julianday(COALESCE(last_used_at, created_at)) > 90 AND importance < 3)
|
|
1323
|
+
OR (hit_count > 10 AND recall_count = 0)
|
|
1324
|
+
)`,
|
|
1325
|
+
[(/* @__PURE__ */ new Date()).toISOString()]
|
|
1326
|
+
);
|
|
1360
1327
|
return result.changes;
|
|
1361
1328
|
}
|
|
1362
1329
|
};
|
|
@@ -1364,34 +1331,34 @@ ORDER BY created_at DESC
|
|
|
1364
1331
|
// src/mcp/entities/task.ts
|
|
1365
1332
|
var TaskEntity = class extends BaseEntity {
|
|
1366
1333
|
insertTask(task) {
|
|
1367
|
-
|
|
1368
|
-
INSERT INTO tasks (
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1334
|
+
this.run(
|
|
1335
|
+
`INSERT INTO tasks (
|
|
1336
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
1337
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
1338
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1339
|
+
[
|
|
1340
|
+
task.id,
|
|
1341
|
+
task.repo,
|
|
1342
|
+
task.task_code,
|
|
1343
|
+
task.phase || null,
|
|
1344
|
+
task.title,
|
|
1345
|
+
task.description || null,
|
|
1346
|
+
task.status || "backlog",
|
|
1347
|
+
task.priority || 3,
|
|
1348
|
+
task.agent || "unknown",
|
|
1349
|
+
task.role || "unknown",
|
|
1350
|
+
task.doc_path || null,
|
|
1351
|
+
task.created_at,
|
|
1352
|
+
task.updated_at,
|
|
1353
|
+
task.finished_at || null,
|
|
1354
|
+
task.canceled_at || null,
|
|
1355
|
+
task.tags ? JSON.stringify(task.tags) : null,
|
|
1356
|
+
task.metadata ? JSON.stringify(task.metadata) : null,
|
|
1357
|
+
task.parent_id || null,
|
|
1358
|
+
task.depends_on || null,
|
|
1359
|
+
task.est_tokens || 0,
|
|
1360
|
+
task.in_progress_at || null
|
|
1361
|
+
]
|
|
1395
1362
|
);
|
|
1396
1363
|
}
|
|
1397
1364
|
updateTask(id, updates) {
|
|
@@ -1413,43 +1380,34 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1413
1380
|
fields.push("updated_at = ?");
|
|
1414
1381
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
1415
1382
|
values.push(id);
|
|
1416
|
-
|
|
1417
|
-
stmt.run(...values);
|
|
1383
|
+
this.run(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
1418
1384
|
}
|
|
1419
1385
|
deleteTask(id) {
|
|
1420
|
-
this.
|
|
1421
|
-
this.
|
|
1386
|
+
this.run("DELETE FROM task_comments WHERE task_id = ?", [id]);
|
|
1387
|
+
this.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
1422
1388
|
}
|
|
1423
1389
|
getTaskById(id) {
|
|
1424
|
-
const row = this.
|
|
1425
|
-
`
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1429
|
-
WHERE t.id = ?
|
|
1430
|
-
`
|
|
1431
|
-
).get(id);
|
|
1390
|
+
const row = this.get(
|
|
1391
|
+
`SELECT t.*, d.task_code as depends_on_code FROM tasks t LEFT JOIN tasks d ON t.depends_on = d.id WHERE t.id = ?`,
|
|
1392
|
+
[id]
|
|
1393
|
+
);
|
|
1432
1394
|
return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(id) } : null;
|
|
1433
1395
|
}
|
|
1434
1396
|
getTaskByCode(repo, taskCode) {
|
|
1435
|
-
const row = this.
|
|
1436
|
-
`
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1440
|
-
WHERE t.repo = ? AND t.task_code = ?
|
|
1441
|
-
`
|
|
1442
|
-
).get(repo, taskCode);
|
|
1397
|
+
const row = this.get(
|
|
1398
|
+
`SELECT t.*, d.task_code as depends_on_code FROM tasks t LEFT JOIN tasks d ON t.depends_on = d.id WHERE t.repo = ? AND t.task_code = ?`,
|
|
1399
|
+
[repo, taskCode]
|
|
1400
|
+
);
|
|
1443
1401
|
return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(row.id) } : null;
|
|
1444
1402
|
}
|
|
1445
1403
|
getTasksByRepo(repo, status, limit, offset, search) {
|
|
1446
1404
|
let query = `
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1405
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1406
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1407
|
+
FROM tasks t
|
|
1408
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1409
|
+
WHERE t.repo = ?
|
|
1410
|
+
`;
|
|
1453
1411
|
const params = [repo];
|
|
1454
1412
|
if (status) {
|
|
1455
1413
|
query += " AND t.status = ?";
|
|
@@ -1461,16 +1419,16 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1461
1419
|
params.push(searchPattern, searchPattern, searchPattern);
|
|
1462
1420
|
}
|
|
1463
1421
|
query += ` ORDER BY
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1422
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1423
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1424
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1425
|
+
WHEN t.status = 'pending' THEN 1
|
|
1426
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1427
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1428
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1429
|
+
ELSE 5 END ASC,
|
|
1430
|
+
t.priority DESC,
|
|
1431
|
+
t.created_at ASC`;
|
|
1474
1432
|
if (limit !== void 0) {
|
|
1475
1433
|
query += " LIMIT ?";
|
|
1476
1434
|
params.push(limit);
|
|
@@ -1479,40 +1437,40 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1479
1437
|
params.push(offset);
|
|
1480
1438
|
}
|
|
1481
1439
|
}
|
|
1482
|
-
const rows = this.
|
|
1440
|
+
const rows = this.all(query, params);
|
|
1483
1441
|
return rows.map((r) => this.rowToTask(r));
|
|
1484
1442
|
}
|
|
1485
1443
|
listRecentTasks(limit = 50, offset = 0) {
|
|
1486
1444
|
const query = `
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
const rows = this.
|
|
1445
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1446
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1447
|
+
FROM tasks t
|
|
1448
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1449
|
+
ORDER BY
|
|
1450
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1451
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1452
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1453
|
+
WHEN t.status = 'pending' THEN 1
|
|
1454
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1455
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1456
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1457
|
+
ELSE 5 END ASC,
|
|
1458
|
+
t.priority DESC,
|
|
1459
|
+
t.created_at ASC
|
|
1460
|
+
LIMIT ? OFFSET ?
|
|
1461
|
+
`;
|
|
1462
|
+
const rows = this.all(query, [limit, offset]);
|
|
1505
1463
|
return rows.map((r) => this.rowToTask(r));
|
|
1506
1464
|
}
|
|
1507
1465
|
getTasksByMultipleStatuses(repo, statuses, limit, offset, search) {
|
|
1508
1466
|
if (!statuses.length) return this.getTasksByRepo(repo, void 0, limit, offset, search);
|
|
1509
1467
|
let query = `
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1468
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1469
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1470
|
+
FROM tasks t
|
|
1471
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1472
|
+
WHERE t.repo = ? AND t.status IN (${statuses.map(() => "?").join(",")})
|
|
1473
|
+
`;
|
|
1516
1474
|
const params = [repo, ...statuses];
|
|
1517
1475
|
if (search) {
|
|
1518
1476
|
query += " AND (t.title LIKE ? OR t.description LIKE ? OR t.task_code LIKE ?)";
|
|
@@ -1520,16 +1478,16 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1520
1478
|
params.push(searchPattern, searchPattern, searchPattern);
|
|
1521
1479
|
}
|
|
1522
1480
|
query += ` ORDER BY
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1481
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1482
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1483
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1484
|
+
WHEN t.status = 'pending' THEN 1
|
|
1485
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1486
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1487
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1488
|
+
ELSE 5 END ASC,
|
|
1489
|
+
t.priority DESC,
|
|
1490
|
+
t.created_at ASC`;
|
|
1533
1491
|
if (limit !== void 0) {
|
|
1534
1492
|
query += " LIMIT ?";
|
|
1535
1493
|
params.push(limit);
|
|
@@ -1538,7 +1496,7 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1538
1496
|
params.push(offset);
|
|
1539
1497
|
}
|
|
1540
1498
|
}
|
|
1541
|
-
const rows = this.
|
|
1499
|
+
const rows = this.all(query, params);
|
|
1542
1500
|
return rows.map((r) => this.rowToTask(r));
|
|
1543
1501
|
}
|
|
1544
1502
|
isTaskCodeDuplicate(repo, task_code, excludeId) {
|
|
@@ -1548,20 +1506,18 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1548
1506
|
query += " AND id != ?";
|
|
1549
1507
|
params.push(excludeId);
|
|
1550
1508
|
}
|
|
1551
|
-
const row = this.
|
|
1552
|
-
return row
|
|
1509
|
+
const row = this.get(query, params);
|
|
1510
|
+
return (row?.count ?? 0) > 0;
|
|
1553
1511
|
}
|
|
1554
1512
|
bulkInsertTasks(tasks) {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
for (const task of tasks2) {
|
|
1564
|
-
insert.run(
|
|
1513
|
+
let count = 0;
|
|
1514
|
+
for (const task of tasks) {
|
|
1515
|
+
this.run(
|
|
1516
|
+
`INSERT INTO tasks (
|
|
1517
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
1518
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
1519
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1520
|
+
[
|
|
1565
1521
|
task.id,
|
|
1566
1522
|
task.repo,
|
|
1567
1523
|
task.task_code,
|
|
@@ -1583,31 +1539,29 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1583
1539
|
task.depends_on || null,
|
|
1584
1540
|
task.est_tokens || 0,
|
|
1585
1541
|
task.in_progress_at || null
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
return insertMany(tasks);
|
|
1542
|
+
]
|
|
1543
|
+
);
|
|
1544
|
+
count++;
|
|
1545
|
+
}
|
|
1546
|
+
return count;
|
|
1592
1547
|
}
|
|
1593
1548
|
insertTaskComment(comment) {
|
|
1594
|
-
this.
|
|
1595
|
-
`
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
comment.created_at
|
|
1549
|
+
this.run(
|
|
1550
|
+
`INSERT INTO task_comments (
|
|
1551
|
+
id, task_id, repo, comment, agent, role, model, previous_status, next_status, created_at
|
|
1552
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1553
|
+
[
|
|
1554
|
+
comment.id,
|
|
1555
|
+
comment.task_id,
|
|
1556
|
+
comment.repo,
|
|
1557
|
+
comment.comment,
|
|
1558
|
+
comment.agent || "unknown",
|
|
1559
|
+
comment.role || "unknown",
|
|
1560
|
+
comment.model || "unknown",
|
|
1561
|
+
comment.previous_status || null,
|
|
1562
|
+
comment.next_status || null,
|
|
1563
|
+
comment.created_at
|
|
1564
|
+
]
|
|
1611
1565
|
);
|
|
1612
1566
|
}
|
|
1613
1567
|
updateTaskComment(id, updates) {
|
|
@@ -1622,35 +1576,29 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1622
1576
|
});
|
|
1623
1577
|
if (fields.length === 0) return;
|
|
1624
1578
|
values.push(id);
|
|
1625
|
-
|
|
1626
|
-
stmt.run(...values);
|
|
1579
|
+
this.run(`UPDATE task_comments SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
1627
1580
|
}
|
|
1628
1581
|
deleteTaskComment(id) {
|
|
1629
|
-
this.
|
|
1582
|
+
this.run("DELETE FROM task_comments WHERE id = ?", [id]);
|
|
1630
1583
|
}
|
|
1631
1584
|
getTaskCommentById(id) {
|
|
1632
|
-
return this.
|
|
1585
|
+
return this.get("SELECT * FROM task_comments WHERE id = ?", [id]) ?? null;
|
|
1633
1586
|
}
|
|
1634
1587
|
getTaskCommentsByTaskId(taskId) {
|
|
1635
|
-
return this.
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
WHERE task_id = ?
|
|
1639
|
-
ORDER BY created_at DESC, id DESC
|
|
1640
|
-
`
|
|
1641
|
-
).all(taskId);
|
|
1588
|
+
return this.all(`SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at DESC, id DESC`, [
|
|
1589
|
+
taskId
|
|
1590
|
+
]);
|
|
1642
1591
|
}
|
|
1643
1592
|
getAllTaskCommentsByRepo(repo) {
|
|
1644
|
-
return this.
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
WHERE repo = ?
|
|
1648
|
-
ORDER BY created_at DESC, id DESC
|
|
1649
|
-
`
|
|
1650
|
-
).all(repo);
|
|
1593
|
+
return this.all(`SELECT * FROM task_comments WHERE repo = ? ORDER BY created_at DESC, id DESC`, [
|
|
1594
|
+
repo
|
|
1595
|
+
]);
|
|
1651
1596
|
}
|
|
1652
1597
|
getTaskStats(repo) {
|
|
1653
|
-
const rows = this.
|
|
1598
|
+
const rows = this.all(
|
|
1599
|
+
"SELECT status, COUNT(*) as count FROM tasks WHERE repo = ? GROUP BY status",
|
|
1600
|
+
[repo]
|
|
1601
|
+
);
|
|
1654
1602
|
const stats = { total: 0, backlog: 0, todo: 0, inProgress: 0, completed: 0, blocked: 0, canceled: 0 };
|
|
1655
1603
|
rows.forEach((r) => {
|
|
1656
1604
|
const count = r.count;
|
|
@@ -1670,35 +1618,30 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1670
1618
|
else if (period === "weekly") dateFilter = "AND date(COALESCE(finished_at, updated_at)) >= date('now', '-7 days')";
|
|
1671
1619
|
else if (period === "monthly")
|
|
1672
1620
|
dateFilter = "AND date(COALESCE(finished_at, updated_at)) >= date('now', '-30 days')";
|
|
1673
|
-
const stats = this.
|
|
1674
|
-
`
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
).get(repo);
|
|
1621
|
+
const stats = this.get(
|
|
1622
|
+
`SELECT
|
|
1623
|
+
COUNT(*) as completed_count,
|
|
1624
|
+
SUM(est_tokens) as total_tokens,
|
|
1625
|
+
AVG(
|
|
1626
|
+
CASE
|
|
1627
|
+
WHEN in_progress_at IS NOT NULL AND finished_at IS NOT NULL
|
|
1628
|
+
THEN (julianday(finished_at) - julianday(in_progress_at)) * 86400.0
|
|
1629
|
+
ELSE NULL
|
|
1630
|
+
END
|
|
1631
|
+
) as avg_duration_seconds
|
|
1632
|
+
FROM tasks
|
|
1633
|
+
WHERE repo = ?
|
|
1634
|
+
AND status = 'completed'
|
|
1635
|
+
${dateFilter}`,
|
|
1636
|
+
[repo]
|
|
1637
|
+
);
|
|
1691
1638
|
let addedDateFilter = "";
|
|
1692
1639
|
if (period === "daily") addedDateFilter = "AND date(created_at) = date('now')";
|
|
1693
1640
|
else if (period === "weekly") addedDateFilter = "AND date(created_at) >= date('now', '-7 days')";
|
|
1694
1641
|
else if (period === "monthly") addedDateFilter = "AND date(created_at) >= date('now', '-30 days')";
|
|
1695
|
-
const added = this.
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
WHERE repo = ?
|
|
1699
|
-
${addedDateFilter}
|
|
1700
|
-
`
|
|
1701
|
-
).get(repo);
|
|
1642
|
+
const added = this.get(`SELECT COUNT(*) as count FROM tasks WHERE repo = ? ${addedDateFilter}`, [
|
|
1643
|
+
repo
|
|
1644
|
+
]);
|
|
1702
1645
|
return {
|
|
1703
1646
|
completed: stats?.completed_count || 0,
|
|
1704
1647
|
tokens: stats?.total_tokens || 0,
|
|
@@ -1723,33 +1666,31 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1723
1666
|
dateFilter = "1=1";
|
|
1724
1667
|
}
|
|
1725
1668
|
const query = `
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
return this.
|
|
1669
|
+
SELECT label, SUM(created) as created, SUM(completed) as completed
|
|
1670
|
+
FROM (
|
|
1671
|
+
SELECT strftime(?, created_at) as label, 1 as created, 0 as completed
|
|
1672
|
+
FROM tasks
|
|
1673
|
+
WHERE repo = ? AND ${dateFilter.replace("COALESCE(finished_at, created_at)", "created_at")}
|
|
1674
|
+
UNION ALL
|
|
1675
|
+
SELECT strftime(?, COALESCE(finished_at, updated_at)) as label, 0 as created, 1 as completed
|
|
1676
|
+
FROM tasks
|
|
1677
|
+
WHERE repo = ? AND status = 'completed' AND ${dateFilter.replace("COALESCE(finished_at, created_at)", "COALESCE(finished_at, updated_at)")}
|
|
1678
|
+
)
|
|
1679
|
+
GROUP BY label
|
|
1680
|
+
ORDER BY label ASC
|
|
1681
|
+
LIMIT 100
|
|
1682
|
+
`;
|
|
1683
|
+
return this.all(query, [
|
|
1684
|
+
labelFormat,
|
|
1685
|
+
repo,
|
|
1686
|
+
labelFormat,
|
|
1687
|
+
repo
|
|
1688
|
+
]);
|
|
1741
1689
|
}
|
|
1742
1690
|
};
|
|
1743
1691
|
|
|
1744
1692
|
// src/mcp/entities/action.ts
|
|
1745
1693
|
var ActionEntity = class extends BaseEntity {
|
|
1746
|
-
constructor(db) {
|
|
1747
|
-
super(db);
|
|
1748
|
-
}
|
|
1749
|
-
/**
|
|
1750
|
-
* Log an action to the audit trail.
|
|
1751
|
-
* Supports both object-based options (modern) and positional arguments (legacy/internal).
|
|
1752
|
-
*/
|
|
1753
1694
|
logAction(action, repo, optionsOrQuery, response, memoryId, taskId, resultCount = 0) {
|
|
1754
1695
|
let query = typeof optionsOrQuery === "string" ? optionsOrQuery : void 0;
|
|
1755
1696
|
let finalResponse = response;
|
|
@@ -1764,39 +1705,38 @@ var ActionEntity = class extends BaseEntity {
|
|
|
1764
1705
|
finalTaskId = optionsOrQuery.taskId || finalTaskId;
|
|
1765
1706
|
finalResultCount = optionsOrQuery.resultCount !== void 0 ? optionsOrQuery.resultCount : finalResultCount;
|
|
1766
1707
|
}
|
|
1767
|
-
|
|
1768
|
-
INSERT INTO action_log (repo, action, query, response, memory_id, task_id, result_count, created_at)
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1708
|
+
this.run(
|
|
1709
|
+
`INSERT INTO action_log (repo, action, query, response, memory_id, task_id, result_count, created_at)
|
|
1710
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1711
|
+
[
|
|
1712
|
+
repo || "",
|
|
1713
|
+
action || "unknown",
|
|
1714
|
+
query || null,
|
|
1715
|
+
finalResponse ? typeof finalResponse === "string" ? finalResponse : JSON.stringify(finalResponse) : null,
|
|
1716
|
+
finalMemoryId || null,
|
|
1717
|
+
finalTaskId || null,
|
|
1718
|
+
finalResultCount ?? 0,
|
|
1719
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1720
|
+
]
|
|
1721
|
+
);
|
|
1781
1722
|
}
|
|
1782
1723
|
getLastActionId() {
|
|
1783
|
-
const row = this.
|
|
1724
|
+
const row = this.get("SELECT MAX(id) as id FROM action_log");
|
|
1784
1725
|
return row?.id || 0;
|
|
1785
1726
|
}
|
|
1786
1727
|
getActionsAfter(id) {
|
|
1787
|
-
return this.
|
|
1788
|
-
`
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
).all(id);
|
|
1728
|
+
return this.all(
|
|
1729
|
+
`SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1730
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1731
|
+
WHERE a.id > ? ORDER BY a.created_at ASC`,
|
|
1732
|
+
[id]
|
|
1733
|
+
);
|
|
1794
1734
|
}
|
|
1795
1735
|
getRecentActions(repo, limit = 10, offset = 0) {
|
|
1796
1736
|
let query = `
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1737
|
+
SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1738
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1739
|
+
`;
|
|
1800
1740
|
const params = [];
|
|
1801
1741
|
if (repo) {
|
|
1802
1742
|
query += " WHERE a.repo = ?";
|
|
@@ -1804,69 +1744,81 @@ var ActionEntity = class extends BaseEntity {
|
|
|
1804
1744
|
}
|
|
1805
1745
|
query += " ORDER BY a.created_at DESC, a.id DESC LIMIT ? OFFSET ?";
|
|
1806
1746
|
params.push(limit, offset);
|
|
1807
|
-
return this.
|
|
1747
|
+
return this.all(query, params);
|
|
1808
1748
|
}
|
|
1809
1749
|
getActionStatsByDate(repo) {
|
|
1810
|
-
return this.
|
|
1811
|
-
`
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
).all(repo);
|
|
1750
|
+
return this.all(
|
|
1751
|
+
`SELECT date(created_at) as date, count(*) as count
|
|
1752
|
+
FROM action_log
|
|
1753
|
+
WHERE repo = ? AND created_at > date('now', '-30 days')
|
|
1754
|
+
GROUP BY date(created_at)
|
|
1755
|
+
ORDER BY date ASC`,
|
|
1756
|
+
[repo]
|
|
1757
|
+
);
|
|
1819
1758
|
}
|
|
1820
1759
|
getActionDistribution(repo) {
|
|
1821
|
-
return this.
|
|
1822
|
-
`
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
).all(repo);
|
|
1760
|
+
return this.all(
|
|
1761
|
+
`SELECT action, count(*) as count
|
|
1762
|
+
FROM action_log
|
|
1763
|
+
WHERE repo = ?
|
|
1764
|
+
GROUP BY action`,
|
|
1765
|
+
[repo]
|
|
1766
|
+
);
|
|
1829
1767
|
}
|
|
1830
1768
|
getActionById(id) {
|
|
1831
|
-
return this.
|
|
1832
|
-
`
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
).get(id);
|
|
1769
|
+
return this.get(
|
|
1770
|
+
`SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1771
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1772
|
+
WHERE a.id = ?`,
|
|
1773
|
+
[id]
|
|
1774
|
+
);
|
|
1838
1775
|
}
|
|
1839
1776
|
};
|
|
1840
1777
|
|
|
1841
1778
|
// src/mcp/entities/system.ts
|
|
1842
1779
|
var SystemEntity = class extends BaseEntity {
|
|
1843
1780
|
listRepos() {
|
|
1844
|
-
const rows = this.
|
|
1781
|
+
const rows = this.all("SELECT DISTINCT repo FROM memories UNION SELECT DISTINCT repo FROM tasks");
|
|
1845
1782
|
return rows.map((r) => r.repo);
|
|
1846
1783
|
}
|
|
1847
1784
|
listRepoNavigation() {
|
|
1848
1785
|
const repos = this.listRepos();
|
|
1849
1786
|
return repos.map((repo) => {
|
|
1850
|
-
const
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1787
|
+
const memoryCountRow = this.get("SELECT COUNT(*) as count FROM memories WHERE repo = ?", [
|
|
1788
|
+
repo
|
|
1789
|
+
]);
|
|
1790
|
+
const taskCountRow = this.get("SELECT COUNT(*) as count FROM tasks WHERE repo = ?", [repo]);
|
|
1791
|
+
const lastActivityRow = this.get(
|
|
1792
|
+
`SELECT MAX(created_at) as last FROM (SELECT created_at FROM memories WHERE repo = ? UNION ALL SELECT created_at FROM tasks WHERE repo = ? UNION ALL SELECT created_at FROM action_log WHERE repo = ?)`,
|
|
1793
|
+
[repo, repo, repo]
|
|
1794
|
+
);
|
|
1855
1795
|
return {
|
|
1856
1796
|
repo,
|
|
1857
|
-
memoryCount,
|
|
1858
|
-
taskCount,
|
|
1859
|
-
lastActivity
|
|
1797
|
+
memoryCount: memoryCountRow?.count ?? 0,
|
|
1798
|
+
taskCount: taskCountRow?.count ?? 0,
|
|
1799
|
+
lastActivity: lastActivityRow?.last ?? null
|
|
1860
1800
|
};
|
|
1861
1801
|
});
|
|
1862
1802
|
}
|
|
1863
1803
|
getDashboardStats(repo) {
|
|
1864
|
-
const memoryStats = this.
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1804
|
+
const memoryStats = this.all(
|
|
1805
|
+
"SELECT type, COUNT(*) as count FROM memories WHERE repo = ? GROUP BY type",
|
|
1806
|
+
[repo]
|
|
1807
|
+
);
|
|
1808
|
+
const taskStats = this.all(
|
|
1809
|
+
"SELECT status, COUNT(*) as count FROM tasks WHERE repo = ? GROUP BY status",
|
|
1810
|
+
[repo]
|
|
1811
|
+
);
|
|
1812
|
+
const recentMemoriesRows = this.all(
|
|
1813
|
+
"SELECT * FROM memories WHERE repo = ? ORDER BY created_at DESC LIMIT 5",
|
|
1814
|
+
[repo]
|
|
1815
|
+
);
|
|
1816
|
+
const recentMemories = recentMemoriesRows.map((r) => this.rowToMemoryEntry(r));
|
|
1817
|
+
const activeTasksRows = this.all(
|
|
1818
|
+
"SELECT * FROM tasks WHERE repo = ? AND status IN ('in_progress', 'pending', 'backlog') ORDER BY priority DESC, created_at ASC LIMIT 5",
|
|
1819
|
+
[repo]
|
|
1820
|
+
);
|
|
1821
|
+
const activeTasks = activeTasksRows.map((r) => this.rowToTask(r));
|
|
1870
1822
|
return {
|
|
1871
1823
|
memoryStats,
|
|
1872
1824
|
taskStats,
|
|
@@ -1875,23 +1827,27 @@ var SystemEntity = class extends BaseEntity {
|
|
|
1875
1827
|
};
|
|
1876
1828
|
}
|
|
1877
1829
|
getGlobalStats() {
|
|
1878
|
-
const
|
|
1879
|
-
const
|
|
1830
|
+
const totalMemoriesRow = this.get("SELECT COUNT(*) as count FROM memories");
|
|
1831
|
+
const totalTasksRow = this.get("SELECT COUNT(*) as count FROM tasks");
|
|
1880
1832
|
const totalRepos = this.listRepos().length;
|
|
1881
1833
|
return {
|
|
1882
|
-
totalMemories,
|
|
1883
|
-
totalTasks,
|
|
1834
|
+
totalMemories: totalMemoriesRow?.count ?? 0,
|
|
1835
|
+
totalTasks: totalTasksRow?.count ?? 0,
|
|
1884
1836
|
totalRepos
|
|
1885
1837
|
};
|
|
1886
1838
|
}
|
|
1887
1839
|
getRepoDetails(repo) {
|
|
1888
|
-
const
|
|
1889
|
-
const
|
|
1890
|
-
const
|
|
1840
|
+
const memoryCountRow = this.get("SELECT COUNT(*) as count FROM memories WHERE repo = ?", [repo]);
|
|
1841
|
+
const taskCountRow = this.get("SELECT COUNT(*) as count FROM tasks WHERE repo = ?", [repo]);
|
|
1842
|
+
const languagesRows = this.all(
|
|
1843
|
+
"SELECT DISTINCT language FROM memories WHERE repo = ? AND language IS NOT NULL",
|
|
1844
|
+
[repo]
|
|
1845
|
+
);
|
|
1846
|
+
const languages = languagesRows.map((r) => r.language);
|
|
1891
1847
|
return {
|
|
1892
1848
|
repo,
|
|
1893
|
-
memoryCount,
|
|
1894
|
-
taskCount,
|
|
1849
|
+
memoryCount: memoryCountRow?.count ?? 0,
|
|
1850
|
+
taskCount: taskCountRow?.count ?? 0,
|
|
1895
1851
|
languages
|
|
1896
1852
|
};
|
|
1897
1853
|
}
|
|
@@ -1900,16 +1856,18 @@ var SystemEntity = class extends BaseEntity {
|
|
|
1900
1856
|
// src/mcp/entities/summary.ts
|
|
1901
1857
|
var SummaryEntity = class extends BaseEntity {
|
|
1902
1858
|
getSummary(repo) {
|
|
1903
|
-
const row = this.
|
|
1859
|
+
const row = this.get(
|
|
1860
|
+
"SELECT summary, updated_at FROM memory_summary WHERE repo = ?",
|
|
1861
|
+
[repo]
|
|
1862
|
+
);
|
|
1904
1863
|
return row || null;
|
|
1905
1864
|
}
|
|
1906
1865
|
upsertSummary(repo, summary) {
|
|
1907
|
-
this.
|
|
1908
|
-
`
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
).run(repo, summary, (/* @__PURE__ */ new Date()).toISOString());
|
|
1866
|
+
this.run(
|
|
1867
|
+
`INSERT INTO memory_summary (repo, summary, updated_at) VALUES (?, ?, ?)
|
|
1868
|
+
ON CONFLICT(repo) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at`,
|
|
1869
|
+
[repo, summary, (/* @__PURE__ */ new Date()).toISOString()]
|
|
1870
|
+
);
|
|
1913
1871
|
}
|
|
1914
1872
|
};
|
|
1915
1873
|
|
|
@@ -1926,42 +1884,80 @@ function resolveDbPath() {
|
|
|
1926
1884
|
return standardPath;
|
|
1927
1885
|
}
|
|
1928
1886
|
var DB_PATH = resolveDbPath();
|
|
1929
|
-
var
|
|
1887
|
+
var dbPathInstance = "";
|
|
1888
|
+
var saveDbFn = null;
|
|
1889
|
+
function createSaveFunction(db, dbPath) {
|
|
1890
|
+
return () => {
|
|
1891
|
+
if (dbPath && dbPath !== ":memory:") {
|
|
1892
|
+
const data = db.export();
|
|
1893
|
+
const buffer = Buffer.from(data);
|
|
1894
|
+
fs2.writeFileSync(dbPath, buffer);
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
var SQLiteStore = class _SQLiteStore {
|
|
1930
1899
|
db;
|
|
1931
1900
|
memories;
|
|
1932
1901
|
tasks;
|
|
1933
1902
|
actions;
|
|
1934
1903
|
system;
|
|
1935
1904
|
summaries;
|
|
1936
|
-
constructor(
|
|
1905
|
+
constructor() {
|
|
1906
|
+
this.db = {};
|
|
1907
|
+
this.memories = {};
|
|
1908
|
+
this.tasks = {};
|
|
1909
|
+
this.actions = {};
|
|
1910
|
+
this.system = {};
|
|
1911
|
+
this.summaries = {};
|
|
1912
|
+
}
|
|
1913
|
+
static async create(dbPath) {
|
|
1937
1914
|
const finalPath = dbPath ?? DB_PATH;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1915
|
+
dbPathInstance = finalPath;
|
|
1916
|
+
const SQL = await initSqlJs();
|
|
1917
|
+
let db;
|
|
1918
|
+
if (finalPath === ":memory:") {
|
|
1919
|
+
db = new SQL.Database();
|
|
1920
|
+
} else {
|
|
1921
|
+
const dbDir = path2.dirname(finalPath);
|
|
1922
|
+
if (!fs2.existsSync(dbDir)) {
|
|
1923
|
+
fs2.mkdirSync(dbDir, { recursive: true });
|
|
1924
|
+
}
|
|
1925
|
+
if (fs2.existsSync(finalPath)) {
|
|
1926
|
+
const fileBuffer = fs2.readFileSync(finalPath);
|
|
1927
|
+
db = new SQL.Database(fileBuffer);
|
|
1928
|
+
} else {
|
|
1929
|
+
db = new SQL.Database();
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
const saveDb = createSaveFunction(db, finalPath);
|
|
1933
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
1934
|
+
db.run("PRAGMA synchronous = NORMAL");
|
|
1935
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
1936
|
+
const migrator = new MigrationManager(db, saveDb);
|
|
1947
1937
|
migrator.migrate();
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1938
|
+
const store = Object.create(_SQLiteStore.prototype);
|
|
1939
|
+
store.db = db;
|
|
1940
|
+
store.memories = new MemoryEntity(db, saveDb);
|
|
1941
|
+
store.tasks = new TaskEntity(db, saveDb);
|
|
1942
|
+
store.actions = new ActionEntity(db, saveDb);
|
|
1943
|
+
store.system = new SystemEntity(db, saveDb);
|
|
1944
|
+
store.summaries = new SummaryEntity(db, saveDb);
|
|
1945
|
+
if (finalPath !== ":memory:") {
|
|
1946
|
+
saveDb();
|
|
1947
|
+
}
|
|
1948
|
+
if (process.env.MCP_SERVER !== "true") {
|
|
1949
|
+
process.stderr.write(`${(/* @__PURE__ */ new Date()).toISOString()} [INFO ] SQLiteStore initialized at ${finalPath}
|
|
1950
|
+
`);
|
|
1951
|
+
}
|
|
1952
|
+
return store;
|
|
1953
|
+
}
|
|
1958
1954
|
getDbPath() {
|
|
1959
|
-
return
|
|
1955
|
+
return dbPathInstance;
|
|
1960
1956
|
}
|
|
1961
|
-
/**
|
|
1962
|
-
* Closes the database connection.
|
|
1963
|
-
*/
|
|
1964
1957
|
close() {
|
|
1958
|
+
if (saveDbFn) {
|
|
1959
|
+
saveDbFn();
|
|
1960
|
+
}
|
|
1965
1961
|
this.db.close();
|
|
1966
1962
|
}
|
|
1967
1963
|
};
|