@vheins/local-memory-mcp 0.7.2 → 0.7.4
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-J4O2HJ2K.js → chunk-BSASVWKJ.js} +92 -33
- package/dist/dashboard/public/assets/{index-CRhOgOlp.js → index-CkSUOqPH.js} +2 -2
- package/dist/dashboard/public/assets/index-OXSJZbwn.css +1 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +105 -26
- package/dist/mcp/server.js +199 -45
- package/dist/prompts/export-task-to-github.md +61 -0
- package/dist/prompts/import-github-issues.md +8 -3
- package/package.json +2 -1
- package/dist/dashboard/public/assets/index-Bd7v94SO.css +0 -1
package/dist/mcp/server.js
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
setLogLevel,
|
|
43
43
|
updateSessionFromInitialize,
|
|
44
44
|
updateSessionRoots
|
|
45
|
-
} from "../chunk-
|
|
45
|
+
} from "../chunk-BSASVWKJ.js";
|
|
46
46
|
|
|
47
47
|
// src/mcp/server.ts
|
|
48
48
|
import readline from "readline";
|
|
@@ -214,7 +214,7 @@ function createMcpResponse(data, summary, options) {
|
|
|
214
214
|
resourceLinks,
|
|
215
215
|
structuredContentPathHint,
|
|
216
216
|
contentSummary,
|
|
217
|
-
includeSerializedStructuredContent =
|
|
217
|
+
includeSerializedStructuredContent = false
|
|
218
218
|
} = options || {};
|
|
219
219
|
void includeSerializedStructuredContent;
|
|
220
220
|
let finalData = data;
|
|
@@ -239,12 +239,12 @@ function createMcpResponse(data, summary, options) {
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
const content = [];
|
|
242
|
-
if (contentSummary
|
|
242
|
+
if (contentSummary && contentSummary.trim().length > 0) {
|
|
243
243
|
content.push({
|
|
244
244
|
type: "text",
|
|
245
245
|
text: contentSummary.trim()
|
|
246
246
|
});
|
|
247
|
-
} else if (summary.trim().length > 0) {
|
|
247
|
+
} else if (summary && summary.trim().length > 0) {
|
|
248
248
|
const pointerText = structuredContentPathHint ? `Read structuredContent.${structuredContentPathHint} for details.` : `Read structuredContent for machine-readable results.`;
|
|
249
249
|
content.push({
|
|
250
250
|
type: "text",
|
|
@@ -264,6 +264,9 @@ function createMcpResponse(data, summary, options) {
|
|
|
264
264
|
structuredContent: finalData,
|
|
265
265
|
isError: false
|
|
266
266
|
};
|
|
267
|
+
if (includeSerializedStructuredContent === false) {
|
|
268
|
+
delete response.structuredContent;
|
|
269
|
+
}
|
|
267
270
|
response.content = content;
|
|
268
271
|
return response;
|
|
269
272
|
}
|
|
@@ -408,11 +411,12 @@ async function handleMemoryStore(params, db2, vectors2) {
|
|
|
408
411
|
{
|
|
409
412
|
contentSummary: `Stored [${entry.code}] "${entry.title}" in repo "${entry.scope.repo}".`,
|
|
410
413
|
structuredContentPathHint: "code",
|
|
414
|
+
includeSerializedStructuredContent: validated.structured,
|
|
411
415
|
resourceLinks: [
|
|
412
416
|
{
|
|
413
417
|
uri: `memory://${entry.id}`,
|
|
414
418
|
name: entry.title,
|
|
415
|
-
description: `Stored memory in repo ${entry.scope.repo}`,
|
|
419
|
+
description: `Stored memory [${entry.code}] in repo ${entry.scope.repo}`,
|
|
416
420
|
mimeType: "application/json",
|
|
417
421
|
annotations: {
|
|
418
422
|
audience: ["assistant"],
|
|
@@ -486,11 +490,12 @@ async function handleMemoryUpdate(params, db2, vectors2) {
|
|
|
486
490
|
`Updated memory ${validated.id} in repo "${existing.scope.repo}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
|
|
487
491
|
{
|
|
488
492
|
structuredContentPathHint: "updatedFields",
|
|
493
|
+
includeSerializedStructuredContent: validated.structured,
|
|
489
494
|
resourceLinks: [
|
|
490
495
|
{
|
|
491
496
|
uri: `memory://${validated.id}`,
|
|
492
497
|
name: existing.title || validated.id,
|
|
493
|
-
description: `Updated memory in repo ${existing.scope.repo}`,
|
|
498
|
+
description: `Updated memory [${existing.code}] in repo ${existing.scope.repo}`,
|
|
494
499
|
mimeType: "application/json",
|
|
495
500
|
annotations: {
|
|
496
501
|
audience: ["assistant"],
|
|
@@ -622,23 +627,21 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
622
627
|
memoriesByType[typeLabel].push(m);
|
|
623
628
|
}
|
|
624
629
|
let contentSummary;
|
|
625
|
-
if (
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
parts.push(`- ${code}|${m.importance}|${m.title}`);
|
|
634
|
-
}
|
|
635
|
-
parts.push("");
|
|
630
|
+
if (paginatedResults.length > 0) {
|
|
631
|
+
const parts = [];
|
|
632
|
+
for (const [memType, items] of Object.entries(memoriesByType)) {
|
|
633
|
+
parts.push(`${capitalize(memType)}:`);
|
|
634
|
+
parts.push("- code|importance|title");
|
|
635
|
+
for (const m of items) {
|
|
636
|
+
const code = m.code || "-";
|
|
637
|
+
parts.push(`- ${code}|${m.importance}|${m.title}`);
|
|
636
638
|
}
|
|
637
|
-
parts.push("
|
|
638
|
-
contentSummary = parts.join("\n").trim();
|
|
639
|
-
} else {
|
|
640
|
-
contentSummary = `No memories found for "${validated.query}" in repo "${validated.repo}".`;
|
|
639
|
+
parts.push("");
|
|
641
640
|
}
|
|
641
|
+
parts.push("Use memory-detail with memory_id (or code) for full content.");
|
|
642
|
+
contentSummary = parts.join("\n").trim();
|
|
643
|
+
} else {
|
|
644
|
+
contentSummary = `No memories found for "${validated.query}" in repo "${validated.repo}".`;
|
|
642
645
|
}
|
|
643
646
|
const structuredData = {
|
|
644
647
|
schema: "memory-search",
|
|
@@ -652,9 +655,10 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
652
655
|
rows
|
|
653
656
|
}
|
|
654
657
|
};
|
|
655
|
-
return createMcpResponse(structuredData, contentSummary || ""
|
|
658
|
+
return createMcpResponse(structuredData, contentSummary || `Found ${total} memories for "${validated.query}".`, {
|
|
656
659
|
contentSummary,
|
|
657
|
-
|
|
660
|
+
structuredContentPathHint: "results",
|
|
661
|
+
includeSerializedStructuredContent: validated.structured
|
|
658
662
|
});
|
|
659
663
|
}
|
|
660
664
|
function capitalize(str) {
|
|
@@ -673,7 +677,7 @@ async function handleMemorySummarize(params, db2) {
|
|
|
673
677
|
${fullSummary}`;
|
|
674
678
|
return createMcpResponse(null, content, {
|
|
675
679
|
contentSummary: content,
|
|
676
|
-
includeSerializedStructuredContent:
|
|
680
|
+
includeSerializedStructuredContent: validated.structured
|
|
677
681
|
});
|
|
678
682
|
}
|
|
679
683
|
|
|
@@ -769,7 +773,7 @@ async function handleMemoryRecap(params, db2) {
|
|
|
769
773
|
};
|
|
770
774
|
return createMcpResponse(structuredData, contentSummary || "", {
|
|
771
775
|
contentSummary,
|
|
772
|
-
includeSerializedStructuredContent:
|
|
776
|
+
includeSerializedStructuredContent: validated.structured
|
|
773
777
|
});
|
|
774
778
|
}
|
|
775
779
|
function capitalize2(str) {
|
|
@@ -900,7 +904,7 @@ async function handleTaskList(args, storage) {
|
|
|
900
904
|
query,
|
|
901
905
|
limit,
|
|
902
906
|
offset,
|
|
903
|
-
structured: isStructuredRequest
|
|
907
|
+
structured: isStructuredRequest = false
|
|
904
908
|
} = validated;
|
|
905
909
|
let statuses = [];
|
|
906
910
|
if (status !== "all") {
|
|
@@ -940,7 +944,19 @@ async function handleTaskList(args, storage) {
|
|
|
940
944
|
}
|
|
941
945
|
return createMcpResponse(structuredData, contentSummary || "", {
|
|
942
946
|
contentSummary,
|
|
943
|
-
includeSerializedStructuredContent:
|
|
947
|
+
includeSerializedStructuredContent: isStructuredRequest,
|
|
948
|
+
resourceLinks: [
|
|
949
|
+
{
|
|
950
|
+
uri: `repository://${encodeURIComponent(repo)}/tasks`,
|
|
951
|
+
name: `Task Index (${repo})`,
|
|
952
|
+
description: `Repository task index for ${repo}`,
|
|
953
|
+
mimeType: "application/json",
|
|
954
|
+
annotations: {
|
|
955
|
+
audience: ["assistant"],
|
|
956
|
+
priority: 0.6
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
]
|
|
944
960
|
});
|
|
945
961
|
}
|
|
946
962
|
async function handleTaskCreate(args, storage) {
|
|
@@ -977,6 +993,11 @@ async function handleTaskCreate(args, storage) {
|
|
|
977
993
|
}
|
|
978
994
|
}
|
|
979
995
|
const statusTimestamps2 = deriveTaskStatusTimestamps(normalizedStatus, now2);
|
|
996
|
+
const tags = [...taskData.tags || []];
|
|
997
|
+
const phaseTag2 = `phase:${taskData.phase}`;
|
|
998
|
+
if (!tags.includes(phaseTag2)) {
|
|
999
|
+
tags.push(phaseTag2);
|
|
1000
|
+
}
|
|
980
1001
|
const task2 = {
|
|
981
1002
|
id: randomUUID2(),
|
|
982
1003
|
repo,
|
|
@@ -995,17 +1016,52 @@ async function handleTaskCreate(args, storage) {
|
|
|
995
1016
|
finished_at: statusTimestamps2.finished_at,
|
|
996
1017
|
canceled_at: statusTimestamps2.canceled_at,
|
|
997
1018
|
est_tokens: taskData.est_tokens ?? 0,
|
|
998
|
-
tags
|
|
1019
|
+
tags,
|
|
999
1020
|
metadata: taskData.metadata || {},
|
|
1000
1021
|
parent_id: taskData.parent_id || null,
|
|
1001
1022
|
depends_on: taskData.depends_on || null
|
|
1002
1023
|
};
|
|
1003
1024
|
storage.tasks.insertTask(task2);
|
|
1004
1025
|
createdTasks.push(task2.task_code);
|
|
1026
|
+
task2._temp_id = task2.id;
|
|
1005
1027
|
}
|
|
1028
|
+
const resourceLinks = bulkTasks.slice(0, 10).map((t) => {
|
|
1029
|
+
const taskCode = t.task_code;
|
|
1030
|
+
const taskId2 = t._temp_id;
|
|
1031
|
+
return {
|
|
1032
|
+
uri: `task://${taskId2}`,
|
|
1033
|
+
name: `Task [${taskCode}]`,
|
|
1034
|
+
description: `Created task [${taskCode}] in repo ${repo}`,
|
|
1035
|
+
mimeType: "application/json",
|
|
1036
|
+
annotations: {
|
|
1037
|
+
audience: ["assistant"],
|
|
1038
|
+
priority: 0.9
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
});
|
|
1042
|
+
resourceLinks.push({
|
|
1043
|
+
uri: `repository://${encodeURIComponent(repo)}/tasks`,
|
|
1044
|
+
name: `Task Index (${repo})`,
|
|
1045
|
+
description: `Repository task index for ${repo}`,
|
|
1046
|
+
mimeType: "application/json",
|
|
1047
|
+
annotations: {
|
|
1048
|
+
audience: ["assistant"],
|
|
1049
|
+
priority: 0.6
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1006
1052
|
return createMcpResponse(
|
|
1007
1053
|
{ success: true, repo, createdCount: bulkTasks.length, taskCodes: createdTasks },
|
|
1008
|
-
`Created ${bulkTasks.length} tasks in repo "${repo}"
|
|
1054
|
+
`Created ${bulkTasks.length} tasks in repo "${repo}".`,
|
|
1055
|
+
{
|
|
1056
|
+
includeSerializedStructuredContent: parsed.structured || false,
|
|
1057
|
+
resourceLinks: resourceLinks.map((link) => ({
|
|
1058
|
+
...link,
|
|
1059
|
+
annotations: {
|
|
1060
|
+
...link.annotations,
|
|
1061
|
+
audience: link.annotations.audience
|
|
1062
|
+
}
|
|
1063
|
+
}))
|
|
1064
|
+
}
|
|
1009
1065
|
);
|
|
1010
1066
|
}
|
|
1011
1067
|
const {
|
|
@@ -1018,7 +1074,6 @@ async function handleTaskCreate(args, storage) {
|
|
|
1018
1074
|
agent,
|
|
1019
1075
|
role,
|
|
1020
1076
|
doc_path,
|
|
1021
|
-
tags,
|
|
1022
1077
|
metadata,
|
|
1023
1078
|
parent_id,
|
|
1024
1079
|
depends_on,
|
|
@@ -1042,6 +1097,11 @@ async function handleTaskCreate(args, storage) {
|
|
|
1042
1097
|
const taskId = randomUUID2();
|
|
1043
1098
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1044
1099
|
const statusTimestamps = deriveTaskStatusTimestamps(status || "backlog", now);
|
|
1100
|
+
const finalTags = [...singleTask.tags || []];
|
|
1101
|
+
const phaseTag = `phase:${phase}`;
|
|
1102
|
+
if (!finalTags.includes(phaseTag)) {
|
|
1103
|
+
finalTags.push(phaseTag);
|
|
1104
|
+
}
|
|
1045
1105
|
const task = {
|
|
1046
1106
|
id: taskId,
|
|
1047
1107
|
repo,
|
|
@@ -1060,7 +1120,7 @@ async function handleTaskCreate(args, storage) {
|
|
|
1060
1120
|
finished_at: statusTimestamps.finished_at,
|
|
1061
1121
|
canceled_at: statusTimestamps.canceled_at,
|
|
1062
1122
|
est_tokens: est_tokens ?? 0,
|
|
1063
|
-
tags:
|
|
1123
|
+
tags: finalTags,
|
|
1064
1124
|
metadata: metadata || {},
|
|
1065
1125
|
parent_id: parent_id || null,
|
|
1066
1126
|
depends_on: depends_on || null
|
|
@@ -1078,7 +1138,32 @@ async function handleTaskCreate(args, storage) {
|
|
|
1078
1138
|
priority: task.priority,
|
|
1079
1139
|
depends_on: task.depends_on
|
|
1080
1140
|
},
|
|
1081
|
-
`Created task [${task.task_code}] ${task.title} in repo "${task.repo}" with status "${task.status}"
|
|
1141
|
+
`Created task [${task.task_code}] ${task.title} in repo "${task.repo}" with status "${task.status}".`,
|
|
1142
|
+
{
|
|
1143
|
+
includeSerializedStructuredContent: parsed.structured || false,
|
|
1144
|
+
resourceLinks: [
|
|
1145
|
+
{
|
|
1146
|
+
uri: `task://${task.id}`,
|
|
1147
|
+
name: `Task [${task.task_code}]`,
|
|
1148
|
+
description: `Created task [${task.task_code}] in repo ${task.repo}`,
|
|
1149
|
+
mimeType: "application/json",
|
|
1150
|
+
annotations: {
|
|
1151
|
+
audience: ["assistant"],
|
|
1152
|
+
priority: 0.9
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
uri: `repository://${encodeURIComponent(task.repo)}/tasks`,
|
|
1157
|
+
name: `Task Index (${task.repo})`,
|
|
1158
|
+
description: `Repository task index for ${task.repo}`,
|
|
1159
|
+
mimeType: "application/json",
|
|
1160
|
+
annotations: {
|
|
1161
|
+
audience: ["assistant"],
|
|
1162
|
+
priority: 0.6
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
]
|
|
1166
|
+
}
|
|
1082
1167
|
);
|
|
1083
1168
|
}
|
|
1084
1169
|
async function handleTaskCreateInteractive(args, storage, options = {}) {
|
|
@@ -1187,6 +1272,7 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1187
1272
|
throw new Error("Either 'id' or 'ids' must be provided for update");
|
|
1188
1273
|
}
|
|
1189
1274
|
let updatedCount = 0;
|
|
1275
|
+
const updatedTasks = [];
|
|
1190
1276
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1191
1277
|
const isStatusChangingGlobal = updates.status !== void 0;
|
|
1192
1278
|
for (const targetId of targetIds) {
|
|
@@ -1200,7 +1286,8 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1200
1286
|
if (!comment || comment.trim() === "") {
|
|
1201
1287
|
throw new Error("comment is required when changing task status");
|
|
1202
1288
|
}
|
|
1203
|
-
|
|
1289
|
+
const isStartable = existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked";
|
|
1290
|
+
if (isStartable && updates.status === "completed") {
|
|
1204
1291
|
throw new Error(
|
|
1205
1292
|
`Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`
|
|
1206
1293
|
);
|
|
@@ -1213,6 +1300,18 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1213
1300
|
throw new Error(`Duplicate task_code: '${updates.task_code}' already exists`);
|
|
1214
1301
|
}
|
|
1215
1302
|
const finalUpdates = { ...updates };
|
|
1303
|
+
if (updates.phase !== void 0 || updates.tags !== void 0) {
|
|
1304
|
+
let currentTags = updates.tags || existingTask.tags || [];
|
|
1305
|
+
currentTags = currentTags.filter((t) => !t.startsWith("phase:"));
|
|
1306
|
+
const finalPhase = updates.phase !== void 0 ? updates.phase : existingTask.phase;
|
|
1307
|
+
if (finalPhase) {
|
|
1308
|
+
const phaseTag = `phase:${finalPhase}`;
|
|
1309
|
+
if (!currentTags.includes(phaseTag)) {
|
|
1310
|
+
currentTags.push(phaseTag);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
finalUpdates.tags = currentTags;
|
|
1314
|
+
}
|
|
1216
1315
|
if (updates.status === "completed") finalUpdates.finished_at = now;
|
|
1217
1316
|
else if (updates.status === "canceled") finalUpdates.canceled_at = now;
|
|
1218
1317
|
else if (updates.status === "in_progress" && existingTask.status !== "in_progress")
|
|
@@ -1235,8 +1334,29 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1235
1334
|
if (updates.status === "completed" && existingTask.status !== "completed") {
|
|
1236
1335
|
await archiveTaskToMemory(targetId, repo, storage, vectors2);
|
|
1237
1336
|
}
|
|
1337
|
+
updatedTasks.push({ id: targetId, code: updates.task_code || existingTask.task_code });
|
|
1238
1338
|
updatedCount++;
|
|
1239
1339
|
}
|
|
1340
|
+
const resourceLinks = updatedTasks.slice(0, 10).map((t) => ({
|
|
1341
|
+
uri: `task://${t.id}`,
|
|
1342
|
+
name: `Task [${t.code}]`,
|
|
1343
|
+
description: `Updated task [${t.code}] in repo ${repo}`,
|
|
1344
|
+
mimeType: "application/json",
|
|
1345
|
+
annotations: {
|
|
1346
|
+
audience: ["assistant"],
|
|
1347
|
+
priority: 0.9
|
|
1348
|
+
}
|
|
1349
|
+
}));
|
|
1350
|
+
resourceLinks.push({
|
|
1351
|
+
uri: `repository://${encodeURIComponent(repo)}/tasks`,
|
|
1352
|
+
name: `Task Index (${repo})`,
|
|
1353
|
+
description: `Repository task index for ${repo}`,
|
|
1354
|
+
mimeType: "application/json",
|
|
1355
|
+
annotations: {
|
|
1356
|
+
audience: ["assistant"],
|
|
1357
|
+
priority: 0.6
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1240
1360
|
return createMcpResponse(
|
|
1241
1361
|
{
|
|
1242
1362
|
success: true,
|
|
@@ -1247,11 +1367,22 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1247
1367
|
updatedCount,
|
|
1248
1368
|
updatedFields: Object.keys(updates)
|
|
1249
1369
|
},
|
|
1250
|
-
`Updated ${updatedCount} task(s) in repo "${repo}"
|
|
1370
|
+
`Updated ${updatedCount} task(s) in repo "${repo}".`,
|
|
1371
|
+
{
|
|
1372
|
+
includeSerializedStructuredContent: updateData.structured,
|
|
1373
|
+
resourceLinks: resourceLinks.map((link) => ({
|
|
1374
|
+
...link,
|
|
1375
|
+
annotations: {
|
|
1376
|
+
...link.annotations,
|
|
1377
|
+
audience: link.annotations.audience
|
|
1378
|
+
}
|
|
1379
|
+
}))
|
|
1380
|
+
}
|
|
1251
1381
|
);
|
|
1252
1382
|
}
|
|
1253
1383
|
async function handleTaskDelete(args, storage) {
|
|
1254
|
-
const
|
|
1384
|
+
const validated = TaskDeleteSchema.parse(args);
|
|
1385
|
+
const { repo, id, ids } = validated;
|
|
1255
1386
|
const targetIds = ids || (id ? [id] : []);
|
|
1256
1387
|
if (targetIds.length === 0) {
|
|
1257
1388
|
throw new Error("Either 'id' or 'ids' must be provided for deletion");
|
|
@@ -1267,7 +1398,22 @@ async function handleTaskDelete(args, storage) {
|
|
|
1267
1398
|
repo,
|
|
1268
1399
|
deletedCount: targetIds.length
|
|
1269
1400
|
},
|
|
1270
|
-
`Deleted ${targetIds.length} task(s) from repo "${repo}"
|
|
1401
|
+
`Deleted ${targetIds.length} task(s) from repo "${repo}".`,
|
|
1402
|
+
{
|
|
1403
|
+
includeSerializedStructuredContent: validated.structured,
|
|
1404
|
+
resourceLinks: [
|
|
1405
|
+
{
|
|
1406
|
+
uri: `repository://${encodeURIComponent(repo)}/tasks`,
|
|
1407
|
+
name: `Task Index (${repo})`,
|
|
1408
|
+
description: `Repository task index for ${repo}`,
|
|
1409
|
+
mimeType: "application/json",
|
|
1410
|
+
annotations: {
|
|
1411
|
+
audience: ["assistant"],
|
|
1412
|
+
priority: 0.6
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
]
|
|
1416
|
+
}
|
|
1271
1417
|
);
|
|
1272
1418
|
}
|
|
1273
1419
|
|
|
@@ -1381,7 +1527,8 @@ ${contextBlock || "No additional context provided."}`
|
|
|
1381
1527
|
},
|
|
1382
1528
|
`Synthesized answer for "${validated.objective}" using repository "${repo}".`,
|
|
1383
1529
|
{
|
|
1384
|
-
structuredContentPathHint: "answer"
|
|
1530
|
+
structuredContentPathHint: "answer",
|
|
1531
|
+
includeSerializedStructuredContent: validated.structured
|
|
1385
1532
|
}
|
|
1386
1533
|
);
|
|
1387
1534
|
}
|
|
@@ -1503,12 +1650,14 @@ async function executeSamplingTool(toolName, rawInput, db2, vectors2) {
|
|
|
1503
1650
|
|
|
1504
1651
|
// src/mcp/tools/memory.delete.ts
|
|
1505
1652
|
async function handleMemoryDelete(params, db2, vectors2, onProgress) {
|
|
1506
|
-
const
|
|
1653
|
+
const validated = MemoryDeleteSchema.parse(params);
|
|
1654
|
+
const { id, ids, repo, structured } = validated;
|
|
1507
1655
|
const targetIds = ids || (id ? [id] : []);
|
|
1508
1656
|
if (targetIds.length === 0) {
|
|
1509
1657
|
throw new Error("Either 'id' or 'ids' must be provided for deletion");
|
|
1510
1658
|
}
|
|
1511
1659
|
let deletedCount = 0;
|
|
1660
|
+
const deletedCodes = [];
|
|
1512
1661
|
let lastRepo = repo || "unknown";
|
|
1513
1662
|
const total = targetIds.length;
|
|
1514
1663
|
let progress = 0;
|
|
@@ -1519,6 +1668,7 @@ async function handleMemoryDelete(params, db2, vectors2, onProgress) {
|
|
|
1519
1668
|
const existing = db2.memories.getById(targetId);
|
|
1520
1669
|
if (existing) {
|
|
1521
1670
|
lastRepo = existing.scope.repo;
|
|
1671
|
+
deletedCodes.push(existing.code || existing.id);
|
|
1522
1672
|
db2.memories.delete(targetId);
|
|
1523
1673
|
await vectors2.remove(targetId);
|
|
1524
1674
|
deletedCount++;
|
|
@@ -1537,11 +1687,13 @@ async function handleMemoryDelete(params, db2, vectors2, onProgress) {
|
|
|
1537
1687
|
id: id || void 0,
|
|
1538
1688
|
ids: ids || void 0,
|
|
1539
1689
|
repo: lastRepo,
|
|
1540
|
-
deletedCount
|
|
1690
|
+
deletedCount,
|
|
1691
|
+
deletedCodes: deletedCount > 10 ? [...deletedCodes.slice(0, 10), "..."] : deletedCodes
|
|
1541
1692
|
},
|
|
1542
|
-
`Deleted ${deletedCount} memory entry(ies) from repo "${lastRepo}".`,
|
|
1693
|
+
`Deleted ${deletedCount} memory entry(ies) ${deletedCount > 0 ? `([${deletedCodes.slice(0, 10).join(", ")}${deletedCount > 10 ? ", ..." : ""}]) ` : ""}from repo "${lastRepo}".`,
|
|
1543
1694
|
{
|
|
1544
1695
|
structuredContentPathHint: "deletedCount",
|
|
1696
|
+
includeSerializedStructuredContent: structured,
|
|
1545
1697
|
resourceLinks: [
|
|
1546
1698
|
{
|
|
1547
1699
|
uri: `repository://${encodeURIComponent(lastRepo)}/memories`,
|
|
@@ -1582,16 +1734,18 @@ async function handleMemoryAcknowledge(params, db2) {
|
|
|
1582
1734
|
id: memory.id,
|
|
1583
1735
|
status: validated.status
|
|
1584
1736
|
},
|
|
1585
|
-
`Acknowledged memory ${memory.
|
|
1737
|
+
`Acknowledged memory [${memory.code}] as "${validated.status}".`,
|
|
1586
1738
|
{
|
|
1587
|
-
structuredContentPathHint: "status"
|
|
1739
|
+
structuredContentPathHint: "status",
|
|
1740
|
+
includeSerializedStructuredContent: validated.structured
|
|
1588
1741
|
}
|
|
1589
1742
|
);
|
|
1590
1743
|
}
|
|
1591
1744
|
|
|
1592
1745
|
// src/mcp/tools/memory.detail.ts
|
|
1593
1746
|
async function handleMemoryDetail(args, storage) {
|
|
1594
|
-
const
|
|
1747
|
+
const validated = MemoryDetailSchema.parse(args);
|
|
1748
|
+
const { id, code } = validated;
|
|
1595
1749
|
let memory;
|
|
1596
1750
|
if (id) {
|
|
1597
1751
|
memory = storage.memories.getById(id);
|
|
@@ -1618,7 +1772,7 @@ async function handleMemoryDetail(args, storage) {
|
|
|
1618
1772
|
const content = lines.join("\n");
|
|
1619
1773
|
return createMcpResponse(memory, content, {
|
|
1620
1774
|
contentSummary: content,
|
|
1621
|
-
includeSerializedStructuredContent:
|
|
1775
|
+
includeSerializedStructuredContent: validated.structured
|
|
1622
1776
|
});
|
|
1623
1777
|
}
|
|
1624
1778
|
|
|
@@ -1670,7 +1824,7 @@ async function handleTaskGet(args, storage) {
|
|
|
1670
1824
|
};
|
|
1671
1825
|
return createMcpResponse(structuredData, contentSummary || "", {
|
|
1672
1826
|
contentSummary,
|
|
1673
|
-
includeSerializedStructuredContent:
|
|
1827
|
+
includeSerializedStructuredContent: isStructuredRequest
|
|
1674
1828
|
});
|
|
1675
1829
|
}
|
|
1676
1830
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: export-task-to-github
|
|
3
|
+
description: Guide for exporting local tasks from Local Memory MCP to GitHub Issues
|
|
4
|
+
arguments:
|
|
5
|
+
- name: owner
|
|
6
|
+
description: GitHub repository owner (e.g., 'vheins')
|
|
7
|
+
required: true
|
|
8
|
+
- name: repo
|
|
9
|
+
description: GitHub repository name (e.g., 'local-memory-mcp')
|
|
10
|
+
required: true
|
|
11
|
+
- name: task_id
|
|
12
|
+
description: Unique ID of the local task to export
|
|
13
|
+
required: true
|
|
14
|
+
agent: Integration Architect
|
|
15
|
+
---
|
|
16
|
+
# Skill: export-task-to-github
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
You are an **Integration Architect**. Your goal is to export a specific local task from our `local-memory-mcp` system into a high-quality **GitHub Issue**.
|
|
20
|
+
|
|
21
|
+
## Instructions
|
|
22
|
+
|
|
23
|
+
### 1. Task Retrieval (MANDATORY)
|
|
24
|
+
1. **Fetch Task Details**: Call `local-memory-mcp` MCP tool `task-detail` using the provided `task_id`.
|
|
25
|
+
2. **Verify Content**: Ensure the task has a clear title and description. If information is missing, use `memory-search` to find related context.
|
|
26
|
+
|
|
27
|
+
### 2. GitHub Sync & Conflict Check
|
|
28
|
+
1. **Search Existing Issues**: Use `github-mcp-server`'s `search_issues` tool. Search for the local `task_code` (e.g., "FEAT-123") or similar keywords in the target repository.
|
|
29
|
+
2. **Avoid Duplicates**: If a Github issue for this task already exists, do NOT create a new one. Instead, update the local task with the existing Github issue URL in metadata.
|
|
30
|
+
|
|
31
|
+
### 3. Issue Creation
|
|
32
|
+
If no duplicate is found, create the GitHub issue using `github-mcp-server`'s `issue_write` (method: 'create'):
|
|
33
|
+
|
|
34
|
+
- **Title**: Use the local task title exactly.
|
|
35
|
+
- **Body**: Use the local task description exactly.
|
|
36
|
+
- **Metadata**: Include the local `task_code` and `task_id` at the bottom of the body for traceability.
|
|
37
|
+
- **Initial Comment**: If the local task has existing comments, post them as the first comment on the newly created GitHub issue using `add_issue_comment`.
|
|
38
|
+
|
|
39
|
+
### 4. Linkage & Cleanup
|
|
40
|
+
1. **Update Local Task**: Once the GitHub issue is created, get the Issue Number and URL.
|
|
41
|
+
2. **Task Update**: Use `local-memory-mcp` tool `task-update` to:
|
|
42
|
+
- Add the GitHub URL to `metadata`.
|
|
43
|
+
- Add a comment stating "Exported to GitHub Issue #{{number}}".
|
|
44
|
+
|
|
45
|
+
### 5. Confirmation
|
|
46
|
+
Provide the link to the newly created (or existing) GitHub issue.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### ✅ ALLOWED OUTPUT (STRICT)
|
|
51
|
+
Your output MUST ONLY consist of calls to:
|
|
52
|
+
- `mcp_local-memory_task-detail`
|
|
53
|
+
- `mcp_local-memory_task-update`
|
|
54
|
+
- `mcp_github-mcp-server_search_issues`
|
|
55
|
+
- `mcp_github-mcp-server_issue_write`
|
|
56
|
+
- `mcp_github-mcp-server_add_issue_comment`
|
|
57
|
+
|
|
58
|
+
**❌ DO NOT:**
|
|
59
|
+
- Output explanations or narrative text during execution.
|
|
60
|
+
- Modify the original title or description of the task when exporting.
|
|
61
|
+
- Create duplicate issues.
|
|
@@ -13,11 +13,16 @@ Please follow these steps:
|
|
|
13
13
|
2. **Review Existing Tasks**: Call `local-memory-mcp` MCP tools `task-list` for the current repository to identify tasks already imported.
|
|
14
14
|
3. **Map and Create**: For each relevant issue that hasn't been imported yet:
|
|
15
15
|
- Use 'task-manage' with action='create'.
|
|
16
|
+
- **MANDATORY**: Keep the original GitHub **title** and **description** exactly as they are. Do NOT summarize or modify them.
|
|
16
17
|
- Set 'task_code' to 'GH-{{issue_number}}' (e.g., GH-123).
|
|
17
18
|
- Set 'title' to the issue title.
|
|
18
|
-
- Set 'description' to the issue body
|
|
19
|
+
- Set 'description' to the issue body.
|
|
19
20
|
- Map GitHub labels to 'tags' if applicable.
|
|
20
21
|
- Default 'phase' to 'backlog' or 'triage'.
|
|
21
22
|
- Set 'metadata' to include the original GitHub URL.
|
|
22
|
-
4. **
|
|
23
|
-
|
|
23
|
+
4. **Import Comments**: If the issue has comments:
|
|
24
|
+
- Use `github-mcp-server`'s `issue_read` tool with `method='get_comments'` to fetch all comments.
|
|
25
|
+
- For each comment, add it to the created task using the `task-update` tool, appending it to the `comments` array or adding a specific comment metadata.
|
|
26
|
+
5. **Avoid Duplicates**: Do not import issues that already have a corresponding 'GH-{{number}}' task code in our system.
|
|
27
|
+
6. **Confirmation**: Provide a summary of how many tasks were successfully created.
|
|
28
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vheins/local-memory-mcp",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "MCP Local Memory Service for coding copilot agents",
|
|
5
5
|
"mcpName": "io.github.vheins/local-memory-mcp",
|
|
6
6
|
"type": "module",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"both": "npm run start & npm run dashboard",
|
|
39
39
|
"test": "vitest --run",
|
|
40
40
|
"test:watch": "vitest",
|
|
41
|
+
"type-check": "tsc --noEmit && svelte-check --tsconfig ./src/dashboard/ui/tsconfig.json",
|
|
41
42
|
"lint": "eslint . --ext .ts,.svelte",
|
|
42
43
|
"lint:fix": "eslint . --ext .ts,.svelte --fix",
|
|
43
44
|
"format": "prettier --write \"src/**/*.{ts,js,svelte}\""
|