@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.
@@ -42,7 +42,7 @@ import {
42
42
  setLogLevel,
43
43
  updateSessionFromInitialize,
44
44
  updateSessionRoots
45
- } from "../chunk-J4O2HJ2K.js";
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 = "auto"
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?.trim().length) {
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 (!validated.structured) {
626
- if (paginatedResults.length > 0) {
627
- const parts = [];
628
- for (const [memType, items] of Object.entries(memoriesByType)) {
629
- parts.push(`${capitalize(memType)}:`);
630
- parts.push("- code|importance|title");
631
- for (const m of items) {
632
- const code = m.code || "-";
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("Use memory-detail with memory_id (or code) for full content.");
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
- includeSerializedStructuredContent: false
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: false
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: false
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: false
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: taskData.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: 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
- if ((existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked") && updates.status === "completed") {
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 { repo, id, ids } = TaskDeleteSchema.parse(args);
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 { id, ids, repo } = MemoryDeleteSchema.parse(params);
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.id} as "${validated.status}".`,
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 { id, code } = MemoryDetailSchema.parse(args);
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: false
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: false
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 (abbreviate if extremely long).
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. **Avoid Duplicates**: Do not import issues that already have a corresponding 'GH-{{number}}' task code in our system.
23
- 5. **Confirmation**: Provide a summary of how many tasks were successfully created.
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.2",
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}\""