@cocaxcode/logbook-mcp 0.1.2 → 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.1" : "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-5CKGZQZD.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-JFMNT5GT.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 '""';
@@ -599,7 +659,7 @@ function parseGitGrepOutput(output) {
599
659
  function registerTodoListTool(server) {
600
660
  server.tool(
601
661
  "logbook_todo_list",
602
- "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.",
603
663
  {
604
664
  status: z4.enum(["pending", "done", "all"]).optional().default("pending").describe("Filtrar por estado (default: pending)"),
605
665
  topic: z4.string().optional().describe("Filtrar por topic"),
@@ -629,10 +689,12 @@ function registerTodoListTool(server) {
629
689
  limit
630
690
  });
631
691
  let codeTodos = [];
692
+ let syncResult = null;
632
693
  if (source !== "manual" && status !== "done") {
633
694
  const repoPath = scope === "project" ? detectRepoPath() : null;
634
- if (repoPath) {
695
+ if (repoPath && repo) {
635
696
  codeTodos = scanCodeTodos(repoPath);
697
+ syncResult = syncCodeTodos(db2, repo.id, codeTodos);
636
698
  if (topic) {
637
699
  codeTodos = codeTodos.filter((ct) => ct.topic_name === topic);
638
700
  }
@@ -658,7 +720,8 @@ function registerTodoListTool(server) {
658
720
  summary: {
659
721
  manual: manualTodos.length,
660
722
  code: codeTodos.length,
661
- total: manualTodos.length + codeTodos.length
723
+ total: manualTodos.length + codeTodos.length,
724
+ ...syncResult && (syncResult.added > 0 || syncResult.resolved > 0) ? { sync: syncResult } : {}
662
725
  }
663
726
  })
664
727
  }]
@@ -824,6 +887,7 @@ function registerLogTool(server) {
824
887
  };
825
888
  const notes = type === "todos" ? [] : getNotes(db2, filters);
826
889
  const completedTodos = type === "notes" ? [] : getCompletedTodos(db2, filters);
890
+ const resolvedCodeTodos = type === "notes" || !repo ? [] : getResolvedCodeTodos(db2, repo.id, dateFrom, dateTo);
827
891
  const entries = [
828
892
  ...notes.map((n) => ({
829
893
  type: "note",
@@ -834,6 +898,11 @@ function registerLogTool(server) {
834
898
  type: "todo",
835
899
  data: t,
836
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
837
906
  }))
838
907
  ].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
839
908
  return {
@@ -847,6 +916,7 @@ function registerLogTool(server) {
847
916
  summary: {
848
917
  notes: notes.length,
849
918
  todos_completed: completedTodos.length,
919
+ code_todos_resolved: resolvedCodeTodos.length,
850
920
  total: entries.length
851
921
  }
852
922
  })
@@ -957,7 +1027,7 @@ function registerSearchTool(server) {
957
1027
  }
958
1028
 
959
1029
  // src/server.ts
960
- var VERSION = true ? "0.1.1" : "0.0.0";
1030
+ var VERSION = true ? "0.1.2" : "0.0.0";
961
1031
  function createServer() {
962
1032
  const server = new McpServer({
963
1033
  name: "logbook-mcp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocaxcode/logbook-mcp",
3
- "version": "0.1.2",
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": {