@vheins/local-memory-mcp 0.14.4 → 0.14.5

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.
@@ -8,8 +8,8 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
11
- <script type="module" crossorigin src="/assets/index-CD8w6eaB.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-CUg8rZCA.css">
11
+ <script type="module" crossorigin src="/assets/index-DtwSMMIE.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-6TruRX0q.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="app"></div>
@@ -16,7 +16,7 @@ import {
16
16
  handleTaskClaim,
17
17
  listResources,
18
18
  logger
19
- } from "../chunk-U67YH7MG.js";
19
+ } from "../chunk-EY6A4764.js";
20
20
 
21
21
  // src/dashboard/server.ts
22
22
  import express from "express";
@@ -60,7 +60,7 @@ import {
60
60
  toContextSlug,
61
61
  updateSessionFromInitialize,
62
62
  updateSessionRoots
63
- } from "../chunk-U67YH7MG.js";
63
+ } from "../chunk-EY6A4764.js";
64
64
 
65
65
  // src/mcp/server.ts
66
66
  import readline from "readline";
@@ -189,6 +189,7 @@ function invalidCompletionParams(message) {
189
189
 
190
190
  // src/mcp/tools/memory.store.ts
191
191
  import { randomUUID } from "crypto";
192
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
192
193
  function hasMetadataLikeTitle(title) {
193
194
  const normalized = title.trim();
194
195
  return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
@@ -201,21 +202,28 @@ function generateShortCode() {
201
202
  }
202
203
  return code;
203
204
  }
204
- async function handleMemoryStore(params, db2, vectors2) {
205
- const validated = MemoryStoreSchema.parse(params);
206
- if (hasMetadataLikeTitle(validated.title)) {
205
+ function resolveMemorySupersedes(value, db2) {
206
+ if (!value) return null;
207
+ if (UUID_REGEX.test(value)) return value;
208
+ const memory = db2.memories.getByCode(value);
209
+ if (!memory) throw new Error(`supersedes: memory with code '${value}' not found`);
210
+ return memory.id;
211
+ }
212
+ async function storeSingleMemory(params, db2, vectors2) {
213
+ if (hasMetadataLikeTitle(params.title)) {
207
214
  throw new Error(
208
215
  "Title appears to contain metadata. Keep title concise and move agent/role/date details into metadata or dedicated fields."
209
216
  );
210
217
  }
211
218
  const now = (/* @__PURE__ */ new Date()).toISOString();
212
219
  const createdAtTime = new Date(now).getTime();
213
- const expires_at = validated.ttlDays != null ? new Date(createdAtTime + validated.ttlDays * 864e5).toISOString() : null;
214
- if (!validated.supersedes && validated.type !== "task_archive") {
220
+ const expires_at = params.ttlDays != null ? new Date(createdAtTime + params.ttlDays * 864e5).toISOString() : null;
221
+ const resolvedSupersedes = resolveMemorySupersedes(params.supersedes, db2);
222
+ if (!resolvedSupersedes && params.type !== "task_archive") {
215
223
  const conflict = await db2.memoryVectors.checkConflicts(
216
- validated.content,
217
- validated.scope.repo,
218
- validated.type,
224
+ params.content,
225
+ params.scope.repo,
226
+ params.type,
219
227
  vectors2,
220
228
  0.55
221
229
  );
@@ -225,45 +233,34 @@ async function handleMemoryStore(params, db2, vectors2) {
225
233
  success: false,
226
234
  error: "MEMORY_CONFLICT",
227
235
  message: `This memory content overlaps significantly with an existing memory (ID: ${conflict.id}).`,
228
- conflicting_memory: {
229
- id: conflict.id,
230
- title: conflict.title,
231
- content: conflict.content
232
- },
236
+ conflicting_memory: { id: conflict.id, title: conflict.title, content: conflict.content },
233
237
  instruction: "Use 'memory-update' on the existing ID, or provide 'supersedes' if this new memory replaces it. If the old memory is no longer relevant, you can delete it first."
234
238
  },
235
- `Rejected due to conflict with ${conflict.id}. Hint: Use 'supersedes' if this replaces the old memory, or 'memory-update' if you're updating it. If the old memory is no longer relevant, delete it first using 'memory-delete'.`,
236
- {
237
- structuredContentPathHint: "conflicting_memory"
238
- }
239
+ `Rejected due to conflict: "${conflict.title}" (${conflict.id.slice(0, 8)}...). Hint: Use 'supersedes' if this replaces the old memory, or 'memory-update' if updating. If no longer relevant, delete first.`
239
240
  );
240
241
  }
241
242
  }
242
- if (validated.supersedes) {
243
- const oldMemory = db2.memories.getById(validated.supersedes);
243
+ if (resolvedSupersedes) {
244
+ const oldMemory = db2.memories.getById(resolvedSupersedes);
244
245
  if (oldMemory) {
245
246
  db2.memories.update(oldMemory.id, { status: "archived" });
246
- logger.info("[Tool] memory.store - archived superseded memory", {
247
- oldId: oldMemory.id,
248
- newId: validated.supersedes
249
- });
250
247
  }
251
248
  }
252
- const tags = validated.tags ?? [];
253
- if (validated.scope.language && !tags.includes(validated.scope.language.toLowerCase())) {
254
- tags.push(validated.scope.language.toLowerCase());
249
+ const tags = params.tags ?? [];
250
+ if (params.scope.language && !tags.includes(params.scope.language.toLowerCase())) {
251
+ tags.push(params.scope.language.toLowerCase());
255
252
  }
256
253
  const entry = {
257
254
  id: randomUUID(),
258
- code: validated.code || generateShortCode(),
259
- type: validated.type,
260
- title: validated.title,
261
- content: validated.content,
262
- importance: validated.importance,
263
- agent: validated.agent,
264
- role: validated.role,
265
- model: validated.model,
266
- scope: validated.scope,
255
+ code: params.code || generateShortCode(),
256
+ type: params.type,
257
+ title: params.title,
258
+ content: params.content,
259
+ importance: params.importance,
260
+ agent: params.agent,
261
+ role: params.role,
262
+ model: params.model,
263
+ scope: params.scope,
267
264
  created_at: now,
268
265
  updated_at: now,
269
266
  completed_at: null,
@@ -271,11 +268,11 @@ async function handleMemoryStore(params, db2, vectors2) {
271
268
  recall_count: 0,
272
269
  last_used_at: null,
273
270
  expires_at,
274
- supersedes: validated.supersedes ?? null,
271
+ supersedes: resolvedSupersedes,
275
272
  status: "active",
276
273
  tags,
277
- metadata: validated.metadata ?? {},
278
- is_global: validated.is_global
274
+ metadata: params.metadata ?? {},
275
+ is_global: params.is_global
279
276
  };
280
277
  db2.memories.insert(entry);
281
278
  try {
@@ -283,13 +280,6 @@ async function handleMemoryStore(params, db2, vectors2) {
283
280
  } catch (error) {
284
281
  logger.warn("Failed to generate vector embedding", { error: String(error) });
285
282
  }
286
- logger.info("[Tool] memory.store", {
287
- repo: validated.scope.repo,
288
- id: entry.id,
289
- title: entry.title,
290
- type: entry.type,
291
- importance: entry.importance
292
- });
293
283
  return createMcpResponse(
294
284
  {
295
285
  success: true,
@@ -303,16 +293,134 @@ async function handleMemoryStore(params, db2, vectors2) {
303
293
  {
304
294
  contentSummary: `Stored [${entry.code}] "${entry.title}" in repo "${entry.scope.repo}".`,
305
295
  structuredContentPathHint: "code",
306
- includeSerializedStructuredContent: validated.structured
296
+ includeSerializedStructuredContent: params.structured
307
297
  }
308
298
  );
309
299
  }
300
+ async function handleMemoryStore(params, db2, vectors2) {
301
+ const validated = MemoryStoreSchema.parse(params);
302
+ if (validated.memories) {
303
+ const now = (/* @__PURE__ */ new Date()).toISOString();
304
+ const entries = [];
305
+ const storedCodes = [];
306
+ for (const mem of validated.memories) {
307
+ if (hasMetadataLikeTitle(mem.title)) {
308
+ throw new Error(
309
+ "Title appears to contain metadata. Keep title concise and move agent/role/date details into metadata or dedicated fields."
310
+ );
311
+ }
312
+ const createdAtTime = new Date(now).getTime();
313
+ const expires_at = mem.ttlDays != null ? new Date(createdAtTime + mem.ttlDays * 864e5).toISOString() : null;
314
+ const resolvedSupersedes = resolveMemorySupersedes(mem.supersedes, db2);
315
+ if (!resolvedSupersedes && mem.type !== "task_archive") {
316
+ const conflict = await db2.memoryVectors.checkConflicts(
317
+ mem.content,
318
+ mem.scope.repo,
319
+ mem.type,
320
+ vectors2,
321
+ 0.55
322
+ );
323
+ if (conflict) {
324
+ return createMcpResponse(
325
+ {
326
+ success: false,
327
+ error: "MEMORY_CONFLICT",
328
+ message: `This memory content overlaps significantly with an existing memory (ID: ${conflict.id}).`,
329
+ conflicting_memory: { id: conflict.id, title: conflict.title, content: conflict.content },
330
+ instruction: "Use 'memory-update' on the existing ID, or provide 'supersedes' if this new memory replaces it."
331
+ },
332
+ `Rejected due to conflict: "${conflict.title}" (${conflict.id.slice(0, 8)}...). Hint: Use 'supersedes' or 'memory-update'.`
333
+ );
334
+ }
335
+ }
336
+ if (resolvedSupersedes) {
337
+ const oldMemory = db2.memories.getById(resolvedSupersedes);
338
+ if (oldMemory) {
339
+ db2.memories.update(oldMemory.id, { status: "archived" });
340
+ }
341
+ }
342
+ const tags = mem.tags ?? [];
343
+ if (mem.scope.language && !tags.includes(mem.scope.language.toLowerCase())) {
344
+ tags.push(mem.scope.language.toLowerCase());
345
+ }
346
+ const code = mem.code || generateShortCode();
347
+ entries.push({
348
+ id: randomUUID(),
349
+ code,
350
+ type: mem.type,
351
+ title: mem.title,
352
+ content: mem.content,
353
+ importance: mem.importance,
354
+ agent: mem.agent,
355
+ role: mem.role,
356
+ model: mem.model,
357
+ scope: mem.scope,
358
+ created_at: now,
359
+ updated_at: now,
360
+ completed_at: null,
361
+ hit_count: 0,
362
+ recall_count: 0,
363
+ last_used_at: null,
364
+ expires_at,
365
+ supersedes: resolvedSupersedes,
366
+ status: "active",
367
+ tags,
368
+ metadata: mem.metadata ?? {},
369
+ is_global: mem.is_global
370
+ });
371
+ storedCodes.push(code);
372
+ }
373
+ db2.memories.bulkInsertMemories(entries);
374
+ for (const entry of entries) {
375
+ try {
376
+ await vectors2.upsert(entry.id, entry.content);
377
+ } catch (error) {
378
+ logger.warn("Failed to generate vector embedding", { error: String(error) });
379
+ }
380
+ }
381
+ const codesStr = storedCodes.length > 0 ? `: ${storedCodes.join(", ")}` : "";
382
+ return createMcpResponse(
383
+ { success: true, repo: validated.memories[0]?.scope.repo, createdCount: validated.memories.length, codes: storedCodes },
384
+ `Stored ${validated.memories.length} memories${codesStr}.`,
385
+ { includeSerializedStructuredContent: validated.structured }
386
+ );
387
+ }
388
+ return storeSingleMemory(
389
+ {
390
+ code: validated.code,
391
+ type: validated.type,
392
+ title: validated.title,
393
+ content: validated.content,
394
+ importance: validated.importance,
395
+ agent: validated.agent,
396
+ role: validated.role,
397
+ model: validated.model,
398
+ scope: validated.scope,
399
+ ttlDays: validated.ttlDays,
400
+ supersedes: validated.supersedes,
401
+ tags: validated.tags,
402
+ metadata: validated.metadata,
403
+ is_global: validated.is_global,
404
+ structured: validated.structured
405
+ },
406
+ db2,
407
+ vectors2
408
+ );
409
+ }
310
410
 
311
411
  // src/mcp/tools/memory.update.ts
412
+ var UUID_REGEX2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
312
413
  function hasMetadataLikeTitle2(title) {
313
414
  const normalized = title.trim();
314
415
  return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
315
416
  }
417
+ function resolveMemorySupersedes2(value, db2) {
418
+ if (!value) return null;
419
+ if (UUID_REGEX2.test(value)) return value;
420
+ const memory = db2.memories.getByCode(value);
421
+ if (!memory) throw new Error(`supersedes: memory with code '${value}' not found`);
422
+ return memory.id;
423
+ }
316
424
  async function handleMemoryUpdate(params, db2, vectors2) {
317
425
  const validated = MemoryUpdateSchema.parse(params);
318
426
  let resolvedId = validated.id;
@@ -346,7 +454,7 @@ async function handleMemoryUpdate(params, db2, vectors2) {
346
454
  if (validated.agent !== void 0) updates.agent = validated.agent;
347
455
  if (validated.role !== void 0) updates.role = validated.role;
348
456
  if (validated.status !== void 0) updates.status = validated.status;
349
- if (validated.supersedes !== void 0) updates.supersedes = validated.supersedes;
457
+ if (validated.supersedes !== void 0) updates.supersedes = resolveMemorySupersedes2(validated.supersedes, db2);
350
458
  if (validated.tags !== void 0) updates.tags = validated.tags;
351
459
  if (validated.metadata !== void 0) updates.metadata = validated.metadata;
352
460
  if (validated.is_global !== void 0) updates.is_global = validated.is_global;
@@ -365,7 +473,7 @@ async function handleMemoryUpdate(params, db2, vectors2) {
365
473
  repo: existing.scope.repo,
366
474
  updatedFields: Object.keys(updates)
367
475
  },
368
- `Updated memory ${resolvedId} in repo "${existing.scope.repo}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
476
+ `Updated memory [${existing.code}] in repo "${existing.scope.repo}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
369
477
  {
370
478
  structuredContentPathHint: "updatedFields",
371
479
  includeSerializedStructuredContent: validated.structured
@@ -647,14 +755,21 @@ function capitalize2(str) {
647
755
 
648
756
  // src/mcp/tools/task.manage.ts
649
757
  import { randomUUID as randomUUID2 } from "crypto";
650
- var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
758
+ var UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
651
759
  function resolveParentId(value, repo, storage) {
652
760
  if (!value) return null;
653
- if (UUID_REGEX.test(value)) return value;
761
+ if (UUID_REGEX3.test(value)) return value;
654
762
  const parent = storage.tasks.getTaskByCode(repo, value);
655
763
  if (!parent) throw new Error(`parent_id: task with code '${value}' not found in repo '${repo}'`);
656
764
  return parent.id;
657
765
  }
766
+ function resolveDependsOn(value, repo, storage) {
767
+ if (!value) return null;
768
+ if (UUID_REGEX3.test(value)) return value;
769
+ const task = storage.tasks.getTaskByCode(repo, value);
770
+ if (!task) throw new Error(`depends_on: task with code '${value}' not found in repo '${repo}'`);
771
+ return task.id;
772
+ }
658
773
  function describeTaskListFilter(status) {
659
774
  if (!status) return "active";
660
775
  if (status === "all") return "all";
@@ -837,15 +952,18 @@ async function handleTaskCreate(args, storage) {
837
952
  const { repo, tasks: bulkTasks, ...singleTask } = parsed;
838
953
  if (bulkTasks) {
839
954
  const createdTasks = [];
955
+ const tasksToInsert = [];
840
956
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
841
957
  const codesInRequest = /* @__PURE__ */ new Set();
958
+ const allCodes = bulkTasks.map((t) => t.task_code);
959
+ const existingCodes = storage.tasks.getExistingTaskCodes(repo, allCodes);
842
960
  const initialStats = storage.taskStats.getTaskStats(repo);
843
961
  let pendingInRequestCount = 0;
844
962
  for (const taskData of bulkTasks) {
845
963
  if (codesInRequest.has(taskData.task_code)) {
846
964
  throw new Error(`Duplicate task_code in request: '${taskData.task_code}'`);
847
965
  }
848
- if (storage.tasks.isTaskCodeDuplicate(repo, taskData.task_code)) {
966
+ if (existingCodes.has(taskData.task_code)) {
849
967
  throw new Error(`Duplicate task_code: '${taskData.task_code}' already exists in repository '${repo}'`);
850
968
  }
851
969
  codesInRequest.add(taskData.task_code);
@@ -891,18 +1009,19 @@ async function handleTaskCreate(args, storage) {
891
1009
  changed_files: [],
892
1010
  metadata: taskData.metadata || {},
893
1011
  parent_id: resolveParentId(taskData.parent_id, repo, storage),
894
- depends_on: taskData.depends_on || null
1012
+ depends_on: resolveDependsOn(taskData.depends_on, repo, storage)
895
1013
  };
896
- storage.tasks.insertTask(task2);
1014
+ tasksToInsert.push(task2);
897
1015
  createdTasks.push(task2.task_code);
898
1016
  if (normalizedStatus === "pending") {
899
1017
  pendingInRequestCount++;
900
1018
  }
901
- task2._temp_id = task2.id;
902
1019
  }
1020
+ storage.tasks.bulkInsertTasks(tasksToInsert);
1021
+ const taskCodesStr = createdTasks.length > 0 ? `: ${createdTasks.join(", ")}` : "";
903
1022
  return createMcpResponse(
904
1023
  { success: true, repo, createdCount: bulkTasks.length, taskCodes: createdTasks },
905
- `Created ${bulkTasks.length} tasks in repo "${repo}".`,
1024
+ `Created ${bulkTasks.length} tasks in repo "${repo}"${taskCodesStr}.`,
906
1025
  { includeSerializedStructuredContent: parsed.structured || false }
907
1026
  );
908
1027
  }
@@ -969,7 +1088,7 @@ async function handleTaskCreate(args, storage) {
969
1088
  changed_files: [],
970
1089
  metadata: metadata || {},
971
1090
  parent_id: resolveParentId(parent_id, repo, storage),
972
- depends_on: depends_on || null
1091
+ depends_on: resolveDependsOn(depends_on, repo, storage)
973
1092
  };
974
1093
  storage.tasks.insertTask(task);
975
1094
  return createMcpResponse(
@@ -1139,6 +1258,9 @@ async function handleTaskUpdate(args, storage, vectors2) {
1139
1258
  if (updates.parent_id !== void 0) {
1140
1259
  finalUpdates.parent_id = resolveParentId(updates.parent_id, repo, storage);
1141
1260
  }
1261
+ if (updates.depends_on !== void 0) {
1262
+ finalUpdates.depends_on = resolveDependsOn(updates.depends_on, repo, storage);
1263
+ }
1142
1264
  if (updates.phase !== void 0 || updates.tags !== void 0) {
1143
1265
  let currentTags = updates.tags || existingTask.tags || [];
1144
1266
  currentTags = currentTags.filter((t) => !t.startsWith("phase:"));
@@ -1183,8 +1305,11 @@ async function handleTaskUpdate(args, storage, vectors2) {
1183
1305
  updatedTasks.push({ id: targetId, code: updates.task_code || existingTask.task_code });
1184
1306
  updatedCount++;
1185
1307
  }
1308
+ const taskCodesStr = updatedTasks.length > 0 ? ` Tasks: ${updatedTasks.map((t) => t.code).join(", ")}.` : "";
1309
+ const fieldsStr = Object.keys(updates).length > 0 ? ` Fields: ${Object.keys(updates).join(", ")}.` : "";
1186
1310
  const isCompleted = updates.status === "completed" && updatedCount > 0;
1187
- let summaryText = isCompleted ? `Updated ${updatedCount} task(s) in repo "${repo}". \u2705 Task marked as completed with commit ${updates.commit_id} (${(updates.changed_files || []).length} files changed).` : `Updated ${updatedCount} task(s) in repo "${repo}".`;
1311
+ let summaryText = isCompleted ? `Updated ${updatedCount} task(s) in repo "${repo}". Task marked as completed with commit ${updates.commit_id} (${(updates.changed_files || []).length} files changed).` : `Updated ${updatedCount} task(s) in repo "${repo}".`;
1312
+ summaryText += `${taskCodesStr}${fieldsStr}`;
1188
1313
  if (releasedClaims || expiredHandoffs) {
1189
1314
  summaryText += ` Auto-closed coordination: released ${releasedClaims} claim(s), expired ${expiredHandoffs} handoff(s).`;
1190
1315
  }
@@ -1233,18 +1358,22 @@ async function handleTaskDelete(args, storage) {
1233
1358
  throw new Error("Either 'id', 'ids', or 'task_code' must be provided for deletion");
1234
1359
  }
1235
1360
  const targetIds = resolvedIds;
1361
+ const tasksToDelete = storage.tasks.getTasksByIds(targetIds);
1362
+ const deletedCodes = tasksToDelete.map((t) => t.task_code);
1236
1363
  for (const targetId of targetIds) {
1237
1364
  storage.tasks.deleteTask(targetId);
1238
1365
  }
1366
+ const deletedCodesStr = deletedCodes.length > 0 ? `: ${deletedCodes.join(", ")}` : "";
1239
1367
  return createMcpResponse(
1240
1368
  {
1241
1369
  success: true,
1242
1370
  id: id || void 0,
1243
1371
  ids: ids || void 0,
1244
1372
  repo,
1245
- deletedCount: targetIds.length
1373
+ deletedCount: targetIds.length,
1374
+ deletedCodes
1246
1375
  },
1247
- `Deleted ${targetIds.length} task(s) from repo "${repo}".`,
1376
+ `Deleted ${targetIds.length} task(s) from repo "${repo}"${deletedCodesStr}.`,
1248
1377
  { includeSerializedStructuredContent: validated.structured }
1249
1378
  );
1250
1379
  }
@@ -1629,6 +1758,7 @@ async function handleMemoryDetail(args, storage) {
1629
1758
 
1630
1759
  // src/mcp/tools/standard.store.ts
1631
1760
  import { randomUUID as randomUUID3 } from "crypto";
1761
+ var UUID_REGEX4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1632
1762
  function generateShortCode2() {
1633
1763
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789";
1634
1764
  let code = "";
@@ -1637,15 +1767,21 @@ function generateShortCode2() {
1637
1767
  }
1638
1768
  return code;
1639
1769
  }
1640
- async function handleStandardStore(params, db2, vectors2) {
1641
- const validated = StandardStoreSchema.parse(params);
1642
- const incomingVersion = validated.version || "1.0.0";
1643
- const incomingLanguage = validated.language ?? null;
1644
- const incomingStack = validated.stack ?? [];
1770
+ function resolveStandardParentId(value, db2) {
1771
+ if (!value) return null;
1772
+ if (UUID_REGEX4.test(value)) return value;
1773
+ const standard = db2.standards.getByCode(value);
1774
+ if (!standard) throw new Error(`parent_id: standard with code '${value}' not found`);
1775
+ return standard.id;
1776
+ }
1777
+ async function storeSingleStandard(params, db2, vectors2) {
1778
+ const incomingVersion = params.version || "1.0.0";
1779
+ const incomingLanguage = params.language ?? null;
1780
+ const incomingStack = params.stack ?? [];
1645
1781
  const conflict = db2.standards.checkConflicts(
1646
- validated.content,
1782
+ params.content,
1647
1783
  incomingVersion,
1648
- validated.repo,
1784
+ params.repo,
1649
1785
  incomingLanguage,
1650
1786
  incomingStack,
1651
1787
  0.82
@@ -1666,31 +1802,30 @@ async function handleStandardStore(params, db2, vectors2) {
1666
1802
  },
1667
1803
  instruction: "Use 'standard-update' on the existing ID to update it. To store a distinct variant, supply a different 'version', 'language', or non-overlapping 'stack'."
1668
1804
  },
1669
- `Rejected: conflicts with standard "${conflict.title}" (${conflict.id}). Update it via 'standard-update', or differentiate by version / language / stack.`,
1670
- { structuredContentPathHint: "conflicting_standard" }
1805
+ `Rejected: conflicts with standard "${conflict.title}" (v${conflict.version}, ${conflict.language || "any"}) [${conflict.id.slice(0, 8)}...]. Update via 'standard-update', or differentiate by version / language / stack.`
1671
1806
  );
1672
1807
  }
1673
1808
  const now = (/* @__PURE__ */ new Date()).toISOString();
1674
1809
  const entry = {
1675
1810
  id: randomUUID3(),
1676
1811
  code: generateShortCode2(),
1677
- title: validated.name,
1678
- content: validated.content,
1679
- parent_id: validated.parent_id || null,
1680
- context: toContextSlug(validated.context || "general"),
1681
- version: validated.version || "1.0.0",
1682
- language: validated.language || null,
1683
- stack: validated.stack || [],
1684
- is_global: validated.is_global !== false,
1685
- repo: validated.repo || null,
1686
- tags: validated.tags || [],
1687
- metadata: validated.metadata,
1812
+ title: params.name,
1813
+ content: params.content,
1814
+ parent_id: resolveStandardParentId(params.parent_id, db2),
1815
+ context: toContextSlug(params.context || "general"),
1816
+ version: params.version || "1.0.0",
1817
+ language: params.language || null,
1818
+ stack: params.stack || [],
1819
+ is_global: params.is_global !== false,
1820
+ repo: params.repo || null,
1821
+ tags: params.tags || [],
1822
+ metadata: params.metadata,
1688
1823
  created_at: now,
1689
1824
  updated_at: now,
1690
1825
  hit_count: 0,
1691
1826
  last_used_at: null,
1692
- agent: validated.agent || "unknown",
1693
- model: validated.model || "unknown"
1827
+ agent: params.agent || "unknown",
1828
+ model: params.model || "unknown"
1694
1829
  };
1695
1830
  db2.standards.insert(entry);
1696
1831
  try {
@@ -1698,13 +1833,6 @@ async function handleStandardStore(params, db2, vectors2) {
1698
1833
  } catch (error) {
1699
1834
  logger.warn("Failed to generate standard vector embedding", { error: String(error) });
1700
1835
  }
1701
- logger.info("[Tool] standard.store - saved coding standard", {
1702
- standardId: entry.id,
1703
- code: entry.code,
1704
- title: entry.title,
1705
- stack: entry.stack,
1706
- language: entry.language
1707
- });
1708
1836
  return createMcpResponse(
1709
1837
  {
1710
1838
  success: true,
@@ -1714,10 +1842,107 @@ async function handleStandardStore(params, db2, vectors2) {
1714
1842
  `Saved coding standard [${entry.code}]: ${entry.title}`,
1715
1843
  {
1716
1844
  structuredContentPathHint: "standard",
1717
- includeSerializedStructuredContent: true
1845
+ includeSerializedStructuredContent: params.structured
1718
1846
  }
1719
1847
  );
1720
1848
  }
1849
+ async function handleStandardStore(params, db2, vectors2) {
1850
+ const validated = StandardStoreSchema.parse(params);
1851
+ if (validated.standards) {
1852
+ const entries = [];
1853
+ const storedCodes = [];
1854
+ for (const std of validated.standards) {
1855
+ const incomingVersion = std.version || "1.0.0";
1856
+ const incomingLanguage = std.language ?? null;
1857
+ const incomingStack = std.stack ?? [];
1858
+ const conflict = db2.standards.checkConflicts(
1859
+ std.content,
1860
+ incomingVersion,
1861
+ void 0,
1862
+ incomingLanguage,
1863
+ incomingStack,
1864
+ 0.82
1865
+ );
1866
+ if (conflict) {
1867
+ return createMcpResponse(
1868
+ {
1869
+ success: false,
1870
+ error: "STANDARD_CONFLICT",
1871
+ message: `This standard's content is highly similar to an existing standard (ID: ${conflict.id}, similarity: ${(conflict.similarity * 100).toFixed(1)}%).`,
1872
+ conflicting_standard: {
1873
+ id: conflict.id,
1874
+ title: conflict.title,
1875
+ version: conflict.version,
1876
+ language: conflict.language,
1877
+ stack: conflict.stack,
1878
+ content: conflict.content
1879
+ },
1880
+ instruction: "Use 'standard-update' on the existing ID to update it. To store a distinct variant, supply a different 'version', 'language', or non-overlapping 'stack'."
1881
+ },
1882
+ `Rejected: conflicts with standard "${conflict.title}" (v${conflict.version}, ${conflict.language || "any"}) [${conflict.id.slice(0, 8)}...]. Update via 'standard-update', or differentiate by version / language / stack.`
1883
+ );
1884
+ }
1885
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1886
+ const code = generateShortCode2();
1887
+ entries.push({
1888
+ id: randomUUID3(),
1889
+ code,
1890
+ title: std.name,
1891
+ content: std.content,
1892
+ parent_id: resolveStandardParentId(std.parent_id, db2),
1893
+ context: toContextSlug(std.context || "general"),
1894
+ version: std.version || "1.0.0",
1895
+ language: std.language || null,
1896
+ stack: std.stack || [],
1897
+ is_global: std.is_global !== false,
1898
+ repo: null,
1899
+ tags: std.tags || [],
1900
+ metadata: std.metadata,
1901
+ created_at: now,
1902
+ updated_at: now,
1903
+ hit_count: 0,
1904
+ last_used_at: null,
1905
+ agent: std.agent || "unknown",
1906
+ model: std.model || "unknown"
1907
+ });
1908
+ storedCodes.push(code);
1909
+ }
1910
+ db2.standards.bulkInsertStandards(entries);
1911
+ for (const entry of entries) {
1912
+ try {
1913
+ await vectors2.upsert(entry.id, buildStandardVectorText(entry), "standard");
1914
+ } catch (error) {
1915
+ logger.warn("Failed to generate standard vector embedding", { error: String(error) });
1916
+ }
1917
+ }
1918
+ const codesStr = storedCodes.length > 0 ? `: ${storedCodes.join(", ")}` : "";
1919
+ return createMcpResponse(
1920
+ { success: true, createdCount: validated.standards.length, codes: storedCodes },
1921
+ `Saved ${validated.standards.length} coding standards${codesStr}.`,
1922
+ { includeSerializedStructuredContent: validated.structured }
1923
+ );
1924
+ }
1925
+ return storeSingleStandard(
1926
+ {
1927
+ name: validated.name,
1928
+ content: validated.content,
1929
+ parent_id: validated.parent_id,
1930
+ context: validated.context,
1931
+ version: validated.version,
1932
+ language: validated.language,
1933
+ stack: validated.stack,
1934
+ repo: validated.repo,
1935
+ is_global: validated.is_global,
1936
+ tags: validated.tags,
1937
+ metadata: validated.metadata,
1938
+ agent: validated.agent,
1939
+ model: validated.model,
1940
+ structured: validated.structured
1941
+ },
1942
+ db2,
1943
+ vectors2
1944
+ );
1945
+ }
1721
1946
 
1722
1947
  // src/mcp/tools/standard.search.ts
1723
1948
  var HYBRID_WEIGHTS_STANDARD = {
@@ -1943,6 +2168,14 @@ async function handleStandardSearch(params, db2, vectors2) {
1943
2168
  }
1944
2169
 
1945
2170
  // src/mcp/tools/standard.update.ts
2171
+ var UUID_REGEX5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2172
+ function resolveStandardParentId2(value, db2) {
2173
+ if (!value) return null;
2174
+ if (UUID_REGEX5.test(value)) return value;
2175
+ const standard = db2.standards.getByCode(value);
2176
+ if (!standard) throw new Error(`parent_id: standard with code '${value}' not found`);
2177
+ return standard.id;
2178
+ }
1946
2179
  async function handleStandardUpdate(params, db2, vectors2) {
1947
2180
  const validated = StandardUpdateSchema.parse(params);
1948
2181
  let resolvedId = validated.id;
@@ -1960,7 +2193,7 @@ async function handleStandardUpdate(params, db2, vectors2) {
1960
2193
  const updates = {};
1961
2194
  if (validated.name !== void 0) updates.title = validated.name;
1962
2195
  if (validated.content !== void 0) updates.content = validated.content;
1963
- if (validated.parent_id !== void 0) updates.parent_id = validated.parent_id;
2196
+ if (validated.parent_id !== void 0) updates.parent_id = resolveStandardParentId2(validated.parent_id, db2);
1964
2197
  if (validated.context !== void 0) updates.context = validated.context;
1965
2198
  if (validated.version !== void 0) updates.version = validated.version;
1966
2199
  if (validated.language !== void 0) updates.language = validated.language;
@@ -1991,7 +2224,7 @@ async function handleStandardUpdate(params, db2, vectors2) {
1991
2224
  code: existing.code,
1992
2225
  updatedFields: Object.keys(updates)
1993
2226
  },
1994
- `Updated coding standard ${resolvedId}. Fields: ${Object.keys(updates).join(", ") || "none"}.`,
2227
+ `Updated coding standard [${existing.code}] in repo "${existing.repo || "global"}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
1995
2228
  {
1996
2229
  structuredContentPathHint: "updatedFields",
1997
2230
  includeSerializedStructuredContent: true