@vheins/local-memory-mcp 0.7.2 → 0.7.3
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-ZCK6RZFX.js} +48 -11
- package/dist/dashboard/public/assets/{index-CRhOgOlp.js → index-Bd8FKzVy.js} +2 -2
- package/dist/dashboard/public/index.html +1 -1
- package/dist/dashboard/server.js +54 -24
- package/dist/mcp/server.js +55 -29
- package/dist/prompts/export-task-to-github.md +61 -0
- package/dist/prompts/import-github-issues.md +8 -3
- package/package.json +2 -1
|
@@ -8,7 +8,7 @@
|
|
|
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-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-Bd8FKzVy.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-Bd7v94SO.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
package/dist/dashboard/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
TOOL_DEFINITIONS,
|
|
7
7
|
listResources,
|
|
8
8
|
logger
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-ZCK6RZFX.js";
|
|
10
10
|
|
|
11
11
|
// src/dashboard/server.ts
|
|
12
12
|
import express from "express";
|
|
@@ -252,15 +252,25 @@ function condenseRecentActions(actions, limit) {
|
|
|
252
252
|
var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
253
253
|
var pkg = { version: "0.0.0" };
|
|
254
254
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
let currentDir = __dirname2;
|
|
256
|
+
let pkgPath = "";
|
|
257
|
+
while (currentDir !== path2.parse(currentDir).root) {
|
|
258
|
+
const checkPath = path2.join(currentDir, "package.json");
|
|
259
|
+
if (fs2.existsSync(checkPath)) {
|
|
260
|
+
pkgPath = checkPath;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
currentDir = path2.dirname(currentDir);
|
|
264
|
+
}
|
|
265
|
+
if (pkgPath) {
|
|
257
266
|
const data = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
258
267
|
if (data.version) pkg.version = data.version;
|
|
259
268
|
}
|
|
260
269
|
} catch {
|
|
261
270
|
}
|
|
262
271
|
var SystemController = class {
|
|
263
|
-
static getHealth(req, res) {
|
|
272
|
+
static async getHealth(req, res) {
|
|
273
|
+
await db.refresh();
|
|
264
274
|
const stats = db.system.getGlobalStats();
|
|
265
275
|
const health = {
|
|
266
276
|
connected: mcpClient.isConnected(),
|
|
@@ -272,8 +282,9 @@ var SystemController = class {
|
|
|
272
282
|
};
|
|
273
283
|
res.json(jsonApiRes(health, "health"));
|
|
274
284
|
}
|
|
275
|
-
static getRepos(req, res) {
|
|
285
|
+
static async getRepos(req, res) {
|
|
276
286
|
try {
|
|
287
|
+
await db.refresh();
|
|
277
288
|
const repos = db.system.listRepoNavigation();
|
|
278
289
|
res.json(
|
|
279
290
|
jsonApiRes(
|
|
@@ -286,8 +297,9 @@ var SystemController = class {
|
|
|
286
297
|
res.status(500).json(jsonApiError(message));
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
|
-
static getStats(req, res) {
|
|
300
|
+
static async getStats(req, res) {
|
|
290
301
|
try {
|
|
302
|
+
await db.refresh();
|
|
291
303
|
const repo = req.query.repo;
|
|
292
304
|
if (!repo) return res.status(400).json(jsonApiError("repo is required", 400));
|
|
293
305
|
const stats = db.system.getDashboardStats(repo);
|
|
@@ -297,8 +309,9 @@ var SystemController = class {
|
|
|
297
309
|
res.status(500).json(jsonApiError(message));
|
|
298
310
|
}
|
|
299
311
|
}
|
|
300
|
-
static getRecentActions(req, res) {
|
|
312
|
+
static async getRecentActions(req, res) {
|
|
301
313
|
try {
|
|
314
|
+
await db.refresh();
|
|
302
315
|
const repo = req.query.repo;
|
|
303
316
|
const pageSize = Math.min(50, Math.max(1, parseInt(req.query.pageSize) || 10));
|
|
304
317
|
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
@@ -344,8 +357,9 @@ var SystemController = class {
|
|
|
344
357
|
const caps = { tools, resources, prompts };
|
|
345
358
|
res.json(jsonApiRes(caps, "capability"));
|
|
346
359
|
}
|
|
347
|
-
static getExport(req, res) {
|
|
360
|
+
static async getExport(req, res) {
|
|
348
361
|
try {
|
|
362
|
+
await db.refresh();
|
|
349
363
|
const { repo } = req.query;
|
|
350
364
|
if (!repo) return res.status(400).json(jsonApiError("repo is required", 400));
|
|
351
365
|
const memories = db.memories.getAllMemoriesWithStats(repo);
|
|
@@ -406,8 +420,9 @@ import { Router as Router2 } from "express";
|
|
|
406
420
|
// src/dashboard/controllers/MemoriesController.ts
|
|
407
421
|
import { randomUUID } from "crypto";
|
|
408
422
|
var MemoriesController = class {
|
|
409
|
-
static list(req, res) {
|
|
423
|
+
static async list(req, res) {
|
|
410
424
|
try {
|
|
425
|
+
await db.refresh();
|
|
411
426
|
const query = req.query;
|
|
412
427
|
const { repo, type, search, minImportance, maxImportance, sortBy, sortOrder } = query;
|
|
413
428
|
const page = Math.max(1, parseInt(query.page || "1", 10));
|
|
@@ -439,8 +454,9 @@ var MemoriesController = class {
|
|
|
439
454
|
res.status(500).json(jsonApiError(message));
|
|
440
455
|
}
|
|
441
456
|
}
|
|
442
|
-
static get(req, res) {
|
|
457
|
+
static async get(req, res) {
|
|
443
458
|
try {
|
|
459
|
+
await db.refresh();
|
|
444
460
|
const memory = db.memories.getByIdWithStats(req.params.id);
|
|
445
461
|
if (!memory) throw new Error("Memory not found");
|
|
446
462
|
db.actions.logAction("read", memory.scope.repo, { memoryId: memory.id, resultCount: 1 });
|
|
@@ -450,8 +466,9 @@ var MemoriesController = class {
|
|
|
450
466
|
res.status(404).json(jsonApiError(message, 404));
|
|
451
467
|
}
|
|
452
468
|
}
|
|
453
|
-
static create(req, res) {
|
|
469
|
+
static async create(req, res) {
|
|
454
470
|
try {
|
|
471
|
+
await db.refresh();
|
|
455
472
|
const attributes = getAttributes(req);
|
|
456
473
|
const { repo, type, content } = attributes;
|
|
457
474
|
if (!repo || !type || !content) return res.status(400).json(jsonApiError("Required fields missing", 400));
|
|
@@ -470,8 +487,9 @@ var MemoriesController = class {
|
|
|
470
487
|
res.status(500).json(jsonApiError(message));
|
|
471
488
|
}
|
|
472
489
|
}
|
|
473
|
-
static update(req, res) {
|
|
490
|
+
static async update(req, res) {
|
|
474
491
|
try {
|
|
492
|
+
await db.refresh();
|
|
475
493
|
const { id } = req.params;
|
|
476
494
|
const existing = db.memories.getByIdWithStats ? db.memories.getByIdWithStats(id) : db.memories.getById(id);
|
|
477
495
|
if (!existing) return res.status(404).json(jsonApiError("Memory not found", 404));
|
|
@@ -496,8 +514,9 @@ var MemoriesController = class {
|
|
|
496
514
|
res.status(500).json(jsonApiError(message));
|
|
497
515
|
}
|
|
498
516
|
}
|
|
499
|
-
static delete(req, res) {
|
|
517
|
+
static async delete(req, res) {
|
|
500
518
|
try {
|
|
519
|
+
await db.refresh();
|
|
501
520
|
const { id } = req.params;
|
|
502
521
|
const existing = db.memories.getByIdWithStats ? db.memories.getByIdWithStats(id) : db.memories.getById(id);
|
|
503
522
|
if (!existing) return res.status(404).json(jsonApiError("Memory not found", 404));
|
|
@@ -509,8 +528,9 @@ var MemoriesController = class {
|
|
|
509
528
|
res.status(500).json(jsonApiError(message));
|
|
510
529
|
}
|
|
511
530
|
}
|
|
512
|
-
static bulkCreate(req, res) {
|
|
531
|
+
static async bulkCreate(req, res) {
|
|
513
532
|
try {
|
|
533
|
+
await db.refresh();
|
|
514
534
|
const { items, repo } = getAttributes(req);
|
|
515
535
|
if (!Array.isArray(items) || !repo)
|
|
516
536
|
return res.status(400).json(jsonApiError("Invalid payload: requires 'items' array and 'repo'", 400));
|
|
@@ -529,8 +549,9 @@ var MemoriesController = class {
|
|
|
529
549
|
res.status(500).json(jsonApiError(message));
|
|
530
550
|
}
|
|
531
551
|
}
|
|
532
|
-
static bulkAction(req, res) {
|
|
552
|
+
static async bulkAction(req, res) {
|
|
533
553
|
try {
|
|
554
|
+
await db.refresh();
|
|
534
555
|
const { action, ids, updates } = getAttributes(req);
|
|
535
556
|
if (!Array.isArray(ids) || !action)
|
|
536
557
|
return res.status(400).json(jsonApiError("Invalid payload: requires 'ids' array and 'action'", 400));
|
|
@@ -576,8 +597,9 @@ import { Router as Router3 } from "express";
|
|
|
576
597
|
// src/dashboard/controllers/TasksController.ts
|
|
577
598
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
578
599
|
var TasksController = class {
|
|
579
|
-
static list(req, res) {
|
|
600
|
+
static async list(req, res) {
|
|
580
601
|
try {
|
|
602
|
+
await db.refresh();
|
|
581
603
|
const query = req.query;
|
|
582
604
|
const { repo, status, search } = query;
|
|
583
605
|
const page = Math.max(1, parseInt(req.query.page || "1", 10));
|
|
@@ -607,8 +629,9 @@ var TasksController = class {
|
|
|
607
629
|
res.status(500).json(jsonApiError(message));
|
|
608
630
|
}
|
|
609
631
|
}
|
|
610
|
-
static get(req, res) {
|
|
632
|
+
static async get(req, res) {
|
|
611
633
|
try {
|
|
634
|
+
await db.refresh();
|
|
612
635
|
const task = db.tasks.getTaskById(req.params.id);
|
|
613
636
|
if (!task) throw new Error("Task not found");
|
|
614
637
|
db.actions.logAction("read", task.repo, { taskId: task.id });
|
|
@@ -618,8 +641,9 @@ var TasksController = class {
|
|
|
618
641
|
res.status(404).json(jsonApiError(message, 404));
|
|
619
642
|
}
|
|
620
643
|
}
|
|
621
|
-
static create(req, res) {
|
|
644
|
+
static async create(req, res) {
|
|
622
645
|
try {
|
|
646
|
+
await db.refresh();
|
|
623
647
|
const attributes = getAttributes(req);
|
|
624
648
|
const { repo, task_code, title } = attributes;
|
|
625
649
|
if (!repo || !task_code || !title) return res.status(400).json(jsonApiError("Required fields missing", 400));
|
|
@@ -639,8 +663,9 @@ var TasksController = class {
|
|
|
639
663
|
res.status(500).json(jsonApiError(message));
|
|
640
664
|
}
|
|
641
665
|
}
|
|
642
|
-
static update(req, res) {
|
|
666
|
+
static async update(req, res) {
|
|
643
667
|
try {
|
|
668
|
+
await db.refresh();
|
|
644
669
|
const { id } = req.params;
|
|
645
670
|
const attributes = getAttributes(req);
|
|
646
671
|
const existingTask = db.tasks.getTaskById(id);
|
|
@@ -683,8 +708,9 @@ var TasksController = class {
|
|
|
683
708
|
res.status(500).json(jsonApiError(message));
|
|
684
709
|
}
|
|
685
710
|
}
|
|
686
|
-
static delete(req, res) {
|
|
711
|
+
static async delete(req, res) {
|
|
687
712
|
try {
|
|
713
|
+
await db.refresh();
|
|
688
714
|
const { id } = req.params;
|
|
689
715
|
const task = db.tasks.getTaskById(id);
|
|
690
716
|
if (!task) return res.status(404).json(jsonApiError("Task not found", 404));
|
|
@@ -696,8 +722,9 @@ var TasksController = class {
|
|
|
696
722
|
res.status(500).json(jsonApiError(message));
|
|
697
723
|
}
|
|
698
724
|
}
|
|
699
|
-
static bulkCreate(req, res) {
|
|
725
|
+
static async bulkCreate(req, res) {
|
|
700
726
|
try {
|
|
727
|
+
await db.refresh();
|
|
701
728
|
const { items, repo } = getAttributes(req);
|
|
702
729
|
if (!Array.isArray(items) || !repo)
|
|
703
730
|
return res.status(400).json(jsonApiError("Invalid payload: requires 'items' array and 'repo'", 400));
|
|
@@ -717,8 +744,9 @@ var TasksController = class {
|
|
|
717
744
|
res.status(500).json(jsonApiError(message));
|
|
718
745
|
}
|
|
719
746
|
}
|
|
720
|
-
static getTimeStats(req, res) {
|
|
747
|
+
static async getTimeStats(req, res) {
|
|
721
748
|
try {
|
|
749
|
+
await db.refresh();
|
|
722
750
|
const { repo } = req.query;
|
|
723
751
|
if (!repo) return res.status(400).json(jsonApiError("repo is required", 400));
|
|
724
752
|
const stats = {
|
|
@@ -745,8 +773,9 @@ var TasksController = class {
|
|
|
745
773
|
res.status(500).json(jsonApiError(message));
|
|
746
774
|
}
|
|
747
775
|
}
|
|
748
|
-
static updateComment(req, res) {
|
|
776
|
+
static async updateComment(req, res) {
|
|
749
777
|
try {
|
|
778
|
+
await db.refresh();
|
|
750
779
|
const { id } = req.params;
|
|
751
780
|
const { comment } = getAttributes(req);
|
|
752
781
|
const existingComment = db.tasks.getTaskCommentById(id);
|
|
@@ -758,8 +787,9 @@ var TasksController = class {
|
|
|
758
787
|
res.status(500).json(jsonApiError(message));
|
|
759
788
|
}
|
|
760
789
|
}
|
|
761
|
-
static deleteComment(req, res) {
|
|
790
|
+
static async deleteComment(req, res) {
|
|
762
791
|
try {
|
|
792
|
+
await db.refresh();
|
|
763
793
|
const { id } = req.params;
|
|
764
794
|
db.tasks.deleteTaskComment(id);
|
|
765
795
|
res.json(jsonApiRes({ message: "Deleted" }, "status"));
|
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-ZCK6RZFX.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 = true
|
|
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
|
}
|
|
@@ -622,23 +625,21 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
622
625
|
memoriesByType[typeLabel].push(m);
|
|
623
626
|
}
|
|
624
627
|
let contentSummary;
|
|
625
|
-
if (
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
parts.push(`- ${code}|${m.importance}|${m.title}`);
|
|
634
|
-
}
|
|
635
|
-
parts.push("");
|
|
628
|
+
if (paginatedResults.length > 0) {
|
|
629
|
+
const parts = [];
|
|
630
|
+
for (const [memType, items] of Object.entries(memoriesByType)) {
|
|
631
|
+
parts.push(`${capitalize(memType)}:`);
|
|
632
|
+
parts.push("- code|importance|title");
|
|
633
|
+
for (const m of items) {
|
|
634
|
+
const code = m.code || "-";
|
|
635
|
+
parts.push(`- ${code}|${m.importance}|${m.title}`);
|
|
636
636
|
}
|
|
637
|
-
parts.push("
|
|
638
|
-
contentSummary = parts.join("\n").trim();
|
|
639
|
-
} else {
|
|
640
|
-
contentSummary = `No memories found for "${validated.query}" in repo "${validated.repo}".`;
|
|
637
|
+
parts.push("");
|
|
641
638
|
}
|
|
639
|
+
parts.push("Use memory-detail with memory_id (or code) for full content.");
|
|
640
|
+
contentSummary = parts.join("\n").trim();
|
|
641
|
+
} else {
|
|
642
|
+
contentSummary = `No memories found for "${validated.query}" in repo "${validated.repo}".`;
|
|
642
643
|
}
|
|
643
644
|
const structuredData = {
|
|
644
645
|
schema: "memory-search",
|
|
@@ -652,9 +653,10 @@ async function handleMemorySearch(params, db2, vectors2) {
|
|
|
652
653
|
rows
|
|
653
654
|
}
|
|
654
655
|
};
|
|
655
|
-
return createMcpResponse(structuredData, contentSummary || ""
|
|
656
|
+
return createMcpResponse(structuredData, contentSummary || `Found ${total} memories for "${validated.query}".`, {
|
|
656
657
|
contentSummary,
|
|
657
|
-
|
|
658
|
+
structuredContentPathHint: "results",
|
|
659
|
+
includeSerializedStructuredContent: validated.structured
|
|
658
660
|
});
|
|
659
661
|
}
|
|
660
662
|
function capitalize(str) {
|
|
@@ -769,7 +771,7 @@ async function handleMemoryRecap(params, db2) {
|
|
|
769
771
|
};
|
|
770
772
|
return createMcpResponse(structuredData, contentSummary || "", {
|
|
771
773
|
contentSummary,
|
|
772
|
-
includeSerializedStructuredContent:
|
|
774
|
+
includeSerializedStructuredContent: validated.structured
|
|
773
775
|
});
|
|
774
776
|
}
|
|
775
777
|
function capitalize2(str) {
|
|
@@ -940,7 +942,7 @@ async function handleTaskList(args, storage) {
|
|
|
940
942
|
}
|
|
941
943
|
return createMcpResponse(structuredData, contentSummary || "", {
|
|
942
944
|
contentSummary,
|
|
943
|
-
includeSerializedStructuredContent:
|
|
945
|
+
includeSerializedStructuredContent: isStructuredRequest
|
|
944
946
|
});
|
|
945
947
|
}
|
|
946
948
|
async function handleTaskCreate(args, storage) {
|
|
@@ -977,6 +979,11 @@ async function handleTaskCreate(args, storage) {
|
|
|
977
979
|
}
|
|
978
980
|
}
|
|
979
981
|
const statusTimestamps2 = deriveTaskStatusTimestamps(normalizedStatus, now2);
|
|
982
|
+
const tags2 = [...taskData.tags || []];
|
|
983
|
+
const phaseTag2 = `phase:${taskData.phase}`;
|
|
984
|
+
if (!tags2.includes(phaseTag2)) {
|
|
985
|
+
tags2.push(phaseTag2);
|
|
986
|
+
}
|
|
980
987
|
const task2 = {
|
|
981
988
|
id: randomUUID2(),
|
|
982
989
|
repo,
|
|
@@ -995,7 +1002,7 @@ async function handleTaskCreate(args, storage) {
|
|
|
995
1002
|
finished_at: statusTimestamps2.finished_at,
|
|
996
1003
|
canceled_at: statusTimestamps2.canceled_at,
|
|
997
1004
|
est_tokens: taskData.est_tokens ?? 0,
|
|
998
|
-
tags:
|
|
1005
|
+
tags: tags2,
|
|
999
1006
|
metadata: taskData.metadata || {},
|
|
1000
1007
|
parent_id: taskData.parent_id || null,
|
|
1001
1008
|
depends_on: taskData.depends_on || null
|
|
@@ -1042,6 +1049,11 @@ async function handleTaskCreate(args, storage) {
|
|
|
1042
1049
|
const taskId = randomUUID2();
|
|
1043
1050
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1044
1051
|
const statusTimestamps = deriveTaskStatusTimestamps(status || "backlog", now);
|
|
1052
|
+
const finalTags = [...singleTask.tags || []];
|
|
1053
|
+
const phaseTag = `phase:${phase}`;
|
|
1054
|
+
if (!finalTags.includes(phaseTag)) {
|
|
1055
|
+
finalTags.push(phaseTag);
|
|
1056
|
+
}
|
|
1045
1057
|
const task = {
|
|
1046
1058
|
id: taskId,
|
|
1047
1059
|
repo,
|
|
@@ -1060,7 +1072,7 @@ async function handleTaskCreate(args, storage) {
|
|
|
1060
1072
|
finished_at: statusTimestamps.finished_at,
|
|
1061
1073
|
canceled_at: statusTimestamps.canceled_at,
|
|
1062
1074
|
est_tokens: est_tokens ?? 0,
|
|
1063
|
-
tags:
|
|
1075
|
+
tags: finalTags,
|
|
1064
1076
|
metadata: metadata || {},
|
|
1065
1077
|
parent_id: parent_id || null,
|
|
1066
1078
|
depends_on: depends_on || null
|
|
@@ -1200,7 +1212,8 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1200
1212
|
if (!comment || comment.trim() === "") {
|
|
1201
1213
|
throw new Error("comment is required when changing task status");
|
|
1202
1214
|
}
|
|
1203
|
-
|
|
1215
|
+
const isStartable = existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked";
|
|
1216
|
+
if (isStartable && updates.status === "completed") {
|
|
1204
1217
|
throw new Error(
|
|
1205
1218
|
`Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`
|
|
1206
1219
|
);
|
|
@@ -1213,6 +1226,18 @@ async function handleTaskUpdate(args, storage, vectors2) {
|
|
|
1213
1226
|
throw new Error(`Duplicate task_code: '${updates.task_code}' already exists`);
|
|
1214
1227
|
}
|
|
1215
1228
|
const finalUpdates = { ...updates };
|
|
1229
|
+
if (updates.phase !== void 0 || updates.tags !== void 0) {
|
|
1230
|
+
let currentTags = updates.tags || existingTask.tags || [];
|
|
1231
|
+
currentTags = currentTags.filter((t) => !t.startsWith("phase:"));
|
|
1232
|
+
const finalPhase = updates.phase !== void 0 ? updates.phase : existingTask.phase;
|
|
1233
|
+
if (finalPhase) {
|
|
1234
|
+
const phaseTag = `phase:${finalPhase}`;
|
|
1235
|
+
if (!currentTags.includes(phaseTag)) {
|
|
1236
|
+
currentTags.push(phaseTag);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
finalUpdates.tags = currentTags;
|
|
1240
|
+
}
|
|
1216
1241
|
if (updates.status === "completed") finalUpdates.finished_at = now;
|
|
1217
1242
|
else if (updates.status === "canceled") finalUpdates.canceled_at = now;
|
|
1218
1243
|
else if (updates.status === "in_progress" && existingTask.status !== "in_progress")
|
|
@@ -1591,7 +1616,8 @@ async function handleMemoryAcknowledge(params, db2) {
|
|
|
1591
1616
|
|
|
1592
1617
|
// src/mcp/tools/memory.detail.ts
|
|
1593
1618
|
async function handleMemoryDetail(args, storage) {
|
|
1594
|
-
const
|
|
1619
|
+
const validated = MemoryDetailSchema.parse(args);
|
|
1620
|
+
const { id, code } = validated;
|
|
1595
1621
|
let memory;
|
|
1596
1622
|
if (id) {
|
|
1597
1623
|
memory = storage.memories.getById(id);
|
|
@@ -1618,7 +1644,7 @@ async function handleMemoryDetail(args, storage) {
|
|
|
1618
1644
|
const content = lines.join("\n");
|
|
1619
1645
|
return createMcpResponse(memory, content, {
|
|
1620
1646
|
contentSummary: content,
|
|
1621
|
-
includeSerializedStructuredContent:
|
|
1647
|
+
includeSerializedStructuredContent: validated.structured
|
|
1622
1648
|
});
|
|
1623
1649
|
}
|
|
1624
1650
|
|
|
@@ -1670,7 +1696,7 @@ async function handleTaskGet(args, storage) {
|
|
|
1670
1696
|
};
|
|
1671
1697
|
return createMcpResponse(structuredData, contentSummary || "", {
|
|
1672
1698
|
contentSummary,
|
|
1673
|
-
includeSerializedStructuredContent:
|
|
1699
|
+
includeSerializedStructuredContent: isStructuredRequest
|
|
1674
1700
|
});
|
|
1675
1701
|
}
|
|
1676
1702
|
|
|
@@ -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.3",
|
|
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}\""
|