@vheins/local-memory-mcp 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CAPABILITIES,
4
- ClaimListSchema,
5
- ClaimReleaseSchema,
6
- HandoffCreateSchema,
7
- HandoffListSchema,
8
- HandoffUpdateSchema,
9
4
  LOG_LEVEL_VALUES,
10
5
  MCP_PROTOCOL_VERSION,
11
6
  MemoryAcknowledgeSchema,
@@ -25,18 +20,19 @@ import {
25
20
  StandardStoreSchema,
26
21
  StandardUpdateSchema,
27
22
  TOOL_DEFINITIONS,
28
- TaskClaimSchema,
29
23
  TaskCreateInteractiveSchema,
30
24
  TaskCreateSchema,
31
25
  TaskDeleteSchema,
32
26
  TaskGetSchema,
33
27
  TaskListSchema,
28
+ TaskSearchSchema,
34
29
  TaskUpdateSchema,
35
30
  addLogSink,
36
31
  buildStandardVectorText,
37
32
  completePromptArgument,
38
33
  completeResourceArgument,
39
34
  createFileSink,
35
+ createMcpResponse,
40
36
  createSessionContext,
41
37
  decodeCursor,
42
38
  encodeCursor,
@@ -44,7 +40,14 @@ import {
44
40
  findContainingRoot,
45
41
  getFilesystemRoots,
46
42
  getLogLevel,
43
+ getPrimaryTextContent,
47
44
  getPrompt,
45
+ handleClaimList,
46
+ handleClaimRelease,
47
+ handleHandoffCreate,
48
+ handleHandoffList,
49
+ handleHandoffUpdate,
50
+ handleTaskClaim,
48
51
  inferRepoFromSession,
49
52
  isPathWithinRoots,
50
53
  listPrompts,
@@ -57,7 +60,7 @@ import {
57
60
  toContextSlug,
58
61
  updateSessionFromInitialize,
59
62
  updateSessionRoots
60
- } from "../chunk-UZWV2DYU.js";
63
+ } from "../chunk-GXQCHTXK.js";
61
64
 
62
65
  // src/mcp/server.ts
63
66
  import readline from "readline";
@@ -186,111 +189,6 @@ function invalidCompletionParams(message) {
186
189
 
187
190
  // src/mcp/tools/memory.store.ts
188
191
  import { randomUUID } from "crypto";
189
-
190
- // src/mcp/utils/mcp-response.ts
191
- import { z } from "zod";
192
- var McpAnnotationsSchema = z.object({
193
- audience: z.array(z.enum(["user", "assistant"])).optional(),
194
- priority: z.number().min(0).max(1).optional(),
195
- lastModified: z.string().optional()
196
- }).strict().optional();
197
- var McpContentSchema = z.discriminatedUnion("type", [
198
- z.object({
199
- type: z.literal("text"),
200
- text: z.string(),
201
- annotations: McpAnnotationsSchema
202
- }),
203
- z.object({
204
- type: z.literal("image"),
205
- data: z.string(),
206
- mimeType: z.string(),
207
- annotations: McpAnnotationsSchema
208
- }),
209
- z.object({
210
- type: z.literal("resource"),
211
- resource: z.object({
212
- uri: z.string(),
213
- mimeType: z.string().optional(),
214
- text: z.string().optional(),
215
- annotations: McpAnnotationsSchema
216
- })
217
- })
218
- ]);
219
- function createMcpResponse(data, summary, options) {
220
- const { structuredContentPathHint, contentSummary, includeSerializedStructuredContent = false } = options || {};
221
- void includeSerializedStructuredContent;
222
- let finalData = data;
223
- if (data && typeof data === "object") {
224
- const cloned = JSON.parse(JSON.stringify(data));
225
- finalData = cloned;
226
- const arrayKeys = ["results", "tasks", "memories", "items"];
227
- let foundArray = false;
228
- for (const key of arrayKeys) {
229
- const value = cloned[key];
230
- if (Array.isArray(value)) {
231
- cloned[key] = value.map(
232
- (item) => pruneMetadata(item)
233
- );
234
- foundArray = true;
235
- }
236
- }
237
- if (Array.isArray(cloned)) {
238
- finalData = cloned.map((item) => pruneMetadata(item));
239
- } else if (!foundArray) {
240
- finalData = pruneMetadata(cloned);
241
- }
242
- }
243
- const content = [];
244
- if (contentSummary && contentSummary.trim().length > 0) {
245
- content.push({
246
- type: "text",
247
- text: contentSummary.trim()
248
- });
249
- } else if (summary && summary.trim().length > 0) {
250
- const pointerText = structuredContentPathHint ? `Read structuredContent.${structuredContentPathHint} for details.` : `Read structuredContent for machine-readable results.`;
251
- content.push({
252
- type: "text",
253
- text: `${summary.trim()} ${pointerText}`
254
- });
255
- }
256
- const response = {
257
- structuredContent: finalData,
258
- isError: false
259
- };
260
- if (includeSerializedStructuredContent === false) {
261
- delete response.structuredContent;
262
- }
263
- response.content = content;
264
- return response;
265
- }
266
- function pruneMetadata(item) {
267
- if (!item || typeof item !== "object") return item;
268
- const pruned = { ...item };
269
- const toRemove = [
270
- "hit_count",
271
- "recall_count",
272
- "last_used_at",
273
- "expires_at",
274
- "agent",
275
- "role",
276
- "model",
277
- "recall_rate",
278
- "vector_version",
279
- "similarity"
280
- // Similarity is useful but adds noise if many results
281
- ];
282
- for (const field of toRemove) {
283
- delete pruned[field];
284
- }
285
- return pruned;
286
- }
287
- function getPrimaryTextContent(response) {
288
- if (!Array.isArray(response.content)) return "";
289
- const textItem = response.content.find((item) => item.type === "text");
290
- return textItem?.type === "text" ? textItem.text : "";
291
- }
292
-
293
- // src/mcp/tools/memory.store.ts
294
192
  function hasMetadataLikeTitle(title) {
295
193
  const normalized = title.trim();
296
194
  return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
@@ -311,9 +209,10 @@ async function handleMemoryStore(params, db2, vectors2) {
311
209
  );
312
210
  }
313
211
  const now = (/* @__PURE__ */ new Date()).toISOString();
314
- const expires_at = validated.ttlDays != null ? new Date(Date.now() + validated.ttlDays * 864e5).toISOString() : null;
212
+ const createdAtTime = new Date(now).getTime();
213
+ const expires_at = validated.ttlDays != null ? new Date(createdAtTime + validated.ttlDays * 864e5).toISOString() : null;
315
214
  if (!validated.supersedes && validated.type !== "task_archive") {
316
- const conflict = await db2.memories.checkConflicts(
215
+ const conflict = await db2.memoryVectors.checkConflicts(
317
216
  validated.content,
318
217
  validated.scope.repo,
319
218
  validated.type,
@@ -416,9 +315,17 @@ function hasMetadataLikeTitle2(title) {
416
315
  }
417
316
  async function handleMemoryUpdate(params, db2, vectors2) {
418
317
  const validated = MemoryUpdateSchema.parse(params);
419
- const existing = db2.memories.getById(validated.id);
318
+ let resolvedId = validated.id;
319
+ if (!resolvedId && validated.code) {
320
+ const byCode = db2.memories.getByCode(validated.code);
321
+ if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
322
+ resolvedId = byCode.id;
323
+ } else if (!resolvedId) {
324
+ throw new Error("Either id or code must be provided");
325
+ }
326
+ const existing = db2.memories.getById(resolvedId);
420
327
  if (!existing) {
421
- throw new Error(`Memory not found: ${validated.id}`);
328
+ throw new Error(`Memory not found: ${resolvedId}`);
422
329
  }
423
330
  const repoFilter = params?.repo || params?.scope?.repo;
424
331
  if (repoFilter && repoFilter !== existing.scope.repo) {
@@ -444,20 +351,21 @@ async function handleMemoryUpdate(params, db2, vectors2) {
444
351
  if (validated.metadata !== void 0) updates.metadata = validated.metadata;
445
352
  if (validated.is_global !== void 0) updates.is_global = validated.is_global;
446
353
  if (validated.completed_at !== void 0) updates.completed_at = validated.completed_at;
447
- db2.memories.update(validated.id, updates);
354
+ db2.memories.update(resolvedId, updates);
448
355
  if (validated.content !== void 0) {
449
- await vectors2.upsert(validated.id, validated.content);
356
+ await vectors2.upsert(resolvedId, validated.content);
450
357
  }
451
- db2.actions.logAction("update", existing.scope.repo, { memoryId: validated.id, resultCount: 1 });
452
- logger.info("[Tool] memory.update", { repo: existing.scope.repo, id: validated.id, fields: Object.keys(updates) });
358
+ db2.actions.logAction("update", existing.scope.repo, { memoryId: resolvedId, resultCount: 1 });
359
+ logger.info("[Tool] memory.update", { repo: existing.scope.repo, id: resolvedId, fields: Object.keys(updates) });
453
360
  return createMcpResponse(
454
361
  {
455
362
  success: true,
456
- id: validated.id,
363
+ id: resolvedId,
364
+ code: existing.code,
457
365
  repo: existing.scope.repo,
458
366
  updatedFields: Object.keys(updates)
459
367
  },
460
- `Updated memory ${validated.id} in repo "${existing.scope.repo}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
368
+ `Updated memory ${resolvedId} in repo "${existing.scope.repo}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
461
369
  {
462
370
  structuredContentPathHint: "updatedFields",
463
371
  includeSerializedStructuredContent: validated.structured
@@ -499,7 +407,7 @@ async function handleMemorySearch(params, db2, vectors2) {
499
407
  const validated = MemorySearchSchema.parse(params);
500
408
  const searchQuery = expandQuery(validated.query, validated.prompt);
501
409
  const fetchLimit = (validated.offset + validated.limit) * 3;
502
- const similarityResults = db2.memories.searchBySimilarity(
410
+ const similarityResults = db2.memoryVectors.searchBySimilarity(
503
411
  searchQuery,
504
412
  validated.repo,
505
413
  fetchLimit,
@@ -815,7 +723,7 @@ function deriveTaskStatusTimestamps(status, now, existingTask) {
815
723
  async function archiveTaskToMemory(taskId, repo, storage, vectors2) {
816
724
  const task = storage.tasks.getTaskById(taskId);
817
725
  if (!task) return;
818
- const comments = storage.tasks.getTaskCommentsByTaskId(taskId);
726
+ const comments = storage.taskComments.getTaskCommentsByTaskId(taskId);
819
727
  let content = `Task: [${task.task_code}] ${task.title}
820
728
  `;
821
729
  content += `Phase: ${task.phase}
@@ -931,7 +839,7 @@ async function handleTaskCreate(args, storage) {
931
839
  const createdTasks = [];
932
840
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
933
841
  const codesInRequest = /* @__PURE__ */ new Set();
934
- const initialStats = storage.tasks.getTaskStats(repo);
842
+ const initialStats = storage.taskStats.getTaskStats(repo);
935
843
  let pendingInRequestCount = 0;
936
844
  for (const taskData of bulkTasks) {
937
845
  if (codesInRequest.has(taskData.task_code)) {
@@ -979,6 +887,8 @@ async function handleTaskCreate(args, storage) {
979
887
  canceled_at: statusTimestamps2.canceled_at,
980
888
  est_tokens: taskData.est_tokens ?? 0,
981
889
  tags,
890
+ commit_id: null,
891
+ changed_files: [],
982
892
  metadata: taskData.metadata || {},
983
893
  parent_id: resolveParentId(taskData.parent_id, repo, storage),
984
894
  depends_on: taskData.depends_on || null
@@ -1021,9 +931,11 @@ async function handleTaskCreate(args, storage) {
1021
931
  throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
1022
932
  }
1023
933
  if (status === "pending") {
1024
- const stats = storage.tasks.getTaskStats(repo);
934
+ const stats = storage.taskStats.getTaskStats(repo);
1025
935
  if (stats.todo >= 10) {
1026
- throw new Error(`Cannot create task as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`);
936
+ throw new Error(
937
+ `Cannot create task as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`
938
+ );
1027
939
  }
1028
940
  }
1029
941
  const taskId = randomUUID2();
@@ -1053,6 +965,8 @@ async function handleTaskCreate(args, storage) {
1053
965
  canceled_at: statusTimestamps.canceled_at,
1054
966
  est_tokens: est_tokens ?? 0,
1055
967
  tags: finalTags,
968
+ commit_id: null,
969
+ changed_files: [],
1056
970
  metadata: metadata || {},
1057
971
  parent_id: resolveParentId(parent_id, repo, storage),
1058
972
  depends_on: depends_on || null
@@ -1246,7 +1160,7 @@ async function handleTaskUpdate(args, storage, vectors2) {
1246
1160
  finalUpdates.in_progress_at = now;
1247
1161
  storage.tasks.updateTask(targetId, finalUpdates);
1248
1162
  if (comment !== void 0 || isStatusChanging) {
1249
- storage.tasks.insertTaskComment({
1163
+ storage.taskComments.insertTaskComment({
1250
1164
  id: randomUUID2(),
1251
1165
  task_id: targetId,
1252
1166
  repo,
@@ -1306,11 +1220,19 @@ async function handleTaskUpdate(args, storage, vectors2) {
1306
1220
  }
1307
1221
  async function handleTaskDelete(args, storage) {
1308
1222
  const validated = TaskDeleteSchema.parse(args);
1309
- const { repo, id, ids } = validated;
1310
- const targetIds = ids || (id ? [id] : []);
1311
- if (targetIds.length === 0) {
1312
- throw new Error("Either 'id' or 'ids' must be provided for deletion");
1223
+ const { repo, id, ids, task_code } = validated;
1224
+ const resolvedIds = [];
1225
+ if (ids) resolvedIds.push(...ids);
1226
+ if (id) resolvedIds.push(id);
1227
+ if (task_code) {
1228
+ const task = storage.tasks.getTaskByCode(repo, task_code);
1229
+ if (!task) throw new Error(`Task not found: ${task_code}`);
1230
+ resolvedIds.push(task.id);
1231
+ }
1232
+ if (resolvedIds.length === 0) {
1233
+ throw new Error("Either 'id', 'ids', or 'task_code' must be provided for deletion");
1313
1234
  }
1235
+ const targetIds = resolvedIds;
1314
1236
  for (const targetId of targetIds) {
1315
1237
  storage.tasks.deleteTask(targetId);
1316
1238
  }
@@ -1561,31 +1483,54 @@ async function executeSamplingTool(toolName, rawInput, db2, vectors2) {
1561
1483
  // src/mcp/tools/memory.delete.ts
1562
1484
  async function handleMemoryDelete(params, db2, vectors2, onProgress) {
1563
1485
  const validated = MemoryDeleteSchema.parse(params);
1564
- const { id, ids, repo, structured } = validated;
1565
- const targetIds = ids || (id ? [id] : []);
1566
- if (targetIds.length === 0) {
1567
- throw new Error("Either 'id' or 'ids' must be provided for deletion");
1568
- }
1486
+ const { id, ids, code, codes, repo, structured } = validated;
1487
+ const resolvedIds = [];
1488
+ if (ids) resolvedIds.push(...ids);
1489
+ if (id) resolvedIds.push(id);
1490
+ if (code) {
1491
+ const entry = db2.memories.getByCode(code);
1492
+ if (!entry) throw new Error(`Memory not found: ${code}`);
1493
+ resolvedIds.push(entry.id);
1494
+ }
1495
+ if (codes) {
1496
+ for (const c of codes) {
1497
+ const entry = db2.memories.getByCode(c);
1498
+ if (!entry) throw new Error(`Memory not found: ${c}`);
1499
+ resolvedIds.push(entry.id);
1500
+ }
1501
+ }
1502
+ if (resolvedIds.length === 0) {
1503
+ throw new Error("Either 'id', 'ids', 'code', or 'codes' must be provided for deletion");
1504
+ }
1505
+ const targetIds = resolvedIds;
1569
1506
  let deletedCount = 0;
1570
1507
  const deletedCodes = [];
1571
1508
  let lastRepo = repo || "unknown";
1572
1509
  const total = targetIds.length;
1573
1510
  let progress = 0;
1511
+ const existingMemories = db2.memories.getByIds(targetIds);
1512
+ const existingMap = new Map(existingMemories.map((m) => [m.id, m]));
1513
+ const validIdsToDelete = [];
1574
1514
  for (const targetId of targetIds) {
1575
- if (onProgress) {
1576
- onProgress(progress, total);
1577
- }
1578
- const existing = db2.memories.getById(targetId);
1515
+ const existing = existingMap.get(targetId);
1579
1516
  if (existing) {
1580
1517
  lastRepo = existing.scope.repo;
1581
1518
  deletedCodes.push(existing.code || existing.id);
1582
- db2.memories.delete(targetId);
1583
- await vectors2.remove(targetId);
1584
- deletedCount++;
1519
+ validIdsToDelete.push(targetId);
1585
1520
  } else if (id) {
1586
1521
  throw new Error(`Memory not found: ${targetId}`);
1587
1522
  }
1588
- progress++;
1523
+ }
1524
+ if (validIdsToDelete.length > 0) {
1525
+ db2.memoryArchives.bulkDeleteMemories(validIdsToDelete);
1526
+ for (const validId of validIdsToDelete) {
1527
+ if (onProgress) {
1528
+ onProgress(progress, total);
1529
+ }
1530
+ await vectors2.remove(validId);
1531
+ progress++;
1532
+ }
1533
+ deletedCount = validIdsToDelete.length;
1589
1534
  }
1590
1535
  if (onProgress) {
1591
1536
  onProgress(progress, total);
@@ -1611,9 +1556,17 @@ async function handleMemoryDelete(params, db2, vectors2, onProgress) {
1611
1556
  // src/mcp/tools/memory.acknowledge.ts
1612
1557
  async function handleMemoryAcknowledge(params, db2) {
1613
1558
  const validated = MemoryAcknowledgeSchema.parse(params);
1614
- const memory = db2.memories.getById(validated.memory_id);
1559
+ let memoryId = validated.memory_id;
1560
+ if (!memoryId && validated.code) {
1561
+ const byCode = db2.memories.getByCode(validated.code);
1562
+ if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
1563
+ memoryId = byCode.id;
1564
+ } else if (!memoryId) {
1565
+ throw new Error("Either memory_id or code must be provided");
1566
+ }
1567
+ const memory = db2.memories.getById(memoryId);
1615
1568
  if (!memory) {
1616
- throw new Error(`Memory with ID ${validated.memory_id} not found.`);
1569
+ throw new Error(`Memory with ID ${memoryId} not found.`);
1617
1570
  }
1618
1571
  if (validated.status === "used") {
1619
1572
  db2.memories.incrementRecallCount(memory.id);
@@ -1674,266 +1627,6 @@ async function handleMemoryDetail(args, storage) {
1674
1627
  });
1675
1628
  }
1676
1629
 
1677
- // src/mcp/tools/handoff.manage.ts
1678
- function buildHandoffListSummary(repo, count, status, fromAgent, toAgent) {
1679
- const parts = [`Found ${count} handoff${count === 1 ? "" : "s"} in repo "${repo}".`];
1680
- if (status) {
1681
- parts.push(`Status filter: ${status}.`);
1682
- }
1683
- if (fromAgent) {
1684
- parts.push(`From agent: ${fromAgent}.`);
1685
- }
1686
- if (toAgent) {
1687
- parts.push(`To agent: ${toAgent}.`);
1688
- }
1689
- return parts.join("\n");
1690
- }
1691
- function buildClaimListSummary(repo, count, agent, activeOnly) {
1692
- const parts = [`Found ${count} claim${count === 1 ? "" : "s"} in repo "${repo}".`];
1693
- if (agent) {
1694
- parts.push(`Agent filter: ${agent}.`);
1695
- }
1696
- if (activeOnly) {
1697
- parts.push("Showing active claims only.");
1698
- }
1699
- return parts.join("\n");
1700
- }
1701
- async function handleHandoffCreate(args, storage) {
1702
- const validated = HandoffCreateSchema.parse(args);
1703
- const { repo, from_agent, to_agent, task_id, task_code, summary, context, expires_at, structured } = validated;
1704
- let resolvedTaskId = task_id ?? null;
1705
- if (!resolvedTaskId && task_code) {
1706
- const task = storage.tasks.getTaskByCode(repo, task_code);
1707
- if (!task) {
1708
- throw new Error(`Task not found: ${task_code} in repo ${repo}`);
1709
- }
1710
- resolvedTaskId = task.id;
1711
- }
1712
- const handoff = storage.handoffs.createHandoff({
1713
- repo,
1714
- from_agent,
1715
- to_agent,
1716
- task_id: resolvedTaskId,
1717
- summary,
1718
- context,
1719
- expires_at
1720
- });
1721
- const contentSummary = [
1722
- `Created handoff ${handoff.id}.`,
1723
- `Repo: ${handoff.repo}`,
1724
- `From: ${handoff.from_agent}`,
1725
- `To: ${handoff.to_agent || "unassigned"}`,
1726
- `Status: ${handoff.status}`,
1727
- `Task ID: ${handoff.task_id || "-"}`,
1728
- `Summary: ${handoff.summary}`
1729
- ].join("\n");
1730
- return createMcpResponse(handoff, contentSummary, {
1731
- contentSummary,
1732
- includeSerializedStructuredContent: structured
1733
- });
1734
- }
1735
- async function handleHandoffList(args, storage) {
1736
- const validated = HandoffListSchema.parse(args);
1737
- const { repo, status, from_agent, to_agent, limit, offset, structured } = validated;
1738
- const handoffs = storage.handoffs.listHandoffs({
1739
- repo,
1740
- status,
1741
- from_agent,
1742
- to_agent,
1743
- limit,
1744
- offset
1745
- });
1746
- const COLUMNS = [
1747
- "id",
1748
- "from_agent",
1749
- "to_agent",
1750
- "task_id",
1751
- "task_code",
1752
- "status",
1753
- "created_at",
1754
- "updated_at",
1755
- "expires_at",
1756
- "summary",
1757
- "context"
1758
- ];
1759
- const rows = handoffs.map((handoff) => [
1760
- handoff.id,
1761
- handoff.from_agent,
1762
- handoff.to_agent,
1763
- handoff.task_id,
1764
- handoff.task_code ?? null,
1765
- handoff.status,
1766
- handoff.created_at,
1767
- handoff.updated_at,
1768
- handoff.expires_at,
1769
- handoff.summary,
1770
- handoff.context
1771
- ]);
1772
- const structuredData = {
1773
- schema: "handoff-list",
1774
- handoffs: {
1775
- columns: [...COLUMNS],
1776
- rows
1777
- },
1778
- count: rows.length,
1779
- offset
1780
- };
1781
- const contentSummary = buildHandoffListSummary(repo, rows.length, status, from_agent, to_agent);
1782
- return createMcpResponse(structuredData, contentSummary, {
1783
- contentSummary,
1784
- includeSerializedStructuredContent: structured
1785
- });
1786
- }
1787
- async function handleHandoffUpdate(args, storage) {
1788
- const validated = HandoffUpdateSchema.parse(args);
1789
- const { id, status, structured } = validated;
1790
- const existing = storage.handoffs.getHandoffById(id);
1791
- if (!existing) {
1792
- throw new Error(`Handoff not found: ${id}`);
1793
- }
1794
- const success = storage.handoffs.updateHandoffStatus(id, status);
1795
- if (!success) {
1796
- throw new Error(`Failed to update handoff: ${id}`);
1797
- }
1798
- const updated = storage.handoffs.getHandoffById(id);
1799
- const result = {
1800
- success,
1801
- id,
1802
- status,
1803
- handoff: updated
1804
- };
1805
- const contentSummary = [`Updated handoff ${id}.`, `Status: ${status}`].join("\n");
1806
- return createMcpResponse(result, contentSummary, {
1807
- contentSummary,
1808
- includeSerializedStructuredContent: structured
1809
- });
1810
- }
1811
- async function handleTaskClaim(args, storage) {
1812
- const validated = TaskClaimSchema.parse(args);
1813
- const { repo, task_id, task_code, agent, role, metadata, structured } = validated;
1814
- let taskId = task_id;
1815
- let resolvedTaskCode;
1816
- if (taskId) {
1817
- const task = storage.tasks.getTaskById(taskId);
1818
- if (!task || task.repo !== repo) {
1819
- throw new Error(`Task not found: ${taskId} in repo ${repo}`);
1820
- }
1821
- resolvedTaskCode = task.task_code;
1822
- } else if (task_code) {
1823
- const task = storage.tasks.getTaskByCode(repo, task_code);
1824
- if (!task) {
1825
- throw new Error(`Task not found: ${task_code} in repo ${repo}`);
1826
- }
1827
- taskId = task.id;
1828
- resolvedTaskCode = task.task_code;
1829
- } else {
1830
- throw new Error("Either task_id or task_code must be provided");
1831
- }
1832
- const claim = storage.handoffs.claimTask({
1833
- repo,
1834
- task_id: taskId,
1835
- agent,
1836
- role,
1837
- metadata
1838
- });
1839
- const responseData = {
1840
- ...claim,
1841
- task_code: resolvedTaskCode
1842
- };
1843
- const contentSummary = [
1844
- `Claimed task ${resolvedTaskCode || claim.task_id}.`,
1845
- `Repo: ${claim.repo}`,
1846
- `Task ID: ${claim.task_id}`,
1847
- `Agent: ${claim.agent}`,
1848
- `Role: ${claim.role}`,
1849
- `Claimed At: ${claim.claimed_at}`
1850
- ].join("\n");
1851
- const response = createMcpResponse(responseData, contentSummary, {
1852
- contentSummary,
1853
- includeSerializedStructuredContent: structured
1854
- });
1855
- if (structured) {
1856
- response.structuredContent = responseData;
1857
- }
1858
- return response;
1859
- }
1860
- async function handleClaimList(args, storage) {
1861
- const validated = ClaimListSchema.parse(args);
1862
- const { repo, agent, active_only, limit, offset, structured } = validated;
1863
- const claims = storage.handoffs.listClaims({
1864
- repo,
1865
- agent,
1866
- active_only,
1867
- limit,
1868
- offset
1869
- });
1870
- const COLUMNS = ["id", "task_id", "task_code", "agent", "role", "claimed_at", "released_at", "metadata"];
1871
- const rows = claims.map((claim) => [
1872
- claim.id,
1873
- claim.task_id,
1874
- claim.task_code ?? null,
1875
- claim.agent,
1876
- claim.role,
1877
- claim.claimed_at,
1878
- claim.released_at,
1879
- claim.metadata
1880
- ]);
1881
- const structuredData = {
1882
- schema: "claim-list",
1883
- claims: {
1884
- columns: [...COLUMNS],
1885
- rows
1886
- },
1887
- count: rows.length,
1888
- offset
1889
- };
1890
- const contentSummary = buildClaimListSummary(repo, rows.length, agent, active_only);
1891
- return createMcpResponse(structuredData, contentSummary, {
1892
- contentSummary,
1893
- includeSerializedStructuredContent: structured
1894
- });
1895
- }
1896
- async function handleClaimRelease(args, storage) {
1897
- const validated = ClaimReleaseSchema.parse(args);
1898
- const { repo, task_id, task_code, agent, structured } = validated;
1899
- let resolvedTaskId = task_id;
1900
- let resolvedTaskCode = task_code ?? null;
1901
- if (resolvedTaskId) {
1902
- const task = storage.tasks.getTaskById(resolvedTaskId);
1903
- if (!task || task.repo !== repo) {
1904
- throw new Error(`Task not found: ${resolvedTaskId} in repo ${repo}`);
1905
- }
1906
- resolvedTaskCode = task.task_code;
1907
- } else if (task_code) {
1908
- const task = storage.tasks.getTaskByCode(repo, task_code);
1909
- if (!task) {
1910
- throw new Error(`Task not found: ${task_code} in repo ${repo}`);
1911
- }
1912
- resolvedTaskId = task.id;
1913
- resolvedTaskCode = task.task_code;
1914
- }
1915
- const success = storage.handoffs.releaseClaim(resolvedTaskId, agent);
1916
- if (!success) {
1917
- throw new Error(`No active claim found for task ${resolvedTaskCode || resolvedTaskId}`);
1918
- }
1919
- const result = {
1920
- success,
1921
- repo,
1922
- task_id: resolvedTaskId,
1923
- task_code: resolvedTaskCode,
1924
- agent: agent ?? null
1925
- };
1926
- const contentSummary = [
1927
- `Released claim for task ${resolvedTaskCode || resolvedTaskId}.`,
1928
- `Repo: ${repo}`,
1929
- agent ? `Agent: ${agent}` : "Agent: any active claimant"
1930
- ].join("\n");
1931
- return createMcpResponse(result, contentSummary, {
1932
- contentSummary,
1933
- includeSerializedStructuredContent: structured
1934
- });
1935
- }
1936
-
1937
1630
  // src/mcp/tools/standard.store.ts
1938
1631
  import { randomUUID as randomUUID3 } from "crypto";
1939
1632
  function generateShortCode2() {
@@ -2252,9 +1945,17 @@ async function handleStandardSearch(params, db2, vectors2) {
2252
1945
  // src/mcp/tools/standard.update.ts
2253
1946
  async function handleStandardUpdate(params, db2, vectors2) {
2254
1947
  const validated = StandardUpdateSchema.parse(params);
2255
- const existing = db2.standards.getById(validated.id);
1948
+ let resolvedId = validated.id;
1949
+ if (!resolvedId && validated.code) {
1950
+ const byCode = db2.standards.getByCode(validated.code);
1951
+ if (!byCode) throw new Error(`Coding standard not found: ${validated.code}`);
1952
+ resolvedId = byCode.id;
1953
+ } else if (!resolvedId) {
1954
+ throw new Error("Either id or code must be provided");
1955
+ }
1956
+ const existing = db2.standards.getById(resolvedId);
2256
1957
  if (!existing) {
2257
- throw new Error(`Coding standard not found: ${validated.id}`);
1958
+ throw new Error(`Coding standard not found: ${resolvedId}`);
2258
1959
  }
2259
1960
  const updates = {};
2260
1961
  if (validated.name !== void 0) updates.title = validated.name;
@@ -2270,26 +1971,27 @@ async function handleStandardUpdate(params, db2, vectors2) {
2270
1971
  if (validated.metadata !== void 0) updates.metadata = validated.metadata;
2271
1972
  if (validated.agent !== void 0) updates.agent = validated.agent;
2272
1973
  if (validated.model !== void 0) updates.model = validated.model;
2273
- db2.standards.update(validated.id, updates);
1974
+ db2.standards.update(resolvedId, updates);
2274
1975
  const merged = {
2275
1976
  ...existing,
2276
1977
  ...updates,
2277
1978
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
2278
1979
  };
2279
1980
  if (validated.name !== void 0 || validated.content !== void 0 || validated.context !== void 0 || validated.version !== void 0 || validated.language !== void 0 || validated.stack !== void 0 || validated.tags !== void 0 || validated.metadata !== void 0) {
2280
- await vectors2.upsert(validated.id, buildStandardVectorText(merged), "standard");
1981
+ await vectors2.upsert(resolvedId, buildStandardVectorText(merged), "standard");
2281
1982
  }
2282
1983
  logger.info("[Tool] standard.update - updated coding standard", {
2283
- standardId: validated.id,
1984
+ standardId: resolvedId,
2284
1985
  fields: Object.keys(updates)
2285
1986
  });
2286
1987
  return createMcpResponse(
2287
1988
  {
2288
1989
  success: true,
2289
- id: validated.id,
1990
+ id: resolvedId,
1991
+ code: existing.code,
2290
1992
  updatedFields: Object.keys(updates)
2291
1993
  },
2292
- `Updated coding standard ${validated.id}. Fields: ${Object.keys(updates).join(", ") || "none"}.`,
1994
+ `Updated coding standard ${resolvedId}. Fields: ${Object.keys(updates).join(", ") || "none"}.`,
2293
1995
  {
2294
1996
  structuredContentPathHint: "updatedFields",
2295
1997
  includeSerializedStructuredContent: true
@@ -2334,11 +2036,26 @@ async function handleStandardDetail(args, storage) {
2334
2036
  // src/mcp/tools/standard.delete.ts
2335
2037
  async function handleStandardDelete(params, db2, vectors2) {
2336
2038
  const validated = StandardDeleteSchema.parse(params);
2337
- const { id, ids, repo, structured } = validated;
2338
- const targetIds = ids || (id ? [id] : []);
2339
- if (targetIds.length === 0) {
2340
- throw new Error("Either 'id' or 'ids' must be provided for deletion");
2341
- }
2039
+ const { id, ids, code, codes, repo, structured } = validated;
2040
+ const resolvedIds = [];
2041
+ if (ids) resolvedIds.push(...ids);
2042
+ if (id) resolvedIds.push(id);
2043
+ if (code) {
2044
+ const entry = db2.standards.getByCode(code);
2045
+ if (!entry) throw new Error(`Coding standard not found: ${code}`);
2046
+ resolvedIds.push(entry.id);
2047
+ }
2048
+ if (codes) {
2049
+ for (const c of codes) {
2050
+ const entry = db2.standards.getByCode(c);
2051
+ if (!entry) throw new Error(`Coding standard not found: ${c}`);
2052
+ resolvedIds.push(entry.id);
2053
+ }
2054
+ }
2055
+ if (resolvedIds.length === 0) {
2056
+ throw new Error("Either 'id', 'ids', 'code', or 'codes' must be provided for deletion");
2057
+ }
2058
+ const targetIds = resolvedIds;
2342
2059
  let deletedCount = 0;
2343
2060
  const deletedTitles = [];
2344
2061
  let lastRepo = repo || "unknown";
@@ -2387,7 +2104,7 @@ async function handleTaskGet(args, storage) {
2387
2104
  if (!task) {
2388
2105
  throw new Error(`Task not found: ${id || task_code} in repo ${repo}`);
2389
2106
  }
2390
- const comments = storage.tasks.getTaskCommentsByTaskId(task.id);
2107
+ const comments = storage.taskComments.getTaskCommentsByTaskId(task.id);
2391
2108
  let contentSummary;
2392
2109
  if (!isStructuredRequest) {
2393
2110
  const lines = [
@@ -2424,6 +2141,62 @@ async function handleTaskGet(args, storage) {
2424
2141
  });
2425
2142
  }
2426
2143
 
2144
+ // src/mcp/tools/task.search.ts
2145
+ async function handleTaskSearch(args, storage) {
2146
+ const validated = TaskSearchSchema.parse(args);
2147
+ const { repo, query, status, limit, offset, structured: isStructuredRequest = false, phase, priority } = validated;
2148
+ let tasks;
2149
+ if (status) {
2150
+ const statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
2151
+ if (statuses.length > 1) {
2152
+ tasks = storage.tasks.getTasksByMultipleStatuses(repo, statuses, void 0, void 0, query);
2153
+ } else {
2154
+ tasks = storage.tasks.getTasksByRepo(repo, status, void 0, void 0, query);
2155
+ }
2156
+ } else {
2157
+ tasks = storage.tasks.getTasksByRepo(repo, void 0, void 0, void 0, query);
2158
+ }
2159
+ if (phase) {
2160
+ const phaseLower = phase.toLowerCase();
2161
+ tasks = tasks.filter((t) => t.phase && t.phase.toLowerCase() === phaseLower);
2162
+ }
2163
+ if (priority !== void 0) {
2164
+ tasks = tasks.filter((t) => t.priority === priority);
2165
+ }
2166
+ const total = tasks.length;
2167
+ const paginated = tasks.slice(offset, offset + limit);
2168
+ const COLUMNS = ["id", "task_code", "title", "status", "priority", "updated_at", "phase"];
2169
+ const rows = paginated.map((t) => [t.id, t.task_code, t.title, t.status, t.priority, t.updated_at, t.phase]);
2170
+ const structuredData = {
2171
+ schema: "task-search",
2172
+ query,
2173
+ count: paginated.length,
2174
+ total,
2175
+ offset,
2176
+ limit,
2177
+ results: {
2178
+ columns: [...COLUMNS],
2179
+ rows
2180
+ }
2181
+ };
2182
+ let contentSummary;
2183
+ if (!isStructuredRequest) {
2184
+ contentSummary = paginated.length > 0 ? `Found ${total} tasks matching "${query}" in repo "${repo}". Use task-detail to fetch full task content.` : `No tasks found for "${query}" in repo "${repo}".`;
2185
+ }
2186
+ logger.info("[Tool] task.search", {
2187
+ repo,
2188
+ query,
2189
+ total,
2190
+ offset,
2191
+ returned: paginated.length
2192
+ });
2193
+ return createMcpResponse(structuredData, contentSummary || `Found ${total} tasks for "${query}".`, {
2194
+ contentSummary,
2195
+ structuredContentPathHint: "results",
2196
+ includeSerializedStructuredContent: isStructuredRequest
2197
+ });
2198
+ }
2199
+
2427
2200
  // src/mcp/router.ts
2428
2201
  function createRouter(db2, vectors2, options) {
2429
2202
  const getSessionContext = options?.getSessionContext;
@@ -2575,6 +2348,8 @@ function createRouter(db2, vectors2, options) {
2575
2348
  return await handleTaskDelete(args, db2);
2576
2349
  case "task-list":
2577
2350
  return await handleTaskList(args, db2);
2351
+ case "task-search":
2352
+ return await handleTaskSearch(args, db2);
2578
2353
  case "task-detail":
2579
2354
  return await handleTaskGet(args, db2);
2580
2355
  default:
@@ -2749,8 +2524,8 @@ logger.info("[Server] startup", { pid: process.pid, version: CAPABILITIES.server
2749
2524
  vectors.initialize().catch((err) => {
2750
2525
  logger.warn("[Server] Initial vector model loading failed. Will retry on first use.", { error: String(err) });
2751
2526
  });
2752
- var expiredArchived = db.memories.archiveExpiredMemories();
2753
- var lowScoreArchived = db.memories.archiveLowScoreMemories();
2527
+ var expiredArchived = db.memoryArchives.archiveExpiredMemories();
2528
+ var lowScoreArchived = db.memoryArchives.archiveLowScoreMemories();
2754
2529
  var totalArchived = (expiredArchived || 0) + (lowScoreArchived || 0);
2755
2530
  if (totalArchived > 0) {
2756
2531
  logger.info(
@@ -2771,9 +2546,7 @@ var logNotificationsEnabled = false;
2771
2546
  var handleMethod = createRouter(db, vectors, {
2772
2547
  getSessionContext: () => session,
2773
2548
  sampleMessage: (params) => requestClient("sampling/createMessage", params),
2774
- // eslint-disable-line @typescript-eslint/no-explicit-any
2775
2549
  elicit: (params) => requestClient("elicitation/create", params),
2776
- // eslint-disable-line @typescript-eslint/no-explicit-any
2777
2550
  onResourcesMutated: (uris) => notifyUpdatedResources(uris)
2778
2551
  });
2779
2552
  addLogSink((payload) => {