@vheins/local-memory-mcp 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,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-Df97JpLg.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-W8_M2hX3.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-Bd7v94SO.css">
13
13
  </head>
14
14
  <body>
@@ -6,7 +6,7 @@ import {
6
6
  TOOL_DEFINITIONS,
7
7
  listResources,
8
8
  logger
9
- } from "../chunk-EQTLHCFK.js";
9
+ } from "../chunk-3I4VJIFK.js";
10
10
 
11
11
  // src/dashboard/server.ts
12
12
  import express from "express";
@@ -189,7 +189,7 @@ function sleep(ms) {
189
189
  }
190
190
 
191
191
  // src/dashboard/lib/context.ts
192
- var db = new SQLiteStore();
192
+ var db = await SQLiteStore.create();
193
193
  var mcpClient = new MCPClient();
194
194
  var startTime = Date.now();
195
195
 
@@ -253,7 +253,8 @@ var pkg = { version: "0.0.0" };
253
253
  try {
254
254
  const pkgPath = path2.join(__dirname2, "../../../package.json");
255
255
  if (fs.existsSync(pkgPath)) {
256
- pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
256
+ const data = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
257
+ if (data.version) pkg.version = data.version;
257
258
  }
258
259
  } catch {
259
260
  }
@@ -324,11 +325,22 @@ var SystemController = class {
324
325
  }
325
326
  }
326
327
  static getCapabilities(req, res) {
327
- const caps = {
328
- tools: TOOL_DEFINITIONS || [],
329
- resources: listResources().resources || [],
330
- prompts: Object.values(PROMPTS) || []
331
- };
328
+ const tools = (TOOL_DEFINITIONS || []).map((tool) => ({
329
+ type: "tool",
330
+ id: tool.name,
331
+ attributes: tool
332
+ }));
333
+ const resources = (listResources().resources || []).map((resource) => ({
334
+ type: "resource",
335
+ id: resource.uri,
336
+ attributes: resource
337
+ }));
338
+ const prompts = (Object.values(PROMPTS) || []).map((prompt) => ({
339
+ type: "prompt",
340
+ id: prompt.name,
341
+ attributes: prompt
342
+ }));
343
+ const caps = { tools, resources, prompts };
332
344
  res.json(jsonApiRes(caps, "capability"));
333
345
  }
334
346
  static getExport(req, res) {
@@ -783,7 +795,8 @@ var pkg2 = { version: "0.0.0" };
783
795
  try {
784
796
  const pkgPath = path3.join(__dirname3, "../../package.json");
785
797
  if (fs2.existsSync(pkgPath)) {
786
- pkg2 = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
798
+ const data = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
799
+ if (data.version) pkg2.version = data.version;
787
800
  }
788
801
  } catch {
789
802
  }
@@ -42,7 +42,7 @@ import {
42
42
  setLogLevel,
43
43
  updateSessionFromInitialize,
44
44
  updateSessionRoots
45
- } from "../chunk-EQTLHCFK.js";
45
+ } from "../chunk-3I4VJIFK.js";
46
46
 
47
47
  // src/mcp/server.ts
48
48
  import readline from "readline";
@@ -299,6 +299,14 @@ function hasMetadataLikeTitle(title) {
299
299
  const normalized = title.trim();
300
300
  return /^\[[^\]]{0,200}(agent:|role:|model:|\d{4}-\d{2}-\d{2}|source_)[^\]]*\]/i.test(normalized);
301
301
  }
302
+ function generateShortCode() {
303
+ const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
304
+ let code = "";
305
+ for (let i = 0; i < 6; i++) {
306
+ code += chars.charAt(Math.floor(Math.random() * chars.length));
307
+ }
308
+ return code;
309
+ }
302
310
  async function handleMemoryStore(params, db2, vectors2) {
303
311
  const validated = MemoryStoreSchema.parse(params);
304
312
  if (hasMetadataLikeTitle(validated.title)) {
@@ -352,6 +360,7 @@ async function handleMemoryStore(params, db2, vectors2) {
352
360
  }
353
361
  const entry = {
354
362
  id: randomUUID(),
363
+ code: validated.code || generateShortCode(),
355
364
  type: validated.type,
356
365
  title: validated.title,
357
366
  content: validated.content,
@@ -390,13 +399,15 @@ async function handleMemoryStore(params, db2, vectors2) {
390
399
  {
391
400
  success: true,
392
401
  id: entry.id,
402
+ code: entry.code,
393
403
  repo: entry.scope.repo,
394
404
  type: entry.type,
395
405
  title: entry.title
396
406
  },
397
- `Stored memory "${entry.title}" in repo "${entry.scope.repo}".`,
407
+ `Stored [${entry.code}] "${entry.title}" in repo "${entry.scope.repo}".`,
398
408
  {
399
- structuredContentPathHint: "id",
409
+ contentSummary: `Stored [${entry.code}] "${entry.title}" in repo "${entry.scope.repo}".`,
410
+ structuredContentPathHint: "code",
400
411
  resourceLinks: [
401
412
  {
402
413
  uri: `memory://${entry.id}`,
@@ -542,19 +553,13 @@ async function handleMemorySearch(params, db2, vectors2) {
542
553
  const currentBranch = validated.scope?.branch;
543
554
  candidates = candidates.map((c) => {
544
555
  let boost = 0;
545
- if (currentBranch && c.memory.scope.branch === currentBranch) {
546
- boost += 0.1;
547
- }
548
- if (currentPath && c.memory.scope.folder && currentPath.includes(c.memory.scope.folder.toLowerCase())) {
549
- boost += 0.15;
550
- }
556
+ if (currentBranch && c.memory.scope.branch === currentBranch) boost += 0.1;
557
+ if (currentPath && c.memory.scope.folder && currentPath.includes(c.memory.scope.folder.toLowerCase())) boost += 0.15;
551
558
  if (currentPath && c.memory.scope.language) {
552
559
  const ext = currentPath.split(".").pop();
553
560
  if (ext && ext.includes(c.memory.scope.language.toLowerCase())) boost += 0.1;
554
561
  }
555
- if (currentTags.length > 0 && c.memory.tags.some((t) => currentTags.includes(t.toLowerCase()))) {
556
- boost += 0.2;
557
- }
562
+ if (currentTags.length > 0 && c.memory.tags.some((t) => currentTags.includes(t.toLowerCase()))) boost += 0.2;
558
563
  return { ...c, similarityScore: Math.min(1, c.similarityScore + boost) };
559
564
  });
560
565
  }
@@ -597,9 +602,7 @@ async function handleMemorySearch(params, db2, vectors2) {
597
602
  scoredMemories.sort((a, b) => b.finalScore - a.finalScore);
598
603
  const threshold = scoredMemories.length <= 5 ? 0.1 : 0.4;
599
604
  let allMatches = scoredMemories.filter((sm) => sm.finalScore >= threshold).map((sm) => sm.memory);
600
- if (allMatches.length === 0 && scoredMemories.length > 0) {
601
- allMatches = [scoredMemories[0].memory];
602
- }
605
+ if (allMatches.length === 0 && scoredMemories.length > 0) allMatches = [scoredMemories[0].memory];
603
606
  const total = allMatches.length;
604
607
  const paginatedResults = allMatches.slice(validated.offset, validated.offset + validated.limit);
605
608
  db2.memories.incrementHitCounts(paginatedResults.map((m) => m.id));
@@ -610,9 +613,34 @@ async function handleMemorySearch(params, db2, vectors2) {
610
613
  offset: validated.offset,
611
614
  returned: paginatedResults.length
612
615
  });
613
- const COLUMNS = ["id", "title", "type", "importance"];
614
- const rows = paginatedResults.map((m) => [m.id, m.title ?? "Untitled", m.type, m.importance]);
615
- const structuredContent = {
616
+ const COLUMNS = ["id", "code", "title", "type", "importance"];
617
+ const rows = paginatedResults.map((m) => [m.id, m.code || "-", m.title ?? "Untitled", m.type, m.importance]);
618
+ const memoriesByType = {};
619
+ for (const m of paginatedResults) {
620
+ const typeLabel = m.type || "unknown";
621
+ if (!memoriesByType[typeLabel]) memoriesByType[typeLabel] = [];
622
+ memoriesByType[typeLabel].push(m);
623
+ }
624
+ 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("");
636
+ }
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}".`;
641
+ }
642
+ }
643
+ const structuredData = {
616
644
  schema: "memory-search",
617
645
  query: validated.query,
618
646
  count: paginatedResults.length,
@@ -624,13 +652,14 @@ async function handleMemorySearch(params, db2, vectors2) {
624
652
  rows
625
653
  }
626
654
  };
627
- const memoryList = paginatedResults.map((m) => `"${m.title}" (ID: ${m.id})`).join(", ");
628
- const contentSummary = paginatedResults.length > 0 ? `Found ${total} memories for "${validated.query}" (showing ${paginatedResults.length} at offset ${validated.offset}): ${memoryList}. Use memory-detail to read full content.` : `No memories found for "${validated.query}" in repo "${validated.repo}".`;
629
- return createMcpResponse(structuredContent, contentSummary, {
655
+ return createMcpResponse(structuredData, contentSummary || "", {
630
656
  contentSummary,
631
657
  includeSerializedStructuredContent: false
632
658
  });
633
659
  }
660
+ function capitalize(str) {
661
+ return str.charAt(0).toUpperCase() + str.slice(1);
662
+ }
634
663
 
635
664
  // src/mcp/tools/memory.summarize.ts
636
665
  async function handleMemorySummarize(params, db2) {
@@ -639,30 +668,13 @@ async function handleMemorySummarize(params, db2) {
639
668
  const fullSummary = `Project summary:
640
669
  - ${summary}`;
641
670
  db2.summaries.upsertSummary(validated.repo, fullSummary);
642
- return createMcpResponse(
643
- {
644
- success: true,
645
- repo: validated.repo,
646
- summary: fullSummary,
647
- signalCount: validated.signals.length
648
- },
649
- `Updated summary for repo "${validated.repo}" with ${validated.signals.length} signals.`,
650
- {
651
- structuredContentPathHint: "summary",
652
- resourceLinks: [
653
- {
654
- uri: `repository://${encodeURIComponent(validated.repo)}/summary`,
655
- name: `Repository Summary (${validated.repo})`,
656
- description: "Repository summary resource",
657
- mimeType: "text/plain",
658
- annotations: {
659
- audience: ["assistant"],
660
- priority: 0.9
661
- }
662
- }
663
- ]
664
- }
665
- );
671
+ const content = `Updated summary for repo "${validated.repo}" with ${validated.signals.length} signals:
672
+
673
+ ${fullSummary}`;
674
+ return createMcpResponse(null, content, {
675
+ contentSummary: content,
676
+ includeSerializedStructuredContent: false
677
+ });
666
678
  }
667
679
 
668
680
  // src/mcp/sampling.ts
@@ -701,36 +713,68 @@ async function handleMemoryRecap(params, db2) {
701
713
  const rows = db2.memories.getRecentMemories(validated.repo, validated.limit, validated.offset, false, [
702
714
  "task_archive"
703
715
  ]);
704
- const COLUMNS = ["id", "title", "type", "importance"];
705
- const topRows = rows.map((row) => [row.id, row.title ?? "Untitled", row.type, row.importance]);
716
+ const COLUMNS = ["id", "code", "title", "type", "importance"];
717
+ const topRows = rows.map((row) => [row.id, row.code || "-", row.title ?? "Untitled", row.type, row.importance]);
706
718
  const byType = {};
707
719
  for (const [type, count] of Object.entries(stats.byType)) {
708
720
  if (type !== "task_archive") {
709
721
  byType[type] = count;
710
722
  }
711
723
  }
712
- const structuredContent = {
724
+ let contentSummary;
725
+ if (!validated.structured) {
726
+ if (total > 0) {
727
+ const parts = [];
728
+ for (const [memType, count] of Object.entries(byType)) {
729
+ if (count > 0) {
730
+ parts.push(`${capitalize2(memType)}: ${count}`);
731
+ }
732
+ }
733
+ const memoriesByType = {};
734
+ for (const row of rows) {
735
+ const typeLabel = row.type || "unknown";
736
+ if (!memoriesByType[typeLabel]) {
737
+ memoriesByType[typeLabel] = [];
738
+ }
739
+ memoriesByType[typeLabel].push(row);
740
+ }
741
+ for (const [memType, items] of Object.entries(memoriesByType)) {
742
+ parts.push("");
743
+ parts.push(`${capitalize2(memType)}:`);
744
+ parts.push("- code|importance|title");
745
+ for (const row of items) {
746
+ const code = row.code || "-";
747
+ parts.push(`- ${code}|${row.importance}|${row.title}`);
748
+ }
749
+ }
750
+ parts.push("");
751
+ parts.push("Use memory-detail with memory_id (or code) for full content.");
752
+ contentSummary = parts.join("\n").trim();
753
+ } else {
754
+ contentSummary = `No memories found for repo "${validated.repo}".`;
755
+ }
756
+ }
757
+ const structuredData = {
713
758
  schema: "memory-recap",
714
759
  repo: validated.repo,
715
760
  count: rows.length,
716
761
  total,
717
762
  offset: validated.offset,
718
763
  limit: validated.limit,
719
- stats: {
720
- by_type: byType
721
- },
764
+ stats: { byType },
722
765
  top: {
723
766
  columns: [...COLUMNS],
724
767
  rows: topRows
725
768
  }
726
769
  };
727
- const memoryList = rows.map((row) => `"${row.title}" (ID: ${row.id})`).join(", ");
728
- const contentSummary = total > 0 ? `Repo "${validated.repo}" has ${total} active memories. Showing ${rows.length} at offset ${validated.offset}: ${memoryList}. Use memory-detail to read full content.` : `No memories found for repo "${validated.repo}".`;
729
- return createMcpResponse(structuredContent, contentSummary, {
770
+ return createMcpResponse(structuredData, contentSummary || "", {
730
771
  contentSummary,
731
772
  includeSerializedStructuredContent: false
732
773
  });
733
774
  }
775
+ function capitalize2(str) {
776
+ return str.charAt(0).toUpperCase() + str.slice(1);
777
+ }
734
778
 
735
779
  // src/mcp/tools/task.manage.ts
736
780
  import { randomUUID as randomUUID2 } from "crypto";
@@ -750,20 +794,36 @@ function describeTaskListFilter(status) {
750
794
  if (labels.length === 2) return `${labels[0]} and ${labels[1]}`;
751
795
  return `${labels.slice(0, -1).join(", ")}, and ${labels[labels.length - 1]}`;
752
796
  }
753
- function buildTaskListSummary(repo, count, status, phase, search, stats) {
797
+ function buildTaskListSummary(repo, count, status, phase, search, tasksByStatus) {
754
798
  const filterLabel = describeTaskListFilter(status);
755
799
  const taskLabel = count === 1 ? "task" : "tasks";
756
- const parts = [`Found ${count} ${filterLabel} ${taskLabel} in repo "${repo}".`];
757
- if (phase) {
758
- parts.push(`Phase filter: ${phase}.`);
800
+ const parts = [];
801
+ if (tasksByStatus && Object.keys(tasksByStatus).length > 0) {
802
+ parts.push("Current Available Tasks:");
803
+ for (const [taskStatus, items] of Object.entries(tasksByStatus)) {
804
+ if (items.length > 0) {
805
+ parts.push("");
806
+ parts.push(`${capitalize3(taskStatus)}:`);
807
+ parts.push("- code|status|priority|title");
808
+ for (const t of items) {
809
+ parts.push(`- ${t.task_code}|${t.status}|${t.priority}|${t.title}`);
810
+ }
811
+ }
812
+ }
813
+ } else {
814
+ parts.push(`Found ${count} ${filterLabel} ${taskLabel} in repo "${repo}".`);
759
815
  }
760
- if (search) {
761
- parts.push(`Search filter: "${search}".`);
816
+ if (phase || search) {
817
+ parts.push("");
818
+ if (phase) parts.push(`Phase filter: ${phase}.`);
819
+ if (search) parts.push(`Search filter: "${search}".`);
762
820
  }
763
- parts.push(`Pending: ${stats?.todo ?? 0}.`);
764
- parts.push(`In progress: ${stats?.inProgress ?? 0}.`);
765
- parts.push(`Use task-detail with Task ID or task_code to read full details.`);
766
- return parts.join(" ");
821
+ parts.push("");
822
+ parts.push("See task-detail with task_code for details.");
823
+ return parts.join("\n").trim();
824
+ }
825
+ function capitalize3(str) {
826
+ return str.charAt(0).toUpperCase() + str.slice(1);
767
827
  }
768
828
  function deriveTaskStatusTimestamps(status, now, existingTask) {
769
829
  const timestamps = {
@@ -817,7 +877,6 @@ Comments & History:
817
877
  title: truncatedTitle,
818
878
  content,
819
879
  importance: Math.min(5, task.priority + 1),
820
- // Slightly higher importance for archived tasks
821
880
  agent: task.agent || "system",
822
881
  role: task.role || "unknown",
823
882
  model: "system",
@@ -833,14 +892,16 @@ Comments & History:
833
892
  }
834
893
  }
835
894
  async function handleTaskList(args, storage) {
895
+ const validated = TaskListSchema.parse(args);
836
896
  const {
837
897
  repo,
838
898
  status = "backlog,pending,in_progress,blocked",
839
899
  phase,
840
900
  query,
841
901
  limit,
842
- offset
843
- } = TaskListSchema.parse(args);
902
+ offset,
903
+ structured: isStructuredRequest
904
+ } = validated;
844
905
  let statuses = [];
845
906
  if (status !== "all") {
846
907
  statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
@@ -856,7 +917,7 @@ async function handleTaskList(args, storage) {
856
917
  t.priority,
857
918
  t.comments_count || 0
858
919
  ]);
859
- const structured = {
920
+ const structuredData = {
860
921
  schema: "task-list",
861
922
  tasks: {
862
923
  columns: [...COLUMNS],
@@ -865,11 +926,20 @@ async function handleTaskList(args, storage) {
865
926
  count: rows.length,
866
927
  offset
867
928
  };
868
- const _taskList = filteredTasks.map((t) => `[${t.task_code}] ${t.title} (ID: ${t.id})`).join(", ");
869
- const taskStats = storage.tasks.getTaskStats(repo);
870
- const summary = buildTaskListSummary(repo, rows.length, status, phase, query, taskStats);
871
- return createMcpResponse(structured, summary, {
872
- contentSummary: summary,
929
+ let contentSummary;
930
+ if (!isStructuredRequest) {
931
+ const tasksByStatus = {};
932
+ for (const t of filteredTasks) {
933
+ const statusLabel = t.status === "in_progress" ? "In Progress" : capitalize3(t.status);
934
+ if (!tasksByStatus[statusLabel]) {
935
+ tasksByStatus[statusLabel] = [];
936
+ }
937
+ tasksByStatus[statusLabel].push(t);
938
+ }
939
+ contentSummary = buildTaskListSummary(repo, rows.length, status, phase, query, tasksByStatus);
940
+ }
941
+ return createMcpResponse(structuredData, contentSummary || "", {
942
+ contentSummary,
873
943
  includeSerializedStructuredContent: false
874
944
  });
875
945
  }
@@ -890,7 +960,9 @@ async function handleTaskCreate(args, storage) {
890
960
  codesInRequest.add(taskData.task_code);
891
961
  const normalizedStatus = taskData.status || "backlog";
892
962
  if (normalizedStatus !== "backlog" && normalizedStatus !== "pending") {
893
- throw new Error(`New tasks must be 'backlog' or 'pending'. Task '${taskData.task_code}' has status '${normalizedStatus}'.`);
963
+ throw new Error(
964
+ `New tasks must be 'backlog' or 'pending'. Task '${taskData.task_code}' has status '${normalizedStatus}'.`
965
+ );
894
966
  }
895
967
  if (normalizedStatus === "pending") {
896
968
  const stats = storage.tasks.getTaskStats(repo);
@@ -899,7 +971,9 @@ async function handleTaskCreate(args, storage) {
899
971
  return t?.status === "pending";
900
972
  }).length;
901
973
  if (stats.todo + pendingInRequest >= 10) {
902
- throw new Error(`Cannot create task '${taskData.task_code}' as 'pending'. Maximum of 10 pending tasks reached.`);
974
+ throw new Error(
975
+ `Cannot create task '${taskData.task_code}' as 'pending'. Maximum of 10 pending tasks reached.`
976
+ );
903
977
  }
904
978
  }
905
979
  const statusTimestamps2 = deriveTaskStatusTimestamps(normalizedStatus, now2);
@@ -934,7 +1008,22 @@ async function handleTaskCreate(args, storage) {
934
1008
  `Created ${bulkTasks.length} tasks in repo "${repo}".`
935
1009
  );
936
1010
  }
937
- const { task_code, phase, title, description, status, priority, agent, role, doc_path, tags, metadata, parent_id, depends_on, est_tokens } = singleTask;
1011
+ const {
1012
+ task_code,
1013
+ phase,
1014
+ title,
1015
+ description,
1016
+ status,
1017
+ priority,
1018
+ agent,
1019
+ role,
1020
+ doc_path,
1021
+ tags,
1022
+ metadata,
1023
+ parent_id,
1024
+ depends_on,
1025
+ est_tokens
1026
+ } = singleTask;
938
1027
  if (!task_code || !phase || !title || !description) {
939
1028
  throw new Error("Missing required fields for single task creation (task_code, phase, title, description)");
940
1029
  }
@@ -1112,7 +1201,9 @@ async function handleTaskUpdate(args, storage, vectors2) {
1112
1201
  throw new Error("comment is required when changing task status");
1113
1202
  }
1114
1203
  if ((existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked") && updates.status === "completed") {
1115
- throw new Error(`Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`);
1204
+ throw new Error(
1205
+ `Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`
1206
+ );
1116
1207
  }
1117
1208
  }
1118
1209
  if (updates.status === "completed" && isStatusChanging && updates.est_tokens === void 0) {
@@ -1124,7 +1215,8 @@ async function handleTaskUpdate(args, storage, vectors2) {
1124
1215
  const finalUpdates = { ...updates };
1125
1216
  if (updates.status === "completed") finalUpdates.finished_at = now;
1126
1217
  else if (updates.status === "canceled") finalUpdates.canceled_at = now;
1127
- else if (updates.status === "in_progress" && existingTask.status !== "in_progress") finalUpdates.in_progress_at = now;
1218
+ else if (updates.status === "in_progress" && existingTask.status !== "in_progress")
1219
+ finalUpdates.in_progress_at = now;
1128
1220
  storage.tasks.updateTask(targetId, finalUpdates);
1129
1221
  if (comment !== void 0 || isStatusChanging) {
1130
1222
  storage.tasks.insertTaskComment({
@@ -1499,29 +1591,41 @@ async function handleMemoryAcknowledge(params, db2) {
1499
1591
 
1500
1592
  // src/mcp/tools/memory.detail.ts
1501
1593
  async function handleMemoryDetail(args, storage) {
1502
- const { id } = MemoryDetailSchema.parse(args);
1503
- const memory = storage.memories.getById(id);
1594
+ const { id, code } = MemoryDetailSchema.parse(args);
1595
+ let memory;
1596
+ if (id) {
1597
+ memory = storage.memories.getById(id);
1598
+ } else if (code) {
1599
+ memory = storage.memories.getByCode(code);
1600
+ }
1504
1601
  if (!memory) {
1505
- throw new Error(`Memory not found: ${id}`);
1506
- }
1507
- storage.memories.incrementHitCount(id);
1508
- const summary = `Memory [${memory.type}] ${memory.title}: ${memory.content.substring(0, 100)}${memory.content.length > 100 ? "..." : ""}`;
1509
- return createMcpResponse(memory, summary, {
1510
- contentSummary: summary,
1511
- includeSerializedStructuredContent: true,
1512
- resourceLinks: [
1513
- {
1514
- uri: `memory://${id}`,
1515
- name: `Memory: ${memory.title}`,
1516
- mimeType: "application/json"
1517
- }
1518
- ]
1602
+ throw new Error(`Memory not found: ${id || code}`);
1603
+ }
1604
+ storage.memories.incrementHitCount(memory.id);
1605
+ const lines = [
1606
+ `Code: ${memory.code || "-"}`,
1607
+ `ID: ${memory.id}`,
1608
+ `Title: ${memory.title}`,
1609
+ `Type: ${memory.type}`,
1610
+ `Importance: ${memory.importance}`,
1611
+ `Created: ${memory.created_at}`
1612
+ ];
1613
+ if (memory.scope?.repo) lines.push(`Repo: ${memory.scope.repo}`);
1614
+ if (memory.scope?.folder) lines.push(`Folder: ${memory.scope.folder}`);
1615
+ if (memory.content) {
1616
+ lines.push("", "--- Content ---", memory.content);
1617
+ }
1618
+ const content = lines.join("\n");
1619
+ return createMcpResponse(memory, content, {
1620
+ contentSummary: content,
1621
+ includeSerializedStructuredContent: false
1519
1622
  });
1520
1623
  }
1521
1624
 
1522
1625
  // src/mcp/tools/task.get.ts
1523
1626
  async function handleTaskGet(args, storage) {
1524
- const { repo, id, task_code } = TaskGetSchema.parse(args);
1627
+ const validated = TaskGetSchema.parse(args);
1628
+ const { repo, id, task_code, structured: isStructuredRequest } = validated;
1525
1629
  let task;
1526
1630
  if (id) {
1527
1631
  task = storage.tasks.getTaskById(id);
@@ -1533,17 +1637,40 @@ async function handleTaskGet(args, storage) {
1533
1637
  if (!task) {
1534
1638
  throw new Error(`Task not found: ${id || task_code} in repo ${repo}`);
1535
1639
  }
1536
- const summary = `Task [${task.task_code}] ${task.title} (${task.status})`;
1537
- return createMcpResponse(task, summary, {
1538
- contentSummary: summary,
1539
- includeSerializedStructuredContent: true,
1540
- resourceLinks: [
1541
- {
1542
- uri: `task://${task.id}`,
1543
- name: `Task: ${task.title}`,
1544
- mimeType: "application/json"
1640
+ const comments = storage.tasks.getTaskCommentsByTaskId(task.id);
1641
+ let contentSummary;
1642
+ if (!isStructuredRequest) {
1643
+ const lines = [
1644
+ `Task: ${task.title}`,
1645
+ `Code: ${task.task_code}`,
1646
+ `Status: ${task.status}`,
1647
+ `Priority: ${task.priority}`,
1648
+ `ID: ${task.id}`
1649
+ ];
1650
+ if (task.phase) lines.push(`Phase: ${task.phase}`);
1651
+ if (task.description) lines.push(`Description: ${task.description}`);
1652
+ if (task.metadata) lines.push(`Metadata: ${JSON.stringify(task.metadata)}`);
1653
+ lines.push(`Created: ${task.created_at}`);
1654
+ if (task.updated_at) lines.push(`Updated: ${task.updated_at}`);
1655
+ if (task.in_progress_at) lines.push(`Started: ${task.in_progress_at}`);
1656
+ if (task.finished_at) lines.push(`Finished: ${task.finished_at}`);
1657
+ if (comments.length > 0) {
1658
+ lines.push("", "--- History ---");
1659
+ for (const c of comments) {
1660
+ const statusChange = c.previous_status || c.next_status ? ` [${c.previous_status || "?"} \u2192 ${c.next_status || "?"}]` : "";
1661
+ const agentInfo = c.agent ? ` (${c.agent})` : "";
1662
+ lines.push(`- ${c.created_at}${statusChange}${agentInfo}: ${c.comment}`);
1545
1663
  }
1546
- ]
1664
+ }
1665
+ contentSummary = lines.join("\n");
1666
+ }
1667
+ const structuredData = {
1668
+ ...task,
1669
+ comments
1670
+ };
1671
+ return createMcpResponse(structuredData, contentSummary || "", {
1672
+ contentSummary,
1673
+ includeSerializedStructuredContent: false
1547
1674
  });
1548
1675
  }
1549
1676
 
@@ -1843,9 +1970,10 @@ var RealVectorStore = class {
1843
1970
 
1844
1971
  // src/mcp/server.ts
1845
1972
  import fs2 from "fs";
1973
+ process.env.MCP_SERVER = "true";
1846
1974
  if (process.argv.includes("doctor")) {
1847
1975
  process.stderr.write("\n\u{1F3E5} MCP Local Memory - System Diagnosis\n\n");
1848
- const db2 = new SQLiteStore();
1976
+ const db2 = await SQLiteStore.create();
1849
1977
  const dbPath = db2.getDbPath();
1850
1978
  process.stderr.write(`\u{1F4C2} Database Path: ${dbPath}
1851
1979
  `);
@@ -1871,7 +1999,7 @@ if (process.argv.includes("doctor")) {
1871
1999
  process.stderr.write("\n\u2728 Diagnosis complete.\n\n");
1872
2000
  process.exit(0);
1873
2001
  }
1874
- var db = new SQLiteStore();
2002
+ var db = await SQLiteStore.create();
1875
2003
  var vectors = new RealVectorStore(db);
1876
2004
  vectors.initialize().catch((err) => {
1877
2005
  logger.warn("[Server] Initial vector model loading failed. Will retry on first use.", { error: String(err) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vheins/local-memory-mcp",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "MCP Local Memory Service for coding copilot agents",
5
5
  "mcpName": "io.github.vheins/local-memory-mcp",
6
6
  "type": "module",
@@ -44,20 +44,19 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@xenova/transformers": "^2.17.2",
47
- "better-sqlite3": "^12.6.2",
48
47
  "chart.js": "^4.5.1",
49
48
  "dompurify": "^3.3.3",
50
49
  "express": "^5.2.1",
51
50
  "gray-matter": "^4.0.3",
52
51
  "marked": "^18.0.0",
53
52
  "qs": "^6.15.0",
53
+ "sql.js": "^1.14.1",
54
54
  "zod": "^4.3.5"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@eslint/js": "^10.0.1",
58
58
  "@fast-check/vitest": "^0.3.0",
59
59
  "@sveltejs/vite-plugin-svelte": "^7.0.0",
60
- "@types/better-sqlite3": "^7.6.13",
61
60
  "@types/dompurify": "^3.0.5",
62
61
  "@types/express": "^5.0.6",
63
62
  "@types/marked": "^5.0.2",