@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.
- package/dist/{chunk-MSP4MIT7.js → chunk-YVPDPZXG.js} +515 -251
- package/dist/dashboard/server.js +61 -35
- package/dist/mcp/server.js +129 -60
- package/dist/prompts/csl-from-docs.md +1 -1
- package/dist/prompts/csl-scrapper.md +1 -1
- package/dist/prompts/server/instructions.md +9 -0
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -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-
|
|
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(
|
|
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(
|
|
719
|
-
|
|
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
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
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) {
|
|
@@ -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)✅
|