@vheins/local-memory-mcp 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mcp-memory-server.js +5 -3
- package/dist/{chunk-EQTLHCFK.js → chunk-3I4VJIFK.js} +645 -549
- package/dist/dashboard/public/assets/{index-Df97JpLg.js → index-W8_M2hX3.js} +6 -6
- package/dist/dashboard/public/index.html +1 -1
- package/dist/dashboard/server.js +22 -9
- package/dist/mcp/server.js +235 -107
- 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,11 +530,20 @@ 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
|
}
|
|
542
537
|
}
|
|
538
|
+
addMemoryCodeColumn() {
|
|
539
|
+
const tableInfo = this.all("PRAGMA table_info(memories)");
|
|
540
|
+
const hasCode = tableInfo.some((col) => col.name === "code");
|
|
541
|
+
if (!hasCode) {
|
|
542
|
+
this.run("ALTER TABLE memories ADD COLUMN code TEXT");
|
|
543
|
+
this.run("CREATE INDEX IF NOT EXISTS idx_memories_code ON memories(code)");
|
|
544
|
+
this.run("CREATE INDEX IF NOT EXISTS idx_memories_repo_code ON memories(repo, code)");
|
|
545
|
+
}
|
|
546
|
+
}
|
|
543
547
|
};
|
|
544
548
|
|
|
545
549
|
// src/mcp/utils/normalize.ts
|
|
@@ -788,13 +792,37 @@ function tokenize(text) {
|
|
|
788
792
|
|
|
789
793
|
// src/mcp/storage/base.ts
|
|
790
794
|
var BaseEntity = class {
|
|
791
|
-
constructor(db) {
|
|
795
|
+
constructor(db, saveDb) {
|
|
792
796
|
this.db = db;
|
|
797
|
+
this.saveDb = saveDb;
|
|
793
798
|
}
|
|
794
799
|
db;
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
800
|
+
saveDb;
|
|
801
|
+
run(sql, params = []) {
|
|
802
|
+
this.db.run(sql, params);
|
|
803
|
+
if (this.saveDb) this.saveDb();
|
|
804
|
+
return { changes: this.db.getRowsModified() };
|
|
805
|
+
}
|
|
806
|
+
exec(sql) {
|
|
807
|
+
this.db.exec(sql);
|
|
808
|
+
if (this.saveDb) this.saveDb();
|
|
809
|
+
}
|
|
810
|
+
all(sql, params = []) {
|
|
811
|
+
const result = this.db.exec(sql, params);
|
|
812
|
+
if (result.length === 0) return [];
|
|
813
|
+
const { columns, values } = result[0];
|
|
814
|
+
return values.map((row) => {
|
|
815
|
+
const obj = {};
|
|
816
|
+
columns.forEach((col, i) => {
|
|
817
|
+
obj[col] = row[i];
|
|
818
|
+
});
|
|
819
|
+
return obj;
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
get(sql, params = []) {
|
|
823
|
+
const rows = this.all(sql, params);
|
|
824
|
+
return rows[0];
|
|
825
|
+
}
|
|
798
826
|
safeJSONParse(json, defaultValue) {
|
|
799
827
|
if (!json) return defaultValue;
|
|
800
828
|
try {
|
|
@@ -803,13 +831,11 @@ var BaseEntity = class {
|
|
|
803
831
|
return defaultValue;
|
|
804
832
|
}
|
|
805
833
|
}
|
|
806
|
-
/**
|
|
807
|
-
* Mapping helper for MemoryEntry
|
|
808
|
-
*/
|
|
809
834
|
rowToMemoryEntry(row) {
|
|
810
835
|
const r = row;
|
|
811
836
|
return {
|
|
812
837
|
id: r.id,
|
|
838
|
+
code: r.code || void 0,
|
|
813
839
|
type: r.type,
|
|
814
840
|
title: r.title || "Untitled",
|
|
815
841
|
content: r.content,
|
|
@@ -836,9 +862,6 @@ var BaseEntity = class {
|
|
|
836
862
|
metadata: this.safeJSONParse(r.metadata, {})
|
|
837
863
|
};
|
|
838
864
|
}
|
|
839
|
-
/**
|
|
840
|
-
* Mapping helper for Task
|
|
841
|
-
*/
|
|
842
865
|
rowToTask(row) {
|
|
843
866
|
const r = row;
|
|
844
867
|
return {
|
|
@@ -866,9 +889,6 @@ var BaseEntity = class {
|
|
|
866
889
|
comments_count: r.comments_count || 0
|
|
867
890
|
};
|
|
868
891
|
}
|
|
869
|
-
/**
|
|
870
|
-
* Vector math utilities (simple bag-of-words implementation)
|
|
871
|
-
*/
|
|
872
892
|
computeVector(text) {
|
|
873
893
|
const tokens = tokenize(text);
|
|
874
894
|
const vector = {};
|
|
@@ -897,34 +917,35 @@ var BaseEntity = class {
|
|
|
897
917
|
// src/mcp/entities/memory.ts
|
|
898
918
|
var MemoryEntity = class extends BaseEntity {
|
|
899
919
|
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
|
-
|
|
920
|
+
this.run(
|
|
921
|
+
`INSERT INTO memories (
|
|
922
|
+
id, code, repo, type, title, content, importance, folder, language,
|
|
923
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
924
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
925
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
926
|
+
[
|
|
927
|
+
entry.id,
|
|
928
|
+
entry.code || null,
|
|
929
|
+
entry.scope.repo,
|
|
930
|
+
entry.type,
|
|
931
|
+
entry.title || null,
|
|
932
|
+
entry.content,
|
|
933
|
+
entry.importance,
|
|
934
|
+
entry.scope.folder || null,
|
|
935
|
+
entry.scope.language || null,
|
|
936
|
+
entry.created_at,
|
|
937
|
+
entry.updated_at,
|
|
938
|
+
entry.expires_at ?? null,
|
|
939
|
+
entry.supersedes ?? null,
|
|
940
|
+
entry.status || "active",
|
|
941
|
+
entry.is_global ? 1 : 0,
|
|
942
|
+
entry.tags ? JSON.stringify(entry.tags) : null,
|
|
943
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null,
|
|
944
|
+
entry.agent || "unknown",
|
|
945
|
+
entry.role || "unknown",
|
|
946
|
+
entry.model || "unknown",
|
|
947
|
+
entry.completed_at || null
|
|
948
|
+
]
|
|
928
949
|
);
|
|
929
950
|
}
|
|
930
951
|
update(id, updates) {
|
|
@@ -964,23 +985,24 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
964
985
|
fields.push("updated_at = ?");
|
|
965
986
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
966
987
|
values.push(id);
|
|
967
|
-
|
|
968
|
-
stmt.run(...values);
|
|
988
|
+
this.run(`UPDATE memories SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
969
989
|
}
|
|
970
990
|
delete(id) {
|
|
971
|
-
this.
|
|
991
|
+
this.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
972
992
|
}
|
|
973
993
|
getById(id) {
|
|
974
|
-
const row = this.
|
|
994
|
+
const row = this.get("SELECT * FROM memories WHERE id = ?", [id]);
|
|
995
|
+
return row ? this.rowToMemoryEntry(row) : null;
|
|
996
|
+
}
|
|
997
|
+
getByCode(code) {
|
|
998
|
+
const row = this.get("SELECT * FROM memories WHERE code = ?", [code]);
|
|
975
999
|
return row ? this.rowToMemoryEntry(row) : null;
|
|
976
1000
|
}
|
|
977
1001
|
getByIdWithStats(id) {
|
|
978
|
-
const row = this.
|
|
979
|
-
`
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
`
|
|
983
|
-
).get(id);
|
|
1002
|
+
const row = this.get(
|
|
1003
|
+
`SELECT *, CASE WHEN hit_count > 0 THEN CAST(recall_count AS REAL) / hit_count ELSE 0 END AS recall_rate FROM memories WHERE id = ?`,
|
|
1004
|
+
[id]
|
|
1005
|
+
);
|
|
984
1006
|
if (!row) return null;
|
|
985
1007
|
return {
|
|
986
1008
|
...this.rowToMemoryEntry(row),
|
|
@@ -999,7 +1021,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
999
1021
|
sql += " AND status = ?";
|
|
1000
1022
|
params.push(options.status);
|
|
1001
1023
|
}
|
|
1002
|
-
const rows = this.
|
|
1024
|
+
const rows = this.all(sql, params);
|
|
1003
1025
|
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1004
1026
|
}
|
|
1005
1027
|
getStats(repo) {
|
|
@@ -1010,7 +1032,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1010
1032
|
params.push(repo);
|
|
1011
1033
|
}
|
|
1012
1034
|
sql += " GROUP BY type";
|
|
1013
|
-
const rows = this.
|
|
1035
|
+
const rows = this.all(sql, params);
|
|
1014
1036
|
const byType = {};
|
|
1015
1037
|
let total = 0;
|
|
1016
1038
|
rows.forEach((row) => {
|
|
@@ -1021,32 +1043,6 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1021
1043
|
}
|
|
1022
1044
|
searchByRepo(repo, query = "", type, limit = 5) {
|
|
1023
1045
|
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
1046
|
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
1047
|
const params = [repo, `%${query}%`, `%${query}%`, `%${query}%`, now];
|
|
1052
1048
|
if (type) {
|
|
@@ -1055,21 +1051,19 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1055
1051
|
}
|
|
1056
1052
|
sql += " ORDER BY importance DESC, created_at DESC LIMIT ?";
|
|
1057
1053
|
params.push(limit);
|
|
1058
|
-
const rows = this.
|
|
1054
|
+
const rows = this.all(sql, params);
|
|
1059
1055
|
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1060
1056
|
}
|
|
1061
1057
|
bulkInsertMemories(entries) {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
for (const entry of entries2) {
|
|
1072
|
-
insert.run(
|
|
1058
|
+
let count = 0;
|
|
1059
|
+
for (const entry of entries) {
|
|
1060
|
+
this.run(
|
|
1061
|
+
`INSERT INTO memories (
|
|
1062
|
+
id, repo, type, title, content, importance, folder, language,
|
|
1063
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
1064
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
1065
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1066
|
+
[
|
|
1073
1067
|
entry.id,
|
|
1074
1068
|
entry.scope.repo,
|
|
1075
1069
|
entry.type,
|
|
@@ -1090,12 +1084,11 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1090
1084
|
entry.role || "unknown",
|
|
1091
1085
|
entry.model || "unknown",
|
|
1092
1086
|
entry.completed_at || null
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
return insertMany(entries);
|
|
1087
|
+
]
|
|
1088
|
+
);
|
|
1089
|
+
count++;
|
|
1090
|
+
}
|
|
1091
|
+
return count;
|
|
1099
1092
|
}
|
|
1100
1093
|
bulkUpdateMemories(ids, updates) {
|
|
1101
1094
|
if (ids.length === 0) return 0;
|
|
@@ -1119,35 +1112,28 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1119
1112
|
if (fields.length === 0) return 0;
|
|
1120
1113
|
fields.push("updated_at = ?");
|
|
1121
1114
|
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);
|
|
1115
|
+
let count = 0;
|
|
1116
|
+
const chunkSize = 500;
|
|
1117
|
+
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
1118
|
+
const chunk = ids.slice(i, i + chunkSize);
|
|
1119
|
+
const result = this.run(
|
|
1120
|
+
`UPDATE memories SET ${fields.join(", ")} WHERE id IN (${chunk.map(() => "?").join(",")})`,
|
|
1121
|
+
[...values, ...chunk]
|
|
1122
|
+
);
|
|
1123
|
+
count += result.changes;
|
|
1124
|
+
}
|
|
1125
|
+
return count;
|
|
1136
1126
|
}
|
|
1137
1127
|
bulkDeleteMemories(ids) {
|
|
1138
1128
|
if (ids.length === 0) return 0;
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
return count;
|
|
1149
|
-
});
|
|
1150
|
-
return deleteMany(ids);
|
|
1129
|
+
let count = 0;
|
|
1130
|
+
const chunkSize = 500;
|
|
1131
|
+
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
1132
|
+
const chunk = ids.slice(i, i + chunkSize);
|
|
1133
|
+
const result = this.run(`DELETE FROM memories WHERE id IN (${chunk.map(() => "?").join(",")})`, chunk);
|
|
1134
|
+
count += result.changes;
|
|
1135
|
+
}
|
|
1136
|
+
return count;
|
|
1151
1137
|
}
|
|
1152
1138
|
getRecentMemories(repo, limit, offset = 0, includeArchived = false, excludeTypes = []) {
|
|
1153
1139
|
let query = "SELECT * FROM memories WHERE repo = ?";
|
|
@@ -1161,7 +1147,7 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1161
1147
|
}
|
|
1162
1148
|
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
|
|
1163
1149
|
params.push(limit, offset);
|
|
1164
|
-
const rows = this.
|
|
1150
|
+
const rows = this.all(query, params);
|
|
1165
1151
|
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1166
1152
|
}
|
|
1167
1153
|
getTotalCount(repo, includeArchived = false, excludeTypes = []) {
|
|
@@ -1172,25 +1158,27 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1172
1158
|
sql += ` AND type NOT IN (${excludeTypes.map(() => "?").join(",")})`;
|
|
1173
1159
|
params.push(...excludeTypes);
|
|
1174
1160
|
}
|
|
1175
|
-
const row = this.
|
|
1176
|
-
return row
|
|
1161
|
+
const row = this.get(sql, params);
|
|
1162
|
+
return row?.count ?? 0;
|
|
1177
1163
|
}
|
|
1178
1164
|
incrementHitCount(id) {
|
|
1179
|
-
this.
|
|
1165
|
+
this.run("UPDATE memories SET hit_count = hit_count + 1, last_used_at = ? WHERE id = ?", [
|
|
1166
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
1167
|
+
id
|
|
1168
|
+
]);
|
|
1180
1169
|
}
|
|
1181
1170
|
incrementHitCounts(ids) {
|
|
1182
1171
|
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
1172
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
transaction(ids);
|
|
1173
|
+
for (const id of ids) {
|
|
1174
|
+
this.run("UPDATE memories SET hit_count = hit_count + 1, last_used_at = ? WHERE id = ?", [now, id]);
|
|
1175
|
+
}
|
|
1191
1176
|
}
|
|
1192
1177
|
incrementRecallCount(id) {
|
|
1193
|
-
this.
|
|
1178
|
+
this.run("UPDATE memories SET recall_count = recall_count + 1, last_used_at = ? WHERE id = ?", [
|
|
1179
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
1180
|
+
id
|
|
1181
|
+
]);
|
|
1194
1182
|
}
|
|
1195
1183
|
getVectorCandidates(repo, limit = 100) {
|
|
1196
1184
|
let sql = `SELECT mv.memory_id, mv.vector FROM memory_vectors mv JOIN memories m ON mv.memory_id = m.id`;
|
|
@@ -1201,41 +1189,38 @@ var MemoryEntity = class extends BaseEntity {
|
|
|
1201
1189
|
}
|
|
1202
1190
|
sql += " LIMIT ?";
|
|
1203
1191
|
params.push(limit);
|
|
1204
|
-
return this.
|
|
1192
|
+
return this.all(sql, params);
|
|
1205
1193
|
}
|
|
1206
1194
|
upsertVectorEmbedding(memoryId, vector) {
|
|
1207
|
-
this.
|
|
1208
|
-
`
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
).run(memoryId, JSON.stringify(vector), (/* @__PURE__ */ new Date()).toISOString());
|
|
1195
|
+
this.run(
|
|
1196
|
+
`INSERT INTO memory_vectors (memory_id, vector, updated_at) VALUES (?, ?, ?)
|
|
1197
|
+
ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, updated_at = excluded.updated_at`,
|
|
1198
|
+
[memoryId, JSON.stringify(vector), (/* @__PURE__ */ new Date()).toISOString()]
|
|
1199
|
+
);
|
|
1213
1200
|
}
|
|
1214
1201
|
getSummary(repo) {
|
|
1215
|
-
const row = this.
|
|
1202
|
+
const row = this.get(
|
|
1203
|
+
"SELECT summary, updated_at FROM memory_summary WHERE repo = ?",
|
|
1204
|
+
[repo]
|
|
1205
|
+
);
|
|
1216
1206
|
return row;
|
|
1217
1207
|
}
|
|
1218
1208
|
getAllMemoriesWithStats(repo) {
|
|
1219
|
-
const rows = this.
|
|
1220
|
-
`
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
WHERE repo = ?
|
|
1224
|
-
ORDER BY created_at DESC
|
|
1225
|
-
`
|
|
1226
|
-
).all(repo);
|
|
1209
|
+
const rows = this.all(
|
|
1210
|
+
`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`,
|
|
1211
|
+
[repo]
|
|
1212
|
+
);
|
|
1227
1213
|
return rows.map((row) => ({
|
|
1228
1214
|
...this.rowToMemoryEntry(row),
|
|
1229
1215
|
recall_rate: row.recall_rate || 0
|
|
1230
1216
|
}));
|
|
1231
1217
|
}
|
|
1232
1218
|
upsertSummary(repo, summary) {
|
|
1233
|
-
this.
|
|
1234
|
-
`
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
).run(repo, summary, (/* @__PURE__ */ new Date()).toISOString());
|
|
1219
|
+
this.run(
|
|
1220
|
+
`INSERT INTO memory_summary (repo, summary, updated_at) VALUES (?, ?, ?)
|
|
1221
|
+
ON CONFLICT(repo) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at`,
|
|
1222
|
+
[repo, summary, (/* @__PURE__ */ new Date()).toISOString()]
|
|
1223
|
+
);
|
|
1239
1224
|
}
|
|
1240
1225
|
listMemoriesForDashboard(options) {
|
|
1241
1226
|
const {
|
|
@@ -1282,9 +1267,10 @@ ORDER BY created_at DESC
|
|
|
1282
1267
|
params.push(`%${search}%`, `%${search}%`);
|
|
1283
1268
|
}
|
|
1284
1269
|
const countSql = `SELECT COUNT(*) as count FROM memories WHERE ${where.join(" AND ")}`;
|
|
1285
|
-
const
|
|
1270
|
+
const totalRow = this.get(countSql, params);
|
|
1271
|
+
const total = totalRow?.count ?? 0;
|
|
1286
1272
|
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.
|
|
1273
|
+
const rows = this.all(dataSql, [...params, limit, offset]);
|
|
1288
1274
|
const items = rows.map((row) => ({
|
|
1289
1275
|
...this.rowToMemoryEntry(row),
|
|
1290
1276
|
recall_rate: row.recall_rate || 0
|
|
@@ -1304,10 +1290,10 @@ ORDER BY created_at DESC
|
|
|
1304
1290
|
let sql = `SELECT * FROM memories WHERE (${where.join(" AND ")}) AND (expires_at IS NULL OR expires_at > ?)`;
|
|
1305
1291
|
if (!includeArchived) sql += " AND status = 'active'";
|
|
1306
1292
|
sql += ` ORDER BY CASE WHEN repo = ? THEN 0 ELSE 1 END, importance DESC, created_at DESC LIMIT 100`;
|
|
1307
|
-
const candidates = this.
|
|
1293
|
+
const candidates = this.all(sql, [...params, now.toISOString(), repo]);
|
|
1308
1294
|
if (candidates.length < 5) {
|
|
1309
1295
|
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.
|
|
1296
|
+
const recent = this.all(recentSql, [...params, now.toISOString()]);
|
|
1311
1297
|
for (const r of recent) {
|
|
1312
1298
|
if (!candidates.find((c) => c.id === r.id)) candidates.push(r);
|
|
1313
1299
|
}
|
|
@@ -1338,25 +1324,21 @@ ORDER BY created_at DESC
|
|
|
1338
1324
|
archiveExpiredMemories(force = false) {
|
|
1339
1325
|
if (process.env.ENABLE_AUTO_ARCHIVE !== "true" && !force) return 0;
|
|
1340
1326
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1341
|
-
const result = this.
|
|
1342
|
-
`
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
`
|
|
1346
|
-
).run(now, now);
|
|
1327
|
+
const result = this.run(
|
|
1328
|
+
`UPDATE memories SET status = 'archived', updated_at = ? WHERE expires_at IS NOT NULL AND expires_at <= ? AND status = 'active'`,
|
|
1329
|
+
[now, now]
|
|
1330
|
+
);
|
|
1347
1331
|
return result.changes;
|
|
1348
1332
|
}
|
|
1349
1333
|
archiveLowScoreMemories(force = false) {
|
|
1350
1334
|
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());
|
|
1335
|
+
const result = this.run(
|
|
1336
|
+
`UPDATE memories SET status = 'archived', updated_at = ? WHERE status = 'active' AND (
|
|
1337
|
+
(julianday('now') - julianday(COALESCE(last_used_at, created_at)) > 90 AND importance < 3)
|
|
1338
|
+
OR (hit_count > 10 AND recall_count = 0)
|
|
1339
|
+
)`,
|
|
1340
|
+
[(/* @__PURE__ */ new Date()).toISOString()]
|
|
1341
|
+
);
|
|
1360
1342
|
return result.changes;
|
|
1361
1343
|
}
|
|
1362
1344
|
};
|
|
@@ -1364,34 +1346,34 @@ ORDER BY created_at DESC
|
|
|
1364
1346
|
// src/mcp/entities/task.ts
|
|
1365
1347
|
var TaskEntity = class extends BaseEntity {
|
|
1366
1348
|
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
|
-
|
|
1349
|
+
this.run(
|
|
1350
|
+
`INSERT INTO tasks (
|
|
1351
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
1352
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
1353
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1354
|
+
[
|
|
1355
|
+
task.id,
|
|
1356
|
+
task.repo,
|
|
1357
|
+
task.task_code,
|
|
1358
|
+
task.phase || null,
|
|
1359
|
+
task.title,
|
|
1360
|
+
task.description || null,
|
|
1361
|
+
task.status || "backlog",
|
|
1362
|
+
task.priority || 3,
|
|
1363
|
+
task.agent || "unknown",
|
|
1364
|
+
task.role || "unknown",
|
|
1365
|
+
task.doc_path || null,
|
|
1366
|
+
task.created_at,
|
|
1367
|
+
task.updated_at,
|
|
1368
|
+
task.finished_at || null,
|
|
1369
|
+
task.canceled_at || null,
|
|
1370
|
+
task.tags ? JSON.stringify(task.tags) : null,
|
|
1371
|
+
task.metadata ? JSON.stringify(task.metadata) : null,
|
|
1372
|
+
task.parent_id || null,
|
|
1373
|
+
task.depends_on || null,
|
|
1374
|
+
task.est_tokens || 0,
|
|
1375
|
+
task.in_progress_at || null
|
|
1376
|
+
]
|
|
1395
1377
|
);
|
|
1396
1378
|
}
|
|
1397
1379
|
updateTask(id, updates) {
|
|
@@ -1413,43 +1395,34 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1413
1395
|
fields.push("updated_at = ?");
|
|
1414
1396
|
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
1415
1397
|
values.push(id);
|
|
1416
|
-
|
|
1417
|
-
stmt.run(...values);
|
|
1398
|
+
this.run(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
1418
1399
|
}
|
|
1419
1400
|
deleteTask(id) {
|
|
1420
|
-
this.
|
|
1421
|
-
this.
|
|
1401
|
+
this.run("DELETE FROM task_comments WHERE task_id = ?", [id]);
|
|
1402
|
+
this.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
1422
1403
|
}
|
|
1423
1404
|
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);
|
|
1405
|
+
const row = this.get(
|
|
1406
|
+
`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 = ?`,
|
|
1407
|
+
[id]
|
|
1408
|
+
);
|
|
1432
1409
|
return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(id) } : null;
|
|
1433
1410
|
}
|
|
1434
1411
|
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);
|
|
1412
|
+
const row = this.get(
|
|
1413
|
+
`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 = ?`,
|
|
1414
|
+
[repo, taskCode]
|
|
1415
|
+
);
|
|
1443
1416
|
return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(row.id) } : null;
|
|
1444
1417
|
}
|
|
1445
1418
|
getTasksByRepo(repo, status, limit, offset, search) {
|
|
1446
1419
|
let query = `
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1420
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1421
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1422
|
+
FROM tasks t
|
|
1423
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1424
|
+
WHERE t.repo = ?
|
|
1425
|
+
`;
|
|
1453
1426
|
const params = [repo];
|
|
1454
1427
|
if (status) {
|
|
1455
1428
|
query += " AND t.status = ?";
|
|
@@ -1461,16 +1434,16 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1461
1434
|
params.push(searchPattern, searchPattern, searchPattern);
|
|
1462
1435
|
}
|
|
1463
1436
|
query += ` ORDER BY
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1437
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1438
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1439
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1440
|
+
WHEN t.status = 'pending' THEN 1
|
|
1441
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1442
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1443
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1444
|
+
ELSE 5 END ASC,
|
|
1445
|
+
t.priority DESC,
|
|
1446
|
+
t.created_at ASC`;
|
|
1474
1447
|
if (limit !== void 0) {
|
|
1475
1448
|
query += " LIMIT ?";
|
|
1476
1449
|
params.push(limit);
|
|
@@ -1479,40 +1452,40 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1479
1452
|
params.push(offset);
|
|
1480
1453
|
}
|
|
1481
1454
|
}
|
|
1482
|
-
const rows = this.
|
|
1455
|
+
const rows = this.all(query, params);
|
|
1483
1456
|
return rows.map((r) => this.rowToTask(r));
|
|
1484
1457
|
}
|
|
1485
1458
|
listRecentTasks(limit = 50, offset = 0) {
|
|
1486
1459
|
const query = `
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
const rows = this.
|
|
1460
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1461
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1462
|
+
FROM tasks t
|
|
1463
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1464
|
+
ORDER BY
|
|
1465
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1466
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1467
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1468
|
+
WHEN t.status = 'pending' THEN 1
|
|
1469
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1470
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1471
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1472
|
+
ELSE 5 END ASC,
|
|
1473
|
+
t.priority DESC,
|
|
1474
|
+
t.created_at ASC
|
|
1475
|
+
LIMIT ? OFFSET ?
|
|
1476
|
+
`;
|
|
1477
|
+
const rows = this.all(query, [limit, offset]);
|
|
1505
1478
|
return rows.map((r) => this.rowToTask(r));
|
|
1506
1479
|
}
|
|
1507
1480
|
getTasksByMultipleStatuses(repo, statuses, limit, offset, search) {
|
|
1508
1481
|
if (!statuses.length) return this.getTasksByRepo(repo, void 0, limit, offset, search);
|
|
1509
1482
|
let query = `
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1483
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1484
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1485
|
+
FROM tasks t
|
|
1486
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1487
|
+
WHERE t.repo = ? AND t.status IN (${statuses.map(() => "?").join(",")})
|
|
1488
|
+
`;
|
|
1516
1489
|
const params = [repo, ...statuses];
|
|
1517
1490
|
if (search) {
|
|
1518
1491
|
query += " AND (t.title LIKE ? OR t.description LIKE ? OR t.task_code LIKE ?)";
|
|
@@ -1520,16 +1493,16 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1520
1493
|
params.push(searchPattern, searchPattern, searchPattern);
|
|
1521
1494
|
}
|
|
1522
1495
|
query += ` ORDER BY
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1496
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1497
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1498
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1499
|
+
WHEN t.status = 'pending' THEN 1
|
|
1500
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1501
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1502
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1503
|
+
ELSE 5 END ASC,
|
|
1504
|
+
t.priority DESC,
|
|
1505
|
+
t.created_at ASC`;
|
|
1533
1506
|
if (limit !== void 0) {
|
|
1534
1507
|
query += " LIMIT ?";
|
|
1535
1508
|
params.push(limit);
|
|
@@ -1538,7 +1511,7 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1538
1511
|
params.push(offset);
|
|
1539
1512
|
}
|
|
1540
1513
|
}
|
|
1541
|
-
const rows = this.
|
|
1514
|
+
const rows = this.all(query, params);
|
|
1542
1515
|
return rows.map((r) => this.rowToTask(r));
|
|
1543
1516
|
}
|
|
1544
1517
|
isTaskCodeDuplicate(repo, task_code, excludeId) {
|
|
@@ -1548,20 +1521,18 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1548
1521
|
query += " AND id != ?";
|
|
1549
1522
|
params.push(excludeId);
|
|
1550
1523
|
}
|
|
1551
|
-
const row = this.
|
|
1552
|
-
return row
|
|
1524
|
+
const row = this.get(query, params);
|
|
1525
|
+
return (row?.count ?? 0) > 0;
|
|
1553
1526
|
}
|
|
1554
1527
|
bulkInsertTasks(tasks) {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
for (const task of tasks2) {
|
|
1564
|
-
insert.run(
|
|
1528
|
+
let count = 0;
|
|
1529
|
+
for (const task of tasks) {
|
|
1530
|
+
this.run(
|
|
1531
|
+
`INSERT INTO tasks (
|
|
1532
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
1533
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
1534
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1535
|
+
[
|
|
1565
1536
|
task.id,
|
|
1566
1537
|
task.repo,
|
|
1567
1538
|
task.task_code,
|
|
@@ -1583,31 +1554,29 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1583
1554
|
task.depends_on || null,
|
|
1584
1555
|
task.est_tokens || 0,
|
|
1585
1556
|
task.in_progress_at || null
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
return insertMany(tasks);
|
|
1557
|
+
]
|
|
1558
|
+
);
|
|
1559
|
+
count++;
|
|
1560
|
+
}
|
|
1561
|
+
return count;
|
|
1592
1562
|
}
|
|
1593
1563
|
insertTaskComment(comment) {
|
|
1594
|
-
this.
|
|
1595
|
-
`
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
comment.created_at
|
|
1564
|
+
this.run(
|
|
1565
|
+
`INSERT INTO task_comments (
|
|
1566
|
+
id, task_id, repo, comment, agent, role, model, previous_status, next_status, created_at
|
|
1567
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1568
|
+
[
|
|
1569
|
+
comment.id,
|
|
1570
|
+
comment.task_id,
|
|
1571
|
+
comment.repo,
|
|
1572
|
+
comment.comment,
|
|
1573
|
+
comment.agent || "unknown",
|
|
1574
|
+
comment.role || "unknown",
|
|
1575
|
+
comment.model || "unknown",
|
|
1576
|
+
comment.previous_status || null,
|
|
1577
|
+
comment.next_status || null,
|
|
1578
|
+
comment.created_at
|
|
1579
|
+
]
|
|
1611
1580
|
);
|
|
1612
1581
|
}
|
|
1613
1582
|
updateTaskComment(id, updates) {
|
|
@@ -1622,35 +1591,29 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1622
1591
|
});
|
|
1623
1592
|
if (fields.length === 0) return;
|
|
1624
1593
|
values.push(id);
|
|
1625
|
-
|
|
1626
|
-
stmt.run(...values);
|
|
1594
|
+
this.run(`UPDATE task_comments SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
1627
1595
|
}
|
|
1628
1596
|
deleteTaskComment(id) {
|
|
1629
|
-
this.
|
|
1597
|
+
this.run("DELETE FROM task_comments WHERE id = ?", [id]);
|
|
1630
1598
|
}
|
|
1631
1599
|
getTaskCommentById(id) {
|
|
1632
|
-
return this.
|
|
1600
|
+
return this.get("SELECT * FROM task_comments WHERE id = ?", [id]) ?? null;
|
|
1633
1601
|
}
|
|
1634
1602
|
getTaskCommentsByTaskId(taskId) {
|
|
1635
|
-
return this.
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
WHERE task_id = ?
|
|
1639
|
-
ORDER BY created_at DESC, id DESC
|
|
1640
|
-
`
|
|
1641
|
-
).all(taskId);
|
|
1603
|
+
return this.all(`SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at DESC, id DESC`, [
|
|
1604
|
+
taskId
|
|
1605
|
+
]);
|
|
1642
1606
|
}
|
|
1643
1607
|
getAllTaskCommentsByRepo(repo) {
|
|
1644
|
-
return this.
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
WHERE repo = ?
|
|
1648
|
-
ORDER BY created_at DESC, id DESC
|
|
1649
|
-
`
|
|
1650
|
-
).all(repo);
|
|
1608
|
+
return this.all(`SELECT * FROM task_comments WHERE repo = ? ORDER BY created_at DESC, id DESC`, [
|
|
1609
|
+
repo
|
|
1610
|
+
]);
|
|
1651
1611
|
}
|
|
1652
1612
|
getTaskStats(repo) {
|
|
1653
|
-
const rows = this.
|
|
1613
|
+
const rows = this.all(
|
|
1614
|
+
"SELECT status, COUNT(*) as count FROM tasks WHERE repo = ? GROUP BY status",
|
|
1615
|
+
[repo]
|
|
1616
|
+
);
|
|
1654
1617
|
const stats = { total: 0, backlog: 0, todo: 0, inProgress: 0, completed: 0, blocked: 0, canceled: 0 };
|
|
1655
1618
|
rows.forEach((r) => {
|
|
1656
1619
|
const count = r.count;
|
|
@@ -1670,35 +1633,30 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1670
1633
|
else if (period === "weekly") dateFilter = "AND date(COALESCE(finished_at, updated_at)) >= date('now', '-7 days')";
|
|
1671
1634
|
else if (period === "monthly")
|
|
1672
1635
|
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);
|
|
1636
|
+
const stats = this.get(
|
|
1637
|
+
`SELECT
|
|
1638
|
+
COUNT(*) as completed_count,
|
|
1639
|
+
SUM(est_tokens) as total_tokens,
|
|
1640
|
+
AVG(
|
|
1641
|
+
CASE
|
|
1642
|
+
WHEN in_progress_at IS NOT NULL AND finished_at IS NOT NULL
|
|
1643
|
+
THEN (julianday(finished_at) - julianday(in_progress_at)) * 86400.0
|
|
1644
|
+
ELSE NULL
|
|
1645
|
+
END
|
|
1646
|
+
) as avg_duration_seconds
|
|
1647
|
+
FROM tasks
|
|
1648
|
+
WHERE repo = ?
|
|
1649
|
+
AND status = 'completed'
|
|
1650
|
+
${dateFilter}`,
|
|
1651
|
+
[repo]
|
|
1652
|
+
);
|
|
1691
1653
|
let addedDateFilter = "";
|
|
1692
1654
|
if (period === "daily") addedDateFilter = "AND date(created_at) = date('now')";
|
|
1693
1655
|
else if (period === "weekly") addedDateFilter = "AND date(created_at) >= date('now', '-7 days')";
|
|
1694
1656
|
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);
|
|
1657
|
+
const added = this.get(`SELECT COUNT(*) as count FROM tasks WHERE repo = ? ${addedDateFilter}`, [
|
|
1658
|
+
repo
|
|
1659
|
+
]);
|
|
1702
1660
|
return {
|
|
1703
1661
|
completed: stats?.completed_count || 0,
|
|
1704
1662
|
tokens: stats?.total_tokens || 0,
|
|
@@ -1723,33 +1681,31 @@ var TaskEntity = class extends BaseEntity {
|
|
|
1723
1681
|
dateFilter = "1=1";
|
|
1724
1682
|
}
|
|
1725
1683
|
const query = `
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
return this.
|
|
1684
|
+
SELECT label, SUM(created) as created, SUM(completed) as completed
|
|
1685
|
+
FROM (
|
|
1686
|
+
SELECT strftime(?, created_at) as label, 1 as created, 0 as completed
|
|
1687
|
+
FROM tasks
|
|
1688
|
+
WHERE repo = ? AND ${dateFilter.replace("COALESCE(finished_at, created_at)", "created_at")}
|
|
1689
|
+
UNION ALL
|
|
1690
|
+
SELECT strftime(?, COALESCE(finished_at, updated_at)) as label, 0 as created, 1 as completed
|
|
1691
|
+
FROM tasks
|
|
1692
|
+
WHERE repo = ? AND status = 'completed' AND ${dateFilter.replace("COALESCE(finished_at, created_at)", "COALESCE(finished_at, updated_at)")}
|
|
1693
|
+
)
|
|
1694
|
+
GROUP BY label
|
|
1695
|
+
ORDER BY label ASC
|
|
1696
|
+
LIMIT 100
|
|
1697
|
+
`;
|
|
1698
|
+
return this.all(query, [
|
|
1699
|
+
labelFormat,
|
|
1700
|
+
repo,
|
|
1701
|
+
labelFormat,
|
|
1702
|
+
repo
|
|
1703
|
+
]);
|
|
1741
1704
|
}
|
|
1742
1705
|
};
|
|
1743
1706
|
|
|
1744
1707
|
// src/mcp/entities/action.ts
|
|
1745
1708
|
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
1709
|
logAction(action, repo, optionsOrQuery, response, memoryId, taskId, resultCount = 0) {
|
|
1754
1710
|
let query = typeof optionsOrQuery === "string" ? optionsOrQuery : void 0;
|
|
1755
1711
|
let finalResponse = response;
|
|
@@ -1764,39 +1720,38 @@ var ActionEntity = class extends BaseEntity {
|
|
|
1764
1720
|
finalTaskId = optionsOrQuery.taskId || finalTaskId;
|
|
1765
1721
|
finalResultCount = optionsOrQuery.resultCount !== void 0 ? optionsOrQuery.resultCount : finalResultCount;
|
|
1766
1722
|
}
|
|
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
|
-
|
|
1723
|
+
this.run(
|
|
1724
|
+
`INSERT INTO action_log (repo, action, query, response, memory_id, task_id, result_count, created_at)
|
|
1725
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1726
|
+
[
|
|
1727
|
+
repo || "",
|
|
1728
|
+
action || "unknown",
|
|
1729
|
+
query || null,
|
|
1730
|
+
finalResponse ? typeof finalResponse === "string" ? finalResponse : JSON.stringify(finalResponse) : null,
|
|
1731
|
+
finalMemoryId || null,
|
|
1732
|
+
finalTaskId || null,
|
|
1733
|
+
finalResultCount ?? 0,
|
|
1734
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1735
|
+
]
|
|
1736
|
+
);
|
|
1781
1737
|
}
|
|
1782
1738
|
getLastActionId() {
|
|
1783
|
-
const row = this.
|
|
1739
|
+
const row = this.get("SELECT MAX(id) as id FROM action_log");
|
|
1784
1740
|
return row?.id || 0;
|
|
1785
1741
|
}
|
|
1786
1742
|
getActionsAfter(id) {
|
|
1787
|
-
return this.
|
|
1788
|
-
`
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
).all(id);
|
|
1743
|
+
return this.all(
|
|
1744
|
+
`SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1745
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1746
|
+
WHERE a.id > ? ORDER BY a.created_at ASC`,
|
|
1747
|
+
[id]
|
|
1748
|
+
);
|
|
1794
1749
|
}
|
|
1795
1750
|
getRecentActions(repo, limit = 10, offset = 0) {
|
|
1796
1751
|
let query = `
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1752
|
+
SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1753
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1754
|
+
`;
|
|
1800
1755
|
const params = [];
|
|
1801
1756
|
if (repo) {
|
|
1802
1757
|
query += " WHERE a.repo = ?";
|
|
@@ -1804,69 +1759,81 @@ var ActionEntity = class extends BaseEntity {
|
|
|
1804
1759
|
}
|
|
1805
1760
|
query += " ORDER BY a.created_at DESC, a.id DESC LIMIT ? OFFSET ?";
|
|
1806
1761
|
params.push(limit, offset);
|
|
1807
|
-
return this.
|
|
1762
|
+
return this.all(query, params);
|
|
1808
1763
|
}
|
|
1809
1764
|
getActionStatsByDate(repo) {
|
|
1810
|
-
return this.
|
|
1811
|
-
`
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
).all(repo);
|
|
1765
|
+
return this.all(
|
|
1766
|
+
`SELECT date(created_at) as date, count(*) as count
|
|
1767
|
+
FROM action_log
|
|
1768
|
+
WHERE repo = ? AND created_at > date('now', '-30 days')
|
|
1769
|
+
GROUP BY date(created_at)
|
|
1770
|
+
ORDER BY date ASC`,
|
|
1771
|
+
[repo]
|
|
1772
|
+
);
|
|
1819
1773
|
}
|
|
1820
1774
|
getActionDistribution(repo) {
|
|
1821
|
-
return this.
|
|
1822
|
-
`
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
).all(repo);
|
|
1775
|
+
return this.all(
|
|
1776
|
+
`SELECT action, count(*) as count
|
|
1777
|
+
FROM action_log
|
|
1778
|
+
WHERE repo = ?
|
|
1779
|
+
GROUP BY action`,
|
|
1780
|
+
[repo]
|
|
1781
|
+
);
|
|
1829
1782
|
}
|
|
1830
1783
|
getActionById(id) {
|
|
1831
|
-
return this.
|
|
1832
|
-
`
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
).get(id);
|
|
1784
|
+
return this.get(
|
|
1785
|
+
`SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1786
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1787
|
+
WHERE a.id = ?`,
|
|
1788
|
+
[id]
|
|
1789
|
+
);
|
|
1838
1790
|
}
|
|
1839
1791
|
};
|
|
1840
1792
|
|
|
1841
1793
|
// src/mcp/entities/system.ts
|
|
1842
1794
|
var SystemEntity = class extends BaseEntity {
|
|
1843
1795
|
listRepos() {
|
|
1844
|
-
const rows = this.
|
|
1796
|
+
const rows = this.all("SELECT DISTINCT repo FROM memories UNION SELECT DISTINCT repo FROM tasks");
|
|
1845
1797
|
return rows.map((r) => r.repo);
|
|
1846
1798
|
}
|
|
1847
1799
|
listRepoNavigation() {
|
|
1848
1800
|
const repos = this.listRepos();
|
|
1849
1801
|
return repos.map((repo) => {
|
|
1850
|
-
const
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1802
|
+
const memoryCountRow = this.get("SELECT COUNT(*) as count FROM memories WHERE repo = ?", [
|
|
1803
|
+
repo
|
|
1804
|
+
]);
|
|
1805
|
+
const taskCountRow = this.get("SELECT COUNT(*) as count FROM tasks WHERE repo = ?", [repo]);
|
|
1806
|
+
const lastActivityRow = this.get(
|
|
1807
|
+
`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 = ?)`,
|
|
1808
|
+
[repo, repo, repo]
|
|
1809
|
+
);
|
|
1855
1810
|
return {
|
|
1856
1811
|
repo,
|
|
1857
|
-
memoryCount,
|
|
1858
|
-
taskCount,
|
|
1859
|
-
lastActivity
|
|
1812
|
+
memoryCount: memoryCountRow?.count ?? 0,
|
|
1813
|
+
taskCount: taskCountRow?.count ?? 0,
|
|
1814
|
+
lastActivity: lastActivityRow?.last ?? null
|
|
1860
1815
|
};
|
|
1861
1816
|
});
|
|
1862
1817
|
}
|
|
1863
1818
|
getDashboardStats(repo) {
|
|
1864
|
-
const memoryStats = this.
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1819
|
+
const memoryStats = this.all(
|
|
1820
|
+
"SELECT type, COUNT(*) as count FROM memories WHERE repo = ? GROUP BY type",
|
|
1821
|
+
[repo]
|
|
1822
|
+
);
|
|
1823
|
+
const taskStats = this.all(
|
|
1824
|
+
"SELECT status, COUNT(*) as count FROM tasks WHERE repo = ? GROUP BY status",
|
|
1825
|
+
[repo]
|
|
1826
|
+
);
|
|
1827
|
+
const recentMemoriesRows = this.all(
|
|
1828
|
+
"SELECT * FROM memories WHERE repo = ? ORDER BY created_at DESC LIMIT 5",
|
|
1829
|
+
[repo]
|
|
1830
|
+
);
|
|
1831
|
+
const recentMemories = recentMemoriesRows.map((r) => this.rowToMemoryEntry(r));
|
|
1832
|
+
const activeTasksRows = this.all(
|
|
1833
|
+
"SELECT * FROM tasks WHERE repo = ? AND status IN ('in_progress', 'pending', 'backlog') ORDER BY priority DESC, created_at ASC LIMIT 5",
|
|
1834
|
+
[repo]
|
|
1835
|
+
);
|
|
1836
|
+
const activeTasks = activeTasksRows.map((r) => this.rowToTask(r));
|
|
1870
1837
|
return {
|
|
1871
1838
|
memoryStats,
|
|
1872
1839
|
taskStats,
|
|
@@ -1875,23 +1842,27 @@ var SystemEntity = class extends BaseEntity {
|
|
|
1875
1842
|
};
|
|
1876
1843
|
}
|
|
1877
1844
|
getGlobalStats() {
|
|
1878
|
-
const
|
|
1879
|
-
const
|
|
1845
|
+
const totalMemoriesRow = this.get("SELECT COUNT(*) as count FROM memories");
|
|
1846
|
+
const totalTasksRow = this.get("SELECT COUNT(*) as count FROM tasks");
|
|
1880
1847
|
const totalRepos = this.listRepos().length;
|
|
1881
1848
|
return {
|
|
1882
|
-
totalMemories,
|
|
1883
|
-
totalTasks,
|
|
1849
|
+
totalMemories: totalMemoriesRow?.count ?? 0,
|
|
1850
|
+
totalTasks: totalTasksRow?.count ?? 0,
|
|
1884
1851
|
totalRepos
|
|
1885
1852
|
};
|
|
1886
1853
|
}
|
|
1887
1854
|
getRepoDetails(repo) {
|
|
1888
|
-
const
|
|
1889
|
-
const
|
|
1890
|
-
const
|
|
1855
|
+
const memoryCountRow = this.get("SELECT COUNT(*) as count FROM memories WHERE repo = ?", [repo]);
|
|
1856
|
+
const taskCountRow = this.get("SELECT COUNT(*) as count FROM tasks WHERE repo = ?", [repo]);
|
|
1857
|
+
const languagesRows = this.all(
|
|
1858
|
+
"SELECT DISTINCT language FROM memories WHERE repo = ? AND language IS NOT NULL",
|
|
1859
|
+
[repo]
|
|
1860
|
+
);
|
|
1861
|
+
const languages = languagesRows.map((r) => r.language);
|
|
1891
1862
|
return {
|
|
1892
1863
|
repo,
|
|
1893
|
-
memoryCount,
|
|
1894
|
-
taskCount,
|
|
1864
|
+
memoryCount: memoryCountRow?.count ?? 0,
|
|
1865
|
+
taskCount: taskCountRow?.count ?? 0,
|
|
1895
1866
|
languages
|
|
1896
1867
|
};
|
|
1897
1868
|
}
|
|
@@ -1900,16 +1871,18 @@ var SystemEntity = class extends BaseEntity {
|
|
|
1900
1871
|
// src/mcp/entities/summary.ts
|
|
1901
1872
|
var SummaryEntity = class extends BaseEntity {
|
|
1902
1873
|
getSummary(repo) {
|
|
1903
|
-
const row = this.
|
|
1874
|
+
const row = this.get(
|
|
1875
|
+
"SELECT summary, updated_at FROM memory_summary WHERE repo = ?",
|
|
1876
|
+
[repo]
|
|
1877
|
+
);
|
|
1904
1878
|
return row || null;
|
|
1905
1879
|
}
|
|
1906
1880
|
upsertSummary(repo, summary) {
|
|
1907
|
-
this.
|
|
1908
|
-
`
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
).run(repo, summary, (/* @__PURE__ */ new Date()).toISOString());
|
|
1881
|
+
this.run(
|
|
1882
|
+
`INSERT INTO memory_summary (repo, summary, updated_at) VALUES (?, ?, ?)
|
|
1883
|
+
ON CONFLICT(repo) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at`,
|
|
1884
|
+
[repo, summary, (/* @__PURE__ */ new Date()).toISOString()]
|
|
1885
|
+
);
|
|
1913
1886
|
}
|
|
1914
1887
|
};
|
|
1915
1888
|
|
|
@@ -1926,44 +1899,104 @@ function resolveDbPath() {
|
|
|
1926
1899
|
return standardPath;
|
|
1927
1900
|
}
|
|
1928
1901
|
var DB_PATH = resolveDbPath();
|
|
1929
|
-
var
|
|
1902
|
+
var dbPathInstance = "";
|
|
1903
|
+
var sqlJsReady = null;
|
|
1904
|
+
var sqlJsModule = null;
|
|
1905
|
+
async function getSqlJs() {
|
|
1906
|
+
if (!sqlJsModule) {
|
|
1907
|
+
sqlJsModule = await initSqlJs();
|
|
1908
|
+
}
|
|
1909
|
+
await sqlJsReady;
|
|
1910
|
+
return sqlJsModule;
|
|
1911
|
+
}
|
|
1912
|
+
function createSaveFunction(db, dbPath) {
|
|
1913
|
+
return () => {
|
|
1914
|
+
if (dbPath && dbPath !== ":memory:") {
|
|
1915
|
+
const data = db.export();
|
|
1916
|
+
const buffer = Buffer.from(data);
|
|
1917
|
+
fs2.writeFileSync(dbPath, buffer);
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
function warmUpSqlJs() {
|
|
1922
|
+
if (!sqlJsReady) {
|
|
1923
|
+
sqlJsReady = (async () => {
|
|
1924
|
+
sqlJsModule = await initSqlJs();
|
|
1925
|
+
})();
|
|
1926
|
+
}
|
|
1927
|
+
return sqlJsReady;
|
|
1928
|
+
}
|
|
1929
|
+
var SQLiteStore = class _SQLiteStore {
|
|
1930
1930
|
db;
|
|
1931
1931
|
memories;
|
|
1932
1932
|
tasks;
|
|
1933
1933
|
actions;
|
|
1934
1934
|
system;
|
|
1935
1935
|
summaries;
|
|
1936
|
+
_ready;
|
|
1936
1937
|
constructor(dbPath) {
|
|
1937
1938
|
const finalPath = dbPath ?? DB_PATH;
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
}
|
|
1942
|
-
this.
|
|
1943
|
-
this.
|
|
1944
|
-
this.
|
|
1945
|
-
this.
|
|
1946
|
-
|
|
1939
|
+
dbPathInstance = finalPath;
|
|
1940
|
+
warmUpSqlJs();
|
|
1941
|
+
this.db = {};
|
|
1942
|
+
this.memories = {};
|
|
1943
|
+
this.tasks = {};
|
|
1944
|
+
this.actions = {};
|
|
1945
|
+
this.system = {};
|
|
1946
|
+
this.summaries = {};
|
|
1947
|
+
this._ready = this._init(finalPath);
|
|
1948
|
+
}
|
|
1949
|
+
async _init(finalPath) {
|
|
1950
|
+
const SQL = await getSqlJs();
|
|
1951
|
+
let db;
|
|
1952
|
+
if (finalPath === ":memory:") {
|
|
1953
|
+
db = new SQL.Database();
|
|
1954
|
+
} else {
|
|
1955
|
+
const dbDir = path2.dirname(finalPath);
|
|
1956
|
+
if (!fs2.existsSync(dbDir)) {
|
|
1957
|
+
fs2.mkdirSync(dbDir, { recursive: true });
|
|
1958
|
+
}
|
|
1959
|
+
if (fs2.existsSync(finalPath)) {
|
|
1960
|
+
const fileBuffer = fs2.readFileSync(finalPath);
|
|
1961
|
+
db = new SQL.Database(fileBuffer);
|
|
1962
|
+
} else {
|
|
1963
|
+
db = new SQL.Database();
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
const saveDb = createSaveFunction(db, finalPath);
|
|
1967
|
+
db.run("PRAGMA journal_mode = WAL");
|
|
1968
|
+
db.run("PRAGMA synchronous = NORMAL");
|
|
1969
|
+
db.run("PRAGMA busy_timeout = 5000");
|
|
1970
|
+
const migrator = new MigrationManager(db, saveDb);
|
|
1947
1971
|
migrator.migrate();
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1972
|
+
migrator.addMemoryCodeColumn();
|
|
1973
|
+
Object.assign(this, {
|
|
1974
|
+
db,
|
|
1975
|
+
memories: new MemoryEntity(db, saveDb),
|
|
1976
|
+
tasks: new TaskEntity(db, saveDb),
|
|
1977
|
+
actions: new ActionEntity(db, saveDb),
|
|
1978
|
+
system: new SystemEntity(db, saveDb),
|
|
1979
|
+
summaries: new SummaryEntity(db, saveDb)
|
|
1980
|
+
});
|
|
1981
|
+
if (finalPath !== ":memory:") {
|
|
1982
|
+
saveDb();
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
async ready() {
|
|
1986
|
+
await this._ready;
|
|
1987
|
+
}
|
|
1988
|
+
static async create(dbPath) {
|
|
1989
|
+
const store = new _SQLiteStore(dbPath);
|
|
1990
|
+
await store.ready();
|
|
1991
|
+
return store;
|
|
1955
1992
|
}
|
|
1956
|
-
/**
|
|
1957
|
-
* Returns the current database file path.
|
|
1958
|
-
*/
|
|
1959
1993
|
getDbPath() {
|
|
1960
|
-
return
|
|
1994
|
+
return dbPathInstance;
|
|
1961
1995
|
}
|
|
1962
|
-
/**
|
|
1963
|
-
* Closes the database connection.
|
|
1964
|
-
*/
|
|
1965
1996
|
close() {
|
|
1966
|
-
this.db.close
|
|
1997
|
+
if (this.db && this.db.close) {
|
|
1998
|
+
this.db.close();
|
|
1999
|
+
}
|
|
1967
2000
|
}
|
|
1968
2001
|
};
|
|
1969
2002
|
|
|
@@ -2087,8 +2120,9 @@ var MemoryTypeSchema = z.enum([
|
|
|
2087
2120
|
"task_archive"
|
|
2088
2121
|
]);
|
|
2089
2122
|
var MemoryStoreSchema = z.object({
|
|
2123
|
+
code: z.string().max(20).optional(),
|
|
2090
2124
|
type: MemoryTypeSchema,
|
|
2091
|
-
title: z.string().min(3).max(
|
|
2125
|
+
title: z.string().min(3).max(255),
|
|
2092
2126
|
content: z.string().min(10),
|
|
2093
2127
|
importance: z.number().min(1).max(5),
|
|
2094
2128
|
agent: z.string().min(1),
|
|
@@ -2104,7 +2138,7 @@ var MemoryStoreSchema = z.object({
|
|
|
2104
2138
|
var MemoryUpdateSchema = z.object({
|
|
2105
2139
|
id: z.string().uuid(),
|
|
2106
2140
|
type: MemoryTypeSchema.optional(),
|
|
2107
|
-
title: z.string().min(3).max(
|
|
2141
|
+
title: z.string().min(3).max(255).optional(),
|
|
2108
2142
|
content: z.string().min(10).optional(),
|
|
2109
2143
|
importance: z.number().min(1).max(5).optional(),
|
|
2110
2144
|
agent: z.string().optional(),
|
|
@@ -2131,7 +2165,8 @@ var MemorySearchSchema = z.object({
|
|
|
2131
2165
|
current_file_path: z.string().optional(),
|
|
2132
2166
|
include_archived: z.boolean().default(false),
|
|
2133
2167
|
current_tags: z.array(z.string()).optional(),
|
|
2134
|
-
scope: MemoryScopeSchema.partial().optional()
|
|
2168
|
+
scope: MemoryScopeSchema.partial().optional(),
|
|
2169
|
+
structured: z.boolean().default(false)
|
|
2135
2170
|
});
|
|
2136
2171
|
var MemoryAcknowledgeSchema = z.object({
|
|
2137
2172
|
memory_id: z.string().uuid(),
|
|
@@ -2141,7 +2176,8 @@ var MemoryAcknowledgeSchema = z.object({
|
|
|
2141
2176
|
var MemoryRecapSchema = z.object({
|
|
2142
2177
|
repo: z.string().min(1).transform(normalizeRepo),
|
|
2143
2178
|
limit: z.number().min(1).max(50).default(20),
|
|
2144
|
-
offset: z.number().min(0).default(0)
|
|
2179
|
+
offset: z.number().min(0).default(0),
|
|
2180
|
+
structured: z.boolean().default(false)
|
|
2145
2181
|
});
|
|
2146
2182
|
var MemoryDeleteSchema = z.object({
|
|
2147
2183
|
repo: z.string().min(1).transform(normalizeRepo).optional(),
|
|
@@ -2201,10 +2237,13 @@ var TaskCreateSchema = z.object({
|
|
|
2201
2237
|
est_tokens: z.number().int().min(0).optional(),
|
|
2202
2238
|
// Allow bulk tasks
|
|
2203
2239
|
tasks: z.array(SingleTaskCreateSchema).min(1).optional()
|
|
2204
|
-
}).refine(
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2240
|
+
}).refine(
|
|
2241
|
+
(data) => {
|
|
2242
|
+
if (data.tasks) return true;
|
|
2243
|
+
return !!(data.task_code && data.phase && data.title && data.description);
|
|
2244
|
+
},
|
|
2245
|
+
{ message: "Either 'tasks' array or single task fields (task_code, phase, title, description) must be provided" }
|
|
2246
|
+
);
|
|
2208
2247
|
var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
|
|
2209
2248
|
repo: z.string().min(1).transform(normalizeRepo).optional()
|
|
2210
2249
|
});
|
|
@@ -2240,14 +2279,16 @@ var TaskListSchema = z.object({
|
|
|
2240
2279
|
phase: z.string().optional(),
|
|
2241
2280
|
query: z.string().optional(),
|
|
2242
2281
|
limit: z.number().min(1).max(100).default(15),
|
|
2243
|
-
offset: z.number().min(0).default(0)
|
|
2282
|
+
offset: z.number().min(0).default(0),
|
|
2283
|
+
structured: z.boolean().default(false)
|
|
2244
2284
|
});
|
|
2245
2285
|
var TaskSearchSchema = z.object({
|
|
2246
2286
|
repo: z.string().min(1).transform(normalizeRepo),
|
|
2247
2287
|
query: z.string().min(1),
|
|
2248
2288
|
status: z.string().optional(),
|
|
2249
2289
|
limit: z.number().min(1).max(100).default(10),
|
|
2250
|
-
offset: z.number().min(0).default(0)
|
|
2290
|
+
offset: z.number().min(0).default(0),
|
|
2291
|
+
structured: z.boolean().default(false)
|
|
2251
2292
|
});
|
|
2252
2293
|
var TaskDeleteSchema = z.object({
|
|
2253
2294
|
repo: z.string().min(1).transform(normalizeRepo),
|
|
@@ -2257,12 +2298,16 @@ var TaskDeleteSchema = z.object({
|
|
|
2257
2298
|
message: "Either 'id' or 'ids' must be provided for deletion"
|
|
2258
2299
|
});
|
|
2259
2300
|
var MemoryDetailSchema = z.object({
|
|
2260
|
-
id: z.string().uuid()
|
|
2301
|
+
id: z.string().uuid().optional(),
|
|
2302
|
+
code: z.string().max(20).optional()
|
|
2303
|
+
}).refine((data) => data.id !== void 0 || data.code !== void 0, {
|
|
2304
|
+
message: "Either id or code must be provided"
|
|
2261
2305
|
});
|
|
2262
2306
|
var TaskGetSchema = z.object({
|
|
2263
2307
|
repo: z.string().min(1).transform(normalizeRepo),
|
|
2264
2308
|
id: z.string().uuid().optional(),
|
|
2265
|
-
task_code: z.string().optional()
|
|
2309
|
+
task_code: z.string().optional(),
|
|
2310
|
+
structured: z.boolean().default(false)
|
|
2266
2311
|
}).refine((data) => data.id !== void 0 || data.task_code !== void 0, {
|
|
2267
2312
|
message: "Either id or task_code must be provided"
|
|
2268
2313
|
});
|
|
@@ -2376,7 +2421,12 @@ var TOOL_DEFINITIONS = [
|
|
|
2376
2421
|
properties: {
|
|
2377
2422
|
repo: { type: "string", description: "Repository name" },
|
|
2378
2423
|
id: { type: "string", format: "uuid", description: "Task ID (optional if task_code is provided)" },
|
|
2379
|
-
task_code: { type: "string", description: "Task code (e.g. TASK-001) (optional if id is provided)" }
|
|
2424
|
+
task_code: { type: "string", description: "Task code (e.g. TASK-001) (optional if id is provided)" },
|
|
2425
|
+
structured: {
|
|
2426
|
+
type: "boolean",
|
|
2427
|
+
default: false,
|
|
2428
|
+
description: "If true, returns structured JSON without the text content details."
|
|
2429
|
+
}
|
|
2380
2430
|
},
|
|
2381
2431
|
required: ["repo"]
|
|
2382
2432
|
}
|
|
@@ -2470,6 +2520,7 @@ var TOOL_DEFINITIONS = [
|
|
|
2470
2520
|
properties: {
|
|
2471
2521
|
success: { type: "boolean" },
|
|
2472
2522
|
id: { type: "string" },
|
|
2523
|
+
code: { type: "string" },
|
|
2473
2524
|
repo: { type: "string" },
|
|
2474
2525
|
type: { type: "string" },
|
|
2475
2526
|
title: { type: "string" },
|
|
@@ -2612,6 +2663,11 @@ var TOOL_DEFINITIONS = [
|
|
|
2612
2663
|
folder: { type: "string" },
|
|
2613
2664
|
language: { type: "string" }
|
|
2614
2665
|
}
|
|
2666
|
+
},
|
|
2667
|
+
structured: {
|
|
2668
|
+
type: "boolean",
|
|
2669
|
+
default: false,
|
|
2670
|
+
description: "If true, returns structured JSON without the text content summary."
|
|
2615
2671
|
}
|
|
2616
2672
|
},
|
|
2617
2673
|
required: ["query", "repo"]
|
|
@@ -2738,6 +2794,11 @@ var TOOL_DEFINITIONS = [
|
|
|
2738
2794
|
minimum: 0,
|
|
2739
2795
|
default: 0,
|
|
2740
2796
|
description: "Number of memories to skip for pagination (optional, default 0)"
|
|
2797
|
+
},
|
|
2798
|
+
structured: {
|
|
2799
|
+
type: "boolean",
|
|
2800
|
+
default: false,
|
|
2801
|
+
description: "If true, returns structured JSON without the text content summary."
|
|
2741
2802
|
}
|
|
2742
2803
|
},
|
|
2743
2804
|
required: ["repo"]
|
|
@@ -2796,7 +2857,12 @@ var TOOL_DEFINITIONS = [
|
|
|
2796
2857
|
repo: { type: "string", description: "Repository name" },
|
|
2797
2858
|
task_code: { type: "string", description: "Unique task code (e.g. TASK-001) (Required for single task)" },
|
|
2798
2859
|
phase: { type: "string", description: "Project phase (Required for single task)" },
|
|
2799
|
-
title: {
|
|
2860
|
+
title: {
|
|
2861
|
+
type: "string",
|
|
2862
|
+
minLength: 3,
|
|
2863
|
+
maxLength: 100,
|
|
2864
|
+
description: "Task objective (Required for single task)"
|
|
2865
|
+
},
|
|
2800
2866
|
description: { type: "string", description: "Detailed description (Required for single task)" },
|
|
2801
2867
|
status: {
|
|
2802
2868
|
type: "string",
|
|
@@ -2845,8 +2911,8 @@ var TOOL_DEFINITIONS = [
|
|
|
2845
2911
|
properties: {
|
|
2846
2912
|
success: { type: "boolean" },
|
|
2847
2913
|
id: { type: "string" },
|
|
2848
|
-
repo: { type: "string" },
|
|
2849
2914
|
task_code: { type: "string" },
|
|
2915
|
+
repo: { type: "string" },
|
|
2850
2916
|
phase: { type: "string" },
|
|
2851
2917
|
title: { type: "string" },
|
|
2852
2918
|
status: { type: "string" },
|
|
@@ -2899,7 +2965,10 @@ var TOOL_DEFINITIONS = [
|
|
|
2899
2965
|
minimum: 0,
|
|
2900
2966
|
description: "Estimated total tokens actually used for this task. Required when status changes to 'completed'."
|
|
2901
2967
|
},
|
|
2902
|
-
force: {
|
|
2968
|
+
force: {
|
|
2969
|
+
type: "boolean",
|
|
2970
|
+
description: "If true, bypasses status transition validation (e.g. pending -> completed)."
|
|
2971
|
+
}
|
|
2903
2972
|
},
|
|
2904
2973
|
required: ["repo"]
|
|
2905
2974
|
},
|
|
@@ -2993,6 +3062,11 @@ var TOOL_DEFINITIONS = [
|
|
|
2993
3062
|
minimum: 0,
|
|
2994
3063
|
default: 0,
|
|
2995
3064
|
description: "Offset for pagination"
|
|
3065
|
+
},
|
|
3066
|
+
structured: {
|
|
3067
|
+
type: "boolean",
|
|
3068
|
+
default: false,
|
|
3069
|
+
description: "If true, returns structured JSON without the text content summary."
|
|
2996
3070
|
}
|
|
2997
3071
|
},
|
|
2998
3072
|
required: ["repo"]
|
|
@@ -3471,7 +3545,29 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
3471
3545
|
import matter from "gray-matter";
|
|
3472
3546
|
var __filename = fileURLToPath3(import.meta.url);
|
|
3473
3547
|
var __dirname2 = path4.dirname(__filename);
|
|
3474
|
-
|
|
3548
|
+
function findPromptDir() {
|
|
3549
|
+
const candidates = [
|
|
3550
|
+
// Production: /dist/prompts (sibling of dist/mcp/)
|
|
3551
|
+
["../../prompts", "../../prompts"],
|
|
3552
|
+
// Dev: /src/mcp/prompts/definitions (next to loader.ts)
|
|
3553
|
+
["./definitions", "./definitions"]
|
|
3554
|
+
].map(([prod, dev]) => {
|
|
3555
|
+
const prodPath = path4.resolve(__dirname2, prod);
|
|
3556
|
+
if (fs3.existsSync(prodPath) && fs3.readdirSync(prodPath).some((f) => f.endsWith(".md"))) {
|
|
3557
|
+
return prodPath;
|
|
3558
|
+
}
|
|
3559
|
+
const devPath = path4.resolve(__dirname2, dev);
|
|
3560
|
+
if (fs3.existsSync(devPath) && fs3.readdirSync(devPath).some((f) => f.endsWith(".md"))) {
|
|
3561
|
+
return devPath;
|
|
3562
|
+
}
|
|
3563
|
+
return null;
|
|
3564
|
+
}).filter(Boolean);
|
|
3565
|
+
if (candidates[0]) {
|
|
3566
|
+
return candidates[0];
|
|
3567
|
+
}
|
|
3568
|
+
return path4.resolve(__dirname2, "./definitions");
|
|
3569
|
+
}
|
|
3570
|
+
var PROMPT_DIR = findPromptDir();
|
|
3475
3571
|
function listPromptFiles() {
|
|
3476
3572
|
if (!fs3.existsSync(PROMPT_DIR)) return [];
|
|
3477
3573
|
return fs3.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
|