@vheins/local-memory-mcp 0.8.0 → 0.8.2

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.
@@ -173,8 +173,8 @@ var LOG_LEVEL_VALUES = Object.keys(LEVELS);
173
173
 
174
174
  // src/mcp/storage/sqlite.ts
175
175
  import Database from "better-sqlite3";
176
- import path2 from "path";
177
- import fs2 from "fs";
176
+ import path3 from "path";
177
+ import fs3 from "fs";
178
178
  import os from "os";
179
179
 
180
180
  // src/mcp/storage/migrations.ts
@@ -783,6 +783,9 @@ var BaseEntity = class {
783
783
  this.db = db;
784
784
  }
785
785
  db;
786
+ transaction(fn) {
787
+ return this.db.transaction(fn)();
788
+ }
786
789
  run(sql, params = []) {
787
790
  const stmt = this.db.prepare(sql);
788
791
  const result = stmt.run(...params);
@@ -1031,40 +1034,42 @@ var MemoryEntity = class extends BaseEntity {
1031
1034
  return rows.map((row) => this.rowToMemoryEntry(row));
1032
1035
  }
1033
1036
  bulkInsertMemories(entries) {
1034
- let count = 0;
1035
- for (const entry of entries) {
1036
- this.run(
1037
- `INSERT INTO memories (
1038
- id, repo, type, title, content, importance, folder, language,
1039
- created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
1040
- supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
1041
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1042
- [
1043
- entry.id,
1044
- entry.scope.repo,
1045
- entry.type,
1046
- entry.title || null,
1047
- entry.content,
1048
- entry.importance,
1049
- entry.scope.folder || null,
1050
- entry.scope.language || null,
1051
- entry.created_at,
1052
- entry.updated_at,
1053
- entry.expires_at ?? null,
1054
- entry.supersedes ?? null,
1055
- entry.status || "active",
1056
- entry.is_global ? 1 : 0,
1057
- entry.tags ? JSON.stringify(entry.tags) : null,
1058
- entry.metadata ? JSON.stringify(entry.metadata) : null,
1059
- entry.agent || "unknown",
1060
- entry.role || "unknown",
1061
- entry.model || "unknown",
1062
- entry.completed_at || null
1063
- ]
1064
- );
1065
- count++;
1066
- }
1067
- return count;
1037
+ return this.transaction(() => {
1038
+ let count = 0;
1039
+ for (const entry of entries) {
1040
+ this.run(
1041
+ `INSERT INTO memories (
1042
+ id, repo, type, title, content, importance, folder, language,
1043
+ created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
1044
+ supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
1045
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1046
+ [
1047
+ entry.id,
1048
+ entry.scope.repo,
1049
+ entry.type,
1050
+ entry.title || null,
1051
+ entry.content,
1052
+ entry.importance,
1053
+ entry.scope.folder || null,
1054
+ entry.scope.language || null,
1055
+ entry.created_at,
1056
+ entry.updated_at,
1057
+ entry.expires_at ?? null,
1058
+ entry.supersedes ?? null,
1059
+ entry.status || "active",
1060
+ entry.is_global ? 1 : 0,
1061
+ entry.tags ? JSON.stringify(entry.tags) : null,
1062
+ entry.metadata ? JSON.stringify(entry.metadata) : null,
1063
+ entry.agent || "unknown",
1064
+ entry.role || "unknown",
1065
+ entry.model || "unknown",
1066
+ entry.completed_at || null
1067
+ ]
1068
+ );
1069
+ count++;
1070
+ }
1071
+ return count;
1072
+ });
1068
1073
  }
1069
1074
  bulkUpdateMemories(ids, updates) {
1070
1075
  if (ids.length === 0) return 0;
@@ -1088,28 +1093,32 @@ var MemoryEntity = class extends BaseEntity {
1088
1093
  if (fields.length === 0) return 0;
1089
1094
  fields.push("updated_at = ?");
1090
1095
  values.push((/* @__PURE__ */ new Date()).toISOString());
1091
- let count = 0;
1092
- const chunkSize = 500;
1093
- for (let i = 0; i < ids.length; i += chunkSize) {
1094
- const chunk = ids.slice(i, i + chunkSize);
1095
- const result = this.run(
1096
- `UPDATE memories SET ${fields.join(", ")} WHERE id IN (${chunk.map(() => "?").join(",")})`,
1097
- [...values, ...chunk]
1098
- );
1099
- count += result.changes;
1100
- }
1101
- return count;
1096
+ return this.transaction(() => {
1097
+ let count = 0;
1098
+ const chunkSize = 500;
1099
+ for (let i = 0; i < ids.length; i += chunkSize) {
1100
+ const chunk = ids.slice(i, i + chunkSize);
1101
+ const result = this.run(
1102
+ `UPDATE memories SET ${fields.join(", ")} WHERE id IN (${chunk.map(() => "?").join(",")})`,
1103
+ [...values, ...chunk]
1104
+ );
1105
+ count += result.changes;
1106
+ }
1107
+ return count;
1108
+ });
1102
1109
  }
1103
1110
  bulkDeleteMemories(ids) {
1104
1111
  if (ids.length === 0) return 0;
1105
- let count = 0;
1106
- const chunkSize = 500;
1107
- for (let i = 0; i < ids.length; i += chunkSize) {
1108
- const chunk = ids.slice(i, i + chunkSize);
1109
- const result = this.run(`DELETE FROM memories WHERE id IN (${chunk.map(() => "?").join(",")})`, chunk);
1110
- count += result.changes;
1111
- }
1112
- return count;
1112
+ return this.transaction(() => {
1113
+ let count = 0;
1114
+ const chunkSize = 500;
1115
+ for (let i = 0; i < ids.length; i += chunkSize) {
1116
+ const chunk = ids.slice(i, i + chunkSize);
1117
+ const result = this.run(`DELETE FROM memories WHERE id IN (${chunk.map(() => "?").join(",")})`, chunk);
1118
+ count += result.changes;
1119
+ }
1120
+ return count;
1121
+ });
1113
1122
  }
1114
1123
  getRecentMemories(repo, limit, offset = 0, includeArchived = false, excludeTypes = [], sortOrder = "DESC") {
1115
1124
  let query = "SELECT * FROM memories WHERE repo = ?";
@@ -1521,40 +1530,42 @@ var TaskEntity = class extends BaseEntity {
1521
1530
  return (row?.count ?? 0) > 0;
1522
1531
  }
1523
1532
  bulkInsertTasks(tasks) {
1524
- let count = 0;
1525
- for (const task of tasks) {
1526
- this.run(
1527
- `INSERT INTO tasks (
1528
- id, repo, task_code, phase, title, description, status, priority,
1529
- agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
1530
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1531
- [
1532
- task.id,
1533
- task.repo,
1534
- task.task_code,
1535
- task.phase || null,
1536
- task.title,
1537
- task.description || null,
1538
- task.status || "backlog",
1539
- task.priority || 3,
1540
- task.agent || "unknown",
1541
- task.role || "unknown",
1542
- task.doc_path || null,
1543
- task.created_at,
1544
- task.updated_at,
1545
- task.finished_at || null,
1546
- task.canceled_at || null,
1547
- task.tags ? JSON.stringify(task.tags) : null,
1548
- task.metadata ? JSON.stringify(task.metadata) : null,
1549
- task.parent_id || null,
1550
- task.depends_on || null,
1551
- task.est_tokens || 0,
1552
- task.in_progress_at || null
1553
- ]
1554
- );
1555
- count++;
1556
- }
1557
- return count;
1533
+ return this.transaction(() => {
1534
+ let count = 0;
1535
+ for (const task of tasks) {
1536
+ this.run(
1537
+ `INSERT INTO tasks (
1538
+ id, repo, task_code, phase, title, description, status, priority,
1539
+ agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
1540
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1541
+ [
1542
+ task.id,
1543
+ task.repo,
1544
+ task.task_code,
1545
+ task.phase || null,
1546
+ task.title,
1547
+ task.description || null,
1548
+ task.status || "backlog",
1549
+ task.priority || 3,
1550
+ task.agent || "unknown",
1551
+ task.role || "unknown",
1552
+ task.doc_path || null,
1553
+ task.created_at,
1554
+ task.updated_at,
1555
+ task.finished_at || null,
1556
+ task.canceled_at || null,
1557
+ task.tags ? JSON.stringify(task.tags) : null,
1558
+ task.metadata ? JSON.stringify(task.metadata) : null,
1559
+ task.parent_id || null,
1560
+ task.depends_on || null,
1561
+ task.est_tokens || 0,
1562
+ task.in_progress_at || null
1563
+ ]
1564
+ );
1565
+ count++;
1566
+ }
1567
+ return count;
1568
+ });
1558
1569
  }
1559
1570
  insertTaskComment(comment) {
1560
1571
  this.run(
@@ -1882,16 +1893,79 @@ var SummaryEntity = class extends BaseEntity {
1882
1893
  }
1883
1894
  };
1884
1895
 
1896
+ // src/mcp/storage/write-lock.ts
1897
+ import lockfile from "proper-lockfile";
1898
+ import path2 from "path";
1899
+ import fs2 from "fs";
1900
+ var LOCK_STALE_MS = 15e3;
1901
+ var LOCK_RETRY_DELAY_MS = 100;
1902
+ var LOCK_RETRY_COUNT = 150;
1903
+ var WriteLock = class {
1904
+ lockTarget;
1905
+ locked = false;
1906
+ constructor(dbPath) {
1907
+ this.lockTarget = dbPath;
1908
+ if (!fs2.existsSync(dbPath)) {
1909
+ fs2.mkdirSync(path2.dirname(dbPath), { recursive: true });
1910
+ fs2.writeFileSync(dbPath, "");
1911
+ }
1912
+ }
1913
+ /**
1914
+ * Acquire the write lock. Waits up to 15s for other processes to release.
1915
+ */
1916
+ async acquire() {
1917
+ await lockfile.lock(this.lockTarget, {
1918
+ stale: LOCK_STALE_MS,
1919
+ retries: {
1920
+ retries: LOCK_RETRY_COUNT,
1921
+ minTimeout: LOCK_RETRY_DELAY_MS,
1922
+ maxTimeout: LOCK_RETRY_DELAY_MS
1923
+ },
1924
+ realpath: false
1925
+ });
1926
+ this.locked = true;
1927
+ }
1928
+ /**
1929
+ * Release the write lock.
1930
+ */
1931
+ async release() {
1932
+ if (!this.locked) return;
1933
+ try {
1934
+ await lockfile.unlock(this.lockTarget, { realpath: false });
1935
+ } catch {
1936
+ }
1937
+ this.locked = false;
1938
+ }
1939
+ /**
1940
+ * Run a synchronous write function under the lock.
1941
+ * Guarantees lock is always released, even on error.
1942
+ */
1943
+ async withLock(fn) {
1944
+ await this.acquire();
1945
+ try {
1946
+ return fn();
1947
+ } finally {
1948
+ await this.release();
1949
+ }
1950
+ }
1951
+ /**
1952
+ * Check if a lock file exists (another process may be writing).
1953
+ */
1954
+ isLocked() {
1955
+ return lockfile.checkSync(this.lockTarget, { realpath: false });
1956
+ }
1957
+ };
1958
+
1885
1959
  // src/mcp/storage/sqlite.ts
1886
1960
  function resolveDbPath() {
1887
1961
  if (process.env.MEMORY_DB_PATH) return process.env.MEMORY_DB_PATH;
1888
- const standardConfigDir = process.platform === "win32" ? path2.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path2.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path2.join(os.homedir(), ".config", "local-memory-mcp");
1889
- const standardPath = path2.join(standardConfigDir, "memory.db");
1890
- if (fs2.existsSync(standardPath)) return standardPath;
1891
- const legacyPath = path2.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
1892
- if (fs2.existsSync(legacyPath)) return legacyPath;
1893
- const localCwdFile = path2.join(process.cwd(), "storage", "memory.db");
1894
- if (fs2.existsSync(localCwdFile)) return localCwdFile;
1962
+ const standardConfigDir = process.platform === "win32" ? path3.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path3.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path3.join(os.homedir(), ".config", "local-memory-mcp");
1963
+ const standardPath = path3.join(standardConfigDir, "memory.db");
1964
+ if (fs3.existsSync(standardPath)) return standardPath;
1965
+ const legacyPath = path3.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
1966
+ if (fs3.existsSync(legacyPath)) return legacyPath;
1967
+ const localCwdFile = path3.join(process.cwd(), "storage", "memory.db");
1968
+ if (fs3.existsSync(localCwdFile)) return localCwdFile;
1895
1969
  return standardPath;
1896
1970
  }
1897
1971
  var DB_PATH = resolveDbPath();
@@ -1902,21 +1976,26 @@ var SQLiteStore = class _SQLiteStore {
1902
1976
  actions;
1903
1977
  system;
1904
1978
  summaries;
1979
+ lock;
1905
1980
  dbPathInstance;
1906
1981
  constructor(dbPath) {
1907
1982
  const finalPath = dbPath ?? DB_PATH;
1908
1983
  this.dbPathInstance = finalPath;
1909
1984
  if (finalPath !== ":memory:") {
1910
- const dbDir = path2.dirname(finalPath);
1911
- if (!fs2.existsSync(dbDir)) {
1912
- fs2.mkdirSync(dbDir, { recursive: true });
1985
+ const dbDir = path3.dirname(finalPath);
1986
+ if (!fs3.existsSync(dbDir)) {
1987
+ fs3.mkdirSync(dbDir, { recursive: true });
1913
1988
  }
1914
1989
  }
1915
1990
  this.db = new Database(finalPath);
1916
1991
  this.db.pragma("journal_mode = WAL");
1917
- this.db.pragma("synchronous = NORMAL");
1918
- this.db.pragma("busy_timeout = 5000");
1992
+ this.db.pragma("synchronous = FULL");
1993
+ this.db.pragma("busy_timeout = 30000");
1919
1994
  this.db.pragma("foreign_keys = ON");
1995
+ this.db.pragma("wal_autocheckpoint = 100");
1996
+ if (finalPath !== ":memory:") {
1997
+ this._startupChecks(finalPath);
1998
+ }
1920
1999
  const migrator = new MigrationManager(this.db);
1921
2000
  migrator.migrate();
1922
2001
  migrator.addMemoryCodeColumn();
@@ -1925,10 +2004,88 @@ var SQLiteStore = class _SQLiteStore {
1925
2004
  this.actions = new ActionEntity(this.db);
1926
2005
  this.system = new SystemEntity(this.db);
1927
2006
  this.summaries = new SummaryEntity(this.db);
2007
+ this.lock = new WriteLock(finalPath);
1928
2008
  }
1929
- async ready() {
2009
+ /**
2010
+ * Run on startup: checkpoint WAL and verify integrity.
2011
+ * If integrity check fails, attempt to restore from backup.
2012
+ */
2013
+ _startupChecks(dbPath) {
2014
+ try {
2015
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
2016
+ logger.debug("[SQLiteStore] WAL checkpoint completed on startup");
2017
+ } catch (err) {
2018
+ logger.warn("[SQLiteStore] WAL checkpoint failed on startup", { error: String(err) });
2019
+ }
2020
+ try {
2021
+ const result = this.db.pragma("integrity_check");
2022
+ const ok = result.length === 1 && result[0].integrity_check === "ok";
2023
+ if (!ok) {
2024
+ logger.error("[SQLiteStore] Integrity check FAILED", { result });
2025
+ this._attemptRecovery(dbPath);
2026
+ } else {
2027
+ logger.debug("[SQLiteStore] Integrity check passed");
2028
+ }
2029
+ } catch (err) {
2030
+ logger.error("[SQLiteStore] Integrity check threw error", { error: String(err) });
2031
+ this._attemptRecovery(dbPath);
2032
+ }
2033
+ }
2034
+ /**
2035
+ * Attempt to recover from a corrupt DB by restoring the latest backup.
2036
+ */
2037
+ _attemptRecovery(dbPath) {
2038
+ const backupPath = dbPath + ".backup";
2039
+ if (fs3.existsSync(backupPath)) {
2040
+ logger.warn("[SQLiteStore] Attempting recovery from backup", { backupPath });
2041
+ try {
2042
+ const corruptPath = `${dbPath}.corrupt_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15)}`;
2043
+ fs3.copyFileSync(dbPath, corruptPath);
2044
+ fs3.copyFileSync(backupPath, dbPath);
2045
+ logger.warn("[SQLiteStore] Recovery successful. Corrupt file saved to", { corruptPath });
2046
+ } catch (err) {
2047
+ logger.error("[SQLiteStore] Recovery failed", { error: String(err) });
2048
+ }
2049
+ } else {
2050
+ logger.error("[SQLiteStore] No backup found for recovery. DB may be corrupt.");
2051
+ }
1930
2052
  }
2053
+ /**
2054
+ * Create a timestamped backup of the DB file.
2055
+ * Called automatically after successful writes (via withWrite).
2056
+ */
2057
+ backup() {
2058
+ if (this.dbPathInstance === ":memory:") return;
2059
+ try {
2060
+ this.db.pragma("wal_checkpoint(PASSIVE)");
2061
+ const backupPath = this.dbPathInstance + ".backup";
2062
+ fs3.copyFileSync(this.dbPathInstance, backupPath);
2063
+ } catch (err) {
2064
+ logger.warn("[SQLiteStore] Backup failed", { error: String(err) });
2065
+ }
2066
+ }
2067
+ /**
2068
+ * Execute a write operation under the file lock.
2069
+ * This is the ONLY way writes should be performed.
2070
+ *
2071
+ * @example
2072
+ * await db.withWrite(() => db.tasks.insertTask(task));
2073
+ */
2074
+ async withWrite(fn) {
2075
+ return this.lock.withLock(fn);
2076
+ }
2077
+ /**
2078
+ * Checkpoint WAL so dashboard (and other readers) see latest data.
2079
+ * Called by dashboard controllers before reads.
2080
+ */
1931
2081
  async refresh() {
2082
+ try {
2083
+ this.db.pragma("wal_checkpoint(PASSIVE)");
2084
+ } catch (err) {
2085
+ logger.warn("[SQLiteStore] refresh checkpoint failed", { error: String(err) });
2086
+ }
2087
+ }
2088
+ async ready() {
1932
2089
  }
1933
2090
  static async create(dbPath) {
1934
2091
  return new _SQLiteStore(dbPath);
@@ -1938,13 +2095,17 @@ var SQLiteStore = class _SQLiteStore {
1938
2095
  }
1939
2096
  close() {
1940
2097
  if (this.db && this.db.open) {
2098
+ try {
2099
+ this.db.pragma("wal_checkpoint(TRUNCATE)");
2100
+ } catch {
2101
+ }
1941
2102
  this.db.close();
1942
2103
  }
1943
2104
  }
1944
2105
  };
1945
2106
 
1946
2107
  // src/mcp/session.ts
1947
- import path3 from "path";
2108
+ import path4 from "path";
1948
2109
  import { fileURLToPath as fileURLToPath2 } from "url";
1949
2110
  function createSessionContext() {
1950
2111
  return {
@@ -2010,7 +2171,7 @@ function getFilesystemRoots(session) {
2010
2171
  for (const root of session.roots) {
2011
2172
  if (!root.uri.startsWith("file://")) continue;
2012
2173
  try {
2013
- resolved.push(path3.resolve(fileURLToPath2(root.uri)));
2174
+ resolved.push(path4.resolve(fileURLToPath2(root.uri)));
2014
2175
  } catch {
2015
2176
  }
2016
2177
  }
@@ -2019,19 +2180,19 @@ function getFilesystemRoots(session) {
2019
2180
  function isPathWithinRoots(targetPath, session) {
2020
2181
  const roots = getFilesystemRoots(session);
2021
2182
  if (roots.length === 0) return true;
2022
- const normalizedTarget = path3.resolve(targetPath);
2183
+ const normalizedTarget = path4.resolve(targetPath);
2023
2184
  return roots.some((rootPath) => {
2024
- const relative = path3.relative(rootPath, normalizedTarget);
2025
- return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
2185
+ const relative = path4.relative(rootPath, normalizedTarget);
2186
+ return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
2026
2187
  });
2027
2188
  }
2028
2189
  function findContainingRoot(targetPath, session) {
2029
2190
  const roots = getFilesystemRoots(session);
2030
2191
  if (roots.length === 0) return null;
2031
- const normalizedTarget = path3.resolve(targetPath);
2192
+ const normalizedTarget = path4.resolve(targetPath);
2032
2193
  for (const rootPath of roots) {
2033
- const relative = path3.relative(rootPath, normalizedTarget);
2034
- if (relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative)) {
2194
+ const relative = path4.relative(rootPath, normalizedTarget);
2195
+ if (relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative)) {
2035
2196
  return rootPath;
2036
2197
  }
2037
2198
  }
@@ -2040,7 +2201,7 @@ function findContainingRoot(targetPath, session) {
2040
2201
  function inferRepoFromSession(session) {
2041
2202
  const roots = getFilesystemRoots(session);
2042
2203
  if (roots.length === 1) {
2043
- return path3.basename(roots[0]);
2204
+ return path4.basename(roots[0]);
2044
2205
  }
2045
2206
  return void 0;
2046
2207
  }
@@ -3468,9 +3629,9 @@ function parseRepoUri(uri) {
3468
3629
  const slashIdx = withoutQuery.indexOf("/");
3469
3630
  if (slashIdx === -1) return null;
3470
3631
  const name = withoutQuery.slice(0, slashIdx);
3471
- const path5 = withoutQuery.slice(slashIdx + 1);
3472
- if (!name || !path5) return null;
3473
- return { name, path: path5, query: new URLSearchParams(queryString) };
3632
+ const path6 = withoutQuery.slice(slashIdx + 1);
3633
+ if (!name || !path6) return null;
3634
+ return { name, path: path6, query: new URLSearchParams(queryString) };
3474
3635
  }
3475
3636
  function paginateEntries(key, entries, params) {
3476
3637
  const limit = normalizeLimit(params?.limit);
@@ -3505,12 +3666,12 @@ function invalidCompletionParams(message) {
3505
3666
  }
3506
3667
 
3507
3668
  // src/mcp/prompts/loader.ts
3508
- import fs3 from "fs";
3509
- import path4 from "path";
3669
+ import fs4 from "fs";
3670
+ import path5 from "path";
3510
3671
  import { fileURLToPath as fileURLToPath3 } from "url";
3511
3672
  import matter from "gray-matter";
3512
3673
  var __filename = fileURLToPath3(import.meta.url);
3513
- var __dirname2 = path4.dirname(__filename);
3674
+ var __dirname2 = path5.dirname(__filename);
3514
3675
  function findPromptDir() {
3515
3676
  const candidates = [
3516
3677
  // Production if chunked into dist/
@@ -3519,28 +3680,28 @@ function findPromptDir() {
3519
3680
  "../prompts",
3520
3681
  // Dev: /src/mcp/prompts/definitions (next to loader.ts)
3521
3682
  "./definitions"
3522
- ].map((relPath) => path4.resolve(__dirname2, relPath));
3683
+ ].map((relPath) => path5.resolve(__dirname2, relPath));
3523
3684
  for (const dir of candidates) {
3524
- if (fs3.existsSync(dir)) {
3525
- const files = fs3.readdirSync(dir);
3685
+ if (fs4.existsSync(dir)) {
3686
+ const files = fs4.readdirSync(dir);
3526
3687
  if (files.some((f) => f.endsWith(".md"))) {
3527
3688
  return dir;
3528
3689
  }
3529
3690
  }
3530
3691
  }
3531
- return path4.resolve(__dirname2, "./definitions");
3692
+ return path5.resolve(__dirname2, "./definitions");
3532
3693
  }
3533
3694
  var PROMPT_DIR = findPromptDir();
3534
3695
  function listPromptFiles() {
3535
- if (!fs3.existsSync(PROMPT_DIR)) return [];
3536
- return fs3.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
3696
+ if (!fs4.existsSync(PROMPT_DIR)) return [];
3697
+ return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
3537
3698
  }
3538
3699
  function loadPromptFromMarkdown(name) {
3539
- const filePath = path4.join(PROMPT_DIR, `${name}.md`);
3540
- if (!fs3.existsSync(filePath)) {
3700
+ const filePath = path5.join(PROMPT_DIR, `${name}.md`);
3701
+ if (!fs4.existsSync(filePath)) {
3541
3702
  throw new Error(`Prompt file not found: ${filePath}`);
3542
3703
  }
3543
- const fileContent = fs3.readFileSync(filePath, "utf-8");
3704
+ const fileContent = fs4.readFileSync(filePath, "utf-8");
3544
3705
  const { data, content } = matter(fileContent);
3545
3706
  return {
3546
3707
  name: data.name || name,