@vheins/local-memory-mcp 0.18.1 → 0.18.3

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.
@@ -81,8 +81,8 @@ function loadServerInstructions() {
81
81
  // src/mcp/capabilities.ts
82
82
  var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
83
83
  var pkgVersion = "0.1.0";
84
- if ("0.18.1") {
85
- pkgVersion = "0.18.1";
84
+ if ("0.18.3") {
85
+ pkgVersion = "0.18.3";
86
86
  } else {
87
87
  let searchDir = __dirname2;
88
88
  for (let i = 0; i < 5; i++) {
@@ -277,14 +277,14 @@ var MigrationManager = class {
277
277
  this.db = db;
278
278
  }
279
279
  db;
280
- run(sql) {
281
- this.db.prepare(sql).run();
280
+ run(sql, ...params) {
281
+ this.db.prepare(sql).run(...params);
282
282
  }
283
283
  exec(sql) {
284
284
  this.db.exec(sql);
285
285
  }
286
- all(sql) {
287
- return this.db.prepare(sql).all();
286
+ all(sql, ...params) {
287
+ return this.db.prepare(sql).all(...params);
288
288
  }
289
289
  get(sql) {
290
290
  return this.db.prepare(sql).get();
@@ -666,6 +666,37 @@ var MigrationManager = class {
666
666
  CREATE INDEX IF NOT EXISTS idx_memories_is_global ON memories(is_global);
667
667
  CREATE INDEX IF NOT EXISTS idx_coding_standards_hit_count ON coding_standards(hit_count);
668
668
  `);
669
+ try {
670
+ this.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_code_owner_repo ON tasks(owner, repo, task_code);`);
671
+ } catch {
672
+ const dupRows = this.all(`
673
+ SELECT task_code, owner, repo, COUNT(*) as cnt
674
+ FROM tasks
675
+ GROUP BY owner, repo, task_code
676
+ HAVING cnt > 1
677
+ `);
678
+ if (dupRows.length > 0) {
679
+ console.log(`Found ${dupRows.length} duplicate task_code(s). Deduplicating...`);
680
+ for (const dup of dupRows) {
681
+ const rowsToDelete = this.all(
682
+ `SELECT id FROM tasks
683
+ WHERE owner = ? AND repo = ? AND task_code = ?
684
+ ORDER BY updated_at DESC
685
+ OFFSET 1`,
686
+ dup.owner,
687
+ dup.repo,
688
+ dup.task_code
689
+ );
690
+ for (const row of rowsToDelete) {
691
+ this.run("DELETE FROM task_comments WHERE task_id = ?", row.id);
692
+ this.run("DELETE FROM tasks WHERE id = ?", row.id);
693
+ }
694
+ console.log(` Deduplicated ${dup.task_code}: kept 1, removed ${rowsToDelete.length}`);
695
+ }
696
+ }
697
+ this.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_code_owner_repo ON tasks(owner, repo, task_code);`);
698
+ console.log("UNIQUE INDEX on (owner, repo, task_code) created after deduplication.");
699
+ }
669
700
  try {
670
701
  this.run("UPDATE tasks SET task_code = substr(id, 1, 8) WHERE task_code IS NULL");
671
702
  } catch {
@@ -1696,6 +1727,9 @@ var MemoryArchiveEntity = class extends BaseEntity {
1696
1727
  };
1697
1728
 
1698
1729
  // src/mcp/entities/task.ts
1730
+ function isSqliteError(err) {
1731
+ return err instanceof Error && typeof err.code === "string";
1732
+ }
1699
1733
  var TaskEntity = class extends BaseEntity {
1700
1734
  coordinationSelect(alias = "t") {
1701
1735
  return `
@@ -1711,40 +1745,52 @@ var TaskEntity = class extends BaseEntity {
1711
1745
  `;
1712
1746
  }
1713
1747
  insertTask(task) {
1714
- this.run(
1715
- `INSERT INTO tasks (
1716
- id, repo, owner, task_code, phase, title, description, status, priority,
1717
- agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, suggested_skills, metadata, parent_id, depends_on, est_tokens, in_progress_at,
1718
- commit_id, changed_files
1719
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1720
- [
1721
- task.id,
1722
- task.repo,
1723
- task.owner || "",
1724
- task.task_code,
1725
- task.phase || null,
1726
- task.title,
1727
- task.description || null,
1728
- task.status || "backlog",
1729
- task.priority || 3,
1730
- task.agent || "unknown",
1731
- task.role || "unknown",
1732
- task.doc_path || null,
1733
- task.created_at,
1734
- task.updated_at,
1735
- task.finished_at || null,
1736
- task.canceled_at || null,
1737
- task.tags ? JSON.stringify(task.tags) : null,
1738
- task.suggested_skills ? JSON.stringify(task.suggested_skills) : null,
1739
- task.metadata ? JSON.stringify(task.metadata) : null,
1740
- task.parent_id || null,
1741
- task.depends_on || null,
1742
- task.est_tokens || 0,
1743
- task.in_progress_at || null,
1744
- task.commit_id || null,
1745
- task.changed_files ? JSON.stringify(task.changed_files) : null
1746
- ]
1747
- );
1748
+ try {
1749
+ this.run(
1750
+ `INSERT INTO tasks (
1751
+ id, repo, owner, task_code, phase, title, description, status, priority,
1752
+ agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, suggested_skills, metadata, parent_id, depends_on, est_tokens, in_progress_at,
1753
+ commit_id, changed_files
1754
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1755
+ [
1756
+ task.id,
1757
+ task.repo,
1758
+ task.owner || "",
1759
+ task.task_code,
1760
+ task.phase || null,
1761
+ task.title,
1762
+ task.description || null,
1763
+ task.status || "backlog",
1764
+ task.priority || 3,
1765
+ task.agent || "unknown",
1766
+ task.role || "unknown",
1767
+ task.doc_path || null,
1768
+ task.created_at,
1769
+ task.updated_at,
1770
+ task.finished_at || null,
1771
+ task.canceled_at || null,
1772
+ task.tags ? JSON.stringify(task.tags) : null,
1773
+ task.suggested_skills ? JSON.stringify(task.suggested_skills) : null,
1774
+ task.metadata ? JSON.stringify(task.metadata) : null,
1775
+ task.parent_id || null,
1776
+ task.depends_on || null,
1777
+ task.est_tokens || 0,
1778
+ task.in_progress_at || null,
1779
+ task.commit_id || null,
1780
+ task.changed_files ? JSON.stringify(task.changed_files) : null
1781
+ ]
1782
+ );
1783
+ } catch (err) {
1784
+ this.handleDuplicateTaskCode(err, task.task_code, task.repo);
1785
+ }
1786
+ }
1787
+ handleDuplicateTaskCode(err, taskCode, repo) {
1788
+ if (isSqliteError(err) && err.code === "SQLITE_CONSTRAINT_UNIQUE") {
1789
+ throw new Error(
1790
+ `Duplicate task_code: '${taskCode}' already exists in repository '${repo}'. The task_code must be unique within the repository.`
1791
+ );
1792
+ }
1793
+ throw err;
1748
1794
  }
1749
1795
  updateTask(id, updates) {
1750
1796
  const fields = [];
@@ -1843,14 +1889,19 @@ var TaskEntity = class extends BaseEntity {
1843
1889
  });
1844
1890
  }
1845
1891
  getTaskByCode(owner, repo, taskCode) {
1892
+ const params = [repo, taskCode];
1893
+ const ownerClause = owner ? "t.owner = ? AND " : "";
1894
+ if (owner) {
1895
+ params.unshift(owner);
1896
+ }
1846
1897
  const row = this.get(
1847
1898
  `SELECT t.*, d.task_code as depends_on_code, p.task_code as parent_code,
1848
1899
  ${this.coordinationSelect("t")}
1849
1900
  FROM tasks t
1850
1901
  LEFT JOIN tasks d ON t.depends_on = d.id
1851
1902
  LEFT JOIN tasks p ON t.parent_id = p.id
1852
- WHERE t.owner = ? AND t.repo = ? AND t.task_code = ?`,
1853
- [owner, repo, taskCode]
1903
+ WHERE ${ownerClause}t.repo = ? AND t.task_code = ?`,
1904
+ params
1854
1905
  );
1855
1906
  return row ? {
1856
1907
  ...this.rowToTask(row),
@@ -1996,8 +2047,12 @@ var TaskEntity = class extends BaseEntity {
1996
2047
  return row?.count ?? 0;
1997
2048
  }
1998
2049
  isTaskCodeDuplicate(owner, repo, task_code, excludeId) {
1999
- let query = "SELECT COUNT(*) as count FROM tasks WHERE owner = ? AND repo = ? AND task_code = ?";
2000
- const params = [owner, repo, task_code];
2050
+ let query = "SELECT COUNT(*) as count FROM tasks WHERE repo = ? AND task_code = ?";
2051
+ const params = [repo, task_code];
2052
+ if (owner) {
2053
+ query = "SELECT COUNT(*) as count FROM tasks WHERE owner = ? AND repo = ? AND task_code = ?";
2054
+ params.unshift(owner);
2055
+ }
2001
2056
  if (excludeId) {
2002
2057
  query += " AND id != ?";
2003
2058
  params.push(excludeId);
@@ -2020,9 +2075,14 @@ var TaskEntity = class extends BaseEntity {
2020
2075
  getExistingTaskCodes(owner, repo, codes) {
2021
2076
  if (codes.length === 0) return /* @__PURE__ */ new Set();
2022
2077
  const placeholders = codes.map(() => "?").join(",");
2078
+ const params = [repo, ...codes];
2079
+ const ownerClause = owner ? "owner = ? AND " : "";
2080
+ if (owner) {
2081
+ params.unshift(owner);
2082
+ }
2023
2083
  const rows = this.all(
2024
- `SELECT task_code FROM tasks WHERE owner = ? AND repo = ? AND task_code IN (${placeholders})`,
2025
- [owner, repo, ...codes]
2084
+ `SELECT task_code FROM tasks WHERE ${ownerClause}repo = ? AND task_code IN (${placeholders})`,
2085
+ params
2026
2086
  );
2027
2087
  return new Set(rows.map((r) => r.task_code));
2028
2088
  }
@@ -4899,7 +4959,7 @@ var TOOL_DEFINITIONS = [
4899
4959
  {
4900
4960
  name: "task-list",
4901
4961
  title: "Task List",
4902
- description: "PRIMARY navigation and search tool for tasks. Returns a compact tabular list of tasks (id, task_code, title, status, priority, updated_at, comments_count). Defaults to in_progress and pending tasks. Use 'query' to filter by code, title, or description. Use 'status' (comma-separated) for specific filters. AGENTS: call this once at start, pick ONE task, then call task-detail.",
4962
+ description: "PRIMARY navigation and search tool for tasks. Returns a compact tabular list of tasks (id, task_code, title, status, priority, updated_at, comments_count). Defaults to in_progress and pending tasks. Use 'query' to filter by code, title, or description. Use 'status' (comma-separated) for specific filters, or 'all' for all statuses. AGENTS: call this once at start, pick ONE task, then call task-detail.",
4903
4963
  annotations: {
4904
4964
  readOnlyHint: true,
4905
4965
  idempotentHint: true,
@@ -4916,7 +4976,7 @@ var TOOL_DEFINITIONS = [
4916
4976
  status: {
4917
4977
  type: "string",
4918
4978
  default: "in_progress,pending",
4919
- description: "Comma-separated status filter (backlog, pending, in_progress, completed, canceled, blocked). Defaults to 'in_progress,pending'."
4979
+ description: "Comma-separated status filter (backlog, pending, in_progress, completed, canceled, blocked) or 'all' for all statuses. Defaults to 'backlog,pending,in_progress,blocked'."
4920
4980
  },
4921
4981
  phase: {
4922
4982
  type: "string",
@@ -6462,6 +6522,7 @@ export {
6462
6522
  MemoryDeleteSchema,
6463
6523
  MemorySummarizeSchema,
6464
6524
  MemorySynthesizeSchema,
6525
+ TaskStatusSchema,
6465
6526
  TaskCreateSchema,
6466
6527
  TaskCreateInteractiveSchema,
6467
6528
  TaskUpdateSchema,
@@ -16,7 +16,7 @@ import {
16
16
  handleTaskClaim,
17
17
  listResources,
18
18
  logger
19
- } from "../chunk-LEXNGJHN.js";
19
+ } from "../chunk-IHDJKNX4.js";
20
20
 
21
21
  // src/dashboard/server.ts
22
22
  import express from "express";
@@ -26,6 +26,7 @@ import {
26
26
  TaskGetSchema,
27
27
  TaskListSchema,
28
28
  TaskSearchSchema,
29
+ TaskStatusSchema,
29
30
  TaskUpdateSchema,
30
31
  addLogSink,
31
32
  buildStandardVectorText,
@@ -62,7 +63,7 @@ import {
62
63
  toContextSlug,
63
64
  updateSessionFromInitialize,
64
65
  updateSessionRoots
65
- } from "../chunk-LEXNGJHN.js";
66
+ } from "../chunk-IHDJKNX4.js";
66
67
 
67
68
  // src/mcp/server.ts
68
69
  import readline from "readline";
@@ -2502,7 +2503,14 @@ async function handleTaskSearch(args, storage) {
2502
2503
  tasks = storage.tasks.getTasksByRepo(owner, repo, status, void 0, void 0, query);
2503
2504
  }
2504
2505
  } else {
2505
- tasks = storage.tasks.getTasksByRepo(owner, repo, void 0, void 0, void 0, query);
2506
+ tasks = storage.tasks.getTasksByMultipleStatuses(
2507
+ owner,
2508
+ repo,
2509
+ [...TaskStatusSchema.options],
2510
+ void 0,
2511
+ void 0,
2512
+ query
2513
+ );
2506
2514
  }
2507
2515
  if (phase) {
2508
2516
  const phaseLower = phase.toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vheins/local-memory-mcp",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
4
4
  "description": "MCP Local Memory Service for coding copilot agents",
5
5
  "mcpName": "io.github.vheins/local-memory-mcp",
6
6
  "type": "module",