@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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- var VERSION = true ? "0.1.0" : "0.0.0";
4
+ var VERSION = true ? "0.1.2" : "0.0.0";
5
5
  async function runCli(argv) {
6
6
  if (argv.includes("--version") || argv.includes("-v")) {
7
7
  console.log(`logbook-mcp v${VERSION}`);
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-F2LQO3GU.js");
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-54ECHTAC.js");
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 TODOs manuales y del codigo (TODO/FIXME/HACK/BUG). Por defecto muestra pendientes del proyecto actual.",
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 dateTo = to ?? new Date(Date.now() + 864e5).toISOString().split("T")[0];
860
- return { dateFrom: from, dateTo };
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.0" : "0.0.0";
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocaxcode/logbook-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server — cuaderno de bitácora del developer. Notas, TODOs y code TODOs sin salir de tu AI.",
5
5
  "type": "module",
6
6
  "bin": {