@vheins/local-memory-mcp 0.16.3 → 0.18.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.
@@ -48,6 +48,7 @@ import {
48
48
  handleHandoffList,
49
49
  handleHandoffUpdate,
50
50
  handleTaskClaim,
51
+ inferOwnerFromSession,
51
52
  inferRepoFromSession,
52
53
  isPathWithinRoots,
53
54
  listPrompts,
@@ -55,12 +56,13 @@ import {
55
56
  listResources,
56
57
  logger,
57
58
  normalizeRepo,
59
+ parseRepoInput,
58
60
  readResource,
59
61
  setLogLevel,
60
62
  toContextSlug,
61
63
  updateSessionFromInitialize,
62
64
  updateSessionRoots
63
- } from "../chunk-MSP4MIT7.js";
65
+ } from "../chunk-YVPDPZXG.js";
64
66
 
65
67
  // src/mcp/server.ts
66
68
  import readline from "readline";
@@ -133,7 +135,7 @@ function getSuggestedRepos(db2, session2) {
133
135
  }
134
136
  function getSuggestedTags(db2) {
135
137
  const values = /* @__PURE__ */ new Set();
136
- const memories = db2.memories.getRecentMemories("", 1e3);
138
+ const memories = db2.memories.getRecentMemories("", "", 1e3);
137
139
  for (const memory of memories) {
138
140
  for (const tag of memory.tags || []) {
139
141
  if (typeof tag === "string" && tag.trim()) {
@@ -146,7 +148,7 @@ function getSuggestedTags(db2) {
146
148
  function getSuggestedTasks(db2, session2, contextArguments) {
147
149
  const repo = typeof contextArguments.repo === "string" && contextArguments.repo.trim() ? contextArguments.repo.trim() : inferRepoFromSession(session2);
148
150
  if (!repo) return [];
149
- return db2.tasks.getTasksByRepo(repo, void 0, 100).map((task) => ({
151
+ return db2.tasks.getTasksByRepo("", repo, void 0, 100).map((task) => ({
150
152
  id: task.id,
151
153
  task_code: task.task_code,
152
154
  title: task.title
@@ -196,7 +198,7 @@ var ENTITY_CONFIG = {
196
198
  memory: { prefix: "MEM", table: "memories", column: "code" },
197
199
  standard: { prefix: "STD", table: "coding_standards", column: "code" }
198
200
  };
199
- function generateNextCode(repo, entityType, storage, batchCodes) {
201
+ function generateNextCode(owner, repo, entityType, storage, batchCodes) {
200
202
  const config = ENTITY_CONFIG[entityType];
201
203
  const pattern = `${config.prefix}-%`;
202
204
  const offset = config.prefix.length + 2;
@@ -204,9 +206,9 @@ function generateNextCode(repo, entityType, storage, batchCodes) {
204
206
  `
205
207
  SELECT MAX(CAST(SUBSTR(${config.column}, ?) AS INTEGER)) as max_seq
206
208
  FROM ${config.table}
207
- WHERE repo = ? AND ${config.column} LIKE ?
209
+ WHERE owner = ? AND repo = ? AND ${config.column} LIKE ?
208
210
  `
209
- ).get(offset, repo, pattern);
211
+ ).get(offset, owner, repo, pattern);
210
212
  let nextSeq = (row?.max_seq ?? 0) + 1;
211
213
  if (batchCodes) {
212
214
  for (const code of batchCodes) {
@@ -247,6 +249,7 @@ async function storeSingleMemory(params, db2, vectors2) {
247
249
  if (!resolvedSupersedes && params.type !== "task_archive") {
248
250
  const conflict = await db2.memoryVectors.checkConflicts(
249
251
  params.content,
252
+ params.scope.owner,
250
253
  params.scope.repo,
251
254
  params.type,
252
255
  vectors2,
@@ -277,7 +280,7 @@ async function storeSingleMemory(params, db2, vectors2) {
277
280
  }
278
281
  const entry = {
279
282
  id: randomUUID(),
280
- code: params.code || generateNextCode(params.scope.repo, "memory", db2),
283
+ code: params.code || generateNextCode(params.scope.owner ?? "", params.scope.repo, "memory", db2),
281
284
  type: params.type,
282
285
  title: params.title,
283
286
  content: params.content,
@@ -285,7 +288,7 @@ async function storeSingleMemory(params, db2, vectors2) {
285
288
  agent: params.agent,
286
289
  role: params.role,
287
290
  model: params.model,
288
- scope: params.scope,
291
+ scope: { ...params.scope, owner: params.scope.owner },
289
292
  created_at: now,
290
293
  updated_at: now,
291
294
  completed_at: null,
@@ -339,7 +342,14 @@ async function handleMemoryStore(params, db2, vectors2) {
339
342
  const expires_at = mem.ttlDays != null ? new Date(createdAtTime + mem.ttlDays * 864e5).toISOString() : null;
340
343
  const resolvedSupersedes = resolveMemorySupersedes(mem.supersedes, db2);
341
344
  if (!resolvedSupersedes && mem.type !== "task_archive") {
342
- const conflict = await db2.memoryVectors.checkConflicts(mem.content, mem.scope.repo, mem.type, vectors2, 0.55);
345
+ const conflict = await db2.memoryVectors.checkConflicts(
346
+ mem.content,
347
+ mem.scope.owner,
348
+ mem.scope.repo,
349
+ mem.type,
350
+ vectors2,
351
+ 0.55
352
+ );
343
353
  if (conflict) {
344
354
  return createMcpResponse(
345
355
  {
@@ -363,7 +373,7 @@ async function handleMemoryStore(params, db2, vectors2) {
363
373
  if (mem.scope.language && !tags.includes(mem.scope.language.toLowerCase())) {
364
374
  tags.push(mem.scope.language.toLowerCase());
365
375
  }
366
- const code = mem.code || generateNextCode(mem.scope.repo, "memory", db2, batchCodes);
376
+ const code = mem.code || generateNextCode(mem.scope.owner ?? "", mem.scope.repo, "memory", db2, batchCodes);
367
377
  batchCodes.add(code);
368
378
  entries.push({
369
379
  id: randomUUID(),
@@ -375,7 +385,7 @@ async function handleMemoryStore(params, db2, vectors2) {
375
385
  agent: mem.agent,
376
386
  role: mem.role,
377
387
  model: mem.model,
378
- scope: mem.scope,
388
+ scope: { ...mem.scope, owner: mem.scope.owner },
379
389
  created_at: now,
380
390
  updated_at: now,
381
391
  completed_at: null,
@@ -489,7 +499,7 @@ async function handleMemoryUpdate(params, db2, vectors2) {
489
499
  if (validated.content !== void 0) {
490
500
  await vectors2.upsert(resolvedId, validated.content);
491
501
  }
492
- db2.actions.logAction("update", existing.scope.repo, { memoryId: resolvedId, resultCount: 1 });
502
+ db2.actions.logAction("update", existing.scope.owner, existing.scope.repo, { memoryId: resolvedId, resultCount: 1 });
493
503
  logger.info("[Tool] memory.update", { repo: existing.scope.repo, id: resolvedId, fields: Object.keys(updates) });
494
504
  return createMcpResponse(
495
505
  {
@@ -543,6 +553,7 @@ async function handleMemorySearch(params, db2, vectors2) {
543
553
  const fetchLimit = (validated.offset + validated.limit) * 3;
544
554
  const similarityResults = db2.memoryVectors.searchBySimilarity(
545
555
  searchQuery,
556
+ validated.owner,
546
557
  validated.repo,
547
558
  fetchLimit,
548
559
  validated.include_archived,
@@ -672,7 +683,7 @@ async function handleMemorySummarize(params, db2) {
672
683
  const summary = validated.signals.join("\n- ");
673
684
  const fullSummary = `Project summary:
674
685
  - ${summary}`;
675
- db2.summaries.upsertSummary(validated.repo, fullSummary);
686
+ db2.summaries.upsertSummary(validated.owner, validated.repo, fullSummary);
676
687
  const content = `Updated summary for repo "${validated.repo}" with ${validated.signals.length} signals:
677
688
 
678
689
  ${fullSummary}`;
@@ -713,11 +724,16 @@ function extractAcceptedElicitationContent(result) {
713
724
  async function handleMemoryRecap(params, db2) {
714
725
  const validated = MemoryRecapSchema.parse(params);
715
726
  logger.info("[Tool] memory.recap", { repo: validated.repo, limit: validated.limit, offset: validated.offset });
716
- const stats = db2.memories.getStats(validated.repo);
717
- const total = db2.memories.getTotalCount(validated.repo, false, ["task_archive"]);
718
- const rows = db2.memories.getRecentMemories(validated.repo, validated.limit, validated.offset, false, [
719
- "task_archive"
720
- ]);
727
+ const stats = db2.memories.getStats(validated.owner, validated.repo);
728
+ const total = db2.memories.getTotalCount(validated.owner, validated.repo, false, ["task_archive"]);
729
+ const rows = db2.memories.getRecentMemories(
730
+ validated.owner,
731
+ validated.repo,
732
+ validated.limit,
733
+ validated.offset,
734
+ false,
735
+ ["task_archive"]
736
+ );
721
737
  const COLUMNS = ["id", "code", "title", "type", "importance"];
722
738
  const topRows = rows.map((row) => [row.id, row.code || "-", row.title ?? "Untitled", row.type, row.importance]);
723
739
  const byType = {};
@@ -782,19 +798,19 @@ function capitalize2(str) {
782
798
  // src/mcp/tools/task.manage.ts
783
799
  import { randomUUID as randomUUID2 } from "crypto";
784
800
  var UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
785
- function resolveParentId(value, repo, storage, localCodeMap) {
801
+ function resolveParentId(value, owner, repo, storage, localCodeMap) {
786
802
  if (!value) return null;
787
803
  if (UUID_REGEX3.test(value)) return value;
788
804
  if (localCodeMap?.has(value)) return localCodeMap.get(value);
789
- const parent = storage.tasks.getTaskByCode(repo, value);
805
+ const parent = storage.tasks.getTaskByCode(owner, repo, value);
790
806
  if (!parent) throw new Error(`parent_id: task with code '${value}' not found in repo '${repo}'`);
791
807
  return parent.id;
792
808
  }
793
- function resolveDependsOn(value, repo, storage, localCodeMap) {
809
+ function resolveDependsOn(value, owner, repo, storage, localCodeMap) {
794
810
  if (!value) return null;
795
811
  if (UUID_REGEX3.test(value)) return value;
796
812
  if (localCodeMap?.has(value)) return localCodeMap.get(value);
797
- const task = storage.tasks.getTaskByCode(repo, value);
813
+ const task = storage.tasks.getTaskByCode(owner, repo, value);
798
814
  if (!task) throw new Error(`depends_on: task with code '${value}' not found in repo '${repo}'`);
799
815
  return task.id;
800
816
  }
@@ -911,7 +927,7 @@ Comments & History:
911
927
  agent: task.agent || "system",
912
928
  role: task.role || "unknown",
913
929
  model: "system",
914
- scope: { repo },
930
+ scope: { repo, owner: task.owner || "" },
915
931
  tags: ["task-archive", ...task.tags],
916
932
  metadata
917
933
  },
@@ -925,6 +941,7 @@ Comments & History:
925
941
  async function handleTaskList(args, storage) {
926
942
  const validated = TaskListSchema.parse(args);
927
943
  const {
944
+ owner,
928
945
  repo,
929
946
  status = "backlog,pending,in_progress,blocked",
930
947
  phase,
@@ -937,7 +954,7 @@ async function handleTaskList(args, storage) {
937
954
  if (status !== "all") {
938
955
  statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
939
956
  }
940
- const tasks = storage.tasks.getTasksByMultipleStatuses(repo, statuses, limit, offset, query);
957
+ const tasks = storage.tasks.getTasksByMultipleStatuses(owner, repo, statuses, limit, offset, query);
941
958
  const filteredTasks = phase ? tasks.filter((t) => t.phase.toLowerCase() === phase.toLowerCase()) : tasks;
942
959
  const COLUMNS = ["id", "task_code", "title", "status", "priority", "updated_at", "comments_count"];
943
960
  const rows = filteredTasks.map((t) => [
@@ -977,7 +994,7 @@ async function handleTaskList(args, storage) {
977
994
  }
978
995
  async function handleTaskCreate(args, storage) {
979
996
  const parsed = TaskCreateSchema.parse(args);
980
- const { repo, tasks: bulkTasks, ...singleTask } = parsed;
997
+ const { owner, repo, tasks: bulkTasks, ...singleTask } = parsed;
981
998
  if (bulkTasks) {
982
999
  const createdTasks = [];
983
1000
  const tasksToInsert = [];
@@ -986,13 +1003,13 @@ async function handleTaskCreate(args, storage) {
986
1003
  const batchCodes = /* @__PURE__ */ new Set();
987
1004
  for (const taskData of bulkTasks) {
988
1005
  if (!taskData.task_code) {
989
- taskData.task_code = generateNextCode(repo, "task", storage, batchCodes);
1006
+ taskData.task_code = generateNextCode(owner ?? "", repo, "task", storage, batchCodes);
990
1007
  }
991
1008
  batchCodes.add(taskData.task_code);
992
1009
  }
993
1010
  const allCodes = bulkTasks.map((t) => t.task_code);
994
- const existingCodes = storage.tasks.getExistingTaskCodes(repo, allCodes);
995
- const initialStats = storage.taskStats.getTaskStats(repo);
1011
+ const existingCodes = storage.tasks.getExistingTaskCodes(owner, repo, allCodes);
1012
+ const initialStats = storage.taskStats.getTaskStats(owner, repo);
996
1013
  let pendingInRequestCount = 0;
997
1014
  const localCodeMap = /* @__PURE__ */ new Map();
998
1015
  for (const taskData of bulkTasks) {
@@ -1027,6 +1044,7 @@ async function handleTaskCreate(args, storage) {
1027
1044
  const taskId2 = localCodeMap.get(code);
1028
1045
  const task2 = {
1029
1046
  id: taskId2,
1047
+ owner,
1030
1048
  repo,
1031
1049
  task_code: code,
1032
1050
  phase: taskData.phase,
@@ -1048,8 +1066,8 @@ async function handleTaskCreate(args, storage) {
1048
1066
  commit_id: null,
1049
1067
  changed_files: [],
1050
1068
  metadata: taskData.metadata || {},
1051
- parent_id: resolveParentId(taskData.parent_id, repo, storage, localCodeMap),
1052
- depends_on: resolveDependsOn(taskData.depends_on, repo, storage, localCodeMap)
1069
+ parent_id: resolveParentId(taskData.parent_id, owner, repo, storage, localCodeMap),
1070
+ depends_on: resolveDependsOn(taskData.depends_on, owner, repo, storage, localCodeMap)
1053
1071
  };
1054
1072
  tasksToInsert.push(task2);
1055
1073
  createdTasks.push(code);
@@ -1083,15 +1101,15 @@ async function handleTaskCreate(args, storage) {
1083
1101
  if (!phase || !title || !description) {
1084
1102
  throw new Error("Missing required fields for single task creation (phase, title, description)");
1085
1103
  }
1086
- const resolvedCode = task_code || generateNextCode(repo, "task", storage);
1087
- if (storage.tasks.isTaskCodeDuplicate(repo, resolvedCode)) {
1104
+ const resolvedCode = task_code || generateNextCode(owner ?? "", repo, "task", storage);
1105
+ if (storage.tasks.isTaskCodeDuplicate(owner, repo, resolvedCode)) {
1088
1106
  throw new Error(`Duplicate task_code: '${resolvedCode}' already exists in repository '${repo}'`);
1089
1107
  }
1090
1108
  if (status !== "backlog" && status !== "pending" && status !== void 0) {
1091
1109
  throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
1092
1110
  }
1093
1111
  if (status === "pending") {
1094
- const stats = storage.taskStats.getTaskStats(repo);
1112
+ const stats = storage.taskStats.getTaskStats(owner, repo);
1095
1113
  if (stats.todo >= 10) {
1096
1114
  throw new Error(
1097
1115
  `Cannot create task as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`
@@ -1108,6 +1126,7 @@ async function handleTaskCreate(args, storage) {
1108
1126
  }
1109
1127
  const task = {
1110
1128
  id: taskId,
1129
+ owner,
1111
1130
  repo,
1112
1131
  task_code: resolvedCode,
1113
1132
  phase,
@@ -1129,8 +1148,8 @@ async function handleTaskCreate(args, storage) {
1129
1148
  commit_id: null,
1130
1149
  changed_files: [],
1131
1150
  metadata: metadata || {},
1132
- parent_id: resolveParentId(parent_id, repo, storage),
1133
- depends_on: resolveDependsOn(depends_on, repo, storage)
1151
+ parent_id: resolveParentId(parent_id, owner, repo, storage),
1152
+ depends_on: resolveDependsOn(depends_on, owner, repo, storage)
1134
1153
  };
1135
1154
  storage.tasks.insertTask(task);
1136
1155
  return createMcpResponse(
@@ -1245,10 +1264,10 @@ function addRequiredStringField(properties, required, task, field, schema) {
1245
1264
  }
1246
1265
  async function handleTaskUpdate(args, storage, vectors2) {
1247
1266
  const updateData = TaskUpdateSchema.parse(args);
1248
- const { repo, id, ids, comment, force, ...updates } = updateData;
1267
+ const { owner, repo, id, ids, comment, force, ...updates } = updateData;
1249
1268
  let resolvedId = id;
1250
1269
  if (!resolvedId && !ids && updates.task_code) {
1251
- const found = storage.tasks.getTaskByCode(repo, updates.task_code);
1270
+ const found = storage.tasks.getTaskByCode(owner, repo, updates.task_code);
1252
1271
  if (!found) throw new Error(`Task not found: ${updates.task_code}`);
1253
1272
  resolvedId = found.id;
1254
1273
  }
@@ -1293,10 +1312,10 @@ async function handleTaskUpdate(args, storage, vectors2) {
1293
1312
  }
1294
1313
  const finalUpdates = { ...updates };
1295
1314
  if (updates.parent_id !== void 0) {
1296
- finalUpdates.parent_id = resolveParentId(updates.parent_id, repo, storage);
1315
+ finalUpdates.parent_id = resolveParentId(updates.parent_id, owner, repo, storage);
1297
1316
  }
1298
1317
  if (updates.depends_on !== void 0) {
1299
- finalUpdates.depends_on = resolveDependsOn(updates.depends_on, repo, storage);
1318
+ finalUpdates.depends_on = resolveDependsOn(updates.depends_on, owner, repo, storage);
1300
1319
  }
1301
1320
  if (updates.phase !== void 0 || updates.tags !== void 0) {
1302
1321
  let currentTags = updates.tags || existingTask.tags || [];
@@ -1322,6 +1341,7 @@ async function handleTaskUpdate(args, storage, vectors2) {
1322
1341
  storage.taskComments.insertTaskComment({
1323
1342
  id: randomUUID2(),
1324
1343
  task_id: targetId,
1344
+ owner,
1325
1345
  repo,
1326
1346
  comment: comment || `Status updated to ${updates.status}`,
1327
1347
  agent: updates.agent || existingTask.agent || "unknown",
@@ -1382,12 +1402,12 @@ async function handleTaskUpdate(args, storage, vectors2) {
1382
1402
  }
1383
1403
  async function handleTaskDelete(args, storage) {
1384
1404
  const validated = TaskDeleteSchema.parse(args);
1385
- const { repo, id, ids, task_code } = validated;
1405
+ const { owner, repo, id, ids, task_code } = validated;
1386
1406
  const resolvedIds = [];
1387
1407
  if (ids) resolvedIds.push(...ids);
1388
1408
  if (id) resolvedIds.push(id);
1389
1409
  if (task_code) {
1390
- const task = storage.tasks.getTaskByCode(repo, task_code);
1410
+ const task = storage.tasks.getTaskByCode(owner, repo, task_code);
1391
1411
  if (!task) throw new Error(`Task not found: ${task_code}`);
1392
1412
  resolvedIds.push(task.id);
1393
1413
  }
@@ -1426,10 +1446,14 @@ async function handleMemorySynthesize(params, db2, vectors2, options = {}) {
1426
1446
  if (!repo) {
1427
1447
  throw new Error("repo is required when repo cannot be inferred from active MCP roots");
1428
1448
  }
1429
- const recap = await handleMemoryRecap({ repo, limit: 8, offset: 0 }, db2);
1449
+ const repoOwner = validated.owner;
1450
+ const recap = await handleMemoryRecap({ owner: repoOwner, repo, limit: 8, offset: 0 }, db2);
1430
1451
  const recapText = getPrimaryTextContent(recap);
1431
- const summary = validated.include_summary ? db2.summaries.getSummary(repo)?.summary : "";
1432
- const taskSnapshot = validated.include_tasks ? await handleTaskList({ repo, status: "backlog,pending,in_progress,blocked", limit: 15, offset: 0 }, db2) : null;
1452
+ const summary = validated.include_summary ? db2.summaries.getSummary(repoOwner, repo)?.summary : "";
1453
+ const taskSnapshot = validated.include_tasks ? await handleTaskList(
1454
+ { owner: repoOwner, repo, status: "backlog,pending,in_progress,blocked", limit: 15, offset: 0 },
1455
+ db2
1456
+ ) : null;
1433
1457
  const taskText = taskSnapshot ? getPrimaryTextContent(taskSnapshot) : "";
1434
1458
  const systemPrompt = [
1435
1459
  "You are a repository memory synthesizer.",
@@ -1493,7 +1517,7 @@ ${contextBlock || "No additional context provided."}`
1493
1517
  content: [
1494
1518
  {
1495
1519
  type: "text",
1496
- text: await executeSamplingTool(toolUse.name, toolUse.input, db2, vectors2)
1520
+ text: await executeSamplingTool(toolUse.name, toolUse.input, db2, vectors2, repoOwner)
1497
1521
  }
1498
1522
  ]
1499
1523
  }))
@@ -1603,11 +1627,12 @@ function buildSamplingTools(session2, useTools) {
1603
1627
  }
1604
1628
  ];
1605
1629
  }
1606
- async function executeSamplingTool(toolName, rawInput, db2, vectors2) {
1630
+ async function executeSamplingTool(toolName, rawInput, db2, vectors2, owner) {
1607
1631
  switch (toolName) {
1608
1632
  case "memory_search": {
1609
1633
  const response = await handleMemorySearch(
1610
1634
  {
1635
+ owner,
1611
1636
  repo: rawInput.repo,
1612
1637
  query: rawInput.query,
1613
1638
  limit: rawInput.limit ?? 5
@@ -1620,6 +1645,7 @@ async function executeSamplingTool(toolName, rawInput, db2, vectors2) {
1620
1645
  case "memory_recap": {
1621
1646
  const response = await handleMemoryRecap(
1622
1647
  {
1648
+ owner,
1623
1649
  repo: rawInput.repo,
1624
1650
  limit: rawInput.limit ?? 8,
1625
1651
  offset: 0
@@ -1631,6 +1657,7 @@ async function executeSamplingTool(toolName, rawInput, db2, vectors2) {
1631
1657
  case "task_list": {
1632
1658
  const response = await handleTaskList(
1633
1659
  {
1660
+ owner,
1634
1661
  repo: rawInput.repo,
1635
1662
  status: rawInput.status,
1636
1663
  search: rawInput.search,
@@ -1810,7 +1837,8 @@ async function storeSingleStandard(params, db2, vectors2) {
1810
1837
  const conflict = db2.standards.checkConflicts(
1811
1838
  params.content,
1812
1839
  incomingVersion,
1813
- params.repo,
1840
+ params.owner,
1841
+ params.repo ?? void 0,
1814
1842
  incomingLanguage,
1815
1843
  incomingStack,
1816
1844
  0.82
@@ -1837,7 +1865,7 @@ async function storeSingleStandard(params, db2, vectors2) {
1837
1865
  const now = (/* @__PURE__ */ new Date()).toISOString();
1838
1866
  const entry = {
1839
1867
  id: randomUUID3(),
1840
- code: generateNextCode(params.repo || "__global__", "standard", db2),
1868
+ code: generateNextCode(params.owner, params.repo || "__global__", "standard", db2),
1841
1869
  title: params.name,
1842
1870
  content: params.content,
1843
1871
  parent_id: resolveStandardParentId(params.parent_id, db2),
@@ -1846,6 +1874,7 @@ async function storeSingleStandard(params, db2, vectors2) {
1846
1874
  language: params.language || null,
1847
1875
  stack: params.stack || [],
1848
1876
  is_global: params.is_global !== false,
1877
+ owner: params.owner,
1849
1878
  repo: params.repo || null,
1850
1879
  tags: params.tags || [],
1851
1880
  metadata: params.metadata,
@@ -1889,7 +1918,8 @@ async function handleStandardStore(params, db2, vectors2) {
1889
1918
  const conflict = db2.standards.checkConflicts(
1890
1919
  std.content,
1891
1920
  incomingVersion,
1892
- void 0,
1921
+ validated.owner,
1922
+ validated.repo,
1893
1923
  incomingLanguage,
1894
1924
  incomingStack,
1895
1925
  0.82
@@ -1914,7 +1944,7 @@ async function handleStandardStore(params, db2, vectors2) {
1914
1944
  );
1915
1945
  }
1916
1946
  const now = (/* @__PURE__ */ new Date()).toISOString();
1917
- const code = generateNextCode(standardRepo, "standard", db2, batchCodes);
1947
+ const code = generateNextCode(validated.owner ?? "", standardRepo, "standard", db2, batchCodes);
1918
1948
  batchCodes.add(code);
1919
1949
  entries.push({
1920
1950
  id: randomUUID3(),
@@ -1927,6 +1957,7 @@ async function handleStandardStore(params, db2, vectors2) {
1927
1957
  language: std.language || null,
1928
1958
  stack: std.stack || [],
1929
1959
  is_global: std.is_global !== false,
1960
+ owner: validated.owner,
1930
1961
  repo: null,
1931
1962
  tags: std.tags || [],
1932
1963
  metadata: std.metadata,
@@ -1963,6 +1994,7 @@ async function handleStandardStore(params, db2, vectors2) {
1963
1994
  version: validated.version,
1964
1995
  language: validated.language,
1965
1996
  stack: validated.stack,
1997
+ owner: validated.owner,
1966
1998
  repo: validated.repo,
1967
1999
  is_global: validated.is_global,
1968
2000
  tags: validated.tags,
@@ -2357,12 +2389,12 @@ async function handleStandardDelete(params, db2, vectors2) {
2357
2389
  // src/mcp/tools/task.get.ts
2358
2390
  async function handleTaskGet(args, storage) {
2359
2391
  const validated = TaskGetSchema.parse(args);
2360
- const { repo, id, task_code, structured: isStructuredRequest } = validated;
2392
+ const { owner, repo, id, task_code, structured: isStructuredRequest } = validated;
2361
2393
  let task;
2362
2394
  if (id) {
2363
2395
  task = storage.tasks.getTaskById(id);
2364
2396
  } else if (task_code) {
2365
- task = storage.tasks.getTaskByCode(repo, task_code);
2397
+ task = storage.tasks.getTaskByCode(owner, repo, task_code);
2366
2398
  } else {
2367
2399
  throw new Error("Either id or task_code must be provided");
2368
2400
  }
@@ -2398,10 +2430,14 @@ async function handleTaskGet(args, storage) {
2398
2430
  if (task.comments_count !== void 0) lines.push(`Comments: ${task.comments_count}`);
2399
2431
  if (task.coordination) {
2400
2432
  if (task.coordination.active_claim_count > 0) {
2401
- lines.push(`Claim: ${task.coordination.active_claim_agent || "?"} (${task.coordination.active_claim_role || ""}) since ${task.coordination.active_claim_claimed_at || ""}`);
2433
+ lines.push(
2434
+ `Claim: ${task.coordination.active_claim_agent || "?"} (${task.coordination.active_claim_role || ""}) since ${task.coordination.active_claim_claimed_at || ""}`
2435
+ );
2402
2436
  }
2403
2437
  if (task.coordination.pending_handoff_count > 0) {
2404
- lines.push(`Handoff: ${task.coordination.pending_handoff_summary || ""} \u2192 ${task.coordination.pending_handoff_to_agent || "?"}`);
2438
+ lines.push(
2439
+ `Handoff: ${task.coordination.pending_handoff_summary || ""} \u2192 ${task.coordination.pending_handoff_to_agent || "?"}`
2440
+ );
2405
2441
  }
2406
2442
  }
2407
2443
  lines.push(`Created: ${task.created_at}`);
@@ -2446,17 +2482,27 @@ async function handleTaskGet(args, storage) {
2446
2482
  // src/mcp/tools/task.search.ts
2447
2483
  async function handleTaskSearch(args, storage) {
2448
2484
  const validated = TaskSearchSchema.parse(args);
2449
- const { repo, query, status, limit, offset, structured: isStructuredRequest = false, phase, priority } = validated;
2485
+ const {
2486
+ owner,
2487
+ repo,
2488
+ query,
2489
+ status,
2490
+ limit,
2491
+ offset,
2492
+ structured: isStructuredRequest = false,
2493
+ phase,
2494
+ priority
2495
+ } = validated;
2450
2496
  let tasks;
2451
2497
  if (status) {
2452
2498
  const statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
2453
2499
  if (statuses.length > 1) {
2454
- tasks = storage.tasks.getTasksByMultipleStatuses(repo, statuses, void 0, void 0, query);
2500
+ tasks = storage.tasks.getTasksByMultipleStatuses(owner, repo, statuses, void 0, void 0, query);
2455
2501
  } else {
2456
- tasks = storage.tasks.getTasksByRepo(repo, status, void 0, void 0, query);
2502
+ tasks = storage.tasks.getTasksByRepo(owner, repo, status, void 0, void 0, query);
2457
2503
  }
2458
2504
  } else {
2459
- tasks = storage.tasks.getTasksByRepo(repo, void 0, void 0, void 0, query);
2505
+ tasks = storage.tasks.getTasksByRepo(owner, repo, void 0, void 0, void 0, query);
2460
2506
  }
2461
2507
  if (phase) {
2462
2508
  const phaseLower = phase.toLowerCase();
@@ -2676,9 +2722,9 @@ function createRouter(db2, vectors2, options) {
2676
2722
  resultCount: Array.isArray(sc?.results) ? sc.results.length : sc?.count || 0
2677
2723
  };
2678
2724
  if (isWrite) {
2679
- db2.actions.logAction(actionType, repo, logOptions);
2725
+ db2.actions.logAction(actionType, "", repo, logOptions);
2680
2726
  } else {
2681
- await db2.withWrite(() => db2.actions.logAction(actionType, repo, logOptions));
2727
+ await db2.withWrite(() => db2.actions.logAction(actionType, "", repo, logOptions));
2682
2728
  }
2683
2729
  } catch (e) {
2684
2730
  logger.error("Failed to log action", { toolName, error: String(e) });
@@ -2760,6 +2806,29 @@ function normalizeToolArguments(args, session2) {
2760
2806
  if (scope && !scope.repo) {
2761
2807
  scope.repo = nextArgs.repo ?? inferRepoFromSession(session2);
2762
2808
  }
2809
+ if (!nextArgs.owner) {
2810
+ const repoVal2 = nextArgs.repo || "";
2811
+ const parsed = parseRepoInput(repoVal2, void 0);
2812
+ nextArgs.owner = parsed.owner || inferOwnerFromSession(session2) || "";
2813
+ }
2814
+ if (scope && !scope.owner) {
2815
+ const repoVal2 = scope.repo || nextArgs.repo || "";
2816
+ const parsed = parseRepoInput(repoVal2, void 0);
2817
+ scope.owner = parsed.owner || nextArgs.owner || inferOwnerFromSession(session2) || "";
2818
+ }
2819
+ const ownerVal = nextArgs.owner || inferOwnerFromSession(session2) || "";
2820
+ const repoVal = nextArgs.repo || inferRepoFromSession(session2) || "";
2821
+ const memories = nextArgs.memories;
2822
+ if (memories) {
2823
+ for (const mem of memories) {
2824
+ const memScope = mem.scope;
2825
+ if (memScope) {
2826
+ if (!memScope.owner)
2827
+ memScope.owner = ownerVal || parseRepoInput(memScope.repo || repoVal, void 0).owner || "";
2828
+ if (!memScope.repo) memScope.repo = repoVal;
2829
+ }
2830
+ }
2831
+ }
2763
2832
  if (typeof nextArgs.current_file_path === "string" && scope) {
2764
2833
  const containingRoot = path2.isAbsolute(nextArgs.current_file_path) ? findContainingRoot(nextArgs.current_file_path, session2) : null;
2765
2834
  if (containingRoot) {
@@ -65,4 +65,4 @@ If you refuse, return exactly:
65
65
  }
66
66
  ```
67
67
 
68
- Path: {{path}} Repo: {{current_repo}}
68
+ Path: {{path}} Owner: {{current_owner}} Repo: {{current_repo}}
@@ -70,4 +70,4 @@ If you refuse, return exactly:
70
70
  }
71
71
  ```
72
72
 
73
- Source: {{source_url}} Repo: {{current_repo}}
73
+ Source: {{source_url}} Owner: {{current_owner}} Repo: {{current_repo}}
@@ -5,6 +5,15 @@ description: Main instructions for the MCP server
5
5
 
6
6
  Local Memory MCP — persistent memory, task coordination, and coding standards for AI agents.
7
7
 
8
+ ## Data Scoping
9
+
10
+ All data (memories, tasks, handoffs, claims) is scoped by **owner/repo**:
11
+
12
+ - **owner** = organization/namespace (e.g., GitHub org, username)
13
+ - **repo** = project/repository name
14
+
15
+ Pass both `owner` and `repo` whenever a tool requires them. The `owner/repo` pair forms the unique data boundary.
16
+
8
17
  ## Session Start Mode
9
18
 
10
19
  Entry=orient → hydrate → ready Guard: S(N) req S(N-1)✅
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vheins/local-memory-mcp",
3
- "version": "0.16.3",
3
+ "version": "0.18.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",