@vheins/local-memory-mcp 0.14.4 → 0.14.6
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.
- package/dist/{chunk-U67YH7MG.js → chunk-GGZWQVNO.js} +175 -36
- package/dist/dashboard/public/assets/index-mjbj_OkQ.js +87 -0
- package/dist/dashboard/public/assets/{index-CUg8rZCA.css → index-wAYh22Zy.css} +1 -1
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +1 -1
- package/dist/mcp/server.js +325 -92
- package/package.json +1 -1
- package/dist/dashboard/public/assets/index-CD8w6eaB.js +0 -87
package/dist/mcp/server.js
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
toContextSlug,
|
|
61
61
|
updateSessionFromInitialize,
|
|
62
62
|
updateSessionRoots
|
|
63
|
-
} from "../chunk-
|
|
63
|
+
} from "../chunk-GGZWQVNO.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
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
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 =
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
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 (
|
|
243
|
-
const oldMemory = db2.memories.getById(
|
|
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 =
|
|
253
|
-
if (
|
|
254
|
-
tags.push(
|
|
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:
|
|
259
|
-
type:
|
|
260
|
-
title:
|
|
261
|
-
content:
|
|
262
|
-
importance:
|
|
263
|
-
agent:
|
|
264
|
-
role:
|
|
265
|
-
model:
|
|
266
|
-
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:
|
|
271
|
+
supersedes: resolvedSupersedes,
|
|
275
272
|
status: "active",
|
|
276
273
|
tags,
|
|
277
|
-
metadata:
|
|
278
|
-
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:
|
|
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 ${
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
1012
|
+
depends_on: resolveDependsOn(taskData.depends_on, repo, storage)
|
|
895
1013
|
};
|
|
896
|
-
|
|
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
|
|
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}".
|
|
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
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
const
|
|
1644
|
-
|
|
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
|
-
|
|
1782
|
+
params.content,
|
|
1647
1783
|
incomingVersion,
|
|
1648
|
-
|
|
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.
|
|
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:
|
|
1678
|
-
content:
|
|
1679
|
-
parent_id:
|
|
1680
|
-
context: toContextSlug(
|
|
1681
|
-
version:
|
|
1682
|
-
language:
|
|
1683
|
-
stack:
|
|
1684
|
-
is_global:
|
|
1685
|
-
repo:
|
|
1686
|
-
tags:
|
|
1687
|
-
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:
|
|
1693
|
-
model:
|
|
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:
|
|
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 ${
|
|
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
|