@vheins/local-memory-mcp 0.8.22 → 0.8.24

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,49 +1,3 @@
1
- // src/mcp/capabilities.ts
2
- import { fileURLToPath } from "url";
3
- import path from "path";
4
- var __dirname = path.dirname(fileURLToPath(import.meta.url));
5
- var pkgVersion = "0.1.0";
6
- if ("0.8.22") {
7
- pkgVersion = "0.8.22";
8
- } else {
9
- let searchDir = __dirname;
10
- for (let i = 0; i < 5; i++) {
11
- const candidate = path.join(searchDir, "package.json");
12
- try {
13
- if (fs.existsSync(candidate)) {
14
- const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
15
- if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
16
- pkgVersion = pkg.version;
17
- break;
18
- }
19
- }
20
- } catch {
21
- }
22
- searchDir = path.dirname(searchDir);
23
- }
24
- }
25
- var MCP_PROTOCOL_VERSION = "2025-11-25";
26
- var CAPABILITIES = {
27
- serverInfo: {
28
- name: "mcp-memory-local",
29
- version: pkgVersion
30
- },
31
- capabilities: {
32
- completions: {},
33
- logging: {},
34
- resources: {
35
- subscribe: true,
36
- listChanged: true
37
- },
38
- tools: {
39
- listChanged: false
40
- },
41
- prompts: {
42
- listChanged: true
43
- }
44
- }
45
- };
46
-
47
1
  // src/mcp/utils/logger.ts
48
2
  import fs from "fs";
49
3
  var LEVELS = {
@@ -185,6 +139,108 @@ function createFileSink(logDir, maxFiles = 5) {
185
139
  };
186
140
  }
187
141
 
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
+
188
244
  // src/mcp/storage/sqlite.ts
189
245
  import Database from "better-sqlite3";
190
246
  import path3 from "path";
@@ -1957,7 +2013,7 @@ var WriteLock = class {
1957
2013
  async withLock(fn) {
1958
2014
  await this.acquire();
1959
2015
  try {
1960
- return fn();
2016
+ return await fn();
1961
2017
  } finally {
1962
2018
  await this.release();
1963
2019
  }
@@ -2118,1721 +2174,1645 @@ var SQLiteStore = class _SQLiteStore {
2118
2174
  }
2119
2175
  };
2120
2176
 
2121
- // src/mcp/session.ts
2122
- import path4 from "path";
2177
+ // src/mcp/capabilities.ts
2123
2178
  import { fileURLToPath as fileURLToPath2 } from "url";
2124
- function createSessionContext() {
2125
- return {
2126
- roots: [],
2127
- supportsRoots: false,
2128
- supportsSampling: false,
2129
- supportsSamplingTools: false,
2130
- supportsElicitation: false,
2131
- supportsElicitationForm: false,
2132
- supportsElicitationUrl: false
2133
- };
2134
- }
2135
- function updateSessionFromInitialize(session, params) {
2136
- const capabilities = params?.capabilities || {};
2137
- session.clientInfo = params?.clientInfo;
2138
- session.clientCapabilities = capabilities;
2139
- session.supportsRoots = Boolean(capabilities.roots);
2140
- session.supportsSampling = Boolean(capabilities.sampling);
2141
- const sampling = capabilities.sampling;
2142
- session.supportsSamplingTools = Boolean(sampling?.tools);
2143
- session.supportsElicitation = Boolean(capabilities.elicitation);
2144
- session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
2145
- session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
2146
- }
2147
- function supportsElicitationMode(capability, mode) {
2148
- if (!capability || typeof capability !== "object") {
2149
- return false;
2150
- }
2151
- const cap = capability;
2152
- if (mode === "form") {
2153
- return Object.keys(cap).length === 0 || typeof cap.form === "object";
2154
- }
2155
- return typeof cap.url === "object";
2156
- }
2157
- function updateSessionRoots(session, roots) {
2158
- const normalized = normalizeRoots(roots);
2159
- const previous = JSON.stringify(session.roots);
2160
- const next = JSON.stringify(normalized);
2161
- session.roots = normalized;
2162
- return previous !== next;
2163
- }
2164
- function normalizeRoots(roots) {
2165
- if (!Array.isArray(roots)) return [];
2166
- const seen = /* @__PURE__ */ new Set();
2167
- const normalized = [];
2168
- for (const root of roots) {
2169
- if (!root || typeof root !== "object") continue;
2170
- const r = root;
2171
- const uri = typeof r.uri === "string" ? r.uri : void 0;
2172
- const name = typeof r.name === "string" ? r.name : void 0;
2173
- if (!uri || seen.has(uri)) continue;
2174
- seen.add(uri);
2175
- normalized.push({ uri, name });
2176
- }
2177
- return normalized;
2178
- }
2179
- function extractRootsFromResult(result) {
2180
- return normalizeRoots(result?.roots);
2181
- }
2182
- function getFilesystemRoots(session) {
2183
- if (!session) return [];
2184
- const resolved = [];
2185
- for (const root of session.roots) {
2186
- if (!root.uri.startsWith("file://")) continue;
2179
+ import path4 from "path";
2180
+ var __dirname = path4.dirname(fileURLToPath2(import.meta.url));
2181
+ var pkgVersion = "0.1.0";
2182
+ if ("0.8.24") {
2183
+ pkgVersion = "0.8.24";
2184
+ } else {
2185
+ let searchDir = __dirname;
2186
+ for (let i = 0; i < 5; i++) {
2187
+ const candidate = path4.join(searchDir, "package.json");
2187
2188
  try {
2188
- resolved.push(path4.resolve(fileURLToPath2(root.uri)));
2189
+ if (fs.existsSync(candidate)) {
2190
+ const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
2191
+ if (pkg.name === "@vheins/local-memory-mcp" && pkg.version) {
2192
+ pkgVersion = pkg.version;
2193
+ break;
2194
+ }
2195
+ }
2189
2196
  } catch {
2190
2197
  }
2198
+ searchDir = path4.dirname(searchDir);
2191
2199
  }
2192
- return resolved;
2193
- }
2194
- function isPathWithinRoots(targetPath, session) {
2195
- const roots = getFilesystemRoots(session);
2196
- if (roots.length === 0) return true;
2197
- const normalizedTarget = path4.resolve(targetPath);
2198
- return roots.some((rootPath) => {
2199
- const relative = path4.relative(rootPath, normalizedTarget);
2200
- return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
2201
- });
2202
2200
  }
2203
- function findContainingRoot(targetPath, session) {
2204
- const roots = getFilesystemRoots(session);
2205
- if (roots.length === 0) return null;
2206
- const normalizedTarget = path4.resolve(targetPath);
2207
- for (const rootPath of roots) {
2208
- const relative = path4.relative(rootPath, normalizedTarget);
2209
- if (relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative)) {
2210
- return rootPath;
2201
+ var MCP_PROTOCOL_VERSION = "2025-11-25";
2202
+ var CAPABILITIES = {
2203
+ serverInfo: {
2204
+ name: "mcp-memory-local",
2205
+ version: pkgVersion
2206
+ },
2207
+ capabilities: {
2208
+ completions: {},
2209
+ logging: {},
2210
+ resources: {
2211
+ subscribe: true,
2212
+ listChanged: true
2213
+ },
2214
+ tools: {
2215
+ listChanged: false
2216
+ },
2217
+ prompts: {
2218
+ listChanged: true
2211
2219
  }
2212
2220
  }
2213
- return null;
2221
+ };
2222
+
2223
+ // src/mcp/utils/pagination.ts
2224
+ function encodeCursor(offset) {
2225
+ return Buffer.from(String(offset), "utf8").toString("base64");
2214
2226
  }
2215
- function inferRepoFromSession(session) {
2216
- const roots = getFilesystemRoots(session);
2217
- if (roots.length === 1) {
2218
- return path4.basename(roots[0]);
2227
+ function decodeCursor(cursor) {
2228
+ if (cursor === void 0 || cursor === null || cursor === "") {
2229
+ return 0;
2219
2230
  }
2220
- return void 0;
2231
+ if (typeof cursor !== "string" || cursor.trim() === "") {
2232
+ throw invalidPaginationParams("Invalid cursor");
2233
+ }
2234
+ let decoded;
2235
+ try {
2236
+ decoded = Buffer.from(cursor, "base64").toString("utf8");
2237
+ } catch {
2238
+ throw invalidPaginationParams("Invalid cursor");
2239
+ }
2240
+ if (!/^\d+$/.test(decoded)) {
2241
+ throw invalidPaginationParams("Invalid cursor");
2242
+ }
2243
+ const offset = Number.parseInt(decoded, 10);
2244
+ if (!Number.isFinite(offset) || offset < 0) {
2245
+ throw invalidPaginationParams("Invalid cursor");
2246
+ }
2247
+ return offset;
2248
+ }
2249
+ function invalidPaginationParams(message) {
2250
+ const error = new Error(message);
2251
+ error.code = -32602;
2252
+ return error;
2221
2253
  }
2222
2254
 
2223
- // src/mcp/tools/schemas.ts
2224
- import { z } from "zod";
2225
- var MemoryScopeSchema = z.object({
2226
- repo: z.string().min(1).transform(normalizeRepo),
2227
- branch: z.string().optional(),
2228
- folder: z.string().optional(),
2229
- language: z.string().optional()
2230
- });
2231
- var MemoryTypeSchema = z.enum([
2232
- "code_fact",
2233
- "decision",
2234
- "mistake",
2235
- "pattern",
2236
- "agent_handoff",
2237
- "agent_registered",
2238
- "file_claim",
2239
- "task_archive"
2240
- ]);
2241
- var MemoryStoreSchema = z.object({
2242
- code: z.string().max(20).optional(),
2243
- type: MemoryTypeSchema,
2244
- title: z.string().min(3).max(255),
2245
- content: z.string().min(10),
2246
- importance: z.number().min(1).max(5),
2247
- agent: z.string().min(1),
2248
- role: z.string().optional().default("unknown"),
2249
- model: z.string().min(1),
2250
- scope: MemoryScopeSchema,
2251
- ttlDays: z.number().min(1).optional(),
2252
- supersedes: z.string().uuid().optional(),
2253
- tags: z.array(z.string()).optional(),
2254
- metadata: z.record(z.string(), z.any()).optional(),
2255
- is_global: z.boolean().default(false),
2256
- structured: z.boolean().default(false)
2257
- });
2258
- var MemoryUpdateSchema = z.object({
2259
- id: z.string().uuid(),
2260
- type: MemoryTypeSchema.optional(),
2261
- title: z.string().min(3).max(255).optional(),
2262
- content: z.string().min(10).optional(),
2263
- importance: z.number().min(1).max(5).optional(),
2264
- agent: z.string().optional(),
2265
- role: z.string().optional(),
2266
- status: z.enum(["active", "archived"]).optional(),
2267
- supersedes: z.string().uuid().optional(),
2268
- tags: z.array(z.string()).optional(),
2269
- metadata: z.record(z.string(), z.any()).optional(),
2270
- is_global: z.boolean().optional(),
2271
- completed_at: z.string().optional(),
2272
- structured: z.boolean().default(false)
2273
- }).refine(
2274
- (data) => data.type !== void 0 || data.content !== void 0 || data.title !== void 0 || data.importance !== void 0 || data.status !== void 0 || data.supersedes !== void 0 || data.tags !== void 0 || data.metadata !== void 0 || data.is_global !== void 0 || data.agent !== void 0 || data.role !== void 0 || data.completed_at !== void 0,
2275
- { message: "At least one field must be provided for update" }
2276
- );
2277
- var MemorySearchSchema = z.object({
2278
- query: z.string().min(3),
2279
- prompt: z.string().optional(),
2280
- repo: z.string().min(1).transform(normalizeRepo),
2281
- types: z.array(MemoryTypeSchema).optional(),
2282
- minImportance: z.number().min(1).max(5).optional(),
2283
- limit: z.number().min(1).max(100).default(5),
2284
- offset: z.number().min(0).default(0),
2285
- includeRecap: z.boolean().default(false),
2286
- current_file_path: z.string().optional(),
2287
- include_archived: z.boolean().default(false),
2288
- current_tags: z.array(z.string()).optional(),
2289
- scope: MemoryScopeSchema.partial().optional(),
2290
- structured: z.boolean().default(false)
2291
- });
2292
- var MemoryAcknowledgeSchema = z.object({
2293
- memory_id: z.string().uuid(),
2294
- status: z.enum(["used", "irrelevant", "contradictory"]),
2295
- application_context: z.string().min(10).optional(),
2296
- structured: z.boolean().default(false)
2297
- });
2298
- var MemoryRecapSchema = z.object({
2299
- repo: z.string().min(1).transform(normalizeRepo),
2300
- limit: z.number().min(1).max(50).default(20),
2301
- offset: z.number().min(0).default(0),
2302
- structured: z.boolean().default(false)
2303
- });
2304
- var MemoryDeleteSchema = z.object({
2305
- repo: z.string().min(1).transform(normalizeRepo).optional(),
2306
- id: z.string().uuid().optional(),
2307
- ids: z.array(z.string().uuid()).min(1).optional(),
2308
- structured: z.boolean().default(false)
2309
- }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2310
- message: "Either 'id' or 'ids' must be provided for deletion"
2311
- });
2312
- var MemorySummarizeSchema = z.object({
2313
- repo: z.string().min(1).transform(normalizeRepo),
2314
- signals: z.array(z.string().max(200)).min(1),
2315
- structured: z.boolean().default(false)
2316
- });
2317
- var MemorySynthesizeSchema = z.object({
2318
- repo: z.string().min(1).transform(normalizeRepo).optional(),
2319
- objective: z.string().min(5),
2320
- current_file_path: z.string().optional(),
2321
- include_summary: z.boolean().default(true),
2322
- include_tasks: z.boolean().default(true),
2323
- use_tools: z.boolean().default(true),
2324
- max_iterations: z.number().int().min(1).max(5).default(3),
2325
- max_tokens: z.number().int().min(128).max(4e3).default(1200),
2326
- structured: z.boolean().default(false)
2327
- });
2328
- var TaskStatusSchema = z.enum(["backlog", "pending", "in_progress", "completed", "canceled", "blocked"]);
2329
- var TaskPrioritySchema = z.number().min(1).max(5);
2330
- var SingleTaskCreateSchema = z.object({
2331
- task_code: z.string().min(1),
2332
- phase: z.string().min(1),
2333
- title: z.string().min(3).max(100),
2334
- description: z.string().min(1),
2335
- status: TaskStatusSchema.default("backlog"),
2336
- priority: TaskPrioritySchema.default(3),
2337
- agent: z.string().optional(),
2338
- role: z.string().optional(),
2339
- doc_path: z.string().optional(),
2340
- tags: z.array(z.string()).optional(),
2341
- metadata: z.record(z.string(), z.any()).optional(),
2342
- parent_id: z.string().uuid().optional(),
2343
- depends_on: z.string().uuid().optional(),
2344
- est_tokens: z.number().int().min(0).optional()
2345
- });
2346
- var TaskCreateSchema = z.object({
2347
- repo: z.string().min(1).transform(normalizeRepo),
2348
- // Allow single task fields at top level (backward compatibility & single use)
2349
- task_code: z.string().min(1).optional(),
2350
- phase: z.string().min(1).optional(),
2351
- title: z.string().min(3).max(100).optional(),
2352
- description: z.string().min(1).optional(),
2353
- status: TaskStatusSchema.optional(),
2354
- priority: TaskPrioritySchema.optional(),
2355
- agent: z.string().optional(),
2356
- role: z.string().optional(),
2357
- doc_path: z.string().optional(),
2358
- tags: z.array(z.string()).optional(),
2359
- metadata: z.record(z.string(), z.any()).optional(),
2360
- parent_id: z.string().uuid().optional(),
2361
- depends_on: z.string().uuid().optional(),
2362
- est_tokens: z.number().int().min(0).optional(),
2363
- // Allow bulk tasks
2364
- tasks: z.array(SingleTaskCreateSchema).min(1).optional(),
2365
- structured: z.boolean().default(false)
2366
- }).refine(
2367
- (data) => {
2368
- if (data.tasks) return true;
2369
- return !!(data.task_code && data.phase && data.title && data.description);
2370
- },
2371
- { message: "Either 'tasks' array or single task fields (task_code, phase, title, description) must be provided" }
2372
- );
2373
- var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
2374
- repo: z.string().min(1).transform(normalizeRepo).optional(),
2375
- structured: z.boolean().default(false)
2376
- });
2377
- var TaskUpdateSchema = z.object({
2378
- repo: z.string().min(1).transform(normalizeRepo),
2379
- id: z.string().uuid().optional(),
2380
- ids: z.array(z.string().uuid()).min(1).optional(),
2381
- task_code: z.string().optional(),
2382
- phase: z.string().optional(),
2383
- title: z.string().min(3).max(100).optional(),
2384
- description: z.string().optional(),
2385
- status: TaskStatusSchema.optional(),
2386
- priority: TaskPrioritySchema.optional(),
2387
- agent: z.string().min(1, "agent name is required").optional(),
2388
- role: z.string().min(1, "agent role is required").optional(),
2389
- model: z.string().optional(),
2390
- comment: z.string().min(1).optional(),
2391
- doc_path: z.string().optional(),
2392
- tags: z.array(z.string()).optional(),
2393
- metadata: z.record(z.string(), z.any()).optional(),
2394
- parent_id: z.string().uuid().optional(),
2395
- depends_on: z.string().uuid().optional(),
2396
- est_tokens: z.number().int().min(0).optional(),
2397
- force: z.boolean().optional(),
2398
- structured: z.boolean().default(false)
2399
- }).refine((data) => data.id !== void 0 || data.ids !== void 0 || data.task_code !== void 0, {
2400
- message: "Either 'id', 'ids', or 'task_code' must be provided for update"
2401
- }).refine((data) => Object.keys(data).length > 2, {
2402
- message: "At least one field besides repo and id/ids must be provided for update"
2403
- });
2404
- var TaskListSchema = z.object({
2405
- repo: z.string().min(1).transform(normalizeRepo),
2406
- status: z.string().optional(),
2407
- phase: z.string().optional(),
2408
- query: z.string().optional(),
2409
- limit: z.number().min(1).max(100).default(15),
2410
- offset: z.number().min(0).default(0),
2411
- structured: z.boolean().default(false)
2412
- });
2413
- var TaskSearchSchema = z.object({
2414
- repo: z.string().min(1).transform(normalizeRepo),
2415
- query: z.string().min(1),
2416
- status: z.string().optional(),
2417
- limit: z.number().min(1).max(100).default(10),
2418
- offset: z.number().min(0).default(0),
2419
- structured: z.boolean().default(false)
2420
- });
2421
- var TaskDeleteSchema = z.object({
2422
- repo: z.string().min(1).transform(normalizeRepo),
2423
- id: z.string().uuid().optional(),
2424
- ids: z.array(z.string().uuid()).min(1).optional(),
2425
- structured: z.boolean().default(false)
2426
- }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2427
- message: "Either 'id' or 'ids' must be provided for deletion"
2428
- });
2429
- var MemoryDetailSchema = z.object({
2430
- id: z.string().uuid().optional(),
2431
- code: z.string().max(20).optional(),
2432
- structured: z.boolean().default(false)
2433
- }).refine((data) => data.id !== void 0 || data.code !== void 0, {
2434
- message: "Either id or code must be provided"
2435
- });
2436
- var TaskGetSchema = z.object({
2437
- repo: z.string().min(1).transform(normalizeRepo),
2438
- id: z.string().uuid().optional(),
2439
- task_code: z.string().optional(),
2440
- structured: z.boolean().default(false)
2441
- }).refine((data) => data.id !== void 0 || data.task_code !== void 0, {
2442
- message: "Either id or task_code must be provided"
2443
- });
2444
- var TOOL_DEFINITIONS = [
2445
- {
2446
- name: "memory-synthesize",
2447
- title: "Memory Synthesize",
2448
- description: "Use client sampling to synthesize a grounded answer from local memory and tasks. Best for project briefings, tradeoff summaries, and context-aware answers.",
2449
- annotations: {
2450
- readOnlyHint: true,
2451
- idempotentHint: true,
2452
- openWorldHint: false
2453
- },
2454
- execution: {
2455
- taskSupport: "optional"
2456
- },
2457
- inputSchema: {
2458
- type: "object",
2459
- properties: {
2460
- repo: { type: "string", description: "Repository name. Optional when a single MCP root is active." },
2461
- objective: { type: "string", minLength: 5, description: "Question or synthesis objective." },
2462
- current_file_path: {
2463
- type: "string",
2464
- description: "Optional absolute file path for workspace-local grounding."
2465
- },
2466
- include_summary: { type: "boolean", default: true },
2467
- include_tasks: { type: "boolean", default: true },
2468
- use_tools: {
2469
- type: "boolean",
2470
- default: true,
2471
- description: "Allow the sampled model to call local memory/task tools during synthesis when the client supports sampling.tools."
2472
- },
2473
- max_iterations: { type: "number", minimum: 1, maximum: 5, default: 3 },
2474
- max_tokens: { type: "number", minimum: 128, maximum: 4e3, default: 1200 },
2475
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON results." }
2476
- },
2477
- required: ["objective"]
2478
- },
2479
- outputSchema: {
2480
- type: "object",
2481
- properties: {
2482
- repo: { type: "string" },
2483
- objective: { type: "string" },
2484
- answer: { type: "string" },
2485
- model: { type: "string" },
2486
- stopReason: { type: "string" },
2487
- iterations: { type: "number" },
2488
- toolCalls: { type: "number" }
2489
- },
2490
- required: ["repo", "objective", "answer", "iterations", "toolCalls"]
2491
- }
2492
- },
2493
- {
2494
- name: "task-create-interactive",
2495
- title: "Interactive Task Create",
2496
- description: "Create a task with MCP elicitation fallback for any missing required fields. Best when an agent knows a task is needed but still needs user confirmation for repo, title, or phase.",
2497
- annotations: {
2498
- readOnlyHint: false,
2499
- idempotentHint: false,
2500
- destructiveHint: false,
2501
- openWorldHint: false
2502
- },
2503
- inputSchema: {
2504
- type: "object",
2505
- properties: {
2506
- repo: {
2507
- type: "string",
2508
- description: "Repository name. Optional when it can be inferred from MCP roots or elicited from the user."
2509
- },
2510
- task_code: { type: "string" },
2511
- phase: { type: "string" },
2512
- title: { type: "string", minLength: 3, maxLength: 100 },
2513
- description: { type: "string", minLength: 1 },
2514
- status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
2515
- priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
2516
- agent: { type: "string" },
2517
- role: { type: "string" },
2518
- doc_path: { type: "string" },
2519
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
2520
- }
2521
- },
2522
- outputSchema: {
2523
- type: "object",
2524
- properties: {
2525
- repo: { type: "string" },
2526
- task_code: { type: "string" },
2527
- phase: { type: "string" },
2528
- title: { type: "string" },
2529
- status: { type: "string" },
2530
- priority: { type: "number" }
2531
- },
2532
- required: ["repo", "task_code", "phase", "title", "status", "priority"]
2533
- }
2534
- },
2535
- {
2536
- name: "memory-detail",
2537
- title: "Memory Detail",
2538
- description: "Fetch full details of a specific memory by ID. Use this when you have a memory ID (e.g. from search results) and need to read the full content.",
2539
- inputSchema: {
2540
- type: "object",
2541
- properties: {
2542
- id: { type: "string", format: "uuid", description: "Memory entry ID" },
2543
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON details." }
2544
- },
2545
- required: ["id"]
2546
- }
2547
- },
2548
- {
2549
- name: "task-detail",
2550
- title: "Task Detail",
2551
- description: "Fetch full details of a specific task by ID or task code. Use this when you have a task ID or code and need to read the full description and comments.",
2552
- inputSchema: {
2553
- type: "object",
2554
- properties: {
2555
- repo: { type: "string", description: "Repository name" },
2556
- id: { type: "string", format: "uuid", description: "Task ID (optional if task_code is provided)" },
2557
- task_code: { type: "string", description: "Task code (e.g. TASK-001) (optional if id is provided)" },
2558
- structured: {
2559
- type: "boolean",
2560
- default: false,
2561
- description: "If true, returns structured JSON without the text content details."
2562
- }
2563
- },
2564
- required: ["repo"]
2255
+ // src/mcp/utils/completion.ts
2256
+ var MAX_COMPLETION_VALUES = 100;
2257
+ function rankCompletionValues(candidates, input) {
2258
+ const unique = [...new Set(candidates.filter(Boolean))];
2259
+ const needle = input.trim().toLowerCase();
2260
+ if (!needle) {
2261
+ return unique.slice(0, MAX_COMPLETION_VALUES);
2262
+ }
2263
+ 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);
2264
+ }
2265
+ function scoreCompletionValue(value, needle) {
2266
+ const haystack = value.toLowerCase();
2267
+ if (haystack === needle) return 100;
2268
+ if (haystack.startsWith(needle)) return 75;
2269
+ if (haystack.includes(needle)) return 50;
2270
+ const compactNeedle = needle.replace(/[\s_-]+/g, "");
2271
+ const compactHaystack = haystack.replace(/[\s_-]+/g, "");
2272
+ if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
2273
+ return 0;
2274
+ }
2275
+
2276
+ // src/mcp/resources/index.ts
2277
+ var DEFAULT_PAGE_SIZE = 25;
2278
+ var MAX_PAGE_SIZE = 100;
2279
+ function listResources(session, params) {
2280
+ const resources = [
2281
+ {
2282
+ uri: "repository://index",
2283
+ name: "Repository Index",
2284
+ title: "Repository Index",
2285
+ description: "List of all known repositories with memory/task counts and last activity",
2286
+ mimeType: "application/json",
2287
+ annotations: {
2288
+ audience: ["assistant"],
2289
+ priority: 1,
2290
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2291
+ }
2292
+ },
2293
+ {
2294
+ uri: "session://roots",
2295
+ name: "Session Roots",
2296
+ title: "Session Roots",
2297
+ description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
2298
+ mimeType: "application/json",
2299
+ size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
2300
+ annotations: {
2301
+ audience: ["assistant"],
2302
+ priority: 0.95,
2303
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2304
+ }
2565
2305
  }
2566
- },
2567
- {
2568
- name: "memory-store",
2569
- title: "Memory Store",
2570
- description: "Store a new memory entry. Keep 'title' concise and human-readable; do not embed agent/role/date metadata in the title. Put auxiliary context into 'metadata'. Use 'tags' for tech-stack and 'is_global' for universal rules.",
2571
- annotations: {
2572
- readOnlyHint: false,
2573
- idempotentHint: false,
2574
- destructiveHint: false,
2575
- openWorldHint: false
2306
+ ];
2307
+ return paginateEntries("resources", resources, params);
2308
+ }
2309
+ function listResourceTemplates(params) {
2310
+ const templates = [
2311
+ // ── Memory ──────────────────────────────────────────────────────────────
2312
+ {
2313
+ uriTemplate: "repository://{name}/memories",
2314
+ name: "Repository Memories",
2315
+ title: "Repository Memories",
2316
+ description: "All active memory entries for a specific repository",
2317
+ mimeType: "application/json",
2318
+ annotations: { audience: ["assistant"], priority: 0.85 }
2576
2319
  },
2577
- inputSchema: {
2578
- type: "object",
2579
- properties: {
2580
- type: {
2581
- type: "string",
2582
- enum: [
2583
- "code_fact",
2584
- "decision",
2585
- "mistake",
2586
- "pattern",
2587
- "agent_handoff",
2588
- "agent_registered",
2589
- "file_claim",
2590
- "task_archive"
2591
- ],
2592
- description: "Type of memory being stored"
2593
- },
2594
- title: {
2595
- type: "string",
2596
- minLength: 3,
2597
- maxLength: 100,
2598
- description: "Short human-readable title for the memory. Do not embed bracketed metadata like agent/role/date prefixes here."
2599
- },
2600
- content: {
2601
- type: "string",
2602
- minLength: 10,
2603
- description: "The memory content"
2604
- },
2605
- importance: {
2606
- type: "number",
2607
- minimum: 1,
2608
- maximum: 5,
2609
- description: "Importance score (1-5)"
2610
- },
2611
- agent: {
2612
- type: "string",
2613
- description: "Name of the agent creating this memory"
2614
- },
2615
- role: {
2616
- type: "string",
2617
- description: "Role of the agent creating this memory"
2618
- },
2619
- model: {
2620
- type: "string",
2621
- description: "AI model used by the agent"
2622
- },
2623
- scope: {
2624
- type: "object",
2625
- properties: {
2626
- repo: { type: "string", description: "Repository name" },
2627
- branch: { type: "string" },
2628
- folder: { type: "string" },
2629
- language: { type: "string" }
2630
- },
2631
- required: ["repo"]
2632
- },
2633
- tags: {
2634
- type: "array",
2635
- items: { type: "string" },
2636
- description: "Technology stack tags (e.g., ['filament', 'laravel'])"
2637
- },
2638
- metadata: {
2639
- type: "object",
2640
- description: "Structured metadata for non-title context such as source agent, claim fields, or timestamps"
2641
- },
2642
- is_global: {
2643
- type: "boolean",
2644
- description: "If true, this memory is shared across all repositories"
2645
- },
2646
- ttlDays: { type: "number", minimum: 1 },
2647
- supersedes: { type: "string", format: "uuid" },
2648
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the stored memory." }
2649
- },
2650
- required: ["type", "title", "content", "importance", "scope", "agent", "model"]
2320
+ {
2321
+ uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
2322
+ name: "Filtered Repository Memories",
2323
+ title: "Filtered Repository Memories",
2324
+ description: "Filter or search memories within a repository by keyword, type, or tag",
2325
+ mimeType: "application/json",
2326
+ annotations: { audience: ["assistant"], priority: 0.8 }
2651
2327
  },
2652
- outputSchema: {
2653
- type: "object",
2654
- properties: {
2655
- success: { type: "boolean" },
2656
- id: { type: "string" },
2657
- code: { type: "string" },
2658
- repo: { type: "string" },
2659
- type: { type: "string" },
2660
- title: { type: "string" },
2661
- error: { type: "string" },
2662
- message: { type: "string" }
2663
- },
2664
- required: ["success"]
2665
- }
2666
- },
2667
- {
2668
- name: "memory-acknowledge",
2669
- title: "Memory Acknowledge",
2670
- description: "Acknowledge the use of a memory or report its irrelevance/contradiction. Mandatory after using memory to generate code.",
2671
- annotations: {
2672
- readOnlyHint: false,
2673
- idempotentHint: false,
2674
- openWorldHint: false
2328
+ {
2329
+ uriTemplate: "memory://{id}",
2330
+ name: "Memory Detail",
2331
+ title: "Memory Detail",
2332
+ description: "Full content and statistics for a specific memory UUID",
2333
+ mimeType: "application/json",
2334
+ annotations: { audience: ["assistant"], priority: 0.75 }
2675
2335
  },
2676
- inputSchema: {
2677
- type: "object",
2678
- properties: {
2679
- memory_id: { type: "string", format: "uuid" },
2680
- status: { type: "string", enum: ["used", "irrelevant", "contradictory"] },
2681
- application_context: { type: "string", minLength: 10 },
2682
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
2683
- },
2684
- required: ["memory_id", "status"]
2336
+ // ── Tasks ────────────────────────────────────────────────────────────────
2337
+ {
2338
+ uriTemplate: "repository://{name}/tasks",
2339
+ name: "Repository Tasks",
2340
+ title: "Repository Tasks",
2341
+ description: "All active tasks for a specific repository",
2342
+ mimeType: "application/json",
2343
+ annotations: { audience: ["assistant"], priority: 0.9 }
2685
2344
  },
2686
- outputSchema: {
2687
- type: "object",
2688
- properties: {
2689
- success: { type: "boolean" },
2690
- id: { type: "string" },
2691
- status: { type: "string" }
2692
- },
2693
- required: ["success", "id", "status"]
2694
- }
2695
- },
2696
- {
2697
- name: "memory-update",
2698
- title: "Memory Update",
2699
- description: "Update an existing memory entry. Keep 'title' concise and move agent/role/date or claim context into 'metadata' instead of the title.",
2700
- annotations: {
2701
- readOnlyHint: false,
2702
- idempotentHint: false,
2703
- destructiveHint: false,
2704
- openWorldHint: false
2345
+ {
2346
+ uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
2347
+ name: "Filtered Repository Tasks",
2348
+ title: "Filtered Repository Tasks",
2349
+ description: "Filter tasks within a repository by status or priority level",
2350
+ mimeType: "application/json",
2351
+ annotations: { audience: ["assistant"], priority: 0.85 }
2705
2352
  },
2706
- inputSchema: {
2707
- type: "object",
2708
- properties: {
2709
- id: { type: "string", format: "uuid" },
2710
- type: {
2711
- type: "string",
2712
- enum: [
2713
- "code_fact",
2714
- "decision",
2715
- "mistake",
2716
- "pattern",
2717
- "agent_handoff",
2718
- "agent_registered",
2719
- "file_claim",
2720
- "task_archive"
2721
- ]
2722
- },
2723
- title: { type: "string", minLength: 3, maxLength: 100 },
2724
- content: { type: "string", minLength: 10 },
2725
- importance: { type: "number", minimum: 1, maximum: 5 },
2726
- agent: { type: "string" },
2727
- role: { type: "string" },
2728
- status: { type: "string", enum: ["active", "archived"] },
2729
- supersedes: { type: "string", format: "uuid" },
2730
- tags: { type: "array", items: { type: "string" } },
2731
- metadata: { type: "object" },
2732
- is_global: { type: "boolean" },
2733
- completed_at: { type: "string" },
2734
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the updated memory." }
2735
- },
2736
- required: ["id"]
2353
+ {
2354
+ uriTemplate: "task://{id}",
2355
+ name: "Task Detail",
2356
+ title: "Task Detail",
2357
+ description: "Full content and comments for a specific task UUID",
2358
+ mimeType: "application/json",
2359
+ annotations: { audience: ["assistant"], priority: 0.8 }
2737
2360
  },
2738
- outputSchema: {
2739
- type: "object",
2740
- properties: {
2741
- success: { type: "boolean" },
2742
- id: { type: "string" },
2743
- repo: { type: "string" },
2744
- updatedFields: {
2745
- type: "array",
2746
- items: { type: "string" }
2747
- }
2748
- },
2749
- required: ["success", "id", "repo", "updatedFields"]
2750
- }
2751
- },
2752
- {
2753
- name: "memory-search",
2754
- title: "Memory Search",
2755
- description: "NAVIGATION LAYER: Returns a pointer table of matching memory IDs only. Returns columns [id, title, type, importance] \u2014 NO content. Retrieve full memory via memory-detail. Use 'current_tags' to find tech-stack specific knowledge from other projects.",
2756
- annotations: {
2757
- readOnlyHint: true,
2758
- idempotentHint: true,
2759
- openWorldHint: false
2361
+ // ── Repository extras ────────────────────────────────────────────────────
2362
+ {
2363
+ uriTemplate: "repository://{name}/summary",
2364
+ name: "Repository Summary",
2365
+ title: "Repository Summary",
2366
+ description: "High-level architectural summary for a repository",
2367
+ mimeType: "text/plain",
2368
+ annotations: { audience: ["assistant"], priority: 0.95 }
2760
2369
  },
2761
- inputSchema: {
2762
- type: "object",
2763
- properties: {
2764
- query: { type: "string", minLength: 3 },
2765
- prompt: { type: "string" },
2766
- repo: { type: "string" },
2767
- current_tags: {
2768
- type: "array",
2769
- items: { type: "string" },
2770
- description: "Active tech stack tags (e.g., ['filament', 'react'])"
2771
- },
2772
- types: {
2773
- type: "array",
2774
- items: {
2775
- type: "string",
2776
- enum: [
2777
- "code_fact",
2778
- "decision",
2779
- "mistake",
2780
- "pattern",
2781
- "agent_handoff",
2782
- "agent_registered",
2783
- "file_claim",
2784
- "task_archive"
2785
- ]
2370
+ {
2371
+ uriTemplate: "repository://{name}/actions",
2372
+ name: "Repository Actions",
2373
+ title: "Repository Actions",
2374
+ description: "Audit log of agent tool actions scoped to a repository",
2375
+ mimeType: "application/json",
2376
+ annotations: { audience: ["assistant"], priority: 0.6 }
2377
+ },
2378
+ // ── Action detail ────────────────────────────────────────────────────────
2379
+ {
2380
+ uriTemplate: "action://{id}",
2381
+ name: "Action Detail",
2382
+ title: "Action Detail",
2383
+ description: "Full details of a specific audit log entry by integer ID",
2384
+ mimeType: "application/json",
2385
+ annotations: { audience: ["assistant"], priority: 0.55 }
2386
+ }
2387
+ ];
2388
+ return paginateEntries("resourceTemplates", templates, params);
2389
+ }
2390
+ function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
2391
+ 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") {
2392
+ if (argumentName === "name") {
2393
+ return rankCompletionValues(dataSources.repos, argumentValue);
2394
+ }
2395
+ }
2396
+ if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
2397
+ if (argumentName === "tag") {
2398
+ return rankCompletionValues(dataSources.tags, argumentValue);
2399
+ }
2400
+ }
2401
+ throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
2402
+ }
2403
+ function readResource(uri, db, session) {
2404
+ logger.info("[Tool] resource.read", { uri });
2405
+ if (uri === "repository://index") {
2406
+ const repos = db.system.listRepoNavigation();
2407
+ const payload = JSON.stringify(repos, null, 2);
2408
+ return {
2409
+ contents: [
2410
+ {
2411
+ uri,
2412
+ mimeType: "application/json",
2413
+ text: payload,
2414
+ size: Buffer.byteLength(payload, "utf8"),
2415
+ annotations: {
2416
+ audience: ["assistant"],
2417
+ priority: 1,
2418
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2786
2419
  }
2787
- },
2788
- minImportance: { type: "number", minimum: 1, maximum: 5 },
2789
- limit: { type: "number", minimum: 1, maximum: 100, default: 5 },
2790
- offset: { type: "number", minimum: 0, default: 0 },
2791
- includeRecap: { type: "boolean", default: false },
2792
- current_file_path: { type: "string" },
2793
- include_archived: { type: "boolean", default: false },
2794
- scope: {
2795
- type: "object",
2796
- properties: {
2797
- repo: { type: "string" },
2798
- branch: { type: "string" },
2799
- folder: { type: "string" },
2800
- language: { type: "string" }
2420
+ }
2421
+ ]
2422
+ };
2423
+ }
2424
+ if (uri === "session://roots") {
2425
+ const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
2426
+ return {
2427
+ contents: [
2428
+ {
2429
+ uri,
2430
+ mimeType: "application/json",
2431
+ text: payload,
2432
+ size: Buffer.byteLength(payload, "utf8"),
2433
+ annotations: {
2434
+ audience: ["assistant"],
2435
+ priority: 0.95,
2436
+ lastModified: (/* @__PURE__ */ new Date()).toISOString()
2801
2437
  }
2802
- },
2803
- structured: {
2804
- type: "boolean",
2805
- default: false,
2806
- description: "If true, returns structured JSON without the text content summary."
2807
2438
  }
2808
- },
2809
- required: ["query", "repo"]
2810
- },
2811
- outputSchema: {
2812
- type: "object",
2813
- properties: {
2814
- schema: { type: "string", enum: ["memory-search"] },
2815
- query: { type: "string" },
2816
- count: { type: "number", description: "Number of rows returned" },
2817
- total: { type: "number", description: "Total matching memories" },
2818
- offset: { type: "number" },
2819
- limit: { type: "number" },
2820
- results: {
2821
- type: "object",
2822
- properties: {
2823
- columns: {
2824
- type: "array",
2825
- items: { type: "string" },
2826
- description: "Column names: [id, title, type, importance]"
2827
- },
2828
- rows: {
2829
- type: "array",
2830
- items: { type: "array" },
2831
- description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
2439
+ ]
2440
+ };
2441
+ }
2442
+ const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
2443
+ if (memoryIdMatch) {
2444
+ const id = memoryIdMatch[1];
2445
+ const entry = db.memories.getByIdWithStats(id);
2446
+ if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
2447
+ const payload = JSON.stringify(entry, null, 2);
2448
+ return {
2449
+ contents: [
2450
+ {
2451
+ uri,
2452
+ mimeType: "application/json",
2453
+ text: payload,
2454
+ size: Buffer.byteLength(payload, "utf8"),
2455
+ annotations: {
2456
+ audience: ["assistant"],
2457
+ priority: 0.75,
2458
+ lastModified: entry.updated_at || entry.created_at
2459
+ }
2460
+ }
2461
+ ]
2462
+ };
2463
+ }
2464
+ const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
2465
+ if (taskIdMatch) {
2466
+ const id = taskIdMatch[1];
2467
+ const task = db.tasks.getTaskById(id);
2468
+ if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
2469
+ const payload = JSON.stringify(task, null, 2);
2470
+ return {
2471
+ contents: [
2472
+ {
2473
+ uri,
2474
+ mimeType: "application/json",
2475
+ text: payload,
2476
+ size: Buffer.byteLength(payload, "utf8"),
2477
+ annotations: {
2478
+ audience: ["assistant"],
2479
+ priority: 0.8,
2480
+ lastModified: task.updated_at || task.created_at
2481
+ }
2482
+ }
2483
+ ]
2484
+ };
2485
+ }
2486
+ const repoBase = parseRepoUri(uri);
2487
+ if (repoBase) {
2488
+ const { name, path: repoPath, query } = repoBase;
2489
+ if (repoPath === "summary") {
2490
+ const summary = db.summaries.getSummary(name);
2491
+ const text = summary?.summary || `No summary available for repository: ${name}`;
2492
+ return {
2493
+ contents: [
2494
+ {
2495
+ uri,
2496
+ mimeType: "text/plain",
2497
+ text,
2498
+ size: Buffer.byteLength(text, "utf8"),
2499
+ annotations: {
2500
+ audience: ["assistant"],
2501
+ priority: 0.95,
2502
+ lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
2832
2503
  }
2833
- },
2834
- required: ["columns", "rows"]
2504
+ }
2505
+ ]
2506
+ };
2507
+ }
2508
+ if (repoPath === "memories") {
2509
+ const search = query.get("search") || "";
2510
+ const type = query.get("type");
2511
+ const tag = query.get("tag");
2512
+ const result = db.memories.listMemoriesForDashboard({
2513
+ repo: name,
2514
+ type: type || void 0,
2515
+ tag: tag || void 0,
2516
+ search: search || void 0,
2517
+ limit: 50
2518
+ });
2519
+ const entries = result.items;
2520
+ const payload = JSON.stringify(entries, null, 2);
2521
+ return {
2522
+ contents: [
2523
+ {
2524
+ uri,
2525
+ mimeType: "application/json",
2526
+ text: payload,
2527
+ size: Buffer.byteLength(payload, "utf8"),
2528
+ annotations: {
2529
+ audience: ["assistant"],
2530
+ priority: 0.85,
2531
+ lastModified: deriveLastModifiedFromCollection(
2532
+ entries.map((e) => e.updated_at || e.created_at)
2533
+ )
2534
+ }
2535
+ }
2536
+ ]
2537
+ };
2538
+ }
2539
+ if (repoPath === "tasks") {
2540
+ const status = query.get("status");
2541
+ const priority = query.get("priority");
2542
+ let tasks;
2543
+ if (status && status !== "all") {
2544
+ const statuses = status.split(",").map((s) => s.trim());
2545
+ tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
2546
+ } else {
2547
+ tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
2548
+ }
2549
+ if (priority) {
2550
+ const p = Number(priority);
2551
+ if (!isNaN(p)) {
2552
+ tasks = tasks.filter((t) => t.priority === p);
2835
2553
  }
2836
- },
2837
- required: ["schema", "query", "count", "total", "offset", "limit", "results"]
2554
+ }
2555
+ const payload = JSON.stringify(tasks, null, 2);
2556
+ return {
2557
+ contents: [
2558
+ {
2559
+ uri,
2560
+ mimeType: "application/json",
2561
+ text: payload,
2562
+ size: Buffer.byteLength(payload, "utf8"),
2563
+ annotations: {
2564
+ audience: ["assistant"],
2565
+ priority: 0.9,
2566
+ lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
2567
+ }
2568
+ }
2569
+ ]
2570
+ };
2838
2571
  }
2839
- },
2840
- {
2841
- name: "memory-summarize",
2842
- title: "Memory Summarize",
2843
- description: "Update the summary for a repository",
2844
- annotations: {
2845
- readOnlyHint: false,
2846
- idempotentHint: false,
2847
- openWorldHint: false
2848
- },
2849
- inputSchema: {
2850
- type: "object",
2851
- properties: {
2852
- repo: { type: "string", description: "Repository name" },
2853
- signals: {
2854
- type: "array",
2855
- items: { type: "string", maxLength: 200 },
2856
- minItems: 1,
2857
- description: "High-level signals to include in summary"
2858
- },
2859
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the summary." }
2860
- },
2861
- required: ["repo", "signals"]
2862
- },
2863
- outputSchema: {
2864
- type: "object",
2865
- properties: {
2866
- success: { type: "boolean" },
2867
- repo: { type: "string" },
2868
- summary: { type: "string" },
2869
- signalCount: { type: "number" }
2870
- },
2871
- required: ["success", "repo", "summary", "signalCount"]
2572
+ if (repoPath === "actions") {
2573
+ const actions = db.actions.getRecentActions(name, 100);
2574
+ const payload = JSON.stringify(actions, null, 2);
2575
+ return {
2576
+ contents: [
2577
+ {
2578
+ uri,
2579
+ mimeType: "application/json",
2580
+ text: payload,
2581
+ size: Buffer.byteLength(payload, "utf8"),
2582
+ annotations: {
2583
+ audience: ["assistant"],
2584
+ priority: 0.6,
2585
+ lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
2586
+ }
2587
+ }
2588
+ ]
2589
+ };
2872
2590
  }
2873
- },
2874
- {
2875
- name: "memory-delete",
2876
- title: "Memory Delete",
2877
- description: "Soft-delete one or more memory entries. Supports single 'id' or bulk 'ids'.",
2878
- annotations: {
2879
- readOnlyHint: false,
2880
- idempotentHint: false,
2881
- destructiveHint: true,
2882
- openWorldHint: false
2883
- },
2884
- inputSchema: {
2885
- type: "object",
2886
- properties: {
2887
- repo: { type: "string", description: "Repository name (optional for single id)" },
2888
- id: { type: "string", format: "uuid", description: "Memory entry ID to delete" },
2889
- ids: {
2890
- type: "array",
2891
- items: { type: "string", format: "uuid" },
2892
- minItems: 1,
2893
- description: "Array of memory IDs to delete"
2894
- },
2895
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
2591
+ }
2592
+ const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
2593
+ if (actionIdMatch) {
2594
+ const id = Number(actionIdMatch[1]);
2595
+ const action = db.actions.getActionById(id);
2596
+ if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
2597
+ const payload = JSON.stringify(action, null, 2);
2598
+ return {
2599
+ contents: [
2600
+ {
2601
+ uri,
2602
+ mimeType: "application/json",
2603
+ text: payload,
2604
+ size: Buffer.byteLength(payload, "utf8"),
2605
+ annotations: {
2606
+ audience: ["assistant"],
2607
+ priority: 0.55,
2608
+ lastModified: action.created_at
2609
+ }
2610
+ }
2611
+ ]
2612
+ };
2613
+ }
2614
+ throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
2615
+ }
2616
+ function parseRepoUri(uri) {
2617
+ const prefix = "repository://";
2618
+ if (!uri.startsWith(prefix)) return null;
2619
+ const rest = uri.slice(prefix.length);
2620
+ const queryStart = rest.indexOf("?");
2621
+ const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
2622
+ const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
2623
+ const slashIdx = withoutQuery.indexOf("/");
2624
+ if (slashIdx === -1) return null;
2625
+ const name = withoutQuery.slice(0, slashIdx);
2626
+ const path6 = withoutQuery.slice(slashIdx + 1);
2627
+ if (!name || !path6) return null;
2628
+ return { name, path: path6, query: new URLSearchParams(queryString) };
2629
+ }
2630
+ function paginateEntries(key, entries, params) {
2631
+ const limit = normalizeLimit(params?.limit);
2632
+ const offset = decodeCursor(params?.cursor);
2633
+ const sliced = entries.slice(offset, offset + limit);
2634
+ const nextOffset = offset + sliced.length;
2635
+ return {
2636
+ [key]: sliced,
2637
+ nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
2638
+ };
2639
+ }
2640
+ function normalizeLimit(limit) {
2641
+ if (typeof limit !== "number" || !Number.isFinite(limit)) {
2642
+ return DEFAULT_PAGE_SIZE;
2643
+ }
2644
+ return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
2645
+ }
2646
+ function deriveLastModifiedFromCollection(values) {
2647
+ const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
2648
+ return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
2649
+ }
2650
+ function resourceNotFound(message, uri) {
2651
+ const error = new Error(message);
2652
+ error.code = -32002;
2653
+ error.data = { uri };
2654
+ return error;
2655
+ }
2656
+ function invalidCompletionParams(message) {
2657
+ const error = new Error(message);
2658
+ error.code = -32602;
2659
+ return error;
2660
+ }
2661
+
2662
+ // src/mcp/prompts/loader.ts
2663
+ import fs4 from "fs";
2664
+ import path5 from "path";
2665
+ import { fileURLToPath as fileURLToPath3 } from "url";
2666
+ import matter from "gray-matter";
2667
+ var __filename = fileURLToPath3(import.meta.url);
2668
+ var __dirname2 = path5.dirname(__filename);
2669
+ function findPromptDir() {
2670
+ const candidates = [
2671
+ // Production if chunked into dist/
2672
+ "./prompts",
2673
+ // Production if inlined into dist/mcp/
2674
+ "../prompts",
2675
+ // Dev: /src/mcp/prompts/definitions (next to loader.ts)
2676
+ "./definitions"
2677
+ ].map((relPath) => path5.resolve(__dirname2, relPath));
2678
+ for (const dir of candidates) {
2679
+ if (fs4.existsSync(dir)) {
2680
+ const files = fs4.readdirSync(dir);
2681
+ if (files.some((f) => f.endsWith(".md"))) {
2682
+ return dir;
2896
2683
  }
2897
- },
2898
- outputSchema: {
2899
- type: "object",
2900
- properties: {
2901
- success: { type: "boolean" },
2902
- id: { type: "string" },
2903
- ids: { type: "array", items: { type: "string" } },
2904
- repo: { type: "string" },
2905
- deletedCount: { type: "number" }
2906
- },
2907
- required: ["success"]
2908
2684
  }
2685
+ }
2686
+ return path5.resolve(__dirname2, "./definitions");
2687
+ }
2688
+ var PROMPT_DIR = findPromptDir();
2689
+ function listPromptFiles() {
2690
+ if (!fs4.existsSync(PROMPT_DIR)) return [];
2691
+ return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
2692
+ }
2693
+ function loadPromptFromMarkdown(name) {
2694
+ const filePath = path5.join(PROMPT_DIR, `${name}.md`);
2695
+ if (!fs4.existsSync(filePath)) {
2696
+ throw new Error(`Prompt file not found: ${filePath}`);
2697
+ }
2698
+ const fileContent = fs4.readFileSync(filePath, "utf-8");
2699
+ const { data, content } = matter(fileContent);
2700
+ return {
2701
+ name: data.name || name,
2702
+ description: data.description || "",
2703
+ arguments: data.arguments || [],
2704
+ agent: data.agent,
2705
+ content: content.trim()
2706
+ };
2707
+ }
2708
+
2709
+ // src/mcp/prompts/registry.ts
2710
+ function createPromptDefinition(loaded) {
2711
+ return {
2712
+ name: loaded.name,
2713
+ description: loaded.description,
2714
+ arguments: loaded.arguments,
2715
+ agent: loaded.agent,
2716
+ messages: [
2717
+ {
2718
+ role: "user",
2719
+ content: {
2720
+ type: "text",
2721
+ text: loaded.content
2722
+ }
2723
+ }
2724
+ ]
2725
+ };
2726
+ }
2727
+ var PROMPTS = {};
2728
+ var promptFiles = listPromptFiles();
2729
+ for (const name of promptFiles) {
2730
+ try {
2731
+ PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
2732
+ } catch (e) {
2733
+ logger.warn(`Failed to load prompt ${name}: ${e}`);
2734
+ }
2735
+ }
2736
+ async function listPrompts(db, session, params) {
2737
+ const allPrompts = Object.values(PROMPTS).map((p) => ({
2738
+ name: p.name,
2739
+ description: p.description,
2740
+ arguments: p.arguments,
2741
+ metadata: p.agent ? { agent: p.agent } : void 0
2742
+ }));
2743
+ const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
2744
+ const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
2745
+ const offset = decodeCursor(params?.cursor);
2746
+ const sliced = allPrompts.slice(offset, offset + limit);
2747
+ const nextOffset = offset + sliced.length;
2748
+ return {
2749
+ prompts: sliced,
2750
+ nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
2751
+ };
2752
+ }
2753
+ async function getPrompt(name, args = {}, db, session) {
2754
+ const prompt = PROMPTS[name];
2755
+ if (!prompt) {
2756
+ throw new Error(`Prompt not found: ${name}`);
2757
+ }
2758
+ const inferredRepo = inferRepoFromSession(session);
2759
+ const messages = prompt.messages.map((m) => {
2760
+ let text = m.content.text;
2761
+ for (const [key, value] of Object.entries(args)) {
2762
+ text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
2763
+ }
2764
+ text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
2765
+ return {
2766
+ ...m,
2767
+ content: {
2768
+ ...m.content,
2769
+ text
2770
+ }
2771
+ };
2772
+ });
2773
+ return {
2774
+ description: prompt.description,
2775
+ messages,
2776
+ metadata: prompt.agent ? { agent: prompt.agent } : void 0
2777
+ };
2778
+ }
2779
+ async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
2780
+ void name;
2781
+ void contextArguments;
2782
+ if (argName === "task_id") {
2783
+ const values = dataSources.tasks.map((t) => t.id);
2784
+ return rankCompletionValues(values, value);
2785
+ }
2786
+ return [];
2787
+ }
2788
+
2789
+ // src/mcp/tools/schemas.ts
2790
+ import { z } from "zod";
2791
+ var MemoryScopeSchema = z.object({
2792
+ repo: z.string().min(1).transform(normalizeRepo),
2793
+ branch: z.string().optional(),
2794
+ folder: z.string().optional(),
2795
+ language: z.string().optional()
2796
+ });
2797
+ var MemoryTypeSchema = z.enum([
2798
+ "code_fact",
2799
+ "decision",
2800
+ "mistake",
2801
+ "pattern",
2802
+ "agent_handoff",
2803
+ "agent_registered",
2804
+ "file_claim",
2805
+ "task_archive"
2806
+ ]);
2807
+ var MemoryStoreSchema = z.object({
2808
+ code: z.string().max(20).optional(),
2809
+ type: MemoryTypeSchema,
2810
+ title: z.string().min(3).max(255),
2811
+ content: z.string().min(10),
2812
+ importance: z.number().min(1).max(5),
2813
+ agent: z.string().min(1),
2814
+ role: z.string().optional().default("unknown"),
2815
+ model: z.string().min(1),
2816
+ scope: MemoryScopeSchema,
2817
+ ttlDays: z.number().min(1).optional(),
2818
+ supersedes: z.string().uuid().optional(),
2819
+ tags: z.array(z.string()).optional(),
2820
+ metadata: z.record(z.string(), z.any()).optional(),
2821
+ is_global: z.boolean().default(false),
2822
+ structured: z.boolean().default(false)
2823
+ });
2824
+ var MemoryUpdateSchema = z.object({
2825
+ id: z.string().uuid(),
2826
+ type: MemoryTypeSchema.optional(),
2827
+ title: z.string().min(3).max(255).optional(),
2828
+ content: z.string().min(10).optional(),
2829
+ importance: z.number().min(1).max(5).optional(),
2830
+ agent: z.string().optional(),
2831
+ role: z.string().optional(),
2832
+ status: z.enum(["active", "archived"]).optional(),
2833
+ supersedes: z.string().uuid().optional(),
2834
+ tags: z.array(z.string()).optional(),
2835
+ metadata: z.record(z.string(), z.any()).optional(),
2836
+ is_global: z.boolean().optional(),
2837
+ completed_at: z.string().optional(),
2838
+ structured: z.boolean().default(false)
2839
+ }).refine(
2840
+ (data) => data.type !== void 0 || data.content !== void 0 || data.title !== void 0 || data.importance !== void 0 || data.status !== void 0 || data.supersedes !== void 0 || data.tags !== void 0 || data.metadata !== void 0 || data.is_global !== void 0 || data.agent !== void 0 || data.role !== void 0 || data.completed_at !== void 0,
2841
+ { message: "At least one field must be provided for update" }
2842
+ );
2843
+ var MemorySearchSchema = z.object({
2844
+ query: z.string().min(3),
2845
+ prompt: z.string().optional(),
2846
+ repo: z.string().min(1).transform(normalizeRepo),
2847
+ types: z.array(MemoryTypeSchema).optional(),
2848
+ minImportance: z.number().min(1).max(5).optional(),
2849
+ limit: z.number().min(1).max(100).default(5),
2850
+ offset: z.number().min(0).default(0),
2851
+ includeRecap: z.boolean().default(false),
2852
+ current_file_path: z.string().optional(),
2853
+ include_archived: z.boolean().default(false),
2854
+ current_tags: z.array(z.string()).optional(),
2855
+ scope: MemoryScopeSchema.partial().optional(),
2856
+ structured: z.boolean().default(false)
2857
+ });
2858
+ var MemoryAcknowledgeSchema = z.object({
2859
+ memory_id: z.string().uuid(),
2860
+ status: z.enum(["used", "irrelevant", "contradictory"]),
2861
+ application_context: z.string().min(10).optional(),
2862
+ structured: z.boolean().default(false)
2863
+ });
2864
+ var MemoryRecapSchema = z.object({
2865
+ repo: z.string().min(1).transform(normalizeRepo),
2866
+ limit: z.number().min(1).max(50).default(20),
2867
+ offset: z.number().min(0).default(0),
2868
+ structured: z.boolean().default(false)
2869
+ });
2870
+ var MemoryDeleteSchema = z.object({
2871
+ repo: z.string().min(1).transform(normalizeRepo).optional(),
2872
+ id: z.string().uuid().optional(),
2873
+ ids: z.array(z.string().uuid()).min(1).optional(),
2874
+ structured: z.boolean().default(false)
2875
+ }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2876
+ message: "Either 'id' or 'ids' must be provided for deletion"
2877
+ });
2878
+ var MemorySummarizeSchema = z.object({
2879
+ repo: z.string().min(1).transform(normalizeRepo),
2880
+ signals: z.array(z.string().max(200)).min(1),
2881
+ structured: z.boolean().default(false)
2882
+ });
2883
+ var MemorySynthesizeSchema = z.object({
2884
+ repo: z.string().min(1).transform(normalizeRepo).optional(),
2885
+ objective: z.string().min(5),
2886
+ current_file_path: z.string().optional(),
2887
+ include_summary: z.boolean().default(true),
2888
+ include_tasks: z.boolean().default(true),
2889
+ use_tools: z.boolean().default(true),
2890
+ max_iterations: z.number().int().min(1).max(5).default(3),
2891
+ max_tokens: z.number().int().min(128).max(4e3).default(1200),
2892
+ structured: z.boolean().default(false)
2893
+ });
2894
+ var TaskStatusSchema = z.enum(["backlog", "pending", "in_progress", "completed", "canceled", "blocked"]);
2895
+ var TaskPrioritySchema = z.number().min(1).max(5);
2896
+ var SingleTaskCreateSchema = z.object({
2897
+ task_code: z.string().min(1),
2898
+ phase: z.string().min(1),
2899
+ title: z.string().min(3).max(100),
2900
+ description: z.string().min(1),
2901
+ status: TaskStatusSchema.default("backlog"),
2902
+ priority: TaskPrioritySchema.default(3),
2903
+ agent: z.string().optional(),
2904
+ role: z.string().optional(),
2905
+ doc_path: z.string().optional(),
2906
+ tags: z.array(z.string()).optional(),
2907
+ metadata: z.record(z.string(), z.any()).optional(),
2908
+ parent_id: z.string().uuid().optional(),
2909
+ depends_on: z.string().uuid().optional(),
2910
+ est_tokens: z.number().int().min(0).optional()
2911
+ });
2912
+ var TaskCreateSchema = z.object({
2913
+ repo: z.string().min(1).transform(normalizeRepo),
2914
+ // Allow single task fields at top level (backward compatibility & single use)
2915
+ task_code: z.string().min(1).optional(),
2916
+ phase: z.string().min(1).optional(),
2917
+ title: z.string().min(3).max(100).optional(),
2918
+ description: z.string().min(1).optional(),
2919
+ status: TaskStatusSchema.optional(),
2920
+ priority: TaskPrioritySchema.optional(),
2921
+ agent: z.string().optional(),
2922
+ role: z.string().optional(),
2923
+ doc_path: z.string().optional(),
2924
+ tags: z.array(z.string()).optional(),
2925
+ metadata: z.record(z.string(), z.any()).optional(),
2926
+ parent_id: z.string().uuid().optional(),
2927
+ depends_on: z.string().uuid().optional(),
2928
+ est_tokens: z.number().int().min(0).optional(),
2929
+ // Allow bulk tasks
2930
+ tasks: z.array(SingleTaskCreateSchema).min(1).optional(),
2931
+ structured: z.boolean().default(false)
2932
+ }).refine(
2933
+ (data) => {
2934
+ if (data.tasks) return true;
2935
+ return !!(data.task_code && data.phase && data.title && data.description);
2909
2936
  },
2937
+ { message: "Either 'tasks' array or single task fields (task_code, phase, title, description) must be provided" }
2938
+ );
2939
+ var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
2940
+ repo: z.string().min(1).transform(normalizeRepo).optional(),
2941
+ structured: z.boolean().default(false)
2942
+ });
2943
+ var TaskUpdateSchema = z.object({
2944
+ repo: z.string().min(1).transform(normalizeRepo),
2945
+ id: z.string().uuid().optional(),
2946
+ ids: z.array(z.string().uuid()).min(1).optional(),
2947
+ task_code: z.string().optional(),
2948
+ phase: z.string().optional(),
2949
+ title: z.string().min(3).max(100).optional(),
2950
+ description: z.string().optional(),
2951
+ status: TaskStatusSchema.optional(),
2952
+ priority: TaskPrioritySchema.optional(),
2953
+ agent: z.string().min(1, "agent name is required").optional(),
2954
+ role: z.string().min(1, "agent role is required").optional(),
2955
+ model: z.string().optional(),
2956
+ comment: z.string().min(1).optional(),
2957
+ doc_path: z.string().optional(),
2958
+ tags: z.array(z.string()).optional(),
2959
+ metadata: z.record(z.string(), z.any()).optional(),
2960
+ parent_id: z.string().uuid().optional(),
2961
+ depends_on: z.string().uuid().optional(),
2962
+ est_tokens: z.number().int().min(0).optional(),
2963
+ force: z.boolean().optional(),
2964
+ structured: z.boolean().default(false)
2965
+ }).refine((data) => data.id !== void 0 || data.ids !== void 0 || data.task_code !== void 0, {
2966
+ message: "Either 'id', 'ids', or 'task_code' must be provided for update"
2967
+ }).refine((data) => Object.keys(data).length > 2, {
2968
+ message: "At least one field besides repo and id/ids must be provided for update"
2969
+ });
2970
+ var TaskListSchema = z.object({
2971
+ repo: z.string().min(1).transform(normalizeRepo),
2972
+ status: z.string().optional(),
2973
+ phase: z.string().optional(),
2974
+ query: z.string().optional(),
2975
+ limit: z.number().min(1).max(100).default(15),
2976
+ offset: z.number().min(0).default(0),
2977
+ structured: z.boolean().default(false)
2978
+ });
2979
+ var TaskSearchSchema = z.object({
2980
+ repo: z.string().min(1).transform(normalizeRepo),
2981
+ query: z.string().min(1),
2982
+ status: z.string().optional(),
2983
+ limit: z.number().min(1).max(100).default(10),
2984
+ offset: z.number().min(0).default(0),
2985
+ structured: z.boolean().default(false)
2986
+ });
2987
+ var TaskDeleteSchema = z.object({
2988
+ repo: z.string().min(1).transform(normalizeRepo),
2989
+ id: z.string().uuid().optional(),
2990
+ ids: z.array(z.string().uuid()).min(1).optional(),
2991
+ structured: z.boolean().default(false)
2992
+ }).refine((data) => data.id !== void 0 || data.ids !== void 0, {
2993
+ message: "Either 'id' or 'ids' must be provided for deletion"
2994
+ });
2995
+ var MemoryDetailSchema = z.object({
2996
+ id: z.string().uuid().optional(),
2997
+ code: z.string().max(20).optional(),
2998
+ structured: z.boolean().default(false)
2999
+ }).refine((data) => data.id !== void 0 || data.code !== void 0, {
3000
+ message: "Either id or code must be provided"
3001
+ });
3002
+ var TaskGetSchema = z.object({
3003
+ repo: z.string().min(1).transform(normalizeRepo),
3004
+ id: z.string().uuid().optional(),
3005
+ task_code: z.string().optional(),
3006
+ structured: z.boolean().default(false)
3007
+ }).refine((data) => data.id !== void 0 || data.task_code !== void 0, {
3008
+ message: "Either id or task_code must be provided"
3009
+ });
3010
+ var TOOL_DEFINITIONS = [
2910
3011
  {
2911
- name: "memory-recap",
2912
- title: "Memory Recap",
2913
- description: "AGGREGATED OVERVIEW LAYER: Returns stats (counts by type) and a pointer table of top memories [id, title, type, importance]. NO content. Use for orientation only \u2014 retrieve full memory via memory-detail.",
3012
+ name: "memory-synthesize",
3013
+ title: "Memory Synthesize",
3014
+ description: "Use client sampling to synthesize a grounded answer from local memory and tasks. Best for project briefings, tradeoff summaries, and context-aware answers.",
2914
3015
  annotations: {
2915
3016
  readOnlyHint: true,
2916
3017
  idempotentHint: true,
2917
3018
  openWorldHint: false
2918
3019
  },
3020
+ execution: {
3021
+ taskSupport: "optional"
3022
+ },
2919
3023
  inputSchema: {
2920
3024
  type: "object",
2921
3025
  properties: {
2922
- repo: { type: "string", description: "Repository name (required)" },
2923
- limit: {
2924
- type: "number",
2925
- minimum: 1,
2926
- maximum: 50,
2927
- default: 20,
2928
- description: "Maximum number of top memories to return in the pointer table"
2929
- },
2930
- offset: {
2931
- type: "number",
2932
- minimum: 0,
2933
- default: 0,
2934
- description: "Number of memories to skip for pagination (optional, default 0)"
3026
+ repo: { type: "string", description: "Repository name. Optional when a single MCP root is active." },
3027
+ objective: { type: "string", minLength: 5, description: "Question or synthesis objective." },
3028
+ current_file_path: {
3029
+ type: "string",
3030
+ description: "Optional absolute file path for workspace-local grounding."
2935
3031
  },
2936
- structured: {
3032
+ include_summary: { type: "boolean", default: true },
3033
+ include_tasks: { type: "boolean", default: true },
3034
+ use_tools: {
2937
3035
  type: "boolean",
2938
- default: false,
2939
- description: "If true, returns structured JSON without the text content summary."
2940
- }
3036
+ default: true,
3037
+ description: "Allow the sampled model to call local memory/task tools during synthesis when the client supports sampling.tools."
3038
+ },
3039
+ max_iterations: { type: "number", minimum: 1, maximum: 5, default: 3 },
3040
+ max_tokens: { type: "number", minimum: 128, maximum: 4e3, default: 1200 },
3041
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON results." }
2941
3042
  },
2942
- required: ["repo"]
3043
+ required: ["objective"]
2943
3044
  },
2944
3045
  outputSchema: {
2945
3046
  type: "object",
2946
3047
  properties: {
2947
- schema: { type: "string", enum: ["memory-recap"] },
2948
3048
  repo: { type: "string" },
2949
- count: { type: "number", description: "Number of rows in the top pointer table" },
2950
- total: { type: "number", description: "Total active memories in repo" },
2951
- offset: { type: "number" },
2952
- limit: { type: "number" },
2953
- stats: {
2954
- type: "object",
2955
- properties: {
2956
- by_type: {
2957
- type: "object",
2958
- description: "Count of active memories per type (e.g. { decision: 3, code_fact: 7 })"
2959
- }
2960
- },
2961
- required: ["by_type"]
2962
- },
2963
- top: {
2964
- type: "object",
2965
- properties: {
2966
- columns: {
2967
- type: "array",
2968
- items: { type: "string" },
2969
- description: "Column names: [id, title, type, importance]"
2970
- },
2971
- rows: {
2972
- type: "array",
2973
- items: { type: "array" },
2974
- description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
2975
- }
2976
- },
2977
- required: ["columns", "rows"]
2978
- }
3049
+ objective: { type: "string" },
3050
+ answer: { type: "string" },
3051
+ model: { type: "string" },
3052
+ stopReason: { type: "string" },
3053
+ iterations: { type: "number" },
3054
+ toolCalls: { type: "number" }
2979
3055
  },
2980
- required: ["schema", "repo", "count", "total", "offset", "limit", "stats", "top"]
3056
+ required: ["repo", "objective", "answer", "iterations", "toolCalls"]
2981
3057
  }
2982
3058
  },
2983
3059
  {
2984
- name: "task-create",
2985
- title: "Task Create",
2986
- description: "Register one or more new tasks in a repository. task_code must be unique within the repository. Supports single task object or an array of tasks for bulk creation.",
3060
+ name: "task-create-interactive",
3061
+ title: "Interactive Task Create",
3062
+ description: "Create a task with MCP elicitation fallback for any missing required fields. Best when an agent knows a task is needed but still needs user confirmation for repo, title, or phase.",
2987
3063
  annotations: {
2988
3064
  readOnlyHint: false,
2989
3065
  idempotentHint: false,
3066
+ destructiveHint: false,
2990
3067
  openWorldHint: false
2991
3068
  },
2992
3069
  inputSchema: {
2993
3070
  type: "object",
2994
3071
  properties: {
2995
- repo: { type: "string", description: "Repository name" },
2996
- task_code: { type: "string", description: "Unique task code (e.g. TASK-001) (Required for single task)" },
2997
- phase: { type: "string", description: "Project phase (Required for single task)" },
2998
- title: {
2999
- type: "string",
3000
- minLength: 3,
3001
- maxLength: 100,
3002
- description: "Task objective (Required for single task)"
3003
- },
3004
- description: { type: "string", description: "Detailed description (Required for single task)" },
3005
- status: {
3072
+ repo: {
3006
3073
  type: "string",
3007
- enum: ["backlog", "pending"],
3008
- default: "backlog",
3009
- description: "New tasks MUST start in 'backlog' if there are already 10 pending tasks. Otherwise can start in 'pending'."
3074
+ description: "Repository name. Optional when it can be inferred from MCP roots or elicited from the user."
3010
3075
  },
3076
+ task_code: { type: "string" },
3077
+ phase: { type: "string" },
3078
+ title: { type: "string", minLength: 3, maxLength: 100 },
3079
+ description: { type: "string", minLength: 1 },
3080
+ status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
3011
3081
  priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3012
3082
  agent: { type: "string" },
3013
3083
  role: { type: "string" },
3014
3084
  doc_path: { type: "string" },
3015
- tags: { type: "array", items: { type: "string" } },
3016
- metadata: { type: "object" },
3017
- parent_id: { type: "string", format: "uuid" },
3018
- depends_on: { type: "string", format: "uuid" },
3019
- est_tokens: { type: "number", minimum: 0, description: "Estimated tokens budget for this task" },
3020
- tasks: {
3021
- type: "array",
3022
- items: {
3023
- type: "object",
3024
- properties: {
3025
- task_code: { type: "string" },
3026
- phase: { type: "string" },
3027
- title: { type: "string", minLength: 3, maxLength: 100 },
3028
- description: { type: "string" },
3029
- status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
3030
- priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3031
- agent: { type: "string" },
3032
- role: { type: "string" },
3033
- doc_path: { type: "string" },
3034
- tags: { type: "array", items: { type: "string" } },
3035
- metadata: { type: "object" },
3036
- parent_id: { type: "string", format: "uuid" },
3037
- depends_on: { type: "string", format: "uuid" },
3038
- est_tokens: { type: "number", minimum: 0 }
3039
- },
3040
- required: ["task_code", "phase", "title", "description"]
3041
- },
3042
- description: "Array of tasks for bulk creation"
3043
- },
3044
3085
  structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3045
- },
3046
- required: ["repo"]
3086
+ }
3047
3087
  },
3048
3088
  outputSchema: {
3049
3089
  type: "object",
3050
3090
  properties: {
3051
- success: { type: "boolean" },
3052
- id: { type: "string" },
3053
- task_code: { type: "string" },
3054
3091
  repo: { type: "string" },
3092
+ task_code: { type: "string" },
3055
3093
  phase: { type: "string" },
3056
3094
  title: { type: "string" },
3057
3095
  status: { type: "string" },
3058
- priority: { type: "number" },
3059
- createdCount: { type: "number" },
3060
- taskCodes: { type: "array", items: { type: "string" } }
3096
+ priority: { type: "number" }
3061
3097
  },
3062
- required: ["success", "repo"]
3098
+ required: ["repo", "task_code", "phase", "title", "status", "priority"]
3063
3099
  }
3064
3100
  },
3065
3101
  {
3066
- name: "task-update",
3067
- title: "Task Update",
3068
- description: "Update one or more tasks. Supports single update via 'id' or bulk update via 'ids'. Provide only the fields that need to be changed. MANDATORY WORKFLOW: You cannot move a task from 'pending' or 'blocked' directly to 'completed'. You MUST move it to 'in_progress' first. When changing status to 'completed', include 'est_tokens' with the estimated total tokens actually used for the task.",
3069
- annotations: {
3070
- readOnlyHint: false,
3071
- idempotentHint: false,
3072
- openWorldHint: false
3073
- },
3102
+ name: "memory-detail",
3103
+ title: "Memory Detail",
3104
+ description: "Fetch full details of a specific memory by ID. Use this when you have a memory ID (e.g. from search results) and need to read the full content.",
3074
3105
  inputSchema: {
3075
3106
  type: "object",
3076
3107
  properties: {
3077
- repo: { type: "string", description: "Repository name" },
3078
- id: { type: "string", format: "uuid", description: "Task ID (for single update)" },
3079
- ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk update)" },
3080
- task_code: { type: "string" },
3081
- phase: { type: "string" },
3082
- title: { type: "string", minLength: 3, maxLength: 100 },
3083
- description: { type: "string" },
3084
- status: {
3085
- type: "string",
3086
- enum: ["backlog", "pending", "in_progress", "completed", "canceled", "blocked"],
3087
- description: "New status. Transitions from 'backlog', 'pending' or 'blocked' to 'completed' are NOT allowed."
3088
- },
3089
- priority: { type: "number", minimum: 1, maximum: 5 },
3090
- agent: { type: "string" },
3091
- role: { type: "string" },
3092
- model: { type: "string" },
3093
- comment: {
3094
- type: "string",
3095
- description: "REQUIRED when changing task status. Explain WHY the status is changing (e.g., 'Starting implementation', 'Blocked by missing API docs', 'Verified fix')."
3096
- },
3097
- doc_path: { type: "string" },
3098
- tags: { type: "array", items: { type: "string" } },
3099
- metadata: { type: "object" },
3100
- parent_id: { type: "string", format: "uuid" },
3101
- depends_on: { type: "string", format: "uuid" },
3102
- est_tokens: {
3103
- type: "number",
3104
- minimum: 0,
3105
- description: "Estimated total tokens actually used for this task. Required when status changes to 'completed'."
3106
- },
3107
- force: {
3108
- type: "boolean",
3109
- description: "If true, bypasses status transition validation (e.g. pending -> completed)."
3110
- },
3111
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3112
- },
3113
- required: ["repo"]
3114
- },
3115
- outputSchema: {
3116
- type: "object",
3117
- properties: {
3118
- success: { type: "boolean" },
3119
- id: { type: "string" },
3120
- ids: { type: "array", items: { type: "string" } },
3121
- repo: { type: "string" },
3122
- status: { type: "string" },
3123
- archivedToMemory: { type: "boolean" },
3124
- updatedFields: {
3125
- type: "array",
3126
- items: { type: "string" }
3127
- },
3128
- updatedCount: { type: "number" }
3108
+ id: { type: "string", format: "uuid", description: "Memory entry ID" },
3109
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON details." }
3129
3110
  },
3130
- required: ["success", "repo"]
3111
+ required: ["id"]
3131
3112
  }
3132
3113
  },
3133
3114
  {
3134
- name: "task-delete",
3135
- title: "Task Delete",
3136
- description: "Delete one or more tasks from a repository. Supports single 'id' or bulk 'ids'.",
3137
- annotations: {
3138
- readOnlyHint: false,
3139
- idempotentHint: false,
3140
- destructiveHint: true,
3141
- openWorldHint: false
3142
- },
3115
+ name: "task-detail",
3116
+ title: "Task Detail",
3117
+ description: "Fetch full details of a specific task by ID or task code. Use this when you have a task ID or code and need to read the full description and comments.",
3143
3118
  inputSchema: {
3144
3119
  type: "object",
3145
3120
  properties: {
3146
3121
  repo: { type: "string", description: "Repository name" },
3147
- id: { type: "string", format: "uuid", description: "Task ID (for single deletion)" },
3148
- ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk deletion)" },
3149
- structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3150
- },
3151
- required: ["repo"]
3152
- },
3153
- outputSchema: {
3154
- type: "object",
3155
- properties: {
3156
- success: { type: "boolean" },
3157
- id: { type: "string" },
3158
- ids: { type: "array", items: { type: "string" } },
3159
- repo: { type: "string" },
3160
- deletedCount: { type: "number" }
3122
+ id: { type: "string", format: "uuid", description: "Task ID (optional if task_code is provided)" },
3123
+ task_code: { type: "string", description: "Task code (e.g. TASK-001) (optional if id is provided)" },
3124
+ structured: {
3125
+ type: "boolean",
3126
+ default: false,
3127
+ description: "If true, returns structured JSON without the text content details."
3128
+ }
3161
3129
  },
3162
- required: ["success", "repo"]
3130
+ required: ["repo"]
3163
3131
  }
3164
3132
  },
3165
3133
  {
3166
- name: "task-list",
3167
- title: "Task List",
3168
- description: "PRIMARY navigation and search tool for tasks. Returns a compact tabular list of tasks (id, task_code, title, status, priority). 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.",
3134
+ name: "memory-store",
3135
+ title: "Memory Store",
3136
+ description: "Store a new memory entry. Keep 'title' concise and human-readable; do not embed agent/role/date metadata in the title. Put auxiliary context into 'metadata'. Use 'tags' for tech-stack and 'is_global' for universal rules.",
3169
3137
  annotations: {
3170
- readOnlyHint: true,
3171
- idempotentHint: true,
3138
+ readOnlyHint: false,
3139
+ idempotentHint: false,
3140
+ destructiveHint: false,
3172
3141
  openWorldHint: false
3173
3142
  },
3174
3143
  inputSchema: {
3175
3144
  type: "object",
3176
3145
  properties: {
3177
- repo: {
3178
- type: "string",
3179
- description: "Repository name"
3180
- },
3181
- status: {
3146
+ type: {
3182
3147
  type: "string",
3183
- default: "in_progress,pending",
3184
- description: "Comma-separated status filter (backlog, pending, in_progress, completed, canceled, blocked). Defaults to 'in_progress,pending'."
3148
+ enum: [
3149
+ "code_fact",
3150
+ "decision",
3151
+ "mistake",
3152
+ "pattern",
3153
+ "agent_handoff",
3154
+ "agent_registered",
3155
+ "file_claim",
3156
+ "task_archive"
3157
+ ],
3158
+ description: "Type of memory being stored"
3185
3159
  },
3186
- phase: {
3160
+ title: {
3187
3161
  type: "string",
3188
- description: "Filter by phase (e.g., 'research', 'implementation')"
3162
+ minLength: 3,
3163
+ maxLength: 100,
3164
+ description: "Short human-readable title for the memory. Do not embed bracketed metadata like agent/role/date prefixes here."
3189
3165
  },
3190
- query: {
3166
+ content: {
3191
3167
  type: "string",
3192
- description: "Search keyword matching task code, title, or description"
3168
+ minLength: 10,
3169
+ description: "The memory content"
3193
3170
  },
3194
- limit: {
3171
+ importance: {
3195
3172
  type: "number",
3196
3173
  minimum: 1,
3197
- maximum: 100,
3198
- default: 5,
3199
- description: "Maximum rows to return (default 5)"
3174
+ maximum: 5,
3175
+ description: "Importance score (1-5)"
3200
3176
  },
3201
- offset: {
3202
- type: "number",
3203
- minimum: 0,
3204
- default: 0,
3205
- description: "Offset for pagination"
3177
+ agent: {
3178
+ type: "string",
3179
+ description: "Name of the agent creating this memory"
3206
3180
  },
3207
- structured: {
3208
- type: "boolean",
3209
- default: false,
3210
- description: "If true, returns structured JSON without the text content summary."
3211
- }
3212
- },
3213
- required: ["repo"]
3214
- },
3215
- outputSchema: {
3216
- type: "object",
3217
- properties: {
3218
- schema: { type: "string", enum: ["task-list"] },
3219
- tasks: {
3181
+ role: {
3182
+ type: "string",
3183
+ description: "Role of the agent creating this memory"
3184
+ },
3185
+ model: {
3186
+ type: "string",
3187
+ description: "AI model used by the agent"
3188
+ },
3189
+ scope: {
3220
3190
  type: "object",
3221
3191
  properties: {
3222
- columns: {
3223
- type: "array",
3224
- items: { type: "string" },
3225
- description: "Column names in order: id, task_code, title, status, priority, comments_count"
3226
- },
3227
- rows: {
3228
- type: "array",
3229
- items: { type: "array" },
3230
- description: "Each row: [id, task_code, title, status, priority, comments_count]. Use task-detail to fetch full task."
3231
- }
3192
+ repo: { type: "string", description: "Repository name" },
3193
+ branch: { type: "string" },
3194
+ folder: { type: "string" },
3195
+ language: { type: "string" }
3232
3196
  },
3233
- required: ["columns", "rows"]
3197
+ required: ["repo"]
3234
3198
  },
3235
- count: { type: "number" },
3236
- offset: { type: "number" }
3237
- },
3238
- required: ["schema", "tasks", "count"]
3239
- }
3240
- }
3241
- ];
3242
-
3243
- // src/mcp/utils/pagination.ts
3244
- function encodeCursor(offset) {
3245
- return Buffer.from(String(offset), "utf8").toString("base64");
3246
- }
3247
- function decodeCursor(cursor) {
3248
- if (cursor === void 0 || cursor === null || cursor === "") {
3249
- return 0;
3250
- }
3251
- if (typeof cursor !== "string" || cursor.trim() === "") {
3252
- throw invalidPaginationParams("Invalid cursor");
3253
- }
3254
- let decoded;
3255
- try {
3256
- decoded = Buffer.from(cursor, "base64").toString("utf8");
3257
- } catch {
3258
- throw invalidPaginationParams("Invalid cursor");
3259
- }
3260
- if (!/^\d+$/.test(decoded)) {
3261
- throw invalidPaginationParams("Invalid cursor");
3262
- }
3263
- const offset = Number.parseInt(decoded, 10);
3264
- if (!Number.isFinite(offset) || offset < 0) {
3265
- throw invalidPaginationParams("Invalid cursor");
3266
- }
3267
- return offset;
3268
- }
3269
- function invalidPaginationParams(message) {
3270
- const error = new Error(message);
3271
- error.code = -32602;
3272
- return error;
3273
- }
3274
-
3275
- // src/mcp/utils/completion.ts
3276
- var MAX_COMPLETION_VALUES = 100;
3277
- function rankCompletionValues(candidates, input) {
3278
- const unique = [...new Set(candidates.filter(Boolean))];
3279
- const needle = input.trim().toLowerCase();
3280
- if (!needle) {
3281
- return unique.slice(0, MAX_COMPLETION_VALUES);
3282
- }
3283
- 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);
3284
- }
3285
- function scoreCompletionValue(value, needle) {
3286
- const haystack = value.toLowerCase();
3287
- if (haystack === needle) return 100;
3288
- if (haystack.startsWith(needle)) return 75;
3289
- if (haystack.includes(needle)) return 50;
3290
- const compactNeedle = needle.replace(/[\s_-]+/g, "");
3291
- const compactHaystack = haystack.replace(/[\s_-]+/g, "");
3292
- if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
3293
- return 0;
3294
- }
3295
-
3296
- // src/mcp/resources/index.ts
3297
- var DEFAULT_PAGE_SIZE = 25;
3298
- var MAX_PAGE_SIZE = 100;
3299
- function listResources(session, params) {
3300
- const resources = [
3301
- {
3302
- uri: "repository://index",
3303
- name: "Repository Index",
3304
- title: "Repository Index",
3305
- description: "List of all known repositories with memory/task counts and last activity",
3306
- mimeType: "application/json",
3307
- annotations: {
3308
- audience: ["assistant"],
3309
- priority: 1,
3310
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3311
- }
3312
- },
3313
- {
3314
- uri: "session://roots",
3315
- name: "Session Roots",
3316
- title: "Session Roots",
3317
- description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
3318
- mimeType: "application/json",
3319
- size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
3320
- annotations: {
3321
- audience: ["assistant"],
3322
- priority: 0.95,
3323
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3324
- }
3325
- }
3326
- ];
3327
- return paginateEntries("resources", resources, params);
3328
- }
3329
- function listResourceTemplates(params) {
3330
- const templates = [
3331
- // ── Memory ──────────────────────────────────────────────────────────────
3332
- {
3333
- uriTemplate: "repository://{name}/memories",
3334
- name: "Repository Memories",
3335
- title: "Repository Memories",
3336
- description: "All active memory entries for a specific repository",
3337
- mimeType: "application/json",
3338
- annotations: { audience: ["assistant"], priority: 0.85 }
3339
- },
3340
- {
3341
- uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
3342
- name: "Filtered Repository Memories",
3343
- title: "Filtered Repository Memories",
3344
- description: "Filter or search memories within a repository by keyword, type, or tag",
3345
- mimeType: "application/json",
3346
- annotations: { audience: ["assistant"], priority: 0.8 }
3347
- },
3348
- {
3349
- uriTemplate: "memory://{id}",
3350
- name: "Memory Detail",
3351
- title: "Memory Detail",
3352
- description: "Full content and statistics for a specific memory UUID",
3353
- mimeType: "application/json",
3354
- annotations: { audience: ["assistant"], priority: 0.75 }
3355
- },
3356
- // ── Tasks ────────────────────────────────────────────────────────────────
3357
- {
3358
- uriTemplate: "repository://{name}/tasks",
3359
- name: "Repository Tasks",
3360
- title: "Repository Tasks",
3361
- description: "All active tasks for a specific repository",
3362
- mimeType: "application/json",
3363
- annotations: { audience: ["assistant"], priority: 0.9 }
3364
- },
3365
- {
3366
- uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
3367
- name: "Filtered Repository Tasks",
3368
- title: "Filtered Repository Tasks",
3369
- description: "Filter tasks within a repository by status or priority level",
3370
- mimeType: "application/json",
3371
- annotations: { audience: ["assistant"], priority: 0.85 }
3372
- },
3373
- {
3374
- uriTemplate: "task://{id}",
3375
- name: "Task Detail",
3376
- title: "Task Detail",
3377
- description: "Full content and comments for a specific task UUID",
3378
- mimeType: "application/json",
3379
- annotations: { audience: ["assistant"], priority: 0.8 }
3199
+ tags: {
3200
+ type: "array",
3201
+ items: { type: "string" },
3202
+ description: "Technology stack tags (e.g., ['filament', 'laravel'])"
3203
+ },
3204
+ metadata: {
3205
+ type: "object",
3206
+ description: "Structured metadata for non-title context such as source agent, claim fields, or timestamps"
3207
+ },
3208
+ is_global: {
3209
+ type: "boolean",
3210
+ description: "If true, this memory is shared across all repositories"
3211
+ },
3212
+ ttlDays: { type: "number", minimum: 1 },
3213
+ supersedes: { type: "string", format: "uuid" },
3214
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the stored memory." }
3215
+ },
3216
+ required: ["type", "title", "content", "importance", "scope", "agent", "model"]
3380
3217
  },
3381
- // ── Repository extras ────────────────────────────────────────────────────
3382
- {
3383
- uriTemplate: "repository://{name}/summary",
3384
- name: "Repository Summary",
3385
- title: "Repository Summary",
3386
- description: "High-level architectural summary for a repository",
3387
- mimeType: "text/plain",
3388
- annotations: { audience: ["assistant"], priority: 0.95 }
3218
+ outputSchema: {
3219
+ type: "object",
3220
+ properties: {
3221
+ success: { type: "boolean" },
3222
+ id: { type: "string" },
3223
+ code: { type: "string" },
3224
+ repo: { type: "string" },
3225
+ type: { type: "string" },
3226
+ title: { type: "string" },
3227
+ error: { type: "string" },
3228
+ message: { type: "string" }
3229
+ },
3230
+ required: ["success"]
3231
+ }
3232
+ },
3233
+ {
3234
+ name: "memory-acknowledge",
3235
+ title: "Memory Acknowledge",
3236
+ description: "Acknowledge the use of a memory or report its irrelevance/contradiction. Mandatory after using memory to generate code.",
3237
+ annotations: {
3238
+ readOnlyHint: false,
3239
+ idempotentHint: false,
3240
+ openWorldHint: false
3389
3241
  },
3390
- {
3391
- uriTemplate: "repository://{name}/actions",
3392
- name: "Repository Actions",
3393
- title: "Repository Actions",
3394
- description: "Audit log of agent tool actions scoped to a repository",
3395
- mimeType: "application/json",
3396
- annotations: { audience: ["assistant"], priority: 0.6 }
3242
+ inputSchema: {
3243
+ type: "object",
3244
+ properties: {
3245
+ memory_id: { type: "string", format: "uuid" },
3246
+ status: { type: "string", enum: ["used", "irrelevant", "contradictory"] },
3247
+ application_context: { type: "string", minLength: 10 },
3248
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3249
+ },
3250
+ required: ["memory_id", "status"]
3397
3251
  },
3398
- // ── Action detail ────────────────────────────────────────────────────────
3399
- {
3400
- uriTemplate: "action://{id}",
3401
- name: "Action Detail",
3402
- title: "Action Detail",
3403
- description: "Full details of a specific audit log entry by integer ID",
3404
- mimeType: "application/json",
3405
- annotations: { audience: ["assistant"], priority: 0.55 }
3406
- }
3407
- ];
3408
- return paginateEntries("resourceTemplates", templates, params);
3409
- }
3410
- function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
3411
- 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") {
3412
- if (argumentName === "name") {
3413
- return rankCompletionValues(dataSources.repos, argumentValue);
3414
- }
3415
- }
3416
- if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
3417
- if (argumentName === "tag") {
3418
- return rankCompletionValues(dataSources.tags, argumentValue);
3252
+ outputSchema: {
3253
+ type: "object",
3254
+ properties: {
3255
+ success: { type: "boolean" },
3256
+ id: { type: "string" },
3257
+ status: { type: "string" }
3258
+ },
3259
+ required: ["success", "id", "status"]
3419
3260
  }
3420
- }
3421
- throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
3422
- }
3423
- function readResource(uri, db, session) {
3424
- logger.info("[Tool] resource.read", { uri });
3425
- if (uri === "repository://index") {
3426
- const repos = db.system.listRepoNavigation();
3427
- const payload = JSON.stringify(repos, null, 2);
3428
- return {
3429
- contents: [
3430
- {
3431
- uri,
3432
- mimeType: "application/json",
3433
- text: payload,
3434
- size: Buffer.byteLength(payload, "utf8"),
3435
- annotations: {
3436
- audience: ["assistant"],
3437
- priority: 1,
3438
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3439
- }
3440
- }
3441
- ]
3442
- };
3443
- }
3444
- if (uri === "session://roots") {
3445
- const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
3446
- return {
3447
- contents: [
3448
- {
3449
- uri,
3450
- mimeType: "application/json",
3451
- text: payload,
3452
- size: Buffer.byteLength(payload, "utf8"),
3453
- annotations: {
3454
- audience: ["assistant"],
3455
- priority: 0.95,
3456
- lastModified: (/* @__PURE__ */ new Date()).toISOString()
3457
- }
3261
+ },
3262
+ {
3263
+ name: "memory-update",
3264
+ title: "Memory Update",
3265
+ description: "Update an existing memory entry. Keep 'title' concise and move agent/role/date or claim context into 'metadata' instead of the title.",
3266
+ annotations: {
3267
+ readOnlyHint: false,
3268
+ idempotentHint: false,
3269
+ destructiveHint: false,
3270
+ openWorldHint: false
3271
+ },
3272
+ inputSchema: {
3273
+ type: "object",
3274
+ properties: {
3275
+ id: { type: "string", format: "uuid" },
3276
+ type: {
3277
+ type: "string",
3278
+ enum: [
3279
+ "code_fact",
3280
+ "decision",
3281
+ "mistake",
3282
+ "pattern",
3283
+ "agent_handoff",
3284
+ "agent_registered",
3285
+ "file_claim",
3286
+ "task_archive"
3287
+ ]
3288
+ },
3289
+ title: { type: "string", minLength: 3, maxLength: 100 },
3290
+ content: { type: "string", minLength: 10 },
3291
+ importance: { type: "number", minimum: 1, maximum: 5 },
3292
+ agent: { type: "string" },
3293
+ role: { type: "string" },
3294
+ status: { type: "string", enum: ["active", "archived"] },
3295
+ supersedes: { type: "string", format: "uuid" },
3296
+ tags: { type: "array", items: { type: "string" } },
3297
+ metadata: { type: "object" },
3298
+ is_global: { type: "boolean" },
3299
+ completed_at: { type: "string" },
3300
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the updated memory." }
3301
+ },
3302
+ required: ["id"]
3303
+ },
3304
+ outputSchema: {
3305
+ type: "object",
3306
+ properties: {
3307
+ success: { type: "boolean" },
3308
+ id: { type: "string" },
3309
+ repo: { type: "string" },
3310
+ updatedFields: {
3311
+ type: "array",
3312
+ items: { type: "string" }
3458
3313
  }
3459
- ]
3460
- };
3461
- }
3462
- const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
3463
- if (memoryIdMatch) {
3464
- const id = memoryIdMatch[1];
3465
- const entry = db.memories.getByIdWithStats(id);
3466
- if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
3467
- const payload = JSON.stringify(entry, null, 2);
3468
- return {
3469
- contents: [
3470
- {
3471
- uri,
3472
- mimeType: "application/json",
3473
- text: payload,
3474
- size: Buffer.byteLength(payload, "utf8"),
3475
- annotations: {
3476
- audience: ["assistant"],
3477
- priority: 0.75,
3478
- lastModified: entry.updated_at || entry.created_at
3314
+ },
3315
+ required: ["success", "id", "repo", "updatedFields"]
3316
+ }
3317
+ },
3318
+ {
3319
+ name: "memory-search",
3320
+ title: "Memory Search",
3321
+ description: "NAVIGATION LAYER: Returns a pointer table of matching memory IDs only. Returns columns [id, title, type, importance] \u2014 NO content. Retrieve full memory via memory-detail. Use 'current_tags' to find tech-stack specific knowledge from other projects.",
3322
+ annotations: {
3323
+ readOnlyHint: true,
3324
+ idempotentHint: true,
3325
+ openWorldHint: false
3326
+ },
3327
+ inputSchema: {
3328
+ type: "object",
3329
+ properties: {
3330
+ query: { type: "string", minLength: 3 },
3331
+ prompt: { type: "string" },
3332
+ repo: { type: "string" },
3333
+ current_tags: {
3334
+ type: "array",
3335
+ items: { type: "string" },
3336
+ description: "Active tech stack tags (e.g., ['filament', 'react'])"
3337
+ },
3338
+ types: {
3339
+ type: "array",
3340
+ items: {
3341
+ type: "string",
3342
+ enum: [
3343
+ "code_fact",
3344
+ "decision",
3345
+ "mistake",
3346
+ "pattern",
3347
+ "agent_handoff",
3348
+ "agent_registered",
3349
+ "file_claim",
3350
+ "task_archive"
3351
+ ]
3479
3352
  }
3480
- }
3481
- ]
3482
- };
3483
- }
3484
- const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
3485
- if (taskIdMatch) {
3486
- const id = taskIdMatch[1];
3487
- const task = db.tasks.getTaskById(id);
3488
- if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
3489
- const payload = JSON.stringify(task, null, 2);
3490
- return {
3491
- contents: [
3492
- {
3493
- uri,
3494
- mimeType: "application/json",
3495
- text: payload,
3496
- size: Buffer.byteLength(payload, "utf8"),
3497
- annotations: {
3498
- audience: ["assistant"],
3499
- priority: 0.8,
3500
- lastModified: task.updated_at || task.created_at
3353
+ },
3354
+ minImportance: { type: "number", minimum: 1, maximum: 5 },
3355
+ limit: { type: "number", minimum: 1, maximum: 100, default: 5 },
3356
+ offset: { type: "number", minimum: 0, default: 0 },
3357
+ includeRecap: { type: "boolean", default: false },
3358
+ current_file_path: { type: "string" },
3359
+ include_archived: { type: "boolean", default: false },
3360
+ scope: {
3361
+ type: "object",
3362
+ properties: {
3363
+ repo: { type: "string" },
3364
+ branch: { type: "string" },
3365
+ folder: { type: "string" },
3366
+ language: { type: "string" }
3501
3367
  }
3368
+ },
3369
+ structured: {
3370
+ type: "boolean",
3371
+ default: false,
3372
+ description: "If true, returns structured JSON without the text content summary."
3502
3373
  }
3503
- ]
3504
- };
3505
- }
3506
- const repoBase = parseRepoUri(uri);
3507
- if (repoBase) {
3508
- const { name, path: repoPath, query } = repoBase;
3509
- if (repoPath === "summary") {
3510
- const summary = db.summaries.getSummary(name);
3511
- const text = summary?.summary || `No summary available for repository: ${name}`;
3512
- return {
3513
- contents: [
3514
- {
3515
- uri,
3516
- mimeType: "text/plain",
3517
- text,
3518
- size: Buffer.byteLength(text, "utf8"),
3519
- annotations: {
3520
- audience: ["assistant"],
3521
- priority: 0.95,
3522
- lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
3374
+ },
3375
+ required: ["query", "repo"]
3376
+ },
3377
+ outputSchema: {
3378
+ type: "object",
3379
+ properties: {
3380
+ schema: { type: "string", enum: ["memory-search"] },
3381
+ query: { type: "string" },
3382
+ count: { type: "number", description: "Number of rows returned" },
3383
+ total: { type: "number", description: "Total matching memories" },
3384
+ offset: { type: "number" },
3385
+ limit: { type: "number" },
3386
+ results: {
3387
+ type: "object",
3388
+ properties: {
3389
+ columns: {
3390
+ type: "array",
3391
+ items: { type: "string" },
3392
+ description: "Column names: [id, title, type, importance]"
3393
+ },
3394
+ rows: {
3395
+ type: "array",
3396
+ items: { type: "array" },
3397
+ description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
3523
3398
  }
3524
- }
3525
- ]
3526
- };
3399
+ },
3400
+ required: ["columns", "rows"]
3401
+ }
3402
+ },
3403
+ required: ["schema", "query", "count", "total", "offset", "limit", "results"]
3527
3404
  }
3528
- if (repoPath === "memories") {
3529
- const search = query.get("search") || "";
3530
- const type = query.get("type");
3531
- const tag = query.get("tag");
3532
- const result = db.memories.listMemoriesForDashboard({
3533
- repo: name,
3534
- type: type || void 0,
3535
- tag: tag || void 0,
3536
- search: search || void 0,
3537
- limit: 50
3538
- });
3539
- const entries = result.items;
3540
- const payload = JSON.stringify(entries, null, 2);
3541
- return {
3542
- contents: [
3543
- {
3544
- uri,
3545
- mimeType: "application/json",
3546
- text: payload,
3547
- size: Buffer.byteLength(payload, "utf8"),
3548
- annotations: {
3549
- audience: ["assistant"],
3550
- priority: 0.85,
3551
- lastModified: deriveLastModifiedFromCollection(
3552
- entries.map((e) => e.updated_at || e.created_at)
3553
- )
3554
- }
3555
- }
3556
- ]
3557
- };
3405
+ },
3406
+ {
3407
+ name: "memory-summarize",
3408
+ title: "Memory Summarize",
3409
+ description: "Update the summary for a repository",
3410
+ annotations: {
3411
+ readOnlyHint: false,
3412
+ idempotentHint: false,
3413
+ openWorldHint: false
3414
+ },
3415
+ inputSchema: {
3416
+ type: "object",
3417
+ properties: {
3418
+ repo: { type: "string", description: "Repository name" },
3419
+ signals: {
3420
+ type: "array",
3421
+ items: { type: "string", maxLength: 200 },
3422
+ minItems: 1,
3423
+ description: "High-level signals to include in summary"
3424
+ },
3425
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON of the summary." }
3426
+ },
3427
+ required: ["repo", "signals"]
3428
+ },
3429
+ outputSchema: {
3430
+ type: "object",
3431
+ properties: {
3432
+ success: { type: "boolean" },
3433
+ repo: { type: "string" },
3434
+ summary: { type: "string" },
3435
+ signalCount: { type: "number" }
3436
+ },
3437
+ required: ["success", "repo", "summary", "signalCount"]
3558
3438
  }
3559
- if (repoPath === "tasks") {
3560
- const status = query.get("status");
3561
- const priority = query.get("priority");
3562
- let tasks;
3563
- if (status && status !== "all") {
3564
- const statuses = status.split(",").map((s) => s.trim());
3565
- tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
3566
- } else {
3567
- tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
3439
+ },
3440
+ {
3441
+ name: "memory-delete",
3442
+ title: "Memory Delete",
3443
+ description: "Soft-delete one or more memory entries. Supports single 'id' or bulk 'ids'.",
3444
+ annotations: {
3445
+ readOnlyHint: false,
3446
+ idempotentHint: false,
3447
+ destructiveHint: true,
3448
+ openWorldHint: false
3449
+ },
3450
+ inputSchema: {
3451
+ type: "object",
3452
+ properties: {
3453
+ repo: { type: "string", description: "Repository name (optional for single id)" },
3454
+ id: { type: "string", format: "uuid", description: "Memory entry ID to delete" },
3455
+ ids: {
3456
+ type: "array",
3457
+ items: { type: "string", format: "uuid" },
3458
+ minItems: 1,
3459
+ description: "Array of memory IDs to delete"
3460
+ },
3461
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3568
3462
  }
3569
- if (priority) {
3570
- const p = Number(priority);
3571
- if (!isNaN(p)) {
3572
- tasks = tasks.filter((t) => t.priority === p);
3463
+ },
3464
+ outputSchema: {
3465
+ type: "object",
3466
+ properties: {
3467
+ success: { type: "boolean" },
3468
+ id: { type: "string" },
3469
+ ids: { type: "array", items: { type: "string" } },
3470
+ repo: { type: "string" },
3471
+ deletedCount: { type: "number" }
3472
+ },
3473
+ required: ["success"]
3474
+ }
3475
+ },
3476
+ {
3477
+ name: "memory-recap",
3478
+ title: "Memory Recap",
3479
+ description: "AGGREGATED OVERVIEW LAYER: Returns stats (counts by type) and a pointer table of top memories [id, title, type, importance]. NO content. Use for orientation only \u2014 retrieve full memory via memory-detail.",
3480
+ annotations: {
3481
+ readOnlyHint: true,
3482
+ idempotentHint: true,
3483
+ openWorldHint: false
3484
+ },
3485
+ inputSchema: {
3486
+ type: "object",
3487
+ properties: {
3488
+ repo: { type: "string", description: "Repository name (required)" },
3489
+ limit: {
3490
+ type: "number",
3491
+ minimum: 1,
3492
+ maximum: 50,
3493
+ default: 20,
3494
+ description: "Maximum number of top memories to return in the pointer table"
3495
+ },
3496
+ offset: {
3497
+ type: "number",
3498
+ minimum: 0,
3499
+ default: 0,
3500
+ description: "Number of memories to skip for pagination (optional, default 0)"
3501
+ },
3502
+ structured: {
3503
+ type: "boolean",
3504
+ default: false,
3505
+ description: "If true, returns structured JSON without the text content summary."
3573
3506
  }
3574
- }
3575
- const payload = JSON.stringify(tasks, null, 2);
3576
- return {
3577
- contents: [
3578
- {
3579
- uri,
3580
- mimeType: "application/json",
3581
- text: payload,
3582
- size: Buffer.byteLength(payload, "utf8"),
3583
- annotations: {
3584
- audience: ["assistant"],
3585
- priority: 0.9,
3586
- lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
3507
+ },
3508
+ required: ["repo"]
3509
+ },
3510
+ outputSchema: {
3511
+ type: "object",
3512
+ properties: {
3513
+ schema: { type: "string", enum: ["memory-recap"] },
3514
+ repo: { type: "string" },
3515
+ count: { type: "number", description: "Number of rows in the top pointer table" },
3516
+ total: { type: "number", description: "Total active memories in repo" },
3517
+ offset: { type: "number" },
3518
+ limit: { type: "number" },
3519
+ stats: {
3520
+ type: "object",
3521
+ properties: {
3522
+ by_type: {
3523
+ type: "object",
3524
+ description: "Count of active memories per type (e.g. { decision: 3, code_fact: 7 })"
3525
+ }
3526
+ },
3527
+ required: ["by_type"]
3528
+ },
3529
+ top: {
3530
+ type: "object",
3531
+ properties: {
3532
+ columns: {
3533
+ type: "array",
3534
+ items: { type: "string" },
3535
+ description: "Column names: [id, title, type, importance]"
3536
+ },
3537
+ rows: {
3538
+ type: "array",
3539
+ items: { type: "array" },
3540
+ description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
3587
3541
  }
3588
- }
3589
- ]
3590
- };
3542
+ },
3543
+ required: ["columns", "rows"]
3544
+ }
3545
+ },
3546
+ required: ["schema", "repo", "count", "total", "offset", "limit", "stats", "top"]
3591
3547
  }
3592
- if (repoPath === "actions") {
3593
- const actions = db.actions.getRecentActions(name, 100);
3594
- const payload = JSON.stringify(actions, null, 2);
3595
- return {
3596
- contents: [
3597
- {
3598
- uri,
3599
- mimeType: "application/json",
3600
- text: payload,
3601
- size: Buffer.byteLength(payload, "utf8"),
3602
- annotations: {
3603
- audience: ["assistant"],
3604
- priority: 0.6,
3605
- lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
3606
- }
3607
- }
3608
- ]
3609
- };
3548
+ },
3549
+ {
3550
+ name: "task-create",
3551
+ title: "Task Create",
3552
+ description: "Register one or more new tasks in a repository. task_code must be unique within the repository. Supports single task object or an array of tasks for bulk creation.",
3553
+ annotations: {
3554
+ readOnlyHint: false,
3555
+ idempotentHint: false,
3556
+ openWorldHint: false
3557
+ },
3558
+ inputSchema: {
3559
+ type: "object",
3560
+ properties: {
3561
+ repo: { type: "string", description: "Repository name" },
3562
+ task_code: { type: "string", description: "Unique task code (e.g. TASK-001) (Required for single task)" },
3563
+ phase: { type: "string", description: "Project phase (Required for single task)" },
3564
+ title: {
3565
+ type: "string",
3566
+ minLength: 3,
3567
+ maxLength: 100,
3568
+ description: "Task objective (Required for single task)"
3569
+ },
3570
+ description: { type: "string", description: "Detailed description (Required for single task)" },
3571
+ status: {
3572
+ type: "string",
3573
+ enum: ["backlog", "pending"],
3574
+ default: "backlog",
3575
+ description: "New tasks MUST start in 'backlog' if there are already 10 pending tasks. Otherwise can start in 'pending'."
3576
+ },
3577
+ priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3578
+ agent: { type: "string" },
3579
+ role: { type: "string" },
3580
+ doc_path: { type: "string" },
3581
+ tags: { type: "array", items: { type: "string" } },
3582
+ metadata: { type: "object" },
3583
+ parent_id: { type: "string", format: "uuid" },
3584
+ depends_on: { type: "string", format: "uuid" },
3585
+ est_tokens: { type: "number", minimum: 0, description: "Estimated tokens budget for this task" },
3586
+ tasks: {
3587
+ type: "array",
3588
+ items: {
3589
+ type: "object",
3590
+ properties: {
3591
+ task_code: { type: "string" },
3592
+ phase: { type: "string" },
3593
+ title: { type: "string", minLength: 3, maxLength: 100 },
3594
+ description: { type: "string" },
3595
+ status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
3596
+ priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
3597
+ agent: { type: "string" },
3598
+ role: { type: "string" },
3599
+ doc_path: { type: "string" },
3600
+ tags: { type: "array", items: { type: "string" } },
3601
+ metadata: { type: "object" },
3602
+ parent_id: { type: "string", format: "uuid" },
3603
+ depends_on: { type: "string", format: "uuid" },
3604
+ est_tokens: { type: "number", minimum: 0 }
3605
+ },
3606
+ required: ["task_code", "phase", "title", "description"]
3607
+ },
3608
+ description: "Array of tasks for bulk creation"
3609
+ },
3610
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3611
+ },
3612
+ required: ["repo"]
3613
+ },
3614
+ outputSchema: {
3615
+ type: "object",
3616
+ properties: {
3617
+ success: { type: "boolean" },
3618
+ id: { type: "string" },
3619
+ task_code: { type: "string" },
3620
+ repo: { type: "string" },
3621
+ phase: { type: "string" },
3622
+ title: { type: "string" },
3623
+ status: { type: "string" },
3624
+ priority: { type: "number" },
3625
+ createdCount: { type: "number" },
3626
+ taskCodes: { type: "array", items: { type: "string" } }
3627
+ },
3628
+ required: ["success", "repo"]
3610
3629
  }
3611
- }
3612
- const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
3613
- if (actionIdMatch) {
3614
- const id = Number(actionIdMatch[1]);
3615
- const action = db.actions.getActionById(id);
3616
- if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
3617
- const payload = JSON.stringify(action, null, 2);
3618
- return {
3619
- contents: [
3620
- {
3621
- uri,
3622
- mimeType: "application/json",
3623
- text: payload,
3624
- size: Buffer.byteLength(payload, "utf8"),
3625
- annotations: {
3626
- audience: ["assistant"],
3627
- priority: 0.55,
3628
- lastModified: action.created_at
3629
- }
3630
- }
3631
- ]
3632
- };
3633
- }
3634
- throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
3635
- }
3636
- function parseRepoUri(uri) {
3637
- const prefix = "repository://";
3638
- if (!uri.startsWith(prefix)) return null;
3639
- const rest = uri.slice(prefix.length);
3640
- const queryStart = rest.indexOf("?");
3641
- const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
3642
- const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
3643
- const slashIdx = withoutQuery.indexOf("/");
3644
- if (slashIdx === -1) return null;
3645
- const name = withoutQuery.slice(0, slashIdx);
3646
- const path6 = withoutQuery.slice(slashIdx + 1);
3647
- if (!name || !path6) return null;
3648
- return { name, path: path6, query: new URLSearchParams(queryString) };
3649
- }
3650
- function paginateEntries(key, entries, params) {
3651
- const limit = normalizeLimit(params?.limit);
3652
- const offset = decodeCursor(params?.cursor);
3653
- const sliced = entries.slice(offset, offset + limit);
3654
- const nextOffset = offset + sliced.length;
3655
- return {
3656
- [key]: sliced,
3657
- nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
3658
- };
3659
- }
3660
- function normalizeLimit(limit) {
3661
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
3662
- return DEFAULT_PAGE_SIZE;
3663
- }
3664
- return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
3665
- }
3666
- function deriveLastModifiedFromCollection(values) {
3667
- const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
3668
- return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
3669
- }
3670
- function resourceNotFound(message, uri) {
3671
- const error = new Error(message);
3672
- error.code = -32002;
3673
- error.data = { uri };
3674
- return error;
3675
- }
3676
- function invalidCompletionParams(message) {
3677
- const error = new Error(message);
3678
- error.code = -32602;
3679
- return error;
3680
- }
3681
-
3682
- // src/mcp/prompts/loader.ts
3683
- import fs4 from "fs";
3684
- import path5 from "path";
3685
- import { fileURLToPath as fileURLToPath3 } from "url";
3686
- import matter from "gray-matter";
3687
- var __filename = fileURLToPath3(import.meta.url);
3688
- var __dirname2 = path5.dirname(__filename);
3689
- function findPromptDir() {
3690
- const candidates = [
3691
- // Production if chunked into dist/
3692
- "./prompts",
3693
- // Production if inlined into dist/mcp/
3694
- "../prompts",
3695
- // Dev: /src/mcp/prompts/definitions (next to loader.ts)
3696
- "./definitions"
3697
- ].map((relPath) => path5.resolve(__dirname2, relPath));
3698
- for (const dir of candidates) {
3699
- if (fs4.existsSync(dir)) {
3700
- const files = fs4.readdirSync(dir);
3701
- if (files.some((f) => f.endsWith(".md"))) {
3702
- return dir;
3703
- }
3630
+ },
3631
+ {
3632
+ name: "task-update",
3633
+ title: "Task Update",
3634
+ description: "Update one or more tasks. Supports single update via 'id' or bulk update via 'ids'. Provide only the fields that need to be changed. MANDATORY WORKFLOW: You cannot move a task from 'pending' or 'blocked' directly to 'completed'. You MUST move it to 'in_progress' first. When changing status to 'completed', include 'est_tokens' with the estimated total tokens actually used for the task.",
3635
+ annotations: {
3636
+ readOnlyHint: false,
3637
+ idempotentHint: false,
3638
+ openWorldHint: false
3639
+ },
3640
+ inputSchema: {
3641
+ type: "object",
3642
+ properties: {
3643
+ repo: { type: "string", description: "Repository name" },
3644
+ id: { type: "string", format: "uuid", description: "Task ID (for single update)" },
3645
+ ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk update)" },
3646
+ task_code: { type: "string" },
3647
+ phase: { type: "string" },
3648
+ title: { type: "string", minLength: 3, maxLength: 100 },
3649
+ description: { type: "string" },
3650
+ status: {
3651
+ type: "string",
3652
+ enum: ["backlog", "pending", "in_progress", "completed", "canceled", "blocked"],
3653
+ description: "New status. Transitions from 'backlog', 'pending' or 'blocked' to 'completed' are NOT allowed."
3654
+ },
3655
+ priority: { type: "number", minimum: 1, maximum: 5 },
3656
+ agent: { type: "string" },
3657
+ role: { type: "string" },
3658
+ model: { type: "string" },
3659
+ comment: {
3660
+ type: "string",
3661
+ description: "REQUIRED when changing task status. Explain WHY the status is changing (e.g., 'Starting implementation', 'Blocked by missing API docs', 'Verified fix')."
3662
+ },
3663
+ doc_path: { type: "string" },
3664
+ tags: { type: "array", items: { type: "string" } },
3665
+ metadata: { type: "object" },
3666
+ parent_id: { type: "string", format: "uuid" },
3667
+ depends_on: { type: "string", format: "uuid" },
3668
+ est_tokens: {
3669
+ type: "number",
3670
+ minimum: 0,
3671
+ description: "Estimated total tokens actually used for this task. Required when status changes to 'completed'."
3672
+ },
3673
+ force: {
3674
+ type: "boolean",
3675
+ description: "If true, bypasses status transition validation (e.g. pending -> completed)."
3676
+ },
3677
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3678
+ },
3679
+ required: ["repo"]
3680
+ },
3681
+ outputSchema: {
3682
+ type: "object",
3683
+ properties: {
3684
+ success: { type: "boolean" },
3685
+ id: { type: "string" },
3686
+ ids: { type: "array", items: { type: "string" } },
3687
+ repo: { type: "string" },
3688
+ status: { type: "string" },
3689
+ archivedToMemory: { type: "boolean" },
3690
+ updatedFields: {
3691
+ type: "array",
3692
+ items: { type: "string" }
3693
+ },
3694
+ updatedCount: { type: "number" }
3695
+ },
3696
+ required: ["success", "repo"]
3704
3697
  }
3705
- }
3706
- return path5.resolve(__dirname2, "./definitions");
3707
- }
3708
- var PROMPT_DIR = findPromptDir();
3709
- function listPromptFiles() {
3710
- if (!fs4.existsSync(PROMPT_DIR)) return [];
3711
- return fs4.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
3712
- }
3713
- function loadPromptFromMarkdown(name) {
3714
- const filePath = path5.join(PROMPT_DIR, `${name}.md`);
3715
- if (!fs4.existsSync(filePath)) {
3716
- throw new Error(`Prompt file not found: ${filePath}`);
3717
- }
3718
- const fileContent = fs4.readFileSync(filePath, "utf-8");
3719
- const { data, content } = matter(fileContent);
3720
- return {
3721
- name: data.name || name,
3722
- description: data.description || "",
3723
- arguments: data.arguments || [],
3724
- agent: data.agent,
3725
- content: content.trim()
3726
- };
3727
- }
3728
-
3729
- // src/mcp/prompts/registry.ts
3730
- function createPromptDefinition(loaded) {
3731
- return {
3732
- name: loaded.name,
3733
- description: loaded.description,
3734
- arguments: loaded.arguments,
3735
- agent: loaded.agent,
3736
- messages: [
3737
- {
3738
- role: "user",
3739
- content: {
3740
- type: "text",
3741
- text: loaded.content
3698
+ },
3699
+ {
3700
+ name: "task-delete",
3701
+ title: "Task Delete",
3702
+ description: "Delete one or more tasks from a repository. Supports single 'id' or bulk 'ids'.",
3703
+ annotations: {
3704
+ readOnlyHint: false,
3705
+ idempotentHint: false,
3706
+ destructiveHint: true,
3707
+ openWorldHint: false
3708
+ },
3709
+ inputSchema: {
3710
+ type: "object",
3711
+ properties: {
3712
+ repo: { type: "string", description: "Repository name" },
3713
+ id: { type: "string", format: "uuid", description: "Task ID (for single deletion)" },
3714
+ ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk deletion)" },
3715
+ structured: { type: "boolean", default: false, description: "If true, returns structured JSON result." }
3716
+ },
3717
+ required: ["repo"]
3718
+ },
3719
+ outputSchema: {
3720
+ type: "object",
3721
+ properties: {
3722
+ success: { type: "boolean" },
3723
+ id: { type: "string" },
3724
+ ids: { type: "array", items: { type: "string" } },
3725
+ repo: { type: "string" },
3726
+ deletedCount: { type: "number" }
3727
+ },
3728
+ required: ["success", "repo"]
3729
+ }
3730
+ },
3731
+ {
3732
+ name: "task-list",
3733
+ title: "Task List",
3734
+ description: "PRIMARY navigation and search tool for tasks. Returns a compact tabular list of tasks (id, task_code, title, status, priority). 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.",
3735
+ annotations: {
3736
+ readOnlyHint: true,
3737
+ idempotentHint: true,
3738
+ openWorldHint: false
3739
+ },
3740
+ inputSchema: {
3741
+ type: "object",
3742
+ properties: {
3743
+ repo: {
3744
+ type: "string",
3745
+ description: "Repository name"
3746
+ },
3747
+ status: {
3748
+ type: "string",
3749
+ default: "in_progress,pending",
3750
+ description: "Comma-separated status filter (backlog, pending, in_progress, completed, canceled, blocked). Defaults to 'in_progress,pending'."
3751
+ },
3752
+ phase: {
3753
+ type: "string",
3754
+ description: "Filter by phase (e.g., 'research', 'implementation')"
3755
+ },
3756
+ query: {
3757
+ type: "string",
3758
+ description: "Search keyword matching task code, title, or description"
3759
+ },
3760
+ limit: {
3761
+ type: "number",
3762
+ minimum: 1,
3763
+ maximum: 100,
3764
+ default: 5,
3765
+ description: "Maximum rows to return (default 5)"
3766
+ },
3767
+ offset: {
3768
+ type: "number",
3769
+ minimum: 0,
3770
+ default: 0,
3771
+ description: "Offset for pagination"
3772
+ },
3773
+ structured: {
3774
+ type: "boolean",
3775
+ default: false,
3776
+ description: "If true, returns structured JSON without the text content summary."
3742
3777
  }
3743
- }
3744
- ]
3745
- };
3746
- }
3747
- var PROMPTS = {};
3748
- var promptFiles = listPromptFiles();
3749
- for (const name of promptFiles) {
3750
- try {
3751
- PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
3752
- } catch (e) {
3753
- logger.warn(`Failed to load prompt ${name}: ${e}`);
3754
- }
3755
- }
3756
- async function listPrompts(db, session, params) {
3757
- const allPrompts = Object.values(PROMPTS).map((p) => ({
3758
- name: p.name,
3759
- description: p.description,
3760
- arguments: p.arguments,
3761
- metadata: p.agent ? { agent: p.agent } : void 0
3762
- }));
3763
- const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
3764
- const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
3765
- const offset = decodeCursor(params?.cursor);
3766
- const sliced = allPrompts.slice(offset, offset + limit);
3767
- const nextOffset = offset + sliced.length;
3768
- return {
3769
- prompts: sliced,
3770
- nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
3771
- };
3772
- }
3773
- async function getPrompt(name, args = {}, db, session) {
3774
- const prompt = PROMPTS[name];
3775
- if (!prompt) {
3776
- throw new Error(`Prompt not found: ${name}`);
3777
- }
3778
- const inferredRepo = inferRepoFromSession(session);
3779
- const messages = prompt.messages.map((m) => {
3780
- let text = m.content.text;
3781
- for (const [key, value] of Object.entries(args)) {
3782
- text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
3778
+ },
3779
+ required: ["repo"]
3780
+ },
3781
+ outputSchema: {
3782
+ type: "object",
3783
+ properties: {
3784
+ schema: { type: "string", enum: ["task-list"] },
3785
+ tasks: {
3786
+ type: "object",
3787
+ properties: {
3788
+ columns: {
3789
+ type: "array",
3790
+ items: { type: "string" },
3791
+ description: "Column names in order: id, task_code, title, status, priority, comments_count"
3792
+ },
3793
+ rows: {
3794
+ type: "array",
3795
+ items: { type: "array" },
3796
+ description: "Each row: [id, task_code, title, status, priority, comments_count]. Use task-detail to fetch full task."
3797
+ }
3798
+ },
3799
+ required: ["columns", "rows"]
3800
+ },
3801
+ count: { type: "number" },
3802
+ offset: { type: "number" }
3803
+ },
3804
+ required: ["schema", "tasks", "count"]
3783
3805
  }
3784
- text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
3785
- return {
3786
- ...m,
3787
- content: {
3788
- ...m.content,
3789
- text
3790
- }
3791
- };
3792
- });
3793
- return {
3794
- description: prompt.description,
3795
- messages,
3796
- metadata: prompt.agent ? { agent: prompt.agent } : void 0
3797
- };
3798
- }
3799
- async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
3800
- void name;
3801
- void contextArguments;
3802
- if (argName === "task_id") {
3803
- const values = dataSources.tasks.map((t) => t.id);
3804
- return rankCompletionValues(values, value);
3805
3806
  }
3806
- return [];
3807
- }
3807
+ ];
3808
3808
 
3809
3809
  export {
3810
- MCP_PROTOCOL_VERSION,
3811
- CAPABILITIES,
3812
3810
  logger,
3813
3811
  setLogLevel,
3814
3812
  getLogLevel,
3815
3813
  addLogSink,
3816
3814
  LOG_LEVEL_VALUES,
3817
3815
  createFileSink,
3818
- normalizeRepo,
3819
- SQLiteStore,
3820
- MemoryStoreSchema,
3821
- MemoryUpdateSchema,
3822
- MemorySearchSchema,
3823
- MemoryAcknowledgeSchema,
3824
- MemoryRecapSchema,
3825
- MemoryDeleteSchema,
3826
- MemorySummarizeSchema,
3827
- MemorySynthesizeSchema,
3828
- TaskCreateSchema,
3829
- TaskCreateInteractiveSchema,
3830
- TaskUpdateSchema,
3831
- TaskListSchema,
3832
- TaskDeleteSchema,
3833
- MemoryDetailSchema,
3834
- TaskGetSchema,
3835
- TOOL_DEFINITIONS,
3836
3816
  encodeCursor,
3837
3817
  decodeCursor,
3838
3818
  listResources,
@@ -3850,5 +3830,25 @@ export {
3850
3830
  PROMPTS,
3851
3831
  listPrompts,
3852
3832
  getPrompt,
3853
- completePromptArgument
3833
+ completePromptArgument,
3834
+ normalizeRepo,
3835
+ MemoryStoreSchema,
3836
+ MemoryUpdateSchema,
3837
+ MemorySearchSchema,
3838
+ MemoryAcknowledgeSchema,
3839
+ MemoryRecapSchema,
3840
+ MemoryDeleteSchema,
3841
+ MemorySummarizeSchema,
3842
+ MemorySynthesizeSchema,
3843
+ TaskCreateSchema,
3844
+ TaskCreateInteractiveSchema,
3845
+ TaskUpdateSchema,
3846
+ TaskListSchema,
3847
+ TaskDeleteSchema,
3848
+ MemoryDetailSchema,
3849
+ TaskGetSchema,
3850
+ TOOL_DEFINITIONS,
3851
+ SQLiteStore,
3852
+ MCP_PROTOCOL_VERSION,
3853
+ CAPABILITIES
3854
3854
  };