@vheins/local-memory-mcp 0.10.12 → 0.11.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,5 +1,131 @@
1
- // src/mcp/utils/logger.ts
1
+ // src/mcp/capabilities.ts
2
+ import { fileURLToPath as fileURLToPath2 } from "url";
3
+ import path2 from "path";
4
+
5
+ // src/mcp/prompts/loader.ts
2
6
  import fs from "fs";
7
+ import path from "path";
8
+ import { fileURLToPath } from "url";
9
+ import matter from "gray-matter";
10
+ var __filename = fileURLToPath(import.meta.url);
11
+ var __dirname = path.dirname(__filename);
12
+ function findPromptDir() {
13
+ const candidates = [
14
+ // Production if chunked into dist/
15
+ "./prompts",
16
+ // Production if inlined into dist/mcp/
17
+ "../prompts",
18
+ // Dev: /src/mcp/prompts/definitions (next to loader.ts)
19
+ "./definitions"
20
+ ].map((relPath) => path.resolve(__dirname, relPath));
21
+ for (const dir of candidates) {
22
+ if (fs.existsSync(dir)) {
23
+ const files = fs.readdirSync(dir);
24
+ if (files.some((f) => f.endsWith(".md"))) {
25
+ return dir;
26
+ }
27
+ }
28
+ }
29
+ return path.resolve(__dirname, "./definitions");
30
+ }
31
+ var PROMPT_DIR = findPromptDir();
32
+ function listPromptFiles() {
33
+ if (!fs.existsSync(PROMPT_DIR)) return [];
34
+ return fs.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
35
+ }
36
+ function loadPromptFromMarkdown(name) {
37
+ const filePath = path.join(PROMPT_DIR, `${name}.md`);
38
+ if (!fs.existsSync(filePath)) {
39
+ throw new Error(`Prompt file not found: ${filePath}`);
40
+ }
41
+ const fileContent = fs.readFileSync(filePath, "utf-8");
42
+ const { data, content } = matter(fileContent);
43
+ return {
44
+ name: data.name || name,
45
+ description: data.description || "",
46
+ arguments: data.arguments || [],
47
+ agent: data.agent,
48
+ content: content.trim()
49
+ };
50
+ }
51
+ function findServerInstructionsDir() {
52
+ const candidates = [
53
+ // Production if chunked into dist/
54
+ "./prompts/server",
55
+ // Production if inlined into dist/mcp/
56
+ "../prompts/server",
57
+ // Dev: /src/mcp/prompts/server (next to loader.ts)
58
+ "./server"
59
+ ].map((relPath) => path.resolve(__dirname, relPath));
60
+ for (const dir of candidates) {
61
+ if (fs.existsSync(dir)) {
62
+ const filePath = path.join(dir, "instructions.md");
63
+ if (fs.existsSync(filePath)) {
64
+ return dir;
65
+ }
66
+ }
67
+ }
68
+ return path.resolve(__dirname, "./server");
69
+ }
70
+ var SERVER_DIR = findServerInstructionsDir();
71
+ function loadServerInstructions() {
72
+ const filePath = path.join(SERVER_DIR, "instructions.md");
73
+ if (!fs.existsSync(filePath)) {
74
+ throw new Error(`Server instructions file not found: ${filePath}`);
75
+ }
76
+ const fileContent = fs.readFileSync(filePath, "utf-8");
77
+ const { content } = matter(fileContent);
78
+ return content.trim();
79
+ }
80
+
81
+ // src/mcp/capabilities.ts
82
+ var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
83
+ var pkgVersion = "0.1.0";
84
+ if ("0.11.0") {
85
+ pkgVersion = "0.11.0";
86
+ } else {
87
+ let searchDir = __dirname2;
88
+ for (let i = 0; i < 5; i++) {
89
+ const candidate = path2.join(searchDir, "package.json");
90
+ try {
91
+ if (fs.existsSync(candidate)) {
92
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
93
+ if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
94
+ pkgVersion = pkg.version;
95
+ break;
96
+ }
97
+ }
98
+ } catch {
99
+ }
100
+ searchDir = path2.dirname(searchDir);
101
+ }
102
+ }
103
+ var MCP_PROTOCOL_VERSION = "2025-03-26";
104
+ var SERVER_INSTRUCTIONS = loadServerInstructions();
105
+ var CAPABILITIES = {
106
+ serverInfo: {
107
+ name: "local-memory-mcp",
108
+ version: pkgVersion,
109
+ instructions: SERVER_INSTRUCTIONS
110
+ },
111
+ capabilities: {
112
+ completions: {},
113
+ logging: {},
114
+ resources: {
115
+ subscribe: true,
116
+ listChanged: true
117
+ },
118
+ tools: {
119
+ listChanged: false
120
+ },
121
+ prompts: {
122
+ listChanged: true
123
+ }
124
+ }
125
+ };
126
+
127
+ // src/mcp/utils/logger.ts
128
+ import fs2 from "fs";
3
129
  var LEVELS = {
4
130
  debug: 0,
5
131
  info: 1,
@@ -119,11 +245,11 @@ function addLogSink(sink) {
119
245
  }
120
246
  var LOG_LEVEL_VALUES = Object.keys(LEVELS);
121
247
  function createFileSink(logDir, maxFiles = 5) {
122
- fs.mkdirSync(logDir, { recursive: true });
123
- const existing = fs.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
248
+ fs2.mkdirSync(logDir, { recursive: true });
249
+ const existing = fs2.readdirSync(logDir).filter((f) => f.startsWith("mcp-") && f.endsWith(".log")).sort();
124
250
  while (existing.length >= maxFiles) {
125
251
  try {
126
- fs.unlinkSync(`${logDir}/${existing.shift()}`);
252
+ fs2.unlinkSync(`${logDir}/${existing.shift()}`);
127
253
  } catch {
128
254
  }
129
255
  }
@@ -133,118 +259,16 @@ function createFileSink(logDir, maxFiles = 5) {
133
259
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${payload.level.toUpperCase()}] [pid:${process.pid}] ${JSON.stringify(payload.data)}
134
260
  `;
135
261
  try {
136
- fs.appendFileSync(logFile, line);
262
+ fs2.appendFileSync(logFile, line);
137
263
  } catch {
138
264
  }
139
265
  };
140
266
  }
141
267
 
142
- // src/mcp/session.ts
143
- import path from "path";
144
- import { fileURLToPath } from "url";
145
- function createSessionContext() {
146
- return {
147
- roots: [],
148
- supportsRoots: false,
149
- supportsSampling: false,
150
- supportsSamplingTools: false,
151
- supportsElicitation: false,
152
- supportsElicitationForm: false,
153
- supportsElicitationUrl: false
154
- };
155
- }
156
- function updateSessionFromInitialize(session, params) {
157
- const capabilities = params?.capabilities || {};
158
- session.clientInfo = params?.clientInfo;
159
- session.clientCapabilities = capabilities;
160
- session.supportsRoots = Boolean(capabilities.roots);
161
- session.supportsSampling = Boolean(capabilities.sampling);
162
- const sampling = capabilities.sampling;
163
- session.supportsSamplingTools = Boolean(sampling?.tools);
164
- session.supportsElicitation = Boolean(capabilities.elicitation);
165
- session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
166
- session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
167
- }
168
- function supportsElicitationMode(capability, mode) {
169
- if (!capability || typeof capability !== "object") {
170
- return false;
171
- }
172
- const cap = capability;
173
- if (mode === "form") {
174
- return Object.keys(cap).length === 0 || typeof cap.form === "object";
175
- }
176
- return typeof cap.url === "object";
177
- }
178
- function updateSessionRoots(session, roots) {
179
- const normalized = normalizeRoots(roots);
180
- const previous = JSON.stringify(session.roots);
181
- const next = JSON.stringify(normalized);
182
- session.roots = normalized;
183
- return previous !== next;
184
- }
185
- function normalizeRoots(roots) {
186
- if (!Array.isArray(roots)) return [];
187
- const seen = /* @__PURE__ */ new Set();
188
- const normalized = [];
189
- for (const root of roots) {
190
- if (!root || typeof root !== "object") continue;
191
- const r = root;
192
- const uri = typeof r.uri === "string" ? r.uri : void 0;
193
- const name = typeof r.name === "string" ? r.name : void 0;
194
- if (!uri || seen.has(uri)) continue;
195
- seen.add(uri);
196
- normalized.push({ uri, name });
197
- }
198
- return normalized;
199
- }
200
- function extractRootsFromResult(result) {
201
- return normalizeRoots(result?.roots);
202
- }
203
- function getFilesystemRoots(session) {
204
- if (!session) return [];
205
- const resolved = [];
206
- for (const root of session.roots) {
207
- if (!root.uri.startsWith("file://")) continue;
208
- try {
209
- resolved.push(path.resolve(fileURLToPath(root.uri)));
210
- } catch {
211
- }
212
- }
213
- return resolved;
214
- }
215
- function isPathWithinRoots(targetPath, session) {
216
- const roots = getFilesystemRoots(session);
217
- if (roots.length === 0) return true;
218
- const normalizedTarget = path.resolve(targetPath);
219
- return roots.some((rootPath) => {
220
- const relative = path.relative(rootPath, normalizedTarget);
221
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
222
- });
223
- }
224
- function findContainingRoot(targetPath, session) {
225
- const roots = getFilesystemRoots(session);
226
- if (roots.length === 0) return null;
227
- const normalizedTarget = path.resolve(targetPath);
228
- for (const rootPath of roots) {
229
- const relative = path.relative(rootPath, normalizedTarget);
230
- if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) {
231
- return rootPath;
232
- }
233
- }
234
- return null;
235
- }
236
- function inferRepoFromSession(session) {
237
- const roots = getFilesystemRoots(session);
238
- if (roots.length === 1) {
239
- return path.basename(roots[0]);
240
- }
241
- return void 0;
242
- }
243
-
244
268
  // src/mcp/storage/sqlite.ts
245
269
  import Database from "better-sqlite3";
246
- import path3 from "path";
247
- import fs3 from "fs";
270
+ import path4 from "path";
271
+ import fs4 from "fs";
248
272
  import os from "os";
249
273
 
250
274
  // src/mcp/storage/migrations.ts
@@ -2736,8 +2760,8 @@ var HandoffEntity = class extends BaseEntity {
2736
2760
 
2737
2761
  // src/mcp/storage/write-lock.ts
2738
2762
  import lockfile from "proper-lockfile";
2739
- import path2 from "path";
2740
- import fs2 from "fs";
2763
+ import path3 from "path";
2764
+ import fs3 from "fs";
2741
2765
  var LOCK_STALE_MS = 3e4;
2742
2766
  var LOCK_RETRY_DELAY_MS = 200;
2743
2767
  var LOCK_RETRY_COUNT = 250;
@@ -2746,9 +2770,9 @@ var WriteLock = class {
2746
2770
  locked = false;
2747
2771
  constructor(dbPath) {
2748
2772
  this.lockTarget = dbPath;
2749
- if (!fs2.existsSync(dbPath)) {
2750
- fs2.mkdirSync(path2.dirname(dbPath), { recursive: true });
2751
- fs2.writeFileSync(dbPath, "");
2773
+ if (!fs3.existsSync(dbPath)) {
2774
+ fs3.mkdirSync(path3.dirname(dbPath), { recursive: true });
2775
+ fs3.writeFileSync(dbPath, "");
2752
2776
  }
2753
2777
  }
2754
2778
  /**
@@ -2800,13 +2824,13 @@ var WriteLock = class {
2800
2824
  // src/mcp/storage/sqlite.ts
2801
2825
  function resolveDbPath() {
2802
2826
  if (process.env.MEMORY_DB_PATH) return process.env.MEMORY_DB_PATH;
2803
- 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");
2804
- const standardPath = path3.join(standardConfigDir, "memory.db");
2805
- if (fs3.existsSync(standardPath)) return standardPath;
2806
- const legacyPath = path3.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
2807
- if (fs3.existsSync(legacyPath)) return legacyPath;
2808
- const localCwdFile = path3.join(process.cwd(), "storage", "memory.db");
2809
- if (fs3.existsSync(localCwdFile)) return localCwdFile;
2827
+ const standardConfigDir = process.platform === "win32" ? path4.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path4.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path4.join(os.homedir(), ".config", "local-memory-mcp");
2828
+ const standardPath = path4.join(standardConfigDir, "memory.db");
2829
+ if (fs4.existsSync(standardPath)) return standardPath;
2830
+ const legacyPath = path4.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
2831
+ if (fs4.existsSync(legacyPath)) return legacyPath;
2832
+ const localCwdFile = path4.join(process.cwd(), "storage", "memory.db");
2833
+ if (fs4.existsSync(localCwdFile)) return localCwdFile;
2810
2834
  return standardPath;
2811
2835
  }
2812
2836
  var DB_PATH = resolveDbPath();
@@ -2825,9 +2849,9 @@ var SQLiteStore = class _SQLiteStore {
2825
2849
  const finalPath = dbPath ?? DB_PATH;
2826
2850
  this.dbPathInstance = finalPath;
2827
2851
  if (finalPath !== ":memory:") {
2828
- const dbDir = path3.dirname(finalPath);
2829
- if (!fs3.existsSync(dbDir)) {
2830
- fs3.mkdirSync(dbDir, { recursive: true });
2852
+ const dbDir = path4.dirname(finalPath);
2853
+ if (!fs4.existsSync(dbDir)) {
2854
+ fs4.mkdirSync(dbDir, { recursive: true });
2831
2855
  }
2832
2856
  }
2833
2857
  this.db = new Database(finalPath);
@@ -2882,12 +2906,12 @@ var SQLiteStore = class _SQLiteStore {
2882
2906
  */
2883
2907
  _attemptRecovery(dbPath) {
2884
2908
  const backupPath = dbPath + ".backup";
2885
- if (fs3.existsSync(backupPath)) {
2909
+ if (fs4.existsSync(backupPath)) {
2886
2910
  logger.warn("[SQLiteStore] Attempting recovery from backup", { backupPath });
2887
2911
  try {
2888
2912
  const corruptPath = `${dbPath}.corrupt_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").slice(0, 15)}`;
2889
- fs3.copyFileSync(dbPath, corruptPath);
2890
- fs3.copyFileSync(backupPath, dbPath);
2913
+ fs4.copyFileSync(dbPath, corruptPath);
2914
+ fs4.copyFileSync(backupPath, dbPath);
2891
2915
  logger.warn("[SQLiteStore] Recovery successful. Corrupt file saved to", { corruptPath });
2892
2916
  } catch (err) {
2893
2917
  logger.error("[SQLiteStore] Recovery failed", { error: String(err) });
@@ -2905,7 +2929,7 @@ var SQLiteStore = class _SQLiteStore {
2905
2929
  try {
2906
2930
  this.db.pragma("wal_checkpoint(PASSIVE)");
2907
2931
  const backupPath = this.dbPathInstance + ".backup";
2908
- fs3.copyFileSync(this.dbPathInstance, backupPath);
2932
+ fs4.copyFileSync(this.dbPathInstance, backupPath);
2909
2933
  } catch (err) {
2910
2934
  logger.warn("[SQLiteStore] Backup failed", { error: String(err) });
2911
2935
  }
@@ -3027,649 +3051,106 @@ var RealVectorStore = class {
3027
3051
  }
3028
3052
  };
3029
3053
 
3030
- // src/mcp/capabilities.ts
3031
- import { fileURLToPath as fileURLToPath3 } from "url";
3054
+ // src/mcp/session.ts
3032
3055
  import path5 from "path";
3033
-
3034
- // src/mcp/prompts/loader.ts
3035
- import fs4 from "fs";
3036
- import path4 from "path";
3037
- import { fileURLToPath as fileURLToPath2 } from "url";
3038
- import matter from "gray-matter";
3039
- var __filename = fileURLToPath2(import.meta.url);
3040
- var __dirname = path4.dirname(__filename);
3041
- function findPromptDir() {
3042
- const candidates = [
3043
- // Production if chunked into dist/
3044
- "./prompts",
3045
- // Production if inlined into dist/mcp/
3046
- "../prompts",
3047
- // Dev: /src/mcp/prompts/definitions (next to loader.ts)
3048
- "./definitions"
3049
- ].map((relPath) => path4.resolve(__dirname, relPath));
3050
- for (const dir of candidates) {
3051
- if (fs4.existsSync(dir)) {
3052
- const files = fs4.readdirSync(dir);
3053
- if (files.some((f) => f.endsWith(".md"))) {
3054
- return dir;
3055
- }
3056
- }
3057
- }
3058
- return path4.resolve(__dirname, "./definitions");
3059
- }
3060
- var PROMPT_DIR = findPromptDir();
3061
- function listPromptFiles() {
3062
- if (!fs4.existsSync(PROMPT_DIR)) return [];
3063
- return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
3064
- }
3065
- function loadPromptFromMarkdown(name) {
3066
- const filePath = path4.join(PROMPT_DIR, `${name}.md`);
3067
- if (!fs4.existsSync(filePath)) {
3068
- throw new Error(`Prompt file not found: ${filePath}`);
3069
- }
3070
- const fileContent = fs4.readFileSync(filePath, "utf-8");
3071
- const { data, content } = matter(fileContent);
3056
+ import { fileURLToPath as fileURLToPath3 } from "url";
3057
+ function createSessionContext() {
3072
3058
  return {
3073
- name: data.name || name,
3074
- description: data.description || "",
3075
- arguments: data.arguments || [],
3076
- agent: data.agent,
3077
- content: content.trim()
3059
+ roots: [],
3060
+ supportsRoots: false,
3061
+ supportsSampling: false,
3062
+ supportsSamplingTools: false,
3063
+ supportsElicitation: false,
3064
+ supportsElicitationForm: false,
3065
+ supportsElicitationUrl: false
3078
3066
  };
3079
3067
  }
3080
- function findServerInstructionsDir() {
3081
- const candidates = [
3082
- // Production if chunked into dist/
3083
- "./prompts/server",
3084
- // Production if inlined into dist/mcp/
3085
- "../prompts/server",
3086
- // Dev: /src/mcp/prompts/server (next to loader.ts)
3087
- "./server"
3088
- ].map((relPath) => path4.resolve(__dirname, relPath));
3089
- for (const dir of candidates) {
3090
- if (fs4.existsSync(dir)) {
3091
- const filePath = path4.join(dir, "instructions.md");
3092
- if (fs4.existsSync(filePath)) {
3093
- return dir;
3094
- }
3095
- }
3068
+ function updateSessionFromInitialize(session, params) {
3069
+ const capabilities = params?.capabilities || {};
3070
+ session.clientInfo = params?.clientInfo;
3071
+ session.clientCapabilities = capabilities;
3072
+ session.supportsRoots = Boolean(capabilities.roots);
3073
+ session.supportsSampling = Boolean(capabilities.sampling);
3074
+ const sampling = capabilities.sampling;
3075
+ session.supportsSamplingTools = Boolean(sampling?.tools);
3076
+ session.supportsElicitation = Boolean(capabilities.elicitation);
3077
+ session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
3078
+ session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
3079
+ }
3080
+ function supportsElicitationMode(capability, mode) {
3081
+ if (!capability || typeof capability !== "object") {
3082
+ return false;
3083
+ }
3084
+ const cap = capability;
3085
+ if (mode === "form") {
3086
+ return Object.keys(cap).length === 0 || typeof cap.form === "object";
3096
3087
  }
3097
- return path4.resolve(__dirname, "./server");
3088
+ return typeof cap.url === "object";
3098
3089
  }
3099
- var SERVER_DIR = findServerInstructionsDir();
3100
- function loadServerInstructions() {
3101
- const filePath = path4.join(SERVER_DIR, "instructions.md");
3102
- if (!fs4.existsSync(filePath)) {
3103
- throw new Error(`Server instructions file not found: ${filePath}`);
3090
+ function updateSessionRoots(session, roots) {
3091
+ const normalized = normalizeRoots(roots);
3092
+ const previous = JSON.stringify(session.roots);
3093
+ const next = JSON.stringify(normalized);
3094
+ session.roots = normalized;
3095
+ return previous !== next;
3096
+ }
3097
+ function normalizeRoots(roots) {
3098
+ if (!Array.isArray(roots)) return [];
3099
+ const seen = /* @__PURE__ */ new Set();
3100
+ const normalized = [];
3101
+ for (const root of roots) {
3102
+ if (!root || typeof root !== "object") continue;
3103
+ const r = root;
3104
+ const uri = typeof r.uri === "string" ? r.uri : void 0;
3105
+ const name = typeof r.name === "string" ? r.name : void 0;
3106
+ if (!uri || seen.has(uri)) continue;
3107
+ seen.add(uri);
3108
+ normalized.push({ uri, name });
3104
3109
  }
3105
- const fileContent = fs4.readFileSync(filePath, "utf-8");
3106
- const { content } = matter(fileContent);
3107
- return content.trim();
3110
+ return normalized;
3108
3111
  }
3109
-
3110
- // src/mcp/capabilities.ts
3111
- var __dirname2 = path5.dirname(fileURLToPath3(import.meta.url));
3112
- var pkgVersion = "0.1.0";
3113
- if ("0.10.12") {
3114
- pkgVersion = "0.10.12";
3115
- } else {
3116
- let searchDir = __dirname2;
3117
- for (let i = 0; i < 5; i++) {
3118
- const candidate = path5.join(searchDir, "package.json");
3112
+ function extractRootsFromResult(result) {
3113
+ return normalizeRoots(result?.roots);
3114
+ }
3115
+ function getFilesystemRoots(session) {
3116
+ if (!session) return [];
3117
+ const resolved = [];
3118
+ for (const root of session.roots) {
3119
+ if (!root.uri.startsWith("file://")) continue;
3119
3120
  try {
3120
- if (fs.existsSync(candidate)) {
3121
- const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
3122
- if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
3123
- pkgVersion = pkg.version;
3124
- break;
3125
- }
3126
- }
3121
+ resolved.push(path5.resolve(fileURLToPath3(root.uri)));
3127
3122
  } catch {
3128
3123
  }
3129
- searchDir = path5.dirname(searchDir);
3130
3124
  }
3125
+ return resolved;
3131
3126
  }
3132
- var MCP_PROTOCOL_VERSION = "2025-03-26";
3133
- var SERVER_INSTRUCTIONS = loadServerInstructions();
3134
- var CAPABILITIES = {
3135
- serverInfo: {
3136
- name: "local-memory-mcp",
3137
- version: pkgVersion,
3138
- instructions: SERVER_INSTRUCTIONS
3139
- },
3140
- capabilities: {
3141
- completions: {},
3142
- logging: {},
3143
- resources: {
3144
- subscribe: true,
3145
- listChanged: true
3146
- },
3147
- tools: {
3148
- listChanged: false
3149
- },
3150
- prompts: {
3151
- listChanged: true
3152
- }
3153
- }
3154
- };
3155
-
3156
- // src/mcp/utils/pagination.ts
3157
- function encodeCursor(offset) {
3158
- return Buffer.from(String(offset), "utf8").toString("base64");
3127
+ function isPathWithinRoots(targetPath, session) {
3128
+ const roots = getFilesystemRoots(session);
3129
+ if (roots.length === 0) return true;
3130
+ const normalizedTarget = path5.resolve(targetPath);
3131
+ return roots.some((rootPath) => {
3132
+ const relative = path5.relative(rootPath, normalizedTarget);
3133
+ return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
3134
+ });
3159
3135
  }
3160
- function decodeCursor(cursor) {
3161
- if (cursor === void 0 || cursor === null || cursor === "") {
3162
- return 0;
3163
- }
3164
- if (typeof cursor !== "string" || cursor.trim() === "") {
3165
- throw invalidPaginationParams("Invalid cursor");
3166
- }
3167
- let decoded;
3168
- try {
3169
- decoded = Buffer.from(cursor, "base64").toString("utf8");
3170
- } catch {
3171
- throw invalidPaginationParams("Invalid cursor");
3172
- }
3173
- if (!/^\d+$/.test(decoded)) {
3174
- throw invalidPaginationParams("Invalid cursor");
3175
- }
3176
- const offset = Number.parseInt(decoded, 10);
3177
- if (!Number.isFinite(offset) || offset < 0) {
3178
- throw invalidPaginationParams("Invalid cursor");
3136
+ function findContainingRoot(targetPath, session) {
3137
+ const roots = getFilesystemRoots(session);
3138
+ if (roots.length === 0) return null;
3139
+ const normalizedTarget = path5.resolve(targetPath);
3140
+ for (const rootPath of roots) {
3141
+ const relative = path5.relative(rootPath, normalizedTarget);
3142
+ if (relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative)) {
3143
+ return rootPath;
3144
+ }
3179
3145
  }
3180
- return offset;
3181
- }
3182
- function invalidPaginationParams(message) {
3183
- const error = new Error(message);
3184
- error.code = -32602;
3185
- return error;
3146
+ return null;
3186
3147
  }
3187
-
3188
- // src/mcp/utils/completion.ts
3189
- var MAX_COMPLETION_VALUES = 100;
3190
- function rankCompletionValues(candidates, input) {
3191
- const unique = [...new Set(candidates.filter(Boolean))];
3192
- const needle = input.trim().toLowerCase();
3193
- if (!needle) {
3194
- return unique.slice(0, MAX_COMPLETION_VALUES);
3148
+ function inferRepoFromSession(session) {
3149
+ const roots = getFilesystemRoots(session);
3150
+ if (roots.length === 1) {
3151
+ return path5.basename(roots[0]);
3195
3152
  }
3196
- return unique.map((value) => ({ value, score: scoreCompletionValue(value, needle) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.value.localeCompare(b.value)).map((entry) => entry.value);
3197
- }
3198
- function scoreCompletionValue(value, needle) {
3199
- const haystack = value.toLowerCase();
3200
- if (haystack === needle) return 100;
3201
- if (haystack.startsWith(needle)) return 75;
3202
- if (haystack.includes(needle)) return 50;
3203
- const compactNeedle = needle.replace(/[\s_-]+/g, "");
3204
- const compactHaystack = haystack.replace(/[\s_-]+/g, "");
3205
- if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
3206
- return 0;
3207
- }
3208
-
3209
- // src/mcp/resources/index.ts
3210
- var DEFAULT_PAGE_SIZE = 25;
3211
- var MAX_PAGE_SIZE = 100;
3212
- function listResources(session, params) {
3213
- const resources = [
3214
- {
3215
- uri: "repository://index",
3216
- name: "Repository Index",
3217
- title: "Repository Index",
3218
- description: "List of all known repositories with memory/task counts and last activity",
3219
- mimeType: "application/json",
3220
- annotations: {
3221
- audience: ["assistant"],
3222
- priority: 1,
3223
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3224
- }
3225
- },
3226
- {
3227
- uri: "session://roots",
3228
- name: "Session Roots",
3229
- title: "Session Roots",
3230
- description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
3231
- mimeType: "application/json",
3232
- size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
3233
- annotations: {
3234
- audience: ["assistant"],
3235
- priority: 0.95,
3236
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3237
- }
3238
- }
3239
- ];
3240
- return paginateEntries("resources", resources, params);
3241
- }
3242
- function listResourceTemplates(params) {
3243
- const templates = [
3244
- // ── Memory ──────────────────────────────────────────────────────────────
3245
- {
3246
- uriTemplate: "repository://{name}/memories",
3247
- name: "Repository Memories",
3248
- title: "Repository Memories",
3249
- description: "All active memory entries for a specific repository",
3250
- mimeType: "application/json",
3251
- annotations: { audience: ["assistant"], priority: 0.85 }
3252
- },
3253
- {
3254
- uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
3255
- name: "Filtered Repository Memories",
3256
- title: "Filtered Repository Memories",
3257
- description: "Filter or search memories within a repository by keyword, type, or tag",
3258
- mimeType: "application/json",
3259
- annotations: { audience: ["assistant"], priority: 0.8 }
3260
- },
3261
- {
3262
- uriTemplate: "memory://{id}",
3263
- name: "Memory Detail",
3264
- title: "Memory Detail",
3265
- description: "Full content and statistics for a specific memory UUID",
3266
- mimeType: "application/json",
3267
- annotations: { audience: ["assistant"], priority: 0.75 }
3268
- },
3269
- // ── Tasks ────────────────────────────────────────────────────────────────
3270
- {
3271
- uriTemplate: "repository://{name}/tasks",
3272
- name: "Repository Tasks",
3273
- title: "Repository Tasks",
3274
- description: "All active tasks for a specific repository",
3275
- mimeType: "application/json",
3276
- annotations: { audience: ["assistant"], priority: 0.9 }
3277
- },
3278
- {
3279
- uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
3280
- name: "Filtered Repository Tasks",
3281
- title: "Filtered Repository Tasks",
3282
- description: "Filter tasks within a repository by status or priority level",
3283
- mimeType: "application/json",
3284
- annotations: { audience: ["assistant"], priority: 0.85 }
3285
- },
3286
- {
3287
- uriTemplate: "task://{id}",
3288
- name: "Task Detail",
3289
- title: "Task Detail",
3290
- description: "Full content and comments for a specific task UUID",
3291
- mimeType: "application/json",
3292
- annotations: { audience: ["assistant"], priority: 0.8 }
3293
- },
3294
- // ── Repository extras ────────────────────────────────────────────────────
3295
- {
3296
- uriTemplate: "repository://{name}/summary",
3297
- name: "Repository Summary",
3298
- title: "Repository Summary",
3299
- description: "High-level architectural summary for a repository",
3300
- mimeType: "text/plain",
3301
- annotations: { audience: ["assistant"], priority: 0.95 }
3302
- },
3303
- {
3304
- uriTemplate: "repository://{name}/actions",
3305
- name: "Repository Actions",
3306
- title: "Repository Actions",
3307
- description: "Audit log of agent tool actions scoped to a repository",
3308
- mimeType: "application/json",
3309
- annotations: { audience: ["assistant"], priority: 0.6 }
3310
- },
3311
- // ── Action detail ────────────────────────────────────────────────────────
3312
- {
3313
- uriTemplate: "action://{id}",
3314
- name: "Action Detail",
3315
- title: "Action Detail",
3316
- description: "Full details of a specific audit log entry by integer ID",
3317
- mimeType: "application/json",
3318
- annotations: { audience: ["assistant"], priority: 0.55 }
3319
- }
3320
- ];
3321
- return paginateEntries("resourceTemplates", templates, params);
3322
- }
3323
- function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
3324
- if (resourceUri === "repository://{name}/memories" || resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}" || resourceUri === "repository://{name}/tasks" || resourceUri === "repository://{name}/tasks?status={status}&priority={priority}" || resourceUri === "repository://{name}/summary" || resourceUri === "repository://{name}/actions") {
3325
- if (argumentName === "name") {
3326
- return rankCompletionValues(dataSources.repos, argumentValue);
3327
- }
3328
- }
3329
- if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
3330
- if (argumentName === "tag") {
3331
- return rankCompletionValues(dataSources.tags, argumentValue);
3332
- }
3333
- }
3334
- throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
3335
- }
3336
- function readResource(uri, db, session) {
3337
- logger.info("[Tool] resource.read", { uri });
3338
- if (uri === "repository://index") {
3339
- const repos = db.system.listRepoNavigation();
3340
- const payload = JSON.stringify(repos, null, 2);
3341
- return {
3342
- contents: [
3343
- {
3344
- uri,
3345
- mimeType: "application/json",
3346
- text: payload,
3347
- size: Buffer.byteLength(payload, "utf8"),
3348
- annotations: {
3349
- audience: ["assistant"],
3350
- priority: 1,
3351
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3352
- }
3353
- }
3354
- ]
3355
- };
3356
- }
3357
- if (uri === "session://roots") {
3358
- const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
3359
- return {
3360
- contents: [
3361
- {
3362
- uri,
3363
- mimeType: "application/json",
3364
- text: payload,
3365
- size: Buffer.byteLength(payload, "utf8"),
3366
- annotations: {
3367
- audience: ["assistant"],
3368
- priority: 0.95,
3369
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3370
- }
3371
- }
3372
- ]
3373
- };
3374
- }
3375
- const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
3376
- if (memoryIdMatch) {
3377
- const id = memoryIdMatch[1];
3378
- const entry = db.memories.getByIdWithStats(id);
3379
- if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
3380
- const payload = JSON.stringify(entry, null, 2);
3381
- return {
3382
- contents: [
3383
- {
3384
- uri,
3385
- mimeType: "application/json",
3386
- text: payload,
3387
- size: Buffer.byteLength(payload, "utf8"),
3388
- annotations: {
3389
- audience: ["assistant"],
3390
- priority: 0.75,
3391
- lastModified: entry.updated_at || entry.created_at
3392
- }
3393
- }
3394
- ]
3395
- };
3396
- }
3397
- const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
3398
- if (taskIdMatch) {
3399
- const id = taskIdMatch[1];
3400
- const task = db.tasks.getTaskById(id);
3401
- if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
3402
- const payload = JSON.stringify(task, null, 2);
3403
- return {
3404
- contents: [
3405
- {
3406
- uri,
3407
- mimeType: "application/json",
3408
- text: payload,
3409
- size: Buffer.byteLength(payload, "utf8"),
3410
- annotations: {
3411
- audience: ["assistant"],
3412
- priority: 0.8,
3413
- lastModified: task.updated_at || task.created_at
3414
- }
3415
- }
3416
- ]
3417
- };
3418
- }
3419
- const repoBase = parseRepoUri(uri);
3420
- if (repoBase) {
3421
- const { name, path: repoPath, query } = repoBase;
3422
- if (repoPath === "summary") {
3423
- const summary = db.summaries.getSummary(name);
3424
- const text = summary?.summary || `No summary available for repository: ${name}`;
3425
- return {
3426
- contents: [
3427
- {
3428
- uri,
3429
- mimeType: "text/plain",
3430
- text,
3431
- size: Buffer.byteLength(text, "utf8"),
3432
- annotations: {
3433
- audience: ["assistant"],
3434
- priority: 0.95,
3435
- lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
3436
- }
3437
- }
3438
- ]
3439
- };
3440
- }
3441
- if (repoPath === "memories") {
3442
- const search = query.get("search") || "";
3443
- const type = query.get("type");
3444
- const tag = query.get("tag");
3445
- const result = db.memories.listMemoriesForDashboard({
3446
- repo: name,
3447
- type: type || void 0,
3448
- tag: tag || void 0,
3449
- search: search || void 0,
3450
- limit: 50
3451
- });
3452
- const entries = result.items;
3453
- const payload = JSON.stringify(entries, null, 2);
3454
- return {
3455
- contents: [
3456
- {
3457
- uri,
3458
- mimeType: "application/json",
3459
- text: payload,
3460
- size: Buffer.byteLength(payload, "utf8"),
3461
- annotations: {
3462
- audience: ["assistant"],
3463
- priority: 0.85,
3464
- lastModified: deriveLastModifiedFromCollection(
3465
- entries.map((e) => e.updated_at || e.created_at)
3466
- )
3467
- }
3468
- }
3469
- ]
3470
- };
3471
- }
3472
- if (repoPath === "tasks") {
3473
- const status = query.get("status");
3474
- const priority = query.get("priority");
3475
- let tasks;
3476
- if (status && status !== "all") {
3477
- const statuses = status.split(",").map((s) => s.trim());
3478
- tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
3479
- } else {
3480
- tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
3481
- }
3482
- if (priority) {
3483
- const p = Number(priority);
3484
- if (!isNaN(p)) {
3485
- tasks = tasks.filter((t) => t.priority === p);
3486
- }
3487
- }
3488
- const payload = JSON.stringify(tasks, null, 2);
3489
- return {
3490
- contents: [
3491
- {
3492
- uri,
3493
- mimeType: "application/json",
3494
- text: payload,
3495
- size: Buffer.byteLength(payload, "utf8"),
3496
- annotations: {
3497
- audience: ["assistant"],
3498
- priority: 0.9,
3499
- lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
3500
- }
3501
- }
3502
- ]
3503
- };
3504
- }
3505
- if (repoPath === "actions") {
3506
- const actions = db.actions.getRecentActions(name, 100);
3507
- const payload = JSON.stringify(actions, null, 2);
3508
- return {
3509
- contents: [
3510
- {
3511
- uri,
3512
- mimeType: "application/json",
3513
- text: payload,
3514
- size: Buffer.byteLength(payload, "utf8"),
3515
- annotations: {
3516
- audience: ["assistant"],
3517
- priority: 0.6,
3518
- lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
3519
- }
3520
- }
3521
- ]
3522
- };
3523
- }
3524
- }
3525
- const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
3526
- if (actionIdMatch) {
3527
- const id = Number(actionIdMatch[1]);
3528
- const action = db.actions.getActionById(id);
3529
- if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
3530
- const payload = JSON.stringify(action, null, 2);
3531
- return {
3532
- contents: [
3533
- {
3534
- uri,
3535
- mimeType: "application/json",
3536
- text: payload,
3537
- size: Buffer.byteLength(payload, "utf8"),
3538
- annotations: {
3539
- audience: ["assistant"],
3540
- priority: 0.55,
3541
- lastModified: action.created_at
3542
- }
3543
- }
3544
- ]
3545
- };
3546
- }
3547
- throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
3548
- }
3549
- function parseRepoUri(uri) {
3550
- const prefix = "repository://";
3551
- if (!uri.startsWith(prefix)) return null;
3552
- const rest = uri.slice(prefix.length);
3553
- const queryStart = rest.indexOf("?");
3554
- const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
3555
- const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
3556
- const slashIdx = withoutQuery.indexOf("/");
3557
- if (slashIdx === -1) return null;
3558
- const name = withoutQuery.slice(0, slashIdx);
3559
- const path6 = withoutQuery.slice(slashIdx + 1);
3560
- if (!name || !path6) return null;
3561
- return { name, path: path6, query: new URLSearchParams(queryString) };
3562
- }
3563
- function paginateEntries(key, entries, params) {
3564
- const limit = normalizeLimit(params?.limit);
3565
- const offset = decodeCursor(params?.cursor);
3566
- const sliced = entries.slice(offset, offset + limit);
3567
- const nextOffset = offset + sliced.length;
3568
- return {
3569
- [key]: sliced,
3570
- nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
3571
- };
3572
- }
3573
- function normalizeLimit(limit) {
3574
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
3575
- return DEFAULT_PAGE_SIZE;
3576
- }
3577
- return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
3578
- }
3579
- function deriveLastModifiedFromCollection(values) {
3580
- const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
3581
- return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
3582
- }
3583
- function resourceNotFound(message, uri) {
3584
- const error = new Error(message);
3585
- error.code = -32002;
3586
- error.data = { uri };
3587
- return error;
3588
- }
3589
- function invalidCompletionParams(message) {
3590
- const error = new Error(message);
3591
- error.code = -32602;
3592
- return error;
3593
- }
3594
-
3595
- // src/mcp/prompts/registry.ts
3596
- function createPromptDefinition(loaded) {
3597
- return {
3598
- name: loaded.name,
3599
- description: loaded.description,
3600
- arguments: loaded.arguments,
3601
- agent: loaded.agent,
3602
- messages: [
3603
- {
3604
- role: "user",
3605
- content: {
3606
- type: "text",
3607
- text: loaded.content
3608
- }
3609
- }
3610
- ]
3611
- };
3612
- }
3613
- var PROMPTS = {};
3614
- var promptFiles = listPromptFiles();
3615
- for (const name of promptFiles) {
3616
- try {
3617
- PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
3618
- } catch (e) {
3619
- logger.warn(`Failed to load prompt ${name}: ${e}`);
3620
- }
3621
- }
3622
- async function listPrompts(db, session, params) {
3623
- const allPrompts = Object.values(PROMPTS).map((p) => ({
3624
- name: p.name,
3625
- description: p.description,
3626
- arguments: p.arguments,
3627
- metadata: p.agent ? { agent: p.agent } : void 0
3628
- }));
3629
- const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
3630
- const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
3631
- const offset = decodeCursor(params?.cursor);
3632
- const sliced = allPrompts.slice(offset, offset + limit);
3633
- const nextOffset = offset + sliced.length;
3634
- return {
3635
- prompts: sliced,
3636
- nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
3637
- };
3638
- }
3639
- async function getPrompt(name, args = {}, db, session) {
3640
- const prompt = PROMPTS[name];
3641
- if (!prompt) {
3642
- throw new Error(`Prompt not found: ${name}`);
3643
- }
3644
- const inferredRepo = inferRepoFromSession(session);
3645
- const messages = prompt.messages.map((m) => {
3646
- let text = m.content.text;
3647
- for (const [key, value] of Object.entries(args)) {
3648
- text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
3649
- }
3650
- text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
3651
- return {
3652
- ...m,
3653
- content: {
3654
- ...m.content,
3655
- text
3656
- }
3657
- };
3658
- });
3659
- return {
3660
- description: prompt.description,
3661
- messages,
3662
- metadata: prompt.agent ? { agent: prompt.agent } : void 0
3663
- };
3664
- }
3665
- async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
3666
- void name;
3667
- void contextArguments;
3668
- if (argName === "task_id") {
3669
- const values = dataSources.tasks.map((t) => t.id);
3670
- return rankCompletionValues(values, value);
3671
- }
3672
- return [];
3153
+ return void 0;
3673
3154
  }
3674
3155
 
3675
3156
  // src/mcp/tools/schemas.ts
@@ -5241,100 +4722,619 @@ var TOOL_DEFINITIONS = [
5241
4722
  destructiveHint: false,
5242
4723
  openWorldHint: false
5243
4724
  },
5244
- inputSchema: {
5245
- type: "object",
5246
- properties: {
5247
- id: { type: "string", description: "Standard ID to update" },
5248
- name: { type: "string", minLength: 3, maxLength: 255 },
5249
- content: { type: "string", minLength: 10 },
5250
- parent_id: { type: "string", format: "uuid", nullable: true },
5251
- context: { type: "string" },
5252
- version: { type: "string" },
5253
- language: { type: "string" },
5254
- stack: { type: "array", items: { type: "string" } },
5255
- repo: { type: "string" },
5256
- is_global: { type: "boolean" },
5257
- tags: { type: "array", items: { type: "string" } },
5258
- metadata: { type: "object" },
5259
- agent: { type: "string" },
5260
- model: { type: "string" },
5261
- structured: { type: "boolean", default: false }
5262
- },
5263
- required: ["id"]
4725
+ inputSchema: {
4726
+ type: "object",
4727
+ properties: {
4728
+ id: { type: "string", description: "Standard ID to update" },
4729
+ name: { type: "string", minLength: 3, maxLength: 255 },
4730
+ content: { type: "string", minLength: 10 },
4731
+ parent_id: { type: "string", format: "uuid", nullable: true },
4732
+ context: { type: "string" },
4733
+ version: { type: "string" },
4734
+ language: { type: "string" },
4735
+ stack: { type: "array", items: { type: "string" } },
4736
+ repo: { type: "string" },
4737
+ is_global: { type: "boolean" },
4738
+ tags: { type: "array", items: { type: "string" } },
4739
+ metadata: { type: "object" },
4740
+ agent: { type: "string" },
4741
+ model: { type: "string" },
4742
+ structured: { type: "boolean", default: false }
4743
+ },
4744
+ required: ["id"]
4745
+ },
4746
+ outputSchema: {
4747
+ type: "object",
4748
+ properties: {
4749
+ success: { type: "boolean" },
4750
+ id: { type: "string" },
4751
+ updatedFields: { type: "array", items: { type: "string" } }
4752
+ },
4753
+ required: ["success", "id", "updatedFields"]
4754
+ }
4755
+ },
4756
+ {
4757
+ name: "standard-search",
4758
+ title: "Standard Search",
4759
+ description: "MANDATORY PRE-IMPLEMENTATION CHECK: Call before any code edit, test edit, refactor, migration, or implementation decision to find applicable coding standards. Returns a compact pointer table; use `standard-detail` for relevant results. If no relevant standards are returned, continue and state that no applicable standards were found.",
4760
+ annotations: {
4761
+ readOnlyHint: true,
4762
+ idempotentHint: true,
4763
+ openWorldHint: false
4764
+ },
4765
+ inputSchema: {
4766
+ type: "object",
4767
+ properties: {
4768
+ query: { type: "string", description: "Search query (optional, searches title/content)" },
4769
+ stack: {
4770
+ type: "array",
4771
+ items: { type: "string" },
4772
+ description: "Technology stack to filter by (e.g., ['react', 'nextjs'])"
4773
+ },
4774
+ tags: {
4775
+ type: "array",
4776
+ items: { type: "string" },
4777
+ description: "Tag filter"
4778
+ },
4779
+ language: { type: "string", description: "Programming language filter" },
4780
+ context: { type: "string", description: "Context/category filter" },
4781
+ version: { type: "string", description: "Version filter" },
4782
+ repo: { type: "string", description: "Repository filter (optional)" },
4783
+ is_global: { type: "boolean", description: "Filter by global/repo-specific" },
4784
+ limit: { type: "number", minimum: 1, maximum: 100, default: 20 },
4785
+ offset: { type: "number", minimum: 0, default: 0 },
4786
+ structured: { type: "boolean", default: false }
4787
+ },
4788
+ required: []
4789
+ },
4790
+ outputSchema: {
4791
+ type: "object",
4792
+ properties: {
4793
+ schema: { type: "string", enum: ["standard-search"] },
4794
+ query: { type: "string" },
4795
+ count: { type: "number", description: "Number of rows returned" },
4796
+ total: { type: "number", description: "Total number of matches before pagination" },
4797
+ offset: { type: "number" },
4798
+ limit: { type: "number" },
4799
+ results: {
4800
+ type: "object",
4801
+ properties: {
4802
+ columns: {
4803
+ type: "array",
4804
+ items: { type: "string" }
4805
+ },
4806
+ rows: {
4807
+ type: "array",
4808
+ items: { type: "array" },
4809
+ description: "Each row includes standard id and pointer metadata. Fetch full content via standard-detail."
4810
+ }
4811
+ },
4812
+ required: ["columns", "rows"]
4813
+ }
4814
+ },
4815
+ required: ["schema", "query", "count", "total", "offset", "limit", "results"]
4816
+ }
4817
+ }
4818
+ ];
4819
+
4820
+ // src/mcp/utils/pagination.ts
4821
+ function encodeCursor(offset) {
4822
+ return Buffer.from(String(offset), "utf8").toString("base64");
4823
+ }
4824
+ function decodeCursor(cursor) {
4825
+ if (cursor === void 0 || cursor === null || cursor === "") {
4826
+ return 0;
4827
+ }
4828
+ if (typeof cursor !== "string" || cursor.trim() === "") {
4829
+ throw invalidPaginationParams("Invalid cursor");
4830
+ }
4831
+ let decoded;
4832
+ try {
4833
+ decoded = Buffer.from(cursor, "base64").toString("utf8");
4834
+ } catch {
4835
+ throw invalidPaginationParams("Invalid cursor");
4836
+ }
4837
+ if (!/^\d+$/.test(decoded)) {
4838
+ throw invalidPaginationParams("Invalid cursor");
4839
+ }
4840
+ const offset = Number.parseInt(decoded, 10);
4841
+ if (!Number.isFinite(offset) || offset < 0) {
4842
+ throw invalidPaginationParams("Invalid cursor");
4843
+ }
4844
+ return offset;
4845
+ }
4846
+ function invalidPaginationParams(message) {
4847
+ const error = new Error(message);
4848
+ error.code = -32602;
4849
+ return error;
4850
+ }
4851
+
4852
+ // src/mcp/utils/completion.ts
4853
+ var MAX_COMPLETION_VALUES = 100;
4854
+ function rankCompletionValues(candidates, input) {
4855
+ const unique = [...new Set(candidates.filter(Boolean))];
4856
+ const needle = input.trim().toLowerCase();
4857
+ if (!needle) {
4858
+ return unique.slice(0, MAX_COMPLETION_VALUES);
4859
+ }
4860
+ return unique.map((value) => ({ value, score: scoreCompletionValue(value, needle) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.value.localeCompare(b.value)).map((entry) => entry.value);
4861
+ }
4862
+ function scoreCompletionValue(value, needle) {
4863
+ const haystack = value.toLowerCase();
4864
+ if (haystack === needle) return 100;
4865
+ if (haystack.startsWith(needle)) return 75;
4866
+ if (haystack.includes(needle)) return 50;
4867
+ const compactNeedle = needle.replace(/[\s_-]+/g, "");
4868
+ const compactHaystack = haystack.replace(/[\s_-]+/g, "");
4869
+ if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
4870
+ return 0;
4871
+ }
4872
+
4873
+ // src/mcp/resources/index.ts
4874
+ var DEFAULT_PAGE_SIZE = 25;
4875
+ var MAX_PAGE_SIZE = 100;
4876
+ function listResources(session, params) {
4877
+ const resources = [
4878
+ {
4879
+ uri: "repository://index",
4880
+ name: "Repository Index",
4881
+ title: "Repository Index",
4882
+ description: "List of all known repositories with memory/task counts and last activity",
4883
+ mimeType: "application/json",
4884
+ annotations: {
4885
+ audience: ["assistant"],
4886
+ priority: 1,
4887
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
4888
+ }
4889
+ },
4890
+ {
4891
+ uri: "session://roots",
4892
+ name: "Session Roots",
4893
+ title: "Session Roots",
4894
+ description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
4895
+ mimeType: "application/json",
4896
+ size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
4897
+ annotations: {
4898
+ audience: ["assistant"],
4899
+ priority: 0.95,
4900
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
4901
+ }
4902
+ }
4903
+ ];
4904
+ return paginateEntries("resources", resources, params);
4905
+ }
4906
+ function listResourceTemplates(params) {
4907
+ const templates = [
4908
+ // ── Memory ──────────────────────────────────────────────────────────────
4909
+ {
4910
+ uriTemplate: "repository://{name}/memories",
4911
+ name: "Repository Memories",
4912
+ title: "Repository Memories",
4913
+ description: "All active memory entries for a specific repository",
4914
+ mimeType: "application/json",
4915
+ annotations: { audience: ["assistant"], priority: 0.85 }
4916
+ },
4917
+ {
4918
+ uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
4919
+ name: "Filtered Repository Memories",
4920
+ title: "Filtered Repository Memories",
4921
+ description: "Filter or search memories within a repository by keyword, type, or tag",
4922
+ mimeType: "application/json",
4923
+ annotations: { audience: ["assistant"], priority: 0.8 }
4924
+ },
4925
+ {
4926
+ uriTemplate: "memory://{id}",
4927
+ name: "Memory Detail",
4928
+ title: "Memory Detail",
4929
+ description: "Full content and statistics for a specific memory UUID",
4930
+ mimeType: "application/json",
4931
+ annotations: { audience: ["assistant"], priority: 0.75 }
4932
+ },
4933
+ // ── Tasks ────────────────────────────────────────────────────────────────
4934
+ {
4935
+ uriTemplate: "repository://{name}/tasks",
4936
+ name: "Repository Tasks",
4937
+ title: "Repository Tasks",
4938
+ description: "All active tasks for a specific repository",
4939
+ mimeType: "application/json",
4940
+ annotations: { audience: ["assistant"], priority: 0.9 }
4941
+ },
4942
+ {
4943
+ uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
4944
+ name: "Filtered Repository Tasks",
4945
+ title: "Filtered Repository Tasks",
4946
+ description: "Filter tasks within a repository by status or priority level",
4947
+ mimeType: "application/json",
4948
+ annotations: { audience: ["assistant"], priority: 0.85 }
4949
+ },
4950
+ {
4951
+ uriTemplate: "task://{id}",
4952
+ name: "Task Detail",
4953
+ title: "Task Detail",
4954
+ description: "Full content and comments for a specific task UUID",
4955
+ mimeType: "application/json",
4956
+ annotations: { audience: ["assistant"], priority: 0.8 }
5264
4957
  },
5265
- outputSchema: {
5266
- type: "object",
5267
- properties: {
5268
- success: { type: "boolean" },
5269
- id: { type: "string" },
5270
- updatedFields: { type: "array", items: { type: "string" } }
5271
- },
5272
- required: ["success", "id", "updatedFields"]
5273
- }
5274
- },
5275
- {
5276
- name: "standard-search",
5277
- title: "Standard Search",
5278
- description: "MANDATORY PRE-IMPLEMENTATION CHECK: Call before any code edit, test edit, refactor, migration, or implementation decision to find applicable coding standards. Returns a compact pointer table; use `standard-detail` for relevant results. If no relevant standards are returned, continue and state that no applicable standards were found.",
5279
- annotations: {
5280
- readOnlyHint: true,
5281
- idempotentHint: true,
5282
- openWorldHint: false
4958
+ // ── Repository extras ────────────────────────────────────────────────────
4959
+ {
4960
+ uriTemplate: "repository://{name}/summary",
4961
+ name: "Repository Summary",
4962
+ title: "Repository Summary",
4963
+ description: "High-level architectural summary for a repository",
4964
+ mimeType: "text/plain",
4965
+ annotations: { audience: ["assistant"], priority: 0.95 }
5283
4966
  },
5284
- inputSchema: {
5285
- type: "object",
5286
- properties: {
5287
- query: { type: "string", description: "Search query (optional, searches title/content)" },
5288
- stack: {
5289
- type: "array",
5290
- items: { type: "string" },
5291
- description: "Technology stack to filter by (e.g., ['react', 'nextjs'])"
5292
- },
5293
- tags: {
5294
- type: "array",
5295
- items: { type: "string" },
5296
- description: "Tag filter"
5297
- },
5298
- language: { type: "string", description: "Programming language filter" },
5299
- context: { type: "string", description: "Context/category filter" },
5300
- version: { type: "string", description: "Version filter" },
5301
- repo: { type: "string", description: "Repository filter (optional)" },
5302
- is_global: { type: "boolean", description: "Filter by global/repo-specific" },
5303
- limit: { type: "number", minimum: 1, maximum: 100, default: 20 },
5304
- offset: { type: "number", minimum: 0, default: 0 },
5305
- structured: { type: "boolean", default: false }
5306
- },
5307
- required: []
4967
+ {
4968
+ uriTemplate: "repository://{name}/actions",
4969
+ name: "Repository Actions",
4970
+ title: "Repository Actions",
4971
+ description: "Audit log of agent tool actions scoped to a repository",
4972
+ mimeType: "application/json",
4973
+ annotations: { audience: ["assistant"], priority: 0.6 }
5308
4974
  },
5309
- outputSchema: {
5310
- type: "object",
5311
- properties: {
5312
- schema: { type: "string", enum: ["standard-search"] },
5313
- query: { type: "string" },
5314
- count: { type: "number", description: "Number of rows returned" },
5315
- total: { type: "number", description: "Total number of matches before pagination" },
5316
- offset: { type: "number" },
5317
- limit: { type: "number" },
5318
- results: {
5319
- type: "object",
5320
- properties: {
5321
- columns: {
5322
- type: "array",
5323
- items: { type: "string" }
5324
- },
5325
- rows: {
5326
- type: "array",
5327
- items: { type: "array" },
5328
- description: "Each row includes standard id and pointer metadata. Fetch full content via standard-detail."
4975
+ // ── Action detail ────────────────────────────────────────────────────────
4976
+ {
4977
+ uriTemplate: "action://{id}",
4978
+ name: "Action Detail",
4979
+ title: "Action Detail",
4980
+ description: "Full details of a specific audit log entry by integer ID",
4981
+ mimeType: "application/json",
4982
+ annotations: { audience: ["assistant"], priority: 0.55 }
4983
+ }
4984
+ ];
4985
+ return paginateEntries("resourceTemplates", templates, params);
4986
+ }
4987
+ function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
4988
+ if (resourceUri === "repository://{name}/memories" || resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}" || resourceUri === "repository://{name}/tasks" || resourceUri === "repository://{name}/tasks?status={status}&priority={priority}" || resourceUri === "repository://{name}/summary" || resourceUri === "repository://{name}/actions") {
4989
+ if (argumentName === "name") {
4990
+ return rankCompletionValues(dataSources.repos, argumentValue);
4991
+ }
4992
+ }
4993
+ if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
4994
+ if (argumentName === "tag") {
4995
+ return rankCompletionValues(dataSources.tags, argumentValue);
4996
+ }
4997
+ }
4998
+ throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
4999
+ }
5000
+ function readResource(uri, db, session) {
5001
+ logger.info("[Tool] resource.read", { uri });
5002
+ if (uri === "repository://index") {
5003
+ const repos = db.system.listRepoNavigation();
5004
+ const payload = JSON.stringify(repos, null, 2);
5005
+ return {
5006
+ contents: [
5007
+ {
5008
+ uri,
5009
+ mimeType: "application/json",
5010
+ text: payload,
5011
+ size: Buffer.byteLength(payload, "utf8"),
5012
+ annotations: {
5013
+ audience: ["assistant"],
5014
+ priority: 1,
5015
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
5016
+ }
5017
+ }
5018
+ ]
5019
+ };
5020
+ }
5021
+ if (uri === "session://roots") {
5022
+ const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
5023
+ return {
5024
+ contents: [
5025
+ {
5026
+ uri,
5027
+ mimeType: "application/json",
5028
+ text: payload,
5029
+ size: Buffer.byteLength(payload, "utf8"),
5030
+ annotations: {
5031
+ audience: ["assistant"],
5032
+ priority: 0.95,
5033
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
5034
+ }
5035
+ }
5036
+ ]
5037
+ };
5038
+ }
5039
+ const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
5040
+ if (memoryIdMatch) {
5041
+ const id = memoryIdMatch[1];
5042
+ const entry = db.memories.getByIdWithStats(id);
5043
+ if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
5044
+ const payload = JSON.stringify(entry, null, 2);
5045
+ return {
5046
+ contents: [
5047
+ {
5048
+ uri,
5049
+ mimeType: "application/json",
5050
+ text: payload,
5051
+ size: Buffer.byteLength(payload, "utf8"),
5052
+ annotations: {
5053
+ audience: ["assistant"],
5054
+ priority: 0.75,
5055
+ lastModified: entry.updated_at || entry.created_at
5056
+ }
5057
+ }
5058
+ ]
5059
+ };
5060
+ }
5061
+ const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
5062
+ if (taskIdMatch) {
5063
+ const id = taskIdMatch[1];
5064
+ const task = db.tasks.getTaskById(id);
5065
+ if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
5066
+ const payload = JSON.stringify(task, null, 2);
5067
+ return {
5068
+ contents: [
5069
+ {
5070
+ uri,
5071
+ mimeType: "application/json",
5072
+ text: payload,
5073
+ size: Buffer.byteLength(payload, "utf8"),
5074
+ annotations: {
5075
+ audience: ["assistant"],
5076
+ priority: 0.8,
5077
+ lastModified: task.updated_at || task.created_at
5078
+ }
5079
+ }
5080
+ ]
5081
+ };
5082
+ }
5083
+ const repoBase = parseRepoUri(uri);
5084
+ if (repoBase) {
5085
+ const { name, path: repoPath, query } = repoBase;
5086
+ if (repoPath === "summary") {
5087
+ const summary = db.summaries.getSummary(name);
5088
+ const text = summary?.summary || `No summary available for repository: ${name}`;
5089
+ return {
5090
+ contents: [
5091
+ {
5092
+ uri,
5093
+ mimeType: "text/plain",
5094
+ text,
5095
+ size: Buffer.byteLength(text, "utf8"),
5096
+ annotations: {
5097
+ audience: ["assistant"],
5098
+ priority: 0.95,
5099
+ lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
5100
+ }
5101
+ }
5102
+ ]
5103
+ };
5104
+ }
5105
+ if (repoPath === "memories") {
5106
+ const search = query.get("search") || "";
5107
+ const type = query.get("type");
5108
+ const tag = query.get("tag");
5109
+ const result = db.memories.listMemoriesForDashboard({
5110
+ repo: name,
5111
+ type: type || void 0,
5112
+ tag: tag || void 0,
5113
+ search: search || void 0,
5114
+ limit: 50
5115
+ });
5116
+ const entries = result.items;
5117
+ const payload = JSON.stringify(entries, null, 2);
5118
+ return {
5119
+ contents: [
5120
+ {
5121
+ uri,
5122
+ mimeType: "application/json",
5123
+ text: payload,
5124
+ size: Buffer.byteLength(payload, "utf8"),
5125
+ annotations: {
5126
+ audience: ["assistant"],
5127
+ priority: 0.85,
5128
+ lastModified: deriveLastModifiedFromCollection(
5129
+ entries.map((e) => e.updated_at || e.created_at)
5130
+ )
5329
5131
  }
5330
- },
5331
- required: ["columns", "rows"]
5132
+ }
5133
+ ]
5134
+ };
5135
+ }
5136
+ if (repoPath === "tasks") {
5137
+ const status = query.get("status");
5138
+ const priority = query.get("priority");
5139
+ let tasks;
5140
+ if (status && status !== "all") {
5141
+ const statuses = status.split(",").map((s) => s.trim());
5142
+ tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
5143
+ } else {
5144
+ tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
5145
+ }
5146
+ if (priority) {
5147
+ const p = Number(priority);
5148
+ if (!isNaN(p)) {
5149
+ tasks = tasks.filter((t) => t.priority === p);
5332
5150
  }
5333
- },
5334
- required: ["schema", "query", "count", "total", "offset", "limit", "results"]
5151
+ }
5152
+ const payload = JSON.stringify(tasks, null, 2);
5153
+ return {
5154
+ contents: [
5155
+ {
5156
+ uri,
5157
+ mimeType: "application/json",
5158
+ text: payload,
5159
+ size: Buffer.byteLength(payload, "utf8"),
5160
+ annotations: {
5161
+ audience: ["assistant"],
5162
+ priority: 0.9,
5163
+ lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
5164
+ }
5165
+ }
5166
+ ]
5167
+ };
5168
+ }
5169
+ if (repoPath === "actions") {
5170
+ const actions = db.actions.getRecentActions(name, 100);
5171
+ const payload = JSON.stringify(actions, null, 2);
5172
+ return {
5173
+ contents: [
5174
+ {
5175
+ uri,
5176
+ mimeType: "application/json",
5177
+ text: payload,
5178
+ size: Buffer.byteLength(payload, "utf8"),
5179
+ annotations: {
5180
+ audience: ["assistant"],
5181
+ priority: 0.6,
5182
+ lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
5183
+ }
5184
+ }
5185
+ ]
5186
+ };
5335
5187
  }
5336
5188
  }
5337
- ];
5189
+ const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
5190
+ if (actionIdMatch) {
5191
+ const id = Number(actionIdMatch[1]);
5192
+ const action = db.actions.getActionById(id);
5193
+ if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
5194
+ const payload = JSON.stringify(action, null, 2);
5195
+ return {
5196
+ contents: [
5197
+ {
5198
+ uri,
5199
+ mimeType: "application/json",
5200
+ text: payload,
5201
+ size: Buffer.byteLength(payload, "utf8"),
5202
+ annotations: {
5203
+ audience: ["assistant"],
5204
+ priority: 0.55,
5205
+ lastModified: action.created_at
5206
+ }
5207
+ }
5208
+ ]
5209
+ };
5210
+ }
5211
+ throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
5212
+ }
5213
+ function parseRepoUri(uri) {
5214
+ const prefix = "repository://";
5215
+ if (!uri.startsWith(prefix)) return null;
5216
+ const rest = uri.slice(prefix.length);
5217
+ const queryStart = rest.indexOf("?");
5218
+ const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
5219
+ const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
5220
+ const slashIdx = withoutQuery.indexOf("/");
5221
+ if (slashIdx === -1) return null;
5222
+ const name = withoutQuery.slice(0, slashIdx);
5223
+ const path6 = withoutQuery.slice(slashIdx + 1);
5224
+ if (!name || !path6) return null;
5225
+ return { name, path: path6, query: new URLSearchParams(queryString) };
5226
+ }
5227
+ function paginateEntries(key, entries, params) {
5228
+ const limit = normalizeLimit(params?.limit);
5229
+ const offset = decodeCursor(params?.cursor);
5230
+ const sliced = entries.slice(offset, offset + limit);
5231
+ const nextOffset = offset + sliced.length;
5232
+ return {
5233
+ [key]: sliced,
5234
+ nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
5235
+ };
5236
+ }
5237
+ function normalizeLimit(limit) {
5238
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
5239
+ return DEFAULT_PAGE_SIZE;
5240
+ }
5241
+ return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
5242
+ }
5243
+ function deriveLastModifiedFromCollection(values) {
5244
+ const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
5245
+ return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
5246
+ }
5247
+ function resourceNotFound(message, uri) {
5248
+ const error = new Error(message);
5249
+ error.code = -32002;
5250
+ error.data = { uri };
5251
+ return error;
5252
+ }
5253
+ function invalidCompletionParams(message) {
5254
+ const error = new Error(message);
5255
+ error.code = -32602;
5256
+ return error;
5257
+ }
5258
+
5259
+ // src/mcp/prompts/registry.ts
5260
+ function createPromptDefinition(loaded) {
5261
+ return {
5262
+ name: loaded.name,
5263
+ description: loaded.description,
5264
+ arguments: loaded.arguments,
5265
+ agent: loaded.agent,
5266
+ messages: [
5267
+ {
5268
+ role: "user",
5269
+ content: {
5270
+ type: "text",
5271
+ text: loaded.content
5272
+ }
5273
+ }
5274
+ ]
5275
+ };
5276
+ }
5277
+ var PROMPTS = {};
5278
+ var promptFiles = listPromptFiles();
5279
+ for (const name of promptFiles) {
5280
+ try {
5281
+ PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
5282
+ } catch (e) {
5283
+ logger.warn(`Failed to load prompt ${name}: ${e}`);
5284
+ }
5285
+ }
5286
+ async function listPrompts(db, session, params) {
5287
+ const allPrompts = Object.values(PROMPTS).map((p) => ({
5288
+ name: p.name,
5289
+ description: p.description,
5290
+ arguments: p.arguments,
5291
+ metadata: p.agent ? { agent: p.agent } : void 0
5292
+ }));
5293
+ const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
5294
+ const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
5295
+ const offset = decodeCursor(params?.cursor);
5296
+ const sliced = allPrompts.slice(offset, offset + limit);
5297
+ const nextOffset = offset + sliced.length;
5298
+ return {
5299
+ prompts: sliced,
5300
+ nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
5301
+ };
5302
+ }
5303
+ async function getPrompt(name, args = {}, db, session) {
5304
+ const prompt = PROMPTS[name];
5305
+ if (!prompt) {
5306
+ throw new Error(`Prompt not found: ${name}`);
5307
+ }
5308
+ const inferredRepo = inferRepoFromSession(session);
5309
+ const messages = prompt.messages.map((m) => {
5310
+ let text = m.content.text;
5311
+ for (const [key, value] of Object.entries(args)) {
5312
+ text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
5313
+ }
5314
+ text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
5315
+ return {
5316
+ ...m,
5317
+ content: {
5318
+ ...m.content,
5319
+ text
5320
+ }
5321
+ };
5322
+ });
5323
+ return {
5324
+ description: prompt.description,
5325
+ messages,
5326
+ metadata: prompt.agent ? { agent: prompt.agent } : void 0
5327
+ };
5328
+ }
5329
+ async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
5330
+ void name;
5331
+ void contextArguments;
5332
+ if (argName === "task_id") {
5333
+ const values = dataSources.tasks.map((t) => t.id);
5334
+ return rankCompletionValues(values, value);
5335
+ }
5336
+ return [];
5337
+ }
5338
5338
 
5339
5339
  // src/mcp/tools/standard.shared.ts
5340
5340
  function toContextSlug(value) {
@@ -5354,31 +5354,17 @@ function buildStandardVectorText(standard) {
5354
5354
  }
5355
5355
 
5356
5356
  export {
5357
+ MCP_PROTOCOL_VERSION,
5358
+ CAPABILITIES,
5357
5359
  logger,
5358
5360
  setLogLevel,
5359
5361
  getLogLevel,
5360
5362
  addLogSink,
5361
5363
  LOG_LEVEL_VALUES,
5362
5364
  createFileSink,
5363
- encodeCursor,
5364
- decodeCursor,
5365
- listResources,
5366
- listResourceTemplates,
5367
- completeResourceArgument,
5368
- readResource,
5369
- createSessionContext,
5370
- updateSessionFromInitialize,
5371
- updateSessionRoots,
5372
- extractRootsFromResult,
5373
- getFilesystemRoots,
5374
- isPathWithinRoots,
5375
- findContainingRoot,
5376
- inferRepoFromSession,
5377
- PROMPTS,
5378
- listPrompts,
5379
- getPrompt,
5380
- completePromptArgument,
5381
5365
  normalizeRepo,
5366
+ SQLiteStore,
5367
+ RealVectorStore,
5382
5368
  MemoryStoreSchema,
5383
5369
  MemoryUpdateSchema,
5384
5370
  MemorySearchSchema,
@@ -5406,10 +5392,24 @@ export {
5406
5392
  StandardUpdateSchema,
5407
5393
  StandardSearchSchema,
5408
5394
  TOOL_DEFINITIONS,
5395
+ encodeCursor,
5396
+ decodeCursor,
5397
+ listResources,
5398
+ listResourceTemplates,
5399
+ completeResourceArgument,
5400
+ readResource,
5401
+ createSessionContext,
5402
+ updateSessionFromInitialize,
5403
+ updateSessionRoots,
5404
+ extractRootsFromResult,
5405
+ getFilesystemRoots,
5406
+ isPathWithinRoots,
5407
+ findContainingRoot,
5408
+ inferRepoFromSession,
5409
+ PROMPTS,
5410
+ listPrompts,
5411
+ getPrompt,
5412
+ completePromptArgument,
5409
5413
  toContextSlug,
5410
- buildStandardVectorText,
5411
- SQLiteStore,
5412
- RealVectorStore,
5413
- MCP_PROTOCOL_VERSION,
5414
- CAPABILITIES
5414
+ buildStandardVectorText
5415
5415
  };