@cocaxcode/logbook-mcp 0.1.1 → 0.2.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/dist/index.js
CHANGED
|
@@ -6,13 +6,13 @@ async function main() {
|
|
|
6
6
|
const hasMcpFlag = argv.includes("--mcp");
|
|
7
7
|
if (hasMcpFlag) {
|
|
8
8
|
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
9
|
-
const { createServer } = await import("./server-
|
|
9
|
+
const { createServer } = await import("./server-RBETEXFK.js");
|
|
10
10
|
const server = createServer();
|
|
11
11
|
const transport = new StdioServerTransport();
|
|
12
12
|
await server.connect(transport);
|
|
13
13
|
console.error("logbook-mcp server running on stdio");
|
|
14
14
|
} else {
|
|
15
|
-
const { runCli } = await import("./cli-
|
|
15
|
+
const { runCli } = await import("./cli-T2BAPOJO.js");
|
|
16
16
|
await runCli(argv);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -57,6 +57,22 @@ CREATE INDEX IF NOT EXISTS idx_todos_topic ON todos(topic_id);
|
|
|
57
57
|
CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status);
|
|
58
58
|
CREATE INDEX IF NOT EXISTS idx_todos_date ON todos(created_at);
|
|
59
59
|
|
|
60
|
+
CREATE TABLE IF NOT EXISTS code_todo_snapshots (
|
|
61
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
62
|
+
repo_id INTEGER NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
|
|
63
|
+
file TEXT NOT NULL,
|
|
64
|
+
line INTEGER NOT NULL,
|
|
65
|
+
tag TEXT NOT NULL,
|
|
66
|
+
content TEXT NOT NULL,
|
|
67
|
+
topic_name TEXT NOT NULL,
|
|
68
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
69
|
+
resolved_at TEXT,
|
|
70
|
+
UNIQUE(repo_id, file, content)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_code_snapshots_repo ON code_todo_snapshots(repo_id);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_code_snapshots_resolved ON code_todo_snapshots(resolved_at);
|
|
75
|
+
|
|
60
76
|
CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(
|
|
61
77
|
content,
|
|
62
78
|
content='notes',
|
|
@@ -349,6 +365,50 @@ function getCompletedTodos(db2, filters = {}) {
|
|
|
349
365
|
`${TODO_WITH_META_SQL} WHERE ${where} ORDER BY t.completed_at DESC`
|
|
350
366
|
).all(...params);
|
|
351
367
|
}
|
|
368
|
+
function syncCodeTodos(db2, repoId, currentTodos) {
|
|
369
|
+
const sync = db2.transaction(() => {
|
|
370
|
+
const existing = db2.prepare("SELECT * FROM code_todo_snapshots WHERE repo_id = ? AND resolved_at IS NULL").all(repoId);
|
|
371
|
+
const currentSet = new Set(currentTodos.map((t) => `${t.file}::${t.content}`));
|
|
372
|
+
let resolved = 0;
|
|
373
|
+
for (const snap of existing) {
|
|
374
|
+
const key = `${snap.file}::${snap.content}`;
|
|
375
|
+
if (!currentSet.has(key)) {
|
|
376
|
+
db2.prepare("UPDATE code_todo_snapshots SET resolved_at = datetime('now') WHERE id = ?").run(snap.id);
|
|
377
|
+
resolved++;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const existingSet = new Set(existing.map((s) => `${s.file}::${s.content}`));
|
|
381
|
+
const allSnapshots = db2.prepare("SELECT file, content FROM code_todo_snapshots WHERE repo_id = ?").all(repoId);
|
|
382
|
+
const allSet = new Set(allSnapshots.map((s) => `${s.file}::${s.content}`));
|
|
383
|
+
let added = 0;
|
|
384
|
+
for (const todo of currentTodos) {
|
|
385
|
+
const key = `${todo.file}::${todo.content}`;
|
|
386
|
+
if (!allSet.has(key)) {
|
|
387
|
+
db2.prepare(
|
|
388
|
+
"INSERT INTO code_todo_snapshots (repo_id, file, line, tag, content, topic_name) VALUES (?, ?, ?, ?, ?, ?)"
|
|
389
|
+
).run(repoId, todo.file, todo.line, todo.tag, todo.content, todo.topic_name);
|
|
390
|
+
added++;
|
|
391
|
+
} else if (existingSet.has(key)) {
|
|
392
|
+
db2.prepare("UPDATE code_todo_snapshots SET line = ? WHERE repo_id = ? AND file = ? AND content = ? AND resolved_at IS NULL").run(todo.line, repoId, todo.file, todo.content);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return { added, resolved };
|
|
396
|
+
});
|
|
397
|
+
return sync();
|
|
398
|
+
}
|
|
399
|
+
function getResolvedCodeTodos(db2, repoId, from, to) {
|
|
400
|
+
const conditions = ["repo_id = ?", "resolved_at IS NOT NULL"];
|
|
401
|
+
const params = [repoId];
|
|
402
|
+
if (from) {
|
|
403
|
+
conditions.push("resolved_at >= ?");
|
|
404
|
+
params.push(from);
|
|
405
|
+
}
|
|
406
|
+
if (to) {
|
|
407
|
+
conditions.push("resolved_at < ?");
|
|
408
|
+
params.push(to);
|
|
409
|
+
}
|
|
410
|
+
return db2.prepare(`SELECT * FROM code_todo_snapshots WHERE ${conditions.join(" AND ")} ORDER BY resolved_at DESC`).all(...params);
|
|
411
|
+
}
|
|
352
412
|
function sanitizeFts(query) {
|
|
353
413
|
const words = query.replace(/\0/g, "").split(/\s+/).filter(Boolean).map((word) => word.replace(/"/g, "")).filter(Boolean);
|
|
354
414
|
if (words.length === 0) return '""';
|
|
@@ -375,6 +435,13 @@ function registerTopicsTool(server) {
|
|
|
375
435
|
content: [{ type: "text", text: 'El parametro "name" es obligatorio para action=add' }]
|
|
376
436
|
};
|
|
377
437
|
}
|
|
438
|
+
const existing = getTopicByName(db2, name);
|
|
439
|
+
if (existing) {
|
|
440
|
+
return {
|
|
441
|
+
isError: true,
|
|
442
|
+
content: [{ type: "text", text: `El topic "${name}" ya existe` }]
|
|
443
|
+
};
|
|
444
|
+
}
|
|
378
445
|
const topic = insertTopic(db2, name, description);
|
|
379
446
|
return {
|
|
380
447
|
content: [{ type: "text", text: JSON.stringify(topic) }]
|
|
@@ -592,7 +659,7 @@ function parseGitGrepOutput(output) {
|
|
|
592
659
|
function registerTodoListTool(server) {
|
|
593
660
|
server.tool(
|
|
594
661
|
"logbook_todo_list",
|
|
595
|
-
"Lista TODOs agrupados por topic. Incluye
|
|
662
|
+
"Lista TODOs agrupados por topic. Incluye manuales y del codigo (TODO/FIXME/HACK/BUG). Sincroniza automaticamente: code TODOs que desaparecen del codigo se marcan como resueltos.",
|
|
596
663
|
{
|
|
597
664
|
status: z4.enum(["pending", "done", "all"]).optional().default("pending").describe("Filtrar por estado (default: pending)"),
|
|
598
665
|
topic: z4.string().optional().describe("Filtrar por topic"),
|
|
@@ -622,10 +689,12 @@ function registerTodoListTool(server) {
|
|
|
622
689
|
limit
|
|
623
690
|
});
|
|
624
691
|
let codeTodos = [];
|
|
692
|
+
let syncResult = null;
|
|
625
693
|
if (source !== "manual" && status !== "done") {
|
|
626
694
|
const repoPath = scope === "project" ? detectRepoPath() : null;
|
|
627
|
-
if (repoPath) {
|
|
695
|
+
if (repoPath && repo) {
|
|
628
696
|
codeTodos = scanCodeTodos(repoPath);
|
|
697
|
+
syncResult = syncCodeTodos(db2, repo.id, codeTodos);
|
|
629
698
|
if (topic) {
|
|
630
699
|
codeTodos = codeTodos.filter((ct) => ct.topic_name === topic);
|
|
631
700
|
}
|
|
@@ -651,7 +720,8 @@ function registerTodoListTool(server) {
|
|
|
651
720
|
summary: {
|
|
652
721
|
manual: manualTodos.length,
|
|
653
722
|
code: codeTodos.length,
|
|
654
|
-
total: manualTodos.length + codeTodos.length
|
|
723
|
+
total: manualTodos.length + codeTodos.length,
|
|
724
|
+
...syncResult && (syncResult.added > 0 || syncResult.resolved > 0) ? { sync: syncResult } : {}
|
|
655
725
|
}
|
|
656
726
|
})
|
|
657
727
|
}]
|
|
@@ -817,6 +887,7 @@ function registerLogTool(server) {
|
|
|
817
887
|
};
|
|
818
888
|
const notes = type === "todos" ? [] : getNotes(db2, filters);
|
|
819
889
|
const completedTodos = type === "notes" ? [] : getCompletedTodos(db2, filters);
|
|
890
|
+
const resolvedCodeTodos = type === "notes" || !repo ? [] : getResolvedCodeTodos(db2, repo.id, dateFrom, dateTo);
|
|
820
891
|
const entries = [
|
|
821
892
|
...notes.map((n) => ({
|
|
822
893
|
type: "note",
|
|
@@ -827,6 +898,11 @@ function registerLogTool(server) {
|
|
|
827
898
|
type: "todo",
|
|
828
899
|
data: t,
|
|
829
900
|
timestamp: t.completed_at ?? t.created_at
|
|
901
|
+
})),
|
|
902
|
+
...resolvedCodeTodos.map((ct) => ({
|
|
903
|
+
type: "code_todo_resolved",
|
|
904
|
+
data: { file: ct.file, line: ct.line, tag: ct.tag, content: ct.content, topic_name: ct.topic_name },
|
|
905
|
+
timestamp: ct.resolved_at
|
|
830
906
|
}))
|
|
831
907
|
].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
832
908
|
return {
|
|
@@ -840,6 +916,7 @@ function registerLogTool(server) {
|
|
|
840
916
|
summary: {
|
|
841
917
|
notes: notes.length,
|
|
842
918
|
todos_completed: completedTodos.length,
|
|
919
|
+
code_todos_resolved: resolvedCodeTodos.length,
|
|
843
920
|
total: entries.length
|
|
844
921
|
}
|
|
845
922
|
})
|
|
@@ -856,8 +933,9 @@ function registerLogTool(server) {
|
|
|
856
933
|
}
|
|
857
934
|
function resolveDates(period, from, to) {
|
|
858
935
|
if (from) {
|
|
859
|
-
const
|
|
860
|
-
|
|
936
|
+
const endDate = to ?? from;
|
|
937
|
+
const nextDay = new Date(new Date(endDate).getTime() + 864e5).toISOString().split("T")[0];
|
|
938
|
+
return { dateFrom: from, dateTo: nextDay };
|
|
861
939
|
}
|
|
862
940
|
const now = /* @__PURE__ */ new Date();
|
|
863
941
|
const today = now.toISOString().split("T")[0];
|
|
@@ -949,7 +1027,7 @@ function registerSearchTool(server) {
|
|
|
949
1027
|
}
|
|
950
1028
|
|
|
951
1029
|
// src/server.ts
|
|
952
|
-
var VERSION = true ? "0.1.
|
|
1030
|
+
var VERSION = true ? "0.1.2" : "0.0.0";
|
|
953
1031
|
function createServer() {
|
|
954
1032
|
const server = new McpServer({
|
|
955
1033
|
name: "logbook-mcp",
|