@vheins/local-memory-mcp 0.18.11 → 0.18.13
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-4RFYQ345.js → chunk-BK2QIQXS.js} +1479 -980
- package/dist/dashboard/public/assets/index-DWRMdSsg.js +150 -0
- package/dist/dashboard/public/index.html +1 -1
- package/dist/dashboard/server.js +25 -2
- package/dist/mcp/server.js +1031 -1020
- package/dist/prompts/learning-retrospective.md +1 -1
- package/dist/prompts/memory-agent-core.md +3 -1
- package/dist/prompts/memory-index-policy.md +2 -2
- package/dist/prompts/review-and-audit.md +1 -1
- package/dist/prompts/scrum-master.md +2 -2
- package/dist/prompts/task-memory-executor.md +1 -1
- package/dist/prompts/tool-usage-guidelines.md +4 -4
- package/package.json +1 -1
- package/dist/dashboard/public/assets/index-BA3gfiDm.js +0 -149
package/dist/mcp/server.js
CHANGED
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
toContextSlug,
|
|
64
64
|
updateSessionFromInitialize,
|
|
65
65
|
updateSessionRoots
|
|
66
|
-
} from "../chunk-
|
|
66
|
+
} from "../chunk-BK2QIQXS.js";
|
|
67
67
|
|
|
68
68
|
// src/mcp/server.ts
|
|
69
69
|
import readline from "readline";
|
|
@@ -797,25 +797,7 @@ function capitalize2(str) {
|
|
|
797
797
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
798
798
|
}
|
|
799
799
|
|
|
800
|
-
// src/mcp/tools/task.
|
|
801
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
802
|
-
var UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
803
|
-
function resolveParentId(value, owner, repo, storage, localCodeMap) {
|
|
804
|
-
if (!value) return null;
|
|
805
|
-
if (UUID_REGEX3.test(value)) return value;
|
|
806
|
-
if (localCodeMap?.has(value)) return localCodeMap.get(value);
|
|
807
|
-
const parent = storage.tasks.getTaskByCode(owner, repo, value);
|
|
808
|
-
if (!parent) throw new Error(`parent_id: task with code '${value}' not found in repo '${repo}'`);
|
|
809
|
-
return parent.id;
|
|
810
|
-
}
|
|
811
|
-
function resolveDependsOn(value, owner, repo, storage, localCodeMap) {
|
|
812
|
-
if (!value) return null;
|
|
813
|
-
if (UUID_REGEX3.test(value)) return value;
|
|
814
|
-
if (localCodeMap?.has(value)) return localCodeMap.get(value);
|
|
815
|
-
const task = storage.tasks.getTaskByCode(owner, repo, value);
|
|
816
|
-
if (!task) throw new Error(`depends_on: task with code '${value}' not found in repo '${repo}'`);
|
|
817
|
-
return task.id;
|
|
818
|
-
}
|
|
800
|
+
// src/mcp/tools/task.list.ts
|
|
819
801
|
function describeTaskListFilter(status) {
|
|
820
802
|
if (!status) return "active";
|
|
821
803
|
if (status === "all") return "all";
|
|
@@ -864,82 +846,6 @@ function buildTaskListSummary(repo, count, status, phase, search, tasksByStatus)
|
|
|
864
846
|
function capitalize3(str) {
|
|
865
847
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
866
848
|
}
|
|
867
|
-
function deriveTaskStatusTimestamps(status, now, existingTask) {
|
|
868
|
-
const timestamps = {
|
|
869
|
-
in_progress_at: null,
|
|
870
|
-
finished_at: null,
|
|
871
|
-
canceled_at: null
|
|
872
|
-
};
|
|
873
|
-
if (status === "in_progress" && existingTask?.status !== "in_progress") {
|
|
874
|
-
timestamps.in_progress_at = now;
|
|
875
|
-
}
|
|
876
|
-
if (status === "completed") {
|
|
877
|
-
timestamps.finished_at = now;
|
|
878
|
-
}
|
|
879
|
-
if (status === "canceled") {
|
|
880
|
-
timestamps.canceled_at = now;
|
|
881
|
-
}
|
|
882
|
-
return timestamps;
|
|
883
|
-
}
|
|
884
|
-
async function archiveTaskToMemory(taskId, repo, storage, vectors2) {
|
|
885
|
-
const task = storage.tasks.getTaskById(taskId);
|
|
886
|
-
if (!task) return;
|
|
887
|
-
const comments = storage.taskComments.getTaskCommentsByTaskId(taskId);
|
|
888
|
-
let content = `Task: [${task.task_code}] ${task.title}
|
|
889
|
-
`;
|
|
890
|
-
content += `Phase: ${task.phase}
|
|
891
|
-
`;
|
|
892
|
-
content += `Description: ${task.description || "No description"}
|
|
893
|
-
`;
|
|
894
|
-
content += `Commit: ${task.commit_id || "N/A"}
|
|
895
|
-
`;
|
|
896
|
-
if (task.changed_files && task.changed_files.length > 0) {
|
|
897
|
-
content += `Files changed:
|
|
898
|
-
`;
|
|
899
|
-
for (const f of task.changed_files) {
|
|
900
|
-
content += ` - ${f}
|
|
901
|
-
`;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
if (comments && comments.length > 0) {
|
|
905
|
-
content += `
|
|
906
|
-
Comments & History:
|
|
907
|
-
`;
|
|
908
|
-
const chronComments = [...comments].reverse();
|
|
909
|
-
for (const c of chronComments) {
|
|
910
|
-
const statusInfo = c.next_status ? ` (Status: ${c.previous_status} -> ${c.next_status})` : "";
|
|
911
|
-
content += `- [${c.created_at}] ${c.agent}${statusInfo}: ${c.comment}
|
|
912
|
-
`;
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
const metadata = {
|
|
916
|
-
task_id: taskId,
|
|
917
|
-
task_code: task.task_code,
|
|
918
|
-
original_metadata: task.metadata
|
|
919
|
-
};
|
|
920
|
-
const title = `Completed Task: ${task.title}`;
|
|
921
|
-
const truncatedTitle = title.length > 100 ? title.substring(0, 97) + "..." : title;
|
|
922
|
-
try {
|
|
923
|
-
await handleMemoryStore(
|
|
924
|
-
{
|
|
925
|
-
type: "task_archive",
|
|
926
|
-
title: truncatedTitle,
|
|
927
|
-
content,
|
|
928
|
-
importance: Math.min(5, task.priority + 1),
|
|
929
|
-
agent: task.agent || "system",
|
|
930
|
-
role: task.role || "unknown",
|
|
931
|
-
model: "system",
|
|
932
|
-
scope: { repo, owner: task.owner || "" },
|
|
933
|
-
tags: ["task-archive", ...task.tags],
|
|
934
|
-
metadata
|
|
935
|
-
},
|
|
936
|
-
storage,
|
|
937
|
-
vectors2
|
|
938
|
-
);
|
|
939
|
-
} catch (error) {
|
|
940
|
-
logger.error("Failed to archive task to memory", { error: String(error) });
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
849
|
async function handleTaskList(args, storage) {
|
|
944
850
|
const validated = TaskListSchema.parse(args);
|
|
945
851
|
const { owner, repo, status, phase, query, limit, offset, structured: isStructuredRequest = false } = validated;
|
|
@@ -985,848 +891,398 @@ async function handleTaskList(args, storage) {
|
|
|
985
891
|
includeSerializedStructuredContent: isStructuredRequest
|
|
986
892
|
});
|
|
987
893
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
894
|
+
|
|
895
|
+
// src/mcp/tools/memory.synthesize.ts
|
|
896
|
+
async function handleMemorySynthesize(params, db2, vectors2, options = {}) {
|
|
897
|
+
const validated = MemorySynthesizeSchema.parse(params);
|
|
898
|
+
const session2 = options.session;
|
|
899
|
+
if (!options.sampleMessage || !session2?.supportsSampling) {
|
|
900
|
+
throw new Error("Client does not advertise MCP sampling support");
|
|
901
|
+
}
|
|
902
|
+
const repo = await resolveRepository(validated.repo, session2, options.elicit);
|
|
903
|
+
if (!repo) {
|
|
904
|
+
throw new Error("repo is required when repo cannot be inferred from active MCP roots");
|
|
905
|
+
}
|
|
906
|
+
const repoOwner = validated.owner;
|
|
907
|
+
const recap = await handleMemoryRecap({ owner: repoOwner, repo, limit: 8, offset: 0 }, db2);
|
|
908
|
+
const recapText = getPrimaryTextContent(recap);
|
|
909
|
+
const summary = validated.include_summary ? db2.summaries.getSummary(repoOwner, repo)?.summary : "";
|
|
910
|
+
const taskSnapshot = validated.include_tasks ? await handleTaskList(
|
|
911
|
+
{ owner: repoOwner, repo, status: "backlog,pending,in_progress,blocked", limit: 15, offset: 0 },
|
|
912
|
+
db2
|
|
913
|
+
) : null;
|
|
914
|
+
const taskText = taskSnapshot ? getPrimaryTextContent(taskSnapshot) : "";
|
|
915
|
+
const systemPrompt = [
|
|
916
|
+
"You are a repository memory synthesizer.",
|
|
917
|
+
"Answer strictly from grounded MCP context and tool results.",
|
|
918
|
+
"If memory is insufficient, say so explicitly instead of inventing details.",
|
|
919
|
+
"Prefer concise, technical answers with explicit caveats when evidence is incomplete."
|
|
920
|
+
].join(" ");
|
|
921
|
+
const contextBlock = [
|
|
922
|
+
`Repository: ${repo}`,
|
|
923
|
+
validated.current_file_path ? `Current file: ${validated.current_file_path}` : "",
|
|
924
|
+
summary ? `Summary:
|
|
925
|
+
${summary}` : "",
|
|
926
|
+
recapText ? `Recent context:
|
|
927
|
+
${recapText}` : "",
|
|
928
|
+
taskText ? `Active tasks:
|
|
929
|
+
${taskText}` : ""
|
|
930
|
+
].filter(Boolean).join("\n\n");
|
|
931
|
+
const messages = [
|
|
932
|
+
{
|
|
933
|
+
role: "user",
|
|
934
|
+
content: {
|
|
935
|
+
type: "text",
|
|
936
|
+
text: `Objective: ${validated.objective}
|
|
937
|
+
|
|
938
|
+
Grounding context:
|
|
939
|
+
${contextBlock || "No additional context provided."}`
|
|
1000
940
|
}
|
|
1001
|
-
batchCodes.add(taskData.task_code);
|
|
1002
|
-
}
|
|
1003
|
-
const allCodes = bulkTasks.map((t) => t.task_code);
|
|
1004
|
-
const existingCodes = storage.tasks.getExistingTaskCodes(owner, repo, allCodes);
|
|
1005
|
-
const initialStats = storage.taskStats.getTaskStats(owner, repo);
|
|
1006
|
-
let pendingInRequestCount = 0;
|
|
1007
|
-
const localCodeMap = /* @__PURE__ */ new Map();
|
|
1008
|
-
for (const taskData of bulkTasks) {
|
|
1009
|
-
localCodeMap.set(taskData.task_code, randomUUID2());
|
|
1010
941
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
`Cannot create task '${code}' as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`
|
|
1028
|
-
);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
const statusTimestamps2 = deriveTaskStatusTimestamps(normalizedStatus, now2);
|
|
1032
|
-
const tags = [...taskData.tags || []];
|
|
1033
|
-
const phaseTag2 = `phase:${taskData.phase}`;
|
|
1034
|
-
if (!tags.includes(phaseTag2)) {
|
|
1035
|
-
tags.push(phaseTag2);
|
|
1036
|
-
}
|
|
1037
|
-
const taskId2 = localCodeMap.get(code);
|
|
1038
|
-
const task2 = {
|
|
1039
|
-
id: taskId2,
|
|
1040
|
-
owner,
|
|
1041
|
-
repo,
|
|
1042
|
-
task_code: code,
|
|
1043
|
-
phase: taskData.phase,
|
|
1044
|
-
title: taskData.title,
|
|
1045
|
-
description: taskData.description,
|
|
1046
|
-
status: normalizedStatus,
|
|
1047
|
-
priority: taskData.priority || 3,
|
|
1048
|
-
agent: taskData.agent || singleTask.agent || "unknown",
|
|
1049
|
-
role: taskData.role || singleTask.role || "unknown",
|
|
1050
|
-
doc_path: taskData.doc_path || null,
|
|
1051
|
-
created_at: now2,
|
|
1052
|
-
updated_at: now2,
|
|
1053
|
-
in_progress_at: statusTimestamps2.in_progress_at,
|
|
1054
|
-
finished_at: statusTimestamps2.finished_at,
|
|
1055
|
-
canceled_at: statusTimestamps2.canceled_at,
|
|
1056
|
-
est_tokens: taskData.est_tokens ?? 0,
|
|
1057
|
-
tags,
|
|
1058
|
-
suggested_skills: taskData.suggested_skills || [],
|
|
1059
|
-
commit_id: null,
|
|
1060
|
-
changed_files: [],
|
|
1061
|
-
metadata: taskData.metadata || {},
|
|
1062
|
-
parent_id: resolveParentId(taskData.parent_id, owner, repo, storage, localCodeMap),
|
|
1063
|
-
depends_on: resolveDependsOn(taskData.depends_on, owner, repo, storage, localCodeMap)
|
|
1064
|
-
};
|
|
1065
|
-
tasksToInsert.push(task2);
|
|
1066
|
-
createdTasks.push(code);
|
|
1067
|
-
if (normalizedStatus === "pending") {
|
|
1068
|
-
pendingInRequestCount++;
|
|
942
|
+
];
|
|
943
|
+
const toolDefinitions = buildSamplingTools(session2, validated.use_tools);
|
|
944
|
+
let lastResponse = null;
|
|
945
|
+
let totalToolCalls = 0;
|
|
946
|
+
let iterations = 0;
|
|
947
|
+
while (iterations < validated.max_iterations) {
|
|
948
|
+
iterations += 1;
|
|
949
|
+
const response = await options.sampleMessage({
|
|
950
|
+
messages,
|
|
951
|
+
systemPrompt,
|
|
952
|
+
maxTokens: validated.max_tokens,
|
|
953
|
+
tools: toolDefinitions.length > 0 ? toolDefinitions : void 0,
|
|
954
|
+
toolChoice: toolDefinitions.length > 0 ? { mode: iterations === validated.max_iterations ? "none" : "auto" } : void 0,
|
|
955
|
+
modelPreferences: {
|
|
956
|
+
intelligencePriority: 0.9,
|
|
957
|
+
speedPriority: 0.4
|
|
1069
958
|
}
|
|
959
|
+
});
|
|
960
|
+
lastResponse = response;
|
|
961
|
+
messages.push({
|
|
962
|
+
role: "assistant",
|
|
963
|
+
content: response.content
|
|
964
|
+
});
|
|
965
|
+
const toolUses = extractToolUses(response.content);
|
|
966
|
+
if (toolUses.length === 0) {
|
|
967
|
+
break;
|
|
1070
968
|
}
|
|
1071
|
-
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
doc_path,
|
|
1089
|
-
metadata,
|
|
1090
|
-
parent_id,
|
|
1091
|
-
depends_on,
|
|
1092
|
-
est_tokens
|
|
1093
|
-
} = singleTask;
|
|
1094
|
-
if (!phase || !title || !description) {
|
|
1095
|
-
throw new Error("Missing required fields for single task creation (phase, title, description)");
|
|
1096
|
-
}
|
|
1097
|
-
const resolvedCode = task_code || generateNextCode(owner ?? "", repo, "task", storage);
|
|
1098
|
-
if (storage.tasks.isTaskCodeDuplicate(owner, repo, resolvedCode)) {
|
|
1099
|
-
throw new Error(`Duplicate task_code: '${resolvedCode}' already exists in repository '${repo}'`);
|
|
1100
|
-
}
|
|
1101
|
-
if (status !== "backlog" && status !== "pending" && status !== void 0) {
|
|
1102
|
-
throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
|
|
1103
|
-
}
|
|
1104
|
-
if (status === "pending") {
|
|
1105
|
-
const stats = storage.taskStats.getTaskStats(owner, repo);
|
|
1106
|
-
if (stats.todo >= 10) {
|
|
1107
|
-
throw new Error(
|
|
1108
|
-
`Cannot create task as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
969
|
+
totalToolCalls += toolUses.length;
|
|
970
|
+
const toolResults = await Promise.all(
|
|
971
|
+
toolUses.map(async (toolUse) => ({
|
|
972
|
+
type: "tool_result",
|
|
973
|
+
toolUseId: toolUse.id,
|
|
974
|
+
content: [
|
|
975
|
+
{
|
|
976
|
+
type: "text",
|
|
977
|
+
text: await executeSamplingTool(toolUse.name, toolUse.input, db2, vectors2, repoOwner)
|
|
978
|
+
}
|
|
979
|
+
]
|
|
980
|
+
}))
|
|
981
|
+
);
|
|
982
|
+
messages.push({
|
|
983
|
+
role: "user",
|
|
984
|
+
content: toolResults
|
|
985
|
+
});
|
|
1111
986
|
}
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const finalTags = [...singleTask.tags || []];
|
|
1116
|
-
const phaseTag = `phase:${phase}`;
|
|
1117
|
-
if (!finalTags.includes(phaseTag)) {
|
|
1118
|
-
finalTags.push(phaseTag);
|
|
987
|
+
const answer = lastResponse ? extractTextFromContent(lastResponse.content).trim() : "";
|
|
988
|
+
if (!answer) {
|
|
989
|
+
throw new Error("Sampling did not return a final text answer");
|
|
1119
990
|
}
|
|
1120
|
-
|
|
1121
|
-
id: taskId,
|
|
1122
|
-
owner,
|
|
991
|
+
logger.info("[Tool] memory.synthesize", {
|
|
1123
992
|
repo,
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
status: status || "backlog",
|
|
1129
|
-
priority: priority || 3,
|
|
1130
|
-
agent: agent || "unknown",
|
|
1131
|
-
role: role || "unknown",
|
|
1132
|
-
doc_path: doc_path || null,
|
|
1133
|
-
created_at: now,
|
|
1134
|
-
updated_at: now,
|
|
1135
|
-
in_progress_at: statusTimestamps.in_progress_at,
|
|
1136
|
-
finished_at: statusTimestamps.finished_at,
|
|
1137
|
-
canceled_at: statusTimestamps.canceled_at,
|
|
1138
|
-
est_tokens: est_tokens ?? 0,
|
|
1139
|
-
tags: finalTags,
|
|
1140
|
-
suggested_skills: singleTask.suggested_skills || [],
|
|
1141
|
-
commit_id: null,
|
|
1142
|
-
changed_files: [],
|
|
1143
|
-
metadata: metadata || {},
|
|
1144
|
-
parent_id: resolveParentId(parent_id, owner, repo, storage),
|
|
1145
|
-
depends_on: resolveDependsOn(depends_on, owner, repo, storage)
|
|
1146
|
-
};
|
|
1147
|
-
storage.tasks.insertTask(task);
|
|
993
|
+
objective: validated.objective,
|
|
994
|
+
iterations,
|
|
995
|
+
toolCalls: totalToolCalls
|
|
996
|
+
});
|
|
1148
997
|
return createMcpResponse(
|
|
1149
998
|
{
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
priority: task.priority,
|
|
1158
|
-
depends_on: task.depends_on
|
|
999
|
+
repo,
|
|
1000
|
+
objective: validated.objective,
|
|
1001
|
+
answer,
|
|
1002
|
+
model: lastResponse?.model,
|
|
1003
|
+
stopReason: lastResponse?.stopReason,
|
|
1004
|
+
iterations,
|
|
1005
|
+
toolCalls: totalToolCalls
|
|
1159
1006
|
},
|
|
1160
|
-
`
|
|
1161
|
-
{
|
|
1007
|
+
`Synthesized answer for "${validated.objective}" using repository "${repo}".`,
|
|
1008
|
+
{
|
|
1009
|
+
structuredContentPathHint: "answer",
|
|
1010
|
+
includeSerializedStructuredContent: true
|
|
1011
|
+
}
|
|
1162
1012
|
);
|
|
1163
1013
|
}
|
|
1164
|
-
async function
|
|
1165
|
-
|
|
1166
|
-
const inferredRepo =
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
};
|
|
1171
|
-
const requestedSchema = buildMissingTaskSchema(draft);
|
|
1172
|
-
let completedDraft = draft;
|
|
1173
|
-
if (Object.keys(requestedSchema.properties).length > 0) {
|
|
1174
|
-
if (!options.session?.supportsElicitationForm || !options.elicit) {
|
|
1175
|
-
throw new Error("Client does not advertise MCP elicitation form support");
|
|
1176
|
-
}
|
|
1177
|
-
const elicited = extractAcceptedElicitationContent(
|
|
1178
|
-
await options.elicit({
|
|
1179
|
-
mode: "form",
|
|
1180
|
-
message: "Please complete the missing task details to create a new task.",
|
|
1181
|
-
requestedSchema
|
|
1182
|
-
})
|
|
1183
|
-
);
|
|
1184
|
-
completedDraft = {
|
|
1185
|
-
...draft,
|
|
1186
|
-
...elicited
|
|
1187
|
-
};
|
|
1014
|
+
async function resolveRepository(repo, session2, elicit) {
|
|
1015
|
+
if (repo) return normalizeRepo(repo);
|
|
1016
|
+
const inferredRepo = inferRepoFromSession(session2);
|
|
1017
|
+
if (inferredRepo) return normalizeRepo(inferredRepo);
|
|
1018
|
+
if (!session2?.supportsElicitationForm || !elicit) {
|
|
1019
|
+
return void 0;
|
|
1188
1020
|
}
|
|
1189
|
-
|
|
1190
|
-
{
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1021
|
+
const elicited = extractAcceptedElicitationContent(
|
|
1022
|
+
await elicit({
|
|
1023
|
+
mode: "form",
|
|
1024
|
+
message: "Repository tidak bisa diinfer dari roots aktif. Pilih repository yang ingin disintesis.",
|
|
1025
|
+
requestedSchema: {
|
|
1026
|
+
type: "object",
|
|
1027
|
+
properties: {
|
|
1028
|
+
repo: {
|
|
1029
|
+
type: "string",
|
|
1030
|
+
title: "Repository",
|
|
1031
|
+
description: "Nama repository yang akan dipakai untuk sintesis memori.",
|
|
1032
|
+
minLength: 1
|
|
1033
|
+
}
|
|
1034
|
+
},
|
|
1035
|
+
required: ["repo"]
|
|
1036
|
+
}
|
|
1037
|
+
})
|
|
1197
1038
|
);
|
|
1039
|
+
return typeof elicited.repo === "string" && elicited.repo.trim() ? normalizeRepo(elicited.repo.trim()) : void 0;
|
|
1198
1040
|
}
|
|
1199
|
-
function
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
addRequiredStringField(properties, required, task, "repo", {
|
|
1203
|
-
title: "Repository",
|
|
1204
|
-
description: "Name of the repository for this task.",
|
|
1205
|
-
minLength: 1
|
|
1206
|
-
});
|
|
1207
|
-
addRequiredStringField(properties, required, task, "phase", {
|
|
1208
|
-
title: "Phase",
|
|
1209
|
-
description: "Project phase or milestone for this task.",
|
|
1210
|
-
minLength: 1
|
|
1211
|
-
});
|
|
1212
|
-
addRequiredStringField(properties, required, task, "title", {
|
|
1213
|
-
title: "Title",
|
|
1214
|
-
description: "Short task title.",
|
|
1215
|
-
minLength: 3,
|
|
1216
|
-
maxLength: 100
|
|
1217
|
-
});
|
|
1218
|
-
addRequiredStringField(properties, required, task, "description", {
|
|
1219
|
-
title: "Description",
|
|
1220
|
-
description: "Detailed description. MUST follow format: 1. Context & Analysis, 2. Step & Implementation, 3. Acceptance & Verification",
|
|
1221
|
-
minLength: 1
|
|
1222
|
-
});
|
|
1223
|
-
if (!task.status) {
|
|
1224
|
-
properties.status = {
|
|
1225
|
-
type: "string",
|
|
1226
|
-
title: "Status",
|
|
1227
|
-
description: "Initial task status.",
|
|
1228
|
-
enum: ["backlog", "pending"],
|
|
1229
|
-
default: "backlog"
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
if (task.priority === void 0) {
|
|
1233
|
-
properties.priority = {
|
|
1234
|
-
type: "integer",
|
|
1235
|
-
title: "Priority",
|
|
1236
|
-
description: "Task priority from 1 to 5.",
|
|
1237
|
-
minimum: 1,
|
|
1238
|
-
maximum: 5,
|
|
1239
|
-
default: 3
|
|
1240
|
-
};
|
|
1041
|
+
function buildSamplingTools(session2, useTools) {
|
|
1042
|
+
if (!useTools || !session2?.supportsSamplingTools) {
|
|
1043
|
+
return [];
|
|
1241
1044
|
}
|
|
1242
|
-
return
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1045
|
+
return [
|
|
1046
|
+
{
|
|
1047
|
+
name: "memory_search",
|
|
1048
|
+
description: "Search local repository memories for relevant context.",
|
|
1049
|
+
inputSchema: {
|
|
1050
|
+
type: "object",
|
|
1051
|
+
properties: {
|
|
1052
|
+
repo: { type: "string" },
|
|
1053
|
+
query: { type: "string" },
|
|
1054
|
+
limit: { type: "number", minimum: 1, maximum: 10 }
|
|
1055
|
+
},
|
|
1056
|
+
required: ["repo", "query"]
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "memory_recap",
|
|
1061
|
+
description: "Fetch a recap of the most recent memories and active tasks.",
|
|
1062
|
+
inputSchema: {
|
|
1063
|
+
type: "object",
|
|
1064
|
+
properties: {
|
|
1065
|
+
repo: { type: "string" },
|
|
1066
|
+
limit: { type: "number", minimum: 1, maximum: 20 }
|
|
1067
|
+
},
|
|
1068
|
+
required: ["repo"]
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
{
|
|
1072
|
+
name: "task_list",
|
|
1073
|
+
description: "List tasks by status or search term for the repository.",
|
|
1074
|
+
inputSchema: {
|
|
1075
|
+
type: "object",
|
|
1076
|
+
properties: {
|
|
1077
|
+
repo: { type: "string" },
|
|
1078
|
+
status: { type: "string" },
|
|
1079
|
+
search: { type: "string" },
|
|
1080
|
+
limit: { type: "number", minimum: 1, maximum: 20 }
|
|
1081
|
+
},
|
|
1082
|
+
required: ["repo"]
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
];
|
|
1247
1086
|
}
|
|
1248
|
-
function
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
if (!resolvedId && !ids && updates.task_code) {
|
|
1263
|
-
const found = storage.tasks.getTaskByCode(owner, repo, updates.task_code);
|
|
1264
|
-
if (!found) throw new Error(`Task not found: ${updates.task_code}`);
|
|
1265
|
-
resolvedId = found.id;
|
|
1266
|
-
}
|
|
1267
|
-
const targetIds = ids || (resolvedId ? [resolvedId] : []);
|
|
1268
|
-
if (targetIds.length === 0) {
|
|
1269
|
-
throw new Error("Either 'id' or 'ids' must be provided for update");
|
|
1270
|
-
}
|
|
1271
|
-
let updatedCount = 0;
|
|
1272
|
-
const updatedTasks = [];
|
|
1273
|
-
const completedTaskIds = [];
|
|
1274
|
-
let releasedClaims = 0;
|
|
1275
|
-
let expiredHandoffs = 0;
|
|
1276
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1277
|
-
const isStatusChangingGlobal = updates.status !== void 0;
|
|
1278
|
-
const existingTasks = storage.tasks.getTasksByIds(targetIds);
|
|
1279
|
-
const taskMap = new Map(existingTasks.map((t) => [t.id, t]));
|
|
1280
|
-
for (const targetId of targetIds) {
|
|
1281
|
-
const existingTask = taskMap.get(targetId);
|
|
1282
|
-
if (!existingTask) {
|
|
1283
|
-
if (id) throw new Error(`Task not found: ${targetId}`);
|
|
1284
|
-
continue;
|
|
1285
|
-
}
|
|
1286
|
-
const isStatusChanging = isStatusChangingGlobal && updates.status !== existingTask.status;
|
|
1287
|
-
if (isStatusChanging && !force) {
|
|
1288
|
-
if (!comment || comment.trim() === "") {
|
|
1289
|
-
throw new Error("comment is required when changing task status");
|
|
1290
|
-
}
|
|
1291
|
-
const isStartable = existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked";
|
|
1292
|
-
if (isStartable && updates.status === "completed") {
|
|
1293
|
-
throw new Error(
|
|
1294
|
-
`Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`
|
|
1295
|
-
);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
if (updates.status === "completed" && isStatusChanging) {
|
|
1299
|
-
if (updates.est_tokens === void 0) {
|
|
1300
|
-
throw new Error("est_tokens is required when changing task status to completed");
|
|
1301
|
-
}
|
|
1302
|
-
const children = storage.tasks.getChildrenByParentId(targetId);
|
|
1303
|
-
const incompleteChildren = children.filter((c) => c.status !== "completed");
|
|
1304
|
-
if (incompleteChildren.length > 0) {
|
|
1305
|
-
const childList = incompleteChildren.map((c) => `[${c.task_code}] ${c.title} (${c.status})`).join("; ");
|
|
1306
|
-
throw new Error(
|
|
1307
|
-
`Cannot complete task [${existingTask.task_code}] "${existingTask.title}" \u2014 it has ${incompleteChildren.length} incomplete child task(s). Complete the following child task(s) first: ${childList}`
|
|
1308
|
-
);
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
if (updates.task_code && storage.tasks.isTaskCodeDuplicate(owner, repo, updates.task_code, targetId)) {
|
|
1312
|
-
throw new Error(`Duplicate task_code: '${updates.task_code}' already exists`);
|
|
1313
|
-
}
|
|
1314
|
-
const finalUpdates = { ...updates };
|
|
1315
|
-
if (updates.parent_id !== void 0) {
|
|
1316
|
-
finalUpdates.parent_id = resolveParentId(updates.parent_id, owner, repo, storage);
|
|
1317
|
-
}
|
|
1318
|
-
if (updates.depends_on !== void 0) {
|
|
1319
|
-
finalUpdates.depends_on = resolveDependsOn(updates.depends_on, owner, repo, storage);
|
|
1320
|
-
}
|
|
1321
|
-
if (updates.phase !== void 0 || updates.tags !== void 0) {
|
|
1322
|
-
let currentTags = updates.tags || existingTask.tags || [];
|
|
1323
|
-
currentTags = currentTags.filter((t) => !t.startsWith("phase:"));
|
|
1324
|
-
const finalPhase = updates.phase !== void 0 ? updates.phase : existingTask.phase;
|
|
1325
|
-
if (finalPhase) {
|
|
1326
|
-
const phaseTag = `phase:${finalPhase}`;
|
|
1327
|
-
if (!currentTags.includes(phaseTag)) {
|
|
1328
|
-
currentTags.push(phaseTag);
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
finalUpdates.tags = currentTags;
|
|
1332
|
-
}
|
|
1333
|
-
if (updates.status === "completed") {
|
|
1334
|
-
finalUpdates.finished_at = now;
|
|
1335
|
-
finalUpdates.commit_id = updates.commit_id;
|
|
1336
|
-
finalUpdates.changed_files = updates.changed_files;
|
|
1337
|
-
} else if (updates.status === "canceled") finalUpdates.canceled_at = now;
|
|
1338
|
-
else if (updates.status === "in_progress" && existingTask.status !== "in_progress")
|
|
1339
|
-
finalUpdates.in_progress_at = now;
|
|
1340
|
-
storage.tasks.updateTask(targetId, finalUpdates);
|
|
1341
|
-
if (comment !== void 0 || isStatusChanging) {
|
|
1342
|
-
storage.taskComments.insertTaskComment({
|
|
1343
|
-
id: randomUUID2(),
|
|
1344
|
-
task_id: targetId,
|
|
1345
|
-
owner,
|
|
1346
|
-
repo,
|
|
1347
|
-
comment: comment || `Status updated to ${updates.status}`,
|
|
1348
|
-
agent: updates.agent || existingTask.agent || "unknown",
|
|
1349
|
-
role: updates.role || existingTask.role || "unknown",
|
|
1350
|
-
model: updates.model || "unknown",
|
|
1351
|
-
previous_status: isStatusChanging ? existingTask.status : null,
|
|
1352
|
-
next_status: isStatusChanging ? updates.status : null,
|
|
1353
|
-
created_at: now
|
|
1354
|
-
});
|
|
1087
|
+
async function executeSamplingTool(toolName, rawInput, db2, vectors2, owner) {
|
|
1088
|
+
switch (toolName) {
|
|
1089
|
+
case "memory_search": {
|
|
1090
|
+
const response = await handleMemorySearch(
|
|
1091
|
+
{
|
|
1092
|
+
owner,
|
|
1093
|
+
repo: rawInput.repo,
|
|
1094
|
+
query: rawInput.query,
|
|
1095
|
+
limit: rawInput.limit ?? 5
|
|
1096
|
+
},
|
|
1097
|
+
db2,
|
|
1098
|
+
vectors2
|
|
1099
|
+
);
|
|
1100
|
+
return getPrimaryTextContent(response);
|
|
1355
1101
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1102
|
+
case "memory_recap": {
|
|
1103
|
+
const response = await handleMemoryRecap(
|
|
1104
|
+
{
|
|
1105
|
+
owner,
|
|
1106
|
+
repo: rawInput.repo,
|
|
1107
|
+
limit: rawInput.limit ?? 8,
|
|
1108
|
+
offset: 0
|
|
1109
|
+
},
|
|
1110
|
+
db2
|
|
1111
|
+
);
|
|
1112
|
+
return getPrimaryTextContent(response);
|
|
1358
1113
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1114
|
+
case "task_list": {
|
|
1115
|
+
const response = await handleTaskList(
|
|
1116
|
+
{
|
|
1117
|
+
owner,
|
|
1118
|
+
repo: rawInput.repo,
|
|
1119
|
+
status: rawInput.status,
|
|
1120
|
+
search: rawInput.search,
|
|
1121
|
+
limit: rawInput.limit ?? 10,
|
|
1122
|
+
offset: 0
|
|
1123
|
+
},
|
|
1124
|
+
db2
|
|
1125
|
+
);
|
|
1126
|
+
return getPrimaryTextContent(response);
|
|
1362
1127
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
const taskCodesStr = updatedTasks.length > 0 ? ` Tasks: ${updatedTasks.map((t) => t.code).join(", ")}.` : "";
|
|
1367
|
-
const fieldsStr = Object.keys(updates).length > 0 ? ` Fields: ${Object.keys(updates).join(", ")}.` : "";
|
|
1368
|
-
const isCompleted = updates.status === "completed" && updatedCount > 0;
|
|
1369
|
-
let summaryText = isCompleted ? `Updated ${updatedCount} task(s) in repo "${repo}". Task marked as completed with commit ${updates.commit_id} (${(updates.changed_files || []).length} files changed).` : `Updated ${updatedCount} task(s) in repo "${repo}".`;
|
|
1370
|
-
summaryText += `${taskCodesStr}${fieldsStr}`;
|
|
1371
|
-
if (releasedClaims || expiredHandoffs) {
|
|
1372
|
-
summaryText += ` Auto-closed coordination: released ${releasedClaims} claim(s), expired ${expiredHandoffs} handoff(s).`;
|
|
1373
|
-
}
|
|
1374
|
-
const response = createMcpResponse(
|
|
1375
|
-
{
|
|
1376
|
-
success: true,
|
|
1377
|
-
id: id || void 0,
|
|
1378
|
-
ids: ids || void 0,
|
|
1379
|
-
repo,
|
|
1380
|
-
status: updates.status,
|
|
1381
|
-
updatedCount,
|
|
1382
|
-
updatedFields: Object.keys(updates),
|
|
1383
|
-
coordinationCleanup: {
|
|
1384
|
-
releasedClaims,
|
|
1385
|
-
expiredHandoffs
|
|
1386
|
-
}
|
|
1387
|
-
},
|
|
1388
|
-
summaryText,
|
|
1389
|
-
{ includeSerializedStructuredContent: updateData.structured }
|
|
1390
|
-
);
|
|
1391
|
-
if (completedTaskIds.length > 0) {
|
|
1392
|
-
setImmediate(async () => {
|
|
1393
|
-
for (const taskId of completedTaskIds) {
|
|
1394
|
-
try {
|
|
1395
|
-
await archiveTaskToMemory(taskId, repo, storage, vectors2);
|
|
1396
|
-
} catch (err) {
|
|
1397
|
-
logger.error("Failed to archive task to memory", { taskId, error: String(err) });
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
});
|
|
1128
|
+
default:
|
|
1129
|
+
throw new Error(`Unsupported sampling tool: ${toolName}`);
|
|
1401
1130
|
}
|
|
1402
|
-
return response;
|
|
1403
1131
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1132
|
+
|
|
1133
|
+
// src/mcp/tools/memory.delete.ts
|
|
1134
|
+
async function handleMemoryDelete(params, db2, vectors2, onProgress) {
|
|
1135
|
+
const validated = MemoryDeleteSchema.parse(params);
|
|
1136
|
+
const { id, ids, code, codes, owner, repo, structured } = validated;
|
|
1407
1137
|
const resolvedIds = [];
|
|
1408
1138
|
if (ids) resolvedIds.push(...ids);
|
|
1409
1139
|
if (id) resolvedIds.push(id);
|
|
1410
|
-
if (
|
|
1411
|
-
const
|
|
1412
|
-
if (!
|
|
1413
|
-
resolvedIds.push(
|
|
1140
|
+
if (code) {
|
|
1141
|
+
const entry = db2.memories.getByCode(code, owner, repo);
|
|
1142
|
+
if (!entry) throw new Error(`Memory not found: ${code}`);
|
|
1143
|
+
resolvedIds.push(entry.id);
|
|
1144
|
+
}
|
|
1145
|
+
if (codes) {
|
|
1146
|
+
for (const c of codes) {
|
|
1147
|
+
const entry = db2.memories.getByCode(c, owner, repo);
|
|
1148
|
+
if (!entry) throw new Error(`Memory not found: ${c}`);
|
|
1149
|
+
resolvedIds.push(entry.id);
|
|
1150
|
+
}
|
|
1414
1151
|
}
|
|
1415
1152
|
if (resolvedIds.length === 0) {
|
|
1416
|
-
throw new Error("Either 'id', 'ids', or '
|
|
1153
|
+
throw new Error("Either 'id', 'ids', 'code', or 'codes' must be provided for deletion");
|
|
1417
1154
|
}
|
|
1418
1155
|
const targetIds = resolvedIds;
|
|
1419
|
-
|
|
1420
|
-
const deletedCodes =
|
|
1156
|
+
let deletedCount = 0;
|
|
1157
|
+
const deletedCodes = [];
|
|
1158
|
+
let lastRepo = repo || "unknown";
|
|
1159
|
+
const total = targetIds.length;
|
|
1160
|
+
let progress = 0;
|
|
1161
|
+
const existingMemories = db2.memories.getByIds(targetIds);
|
|
1162
|
+
const existingMap = new Map(existingMemories.map((m) => [m.id, m]));
|
|
1163
|
+
const validIdsToDelete = [];
|
|
1421
1164
|
for (const targetId of targetIds) {
|
|
1422
|
-
|
|
1165
|
+
const existing = existingMap.get(targetId);
|
|
1166
|
+
if (existing) {
|
|
1167
|
+
lastRepo = existing.scope.repo;
|
|
1168
|
+
deletedCodes.push(existing.code || existing.id);
|
|
1169
|
+
validIdsToDelete.push(targetId);
|
|
1170
|
+
} else if (id) {
|
|
1171
|
+
throw new Error(`Memory not found: ${targetId}`);
|
|
1172
|
+
}
|
|
1423
1173
|
}
|
|
1424
|
-
|
|
1174
|
+
if (validIdsToDelete.length > 0) {
|
|
1175
|
+
db2.memoryArchives.bulkDeleteMemories(validIdsToDelete);
|
|
1176
|
+
for (const validId of validIdsToDelete) {
|
|
1177
|
+
if (onProgress) {
|
|
1178
|
+
onProgress(progress, total);
|
|
1179
|
+
}
|
|
1180
|
+
await vectors2.remove(validId);
|
|
1181
|
+
progress++;
|
|
1182
|
+
}
|
|
1183
|
+
deletedCount = validIdsToDelete.length;
|
|
1184
|
+
}
|
|
1185
|
+
if (onProgress) {
|
|
1186
|
+
onProgress(progress, total);
|
|
1187
|
+
}
|
|
1188
|
+
logger.info("[Tool] memory.delete", { repo: lastRepo, count: deletedCount });
|
|
1425
1189
|
return createMcpResponse(
|
|
1426
1190
|
{
|
|
1427
1191
|
success: true,
|
|
1428
1192
|
id: id || void 0,
|
|
1429
1193
|
ids: ids || void 0,
|
|
1430
|
-
repo,
|
|
1431
|
-
deletedCount
|
|
1432
|
-
deletedCodes
|
|
1194
|
+
repo: lastRepo,
|
|
1195
|
+
deletedCount,
|
|
1196
|
+
deletedCodes: deletedCount > 10 ? [...deletedCodes.slice(0, 10), "..."] : deletedCodes
|
|
1433
1197
|
},
|
|
1434
|
-
`Deleted ${
|
|
1435
|
-
{
|
|
1198
|
+
`Deleted ${deletedCount} memory entry(ies) from repo "${lastRepo}".`,
|
|
1199
|
+
{
|
|
1200
|
+
structuredContentPathHint: "deletedCount",
|
|
1201
|
+
includeSerializedStructuredContent: structured
|
|
1202
|
+
}
|
|
1436
1203
|
);
|
|
1437
1204
|
}
|
|
1438
1205
|
|
|
1439
|
-
// src/mcp/tools/memory.
|
|
1440
|
-
async function
|
|
1441
|
-
const validated =
|
|
1442
|
-
|
|
1443
|
-
if (!
|
|
1444
|
-
|
|
1206
|
+
// src/mcp/tools/memory.acknowledge.ts
|
|
1207
|
+
async function handleMemoryAcknowledge(params, db2) {
|
|
1208
|
+
const validated = MemoryAcknowledgeSchema.parse(params);
|
|
1209
|
+
let memoryId = validated.memory_id;
|
|
1210
|
+
if (!memoryId && validated.code) {
|
|
1211
|
+
const byCode = db2.memories.getByCode(validated.code, validated.owner, validated.repo);
|
|
1212
|
+
if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
|
|
1213
|
+
memoryId = byCode.id;
|
|
1214
|
+
} else if (!memoryId) {
|
|
1215
|
+
throw new Error("Either memory_id or code must be provided");
|
|
1445
1216
|
}
|
|
1446
|
-
const
|
|
1447
|
-
if (!
|
|
1448
|
-
throw new Error(
|
|
1217
|
+
const memory = db2.memories.getById(memoryId);
|
|
1218
|
+
if (!memory) {
|
|
1219
|
+
throw new Error(`Memory with ID ${memoryId} not found.`);
|
|
1449
1220
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
) : null;
|
|
1458
|
-
const taskText = taskSnapshot ? getPrimaryTextContent(taskSnapshot) : "";
|
|
1459
|
-
const systemPrompt = [
|
|
1460
|
-
"You are a repository memory synthesizer.",
|
|
1461
|
-
"Answer strictly from grounded MCP context and tool results.",
|
|
1462
|
-
"If memory is insufficient, say so explicitly instead of inventing details.",
|
|
1463
|
-
"Prefer concise, technical answers with explicit caveats when evidence is incomplete."
|
|
1464
|
-
].join(" ");
|
|
1465
|
-
const contextBlock = [
|
|
1466
|
-
`Repository: ${repo}`,
|
|
1467
|
-
validated.current_file_path ? `Current file: ${validated.current_file_path}` : "",
|
|
1468
|
-
summary ? `Summary:
|
|
1469
|
-
${summary}` : "",
|
|
1470
|
-
recapText ? `Recent context:
|
|
1471
|
-
${recapText}` : "",
|
|
1472
|
-
taskText ? `Active tasks:
|
|
1473
|
-
${taskText}` : ""
|
|
1474
|
-
].filter(Boolean).join("\n\n");
|
|
1475
|
-
const messages = [
|
|
1476
|
-
{
|
|
1477
|
-
role: "user",
|
|
1478
|
-
content: {
|
|
1479
|
-
type: "text",
|
|
1480
|
-
text: `Objective: ${validated.objective}
|
|
1481
|
-
|
|
1482
|
-
Grounding context:
|
|
1483
|
-
${contextBlock || "No additional context provided."}`
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
];
|
|
1487
|
-
const toolDefinitions = buildSamplingTools(session2, validated.use_tools);
|
|
1488
|
-
let lastResponse = null;
|
|
1489
|
-
let totalToolCalls = 0;
|
|
1490
|
-
let iterations = 0;
|
|
1491
|
-
while (iterations < validated.max_iterations) {
|
|
1492
|
-
iterations += 1;
|
|
1493
|
-
const response = await options.sampleMessage({
|
|
1494
|
-
messages,
|
|
1495
|
-
systemPrompt,
|
|
1496
|
-
maxTokens: validated.max_tokens,
|
|
1497
|
-
tools: toolDefinitions.length > 0 ? toolDefinitions : void 0,
|
|
1498
|
-
toolChoice: toolDefinitions.length > 0 ? { mode: iterations === validated.max_iterations ? "none" : "auto" } : void 0,
|
|
1499
|
-
modelPreferences: {
|
|
1500
|
-
intelligencePriority: 0.9,
|
|
1501
|
-
speedPriority: 0.4
|
|
1502
|
-
}
|
|
1503
|
-
});
|
|
1504
|
-
lastResponse = response;
|
|
1505
|
-
messages.push({
|
|
1506
|
-
role: "assistant",
|
|
1507
|
-
content: response.content
|
|
1508
|
-
});
|
|
1509
|
-
const toolUses = extractToolUses(response.content);
|
|
1510
|
-
if (toolUses.length === 0) {
|
|
1511
|
-
break;
|
|
1512
|
-
}
|
|
1513
|
-
totalToolCalls += toolUses.length;
|
|
1514
|
-
const toolResults = await Promise.all(
|
|
1515
|
-
toolUses.map(async (toolUse) => ({
|
|
1516
|
-
type: "tool_result",
|
|
1517
|
-
toolUseId: toolUse.id,
|
|
1518
|
-
content: [
|
|
1519
|
-
{
|
|
1520
|
-
type: "text",
|
|
1521
|
-
text: await executeSamplingTool(toolUse.name, toolUse.input, db2, vectors2, repoOwner)
|
|
1522
|
-
}
|
|
1523
|
-
]
|
|
1524
|
-
}))
|
|
1525
|
-
);
|
|
1526
|
-
messages.push({
|
|
1527
|
-
role: "user",
|
|
1528
|
-
content: toolResults
|
|
1221
|
+
if (validated.status === "used") {
|
|
1222
|
+
db2.memories.incrementRecallCount(memory.id);
|
|
1223
|
+
logger.info("[Tool] memory.acknowledge - used", { id: memory.id, context: validated.application_context });
|
|
1224
|
+
} else if (validated.status === "contradictory") {
|
|
1225
|
+
logger.warn("[Tool] memory.acknowledge - contradiction reported", {
|
|
1226
|
+
id: memory.id,
|
|
1227
|
+
context: validated.application_context
|
|
1529
1228
|
});
|
|
1229
|
+
} else {
|
|
1230
|
+
logger.info("[Tool] memory.acknowledge - irrelevant", { id: memory.id, context: validated.application_context });
|
|
1530
1231
|
}
|
|
1531
|
-
const answer = lastResponse ? extractTextFromContent(lastResponse.content).trim() : "";
|
|
1532
|
-
if (!answer) {
|
|
1533
|
-
throw new Error("Sampling did not return a final text answer");
|
|
1534
|
-
}
|
|
1535
|
-
logger.info("[Tool] memory.synthesize", {
|
|
1536
|
-
repo,
|
|
1537
|
-
objective: validated.objective,
|
|
1538
|
-
iterations,
|
|
1539
|
-
toolCalls: totalToolCalls
|
|
1540
|
-
});
|
|
1541
1232
|
return createMcpResponse(
|
|
1542
1233
|
{
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
model: lastResponse?.model,
|
|
1547
|
-
stopReason: lastResponse?.stopReason,
|
|
1548
|
-
iterations,
|
|
1549
|
-
toolCalls: totalToolCalls
|
|
1234
|
+
success: true,
|
|
1235
|
+
id: memory.id,
|
|
1236
|
+
status: validated.status
|
|
1550
1237
|
},
|
|
1551
|
-
`
|
|
1238
|
+
`Acknowledged memory [${memory.code}] as "${validated.status}".`,
|
|
1552
1239
|
{
|
|
1553
|
-
structuredContentPathHint: "
|
|
1554
|
-
includeSerializedStructuredContent:
|
|
1240
|
+
structuredContentPathHint: "status",
|
|
1241
|
+
includeSerializedStructuredContent: validated.structured
|
|
1555
1242
|
}
|
|
1556
1243
|
);
|
|
1557
1244
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1245
|
+
|
|
1246
|
+
// src/mcp/tools/memory.detail.ts
|
|
1247
|
+
async function handleMemoryDetail(args, storage) {
|
|
1248
|
+
const validated = MemoryDetailSchema.parse(args);
|
|
1249
|
+
const { id, code, owner, repo } = validated;
|
|
1250
|
+
let memory;
|
|
1251
|
+
if (id) {
|
|
1252
|
+
memory = storage.memories.getById(id);
|
|
1253
|
+
} else if (code) {
|
|
1254
|
+
memory = storage.memories.getByCode(code, owner, repo);
|
|
1564
1255
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
mode: "form",
|
|
1568
|
-
message: "Repository tidak bisa diinfer dari roots aktif. Pilih repository yang ingin disintesis.",
|
|
1569
|
-
requestedSchema: {
|
|
1570
|
-
type: "object",
|
|
1571
|
-
properties: {
|
|
1572
|
-
repo: {
|
|
1573
|
-
type: "string",
|
|
1574
|
-
title: "Repository",
|
|
1575
|
-
description: "Nama repository yang akan dipakai untuk sintesis memori.",
|
|
1576
|
-
minLength: 1
|
|
1577
|
-
}
|
|
1578
|
-
},
|
|
1579
|
-
required: ["repo"]
|
|
1580
|
-
}
|
|
1581
|
-
})
|
|
1582
|
-
);
|
|
1583
|
-
return typeof elicited.repo === "string" && elicited.repo.trim() ? normalizeRepo(elicited.repo.trim()) : void 0;
|
|
1584
|
-
}
|
|
1585
|
-
function buildSamplingTools(session2, useTools) {
|
|
1586
|
-
if (!useTools || !session2?.supportsSamplingTools) {
|
|
1587
|
-
return [];
|
|
1256
|
+
if (!memory) {
|
|
1257
|
+
throw new Error(`Memory not found: ${id || code}`);
|
|
1588
1258
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
query: { type: "string" },
|
|
1598
|
-
limit: { type: "number", minimum: 1, maximum: 10 }
|
|
1599
|
-
},
|
|
1600
|
-
required: ["repo", "query"]
|
|
1601
|
-
}
|
|
1602
|
-
},
|
|
1603
|
-
{
|
|
1604
|
-
name: "memory_recap",
|
|
1605
|
-
description: "Fetch a recap of the most recent memories and active tasks.",
|
|
1606
|
-
inputSchema: {
|
|
1607
|
-
type: "object",
|
|
1608
|
-
properties: {
|
|
1609
|
-
repo: { type: "string" },
|
|
1610
|
-
limit: { type: "number", minimum: 1, maximum: 20 }
|
|
1611
|
-
},
|
|
1612
|
-
required: ["repo"]
|
|
1613
|
-
}
|
|
1614
|
-
},
|
|
1615
|
-
{
|
|
1616
|
-
name: "task_list",
|
|
1617
|
-
description: "List tasks by status or search term for the repository.",
|
|
1618
|
-
inputSchema: {
|
|
1619
|
-
type: "object",
|
|
1620
|
-
properties: {
|
|
1621
|
-
repo: { type: "string" },
|
|
1622
|
-
status: { type: "string" },
|
|
1623
|
-
search: { type: "string" },
|
|
1624
|
-
limit: { type: "number", minimum: 1, maximum: 20 }
|
|
1625
|
-
},
|
|
1626
|
-
required: ["repo"]
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1259
|
+
storage.memories.incrementHitCount(memory.id);
|
|
1260
|
+
const lines = [
|
|
1261
|
+
`Code: ${memory.code || "-"}`,
|
|
1262
|
+
`ID: ${memory.id}`,
|
|
1263
|
+
`Title: ${memory.title}`,
|
|
1264
|
+
`Type: ${memory.type}`,
|
|
1265
|
+
`Importance: ${memory.importance}`,
|
|
1266
|
+
`Created: ${memory.created_at}`
|
|
1629
1267
|
];
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
},
|
|
1641
|
-
db2,
|
|
1642
|
-
vectors2
|
|
1643
|
-
);
|
|
1644
|
-
return getPrimaryTextContent(response);
|
|
1645
|
-
}
|
|
1646
|
-
case "memory_recap": {
|
|
1647
|
-
const response = await handleMemoryRecap(
|
|
1648
|
-
{
|
|
1649
|
-
owner,
|
|
1650
|
-
repo: rawInput.repo,
|
|
1651
|
-
limit: rawInput.limit ?? 8,
|
|
1652
|
-
offset: 0
|
|
1653
|
-
},
|
|
1654
|
-
db2
|
|
1655
|
-
);
|
|
1656
|
-
return getPrimaryTextContent(response);
|
|
1657
|
-
}
|
|
1658
|
-
case "task_list": {
|
|
1659
|
-
const response = await handleTaskList(
|
|
1660
|
-
{
|
|
1661
|
-
owner,
|
|
1662
|
-
repo: rawInput.repo,
|
|
1663
|
-
status: rawInput.status,
|
|
1664
|
-
search: rawInput.search,
|
|
1665
|
-
limit: rawInput.limit ?? 10,
|
|
1666
|
-
offset: 0
|
|
1667
|
-
},
|
|
1668
|
-
db2
|
|
1669
|
-
);
|
|
1670
|
-
return getPrimaryTextContent(response);
|
|
1671
|
-
}
|
|
1672
|
-
default:
|
|
1673
|
-
throw new Error(`Unsupported sampling tool: ${toolName}`);
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
// src/mcp/tools/memory.delete.ts
|
|
1678
|
-
async function handleMemoryDelete(params, db2, vectors2, onProgress) {
|
|
1679
|
-
const validated = MemoryDeleteSchema.parse(params);
|
|
1680
|
-
const { id, ids, code, codes, owner, repo, structured } = validated;
|
|
1681
|
-
const resolvedIds = [];
|
|
1682
|
-
if (ids) resolvedIds.push(...ids);
|
|
1683
|
-
if (id) resolvedIds.push(id);
|
|
1684
|
-
if (code) {
|
|
1685
|
-
const entry = db2.memories.getByCode(code, owner, repo);
|
|
1686
|
-
if (!entry) throw new Error(`Memory not found: ${code}`);
|
|
1687
|
-
resolvedIds.push(entry.id);
|
|
1688
|
-
}
|
|
1689
|
-
if (codes) {
|
|
1690
|
-
for (const c of codes) {
|
|
1691
|
-
const entry = db2.memories.getByCode(c, owner, repo);
|
|
1692
|
-
if (!entry) throw new Error(`Memory not found: ${c}`);
|
|
1693
|
-
resolvedIds.push(entry.id);
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
if (resolvedIds.length === 0) {
|
|
1697
|
-
throw new Error("Either 'id', 'ids', 'code', or 'codes' must be provided for deletion");
|
|
1698
|
-
}
|
|
1699
|
-
const targetIds = resolvedIds;
|
|
1700
|
-
let deletedCount = 0;
|
|
1701
|
-
const deletedCodes = [];
|
|
1702
|
-
let lastRepo = repo || "unknown";
|
|
1703
|
-
const total = targetIds.length;
|
|
1704
|
-
let progress = 0;
|
|
1705
|
-
const existingMemories = db2.memories.getByIds(targetIds);
|
|
1706
|
-
const existingMap = new Map(existingMemories.map((m) => [m.id, m]));
|
|
1707
|
-
const validIdsToDelete = [];
|
|
1708
|
-
for (const targetId of targetIds) {
|
|
1709
|
-
const existing = existingMap.get(targetId);
|
|
1710
|
-
if (existing) {
|
|
1711
|
-
lastRepo = existing.scope.repo;
|
|
1712
|
-
deletedCodes.push(existing.code || existing.id);
|
|
1713
|
-
validIdsToDelete.push(targetId);
|
|
1714
|
-
} else if (id) {
|
|
1715
|
-
throw new Error(`Memory not found: ${targetId}`);
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
if (validIdsToDelete.length > 0) {
|
|
1719
|
-
db2.memoryArchives.bulkDeleteMemories(validIdsToDelete);
|
|
1720
|
-
for (const validId of validIdsToDelete) {
|
|
1721
|
-
if (onProgress) {
|
|
1722
|
-
onProgress(progress, total);
|
|
1723
|
-
}
|
|
1724
|
-
await vectors2.remove(validId);
|
|
1725
|
-
progress++;
|
|
1726
|
-
}
|
|
1727
|
-
deletedCount = validIdsToDelete.length;
|
|
1728
|
-
}
|
|
1729
|
-
if (onProgress) {
|
|
1730
|
-
onProgress(progress, total);
|
|
1731
|
-
}
|
|
1732
|
-
logger.info("[Tool] memory.delete", { repo: lastRepo, count: deletedCount });
|
|
1733
|
-
return createMcpResponse(
|
|
1734
|
-
{
|
|
1735
|
-
success: true,
|
|
1736
|
-
id: id || void 0,
|
|
1737
|
-
ids: ids || void 0,
|
|
1738
|
-
repo: lastRepo,
|
|
1739
|
-
deletedCount,
|
|
1740
|
-
deletedCodes: deletedCount > 10 ? [...deletedCodes.slice(0, 10), "..."] : deletedCodes
|
|
1741
|
-
},
|
|
1742
|
-
`Deleted ${deletedCount} memory entry(ies) from repo "${lastRepo}".`,
|
|
1743
|
-
{
|
|
1744
|
-
structuredContentPathHint: "deletedCount",
|
|
1745
|
-
includeSerializedStructuredContent: structured
|
|
1746
|
-
}
|
|
1747
|
-
);
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
|
-
// src/mcp/tools/memory.acknowledge.ts
|
|
1751
|
-
async function handleMemoryAcknowledge(params, db2) {
|
|
1752
|
-
const validated = MemoryAcknowledgeSchema.parse(params);
|
|
1753
|
-
let memoryId = validated.memory_id;
|
|
1754
|
-
if (!memoryId && validated.code) {
|
|
1755
|
-
const byCode = db2.memories.getByCode(validated.code, validated.owner, validated.repo);
|
|
1756
|
-
if (!byCode) throw new Error(`Memory not found: ${validated.code}`);
|
|
1757
|
-
memoryId = byCode.id;
|
|
1758
|
-
} else if (!memoryId) {
|
|
1759
|
-
throw new Error("Either memory_id or code must be provided");
|
|
1760
|
-
}
|
|
1761
|
-
const memory = db2.memories.getById(memoryId);
|
|
1762
|
-
if (!memory) {
|
|
1763
|
-
throw new Error(`Memory with ID ${memoryId} not found.`);
|
|
1764
|
-
}
|
|
1765
|
-
if (validated.status === "used") {
|
|
1766
|
-
db2.memories.incrementRecallCount(memory.id);
|
|
1767
|
-
logger.info("[Tool] memory.acknowledge - used", { id: memory.id, context: validated.application_context });
|
|
1768
|
-
} else if (validated.status === "contradictory") {
|
|
1769
|
-
logger.warn("[Tool] memory.acknowledge - contradiction reported", {
|
|
1770
|
-
id: memory.id,
|
|
1771
|
-
context: validated.application_context
|
|
1772
|
-
});
|
|
1773
|
-
} else {
|
|
1774
|
-
logger.info("[Tool] memory.acknowledge - irrelevant", { id: memory.id, context: validated.application_context });
|
|
1775
|
-
}
|
|
1776
|
-
return createMcpResponse(
|
|
1777
|
-
{
|
|
1778
|
-
success: true,
|
|
1779
|
-
id: memory.id,
|
|
1780
|
-
status: validated.status
|
|
1781
|
-
},
|
|
1782
|
-
`Acknowledged memory [${memory.code}] as "${validated.status}".`,
|
|
1783
|
-
{
|
|
1784
|
-
structuredContentPathHint: "status",
|
|
1785
|
-
includeSerializedStructuredContent: validated.structured
|
|
1786
|
-
}
|
|
1787
|
-
);
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
// src/mcp/tools/memory.detail.ts
|
|
1791
|
-
async function handleMemoryDetail(args, storage) {
|
|
1792
|
-
const validated = MemoryDetailSchema.parse(args);
|
|
1793
|
-
const { id, code, owner, repo } = validated;
|
|
1794
|
-
let memory;
|
|
1795
|
-
if (id) {
|
|
1796
|
-
memory = storage.memories.getById(id);
|
|
1797
|
-
} else if (code) {
|
|
1798
|
-
memory = storage.memories.getByCode(code, owner, repo);
|
|
1799
|
-
}
|
|
1800
|
-
if (!memory) {
|
|
1801
|
-
throw new Error(`Memory not found: ${id || code}`);
|
|
1802
|
-
}
|
|
1803
|
-
storage.memories.incrementHitCount(memory.id);
|
|
1804
|
-
const lines = [
|
|
1805
|
-
`Code: ${memory.code || "-"}`,
|
|
1806
|
-
`ID: ${memory.id}`,
|
|
1807
|
-
`Title: ${memory.title}`,
|
|
1808
|
-
`Type: ${memory.type}`,
|
|
1809
|
-
`Importance: ${memory.importance}`,
|
|
1810
|
-
`Created: ${memory.created_at}`
|
|
1811
|
-
];
|
|
1812
|
-
if (memory.scope?.repo) lines.push(`Repo: ${memory.scope.repo}`);
|
|
1813
|
-
if (memory.scope?.folder) lines.push(`Folder: ${memory.scope.folder}`);
|
|
1814
|
-
if (memory.content) {
|
|
1815
|
-
lines.push("", "--- Content ---", memory.content);
|
|
1816
|
-
}
|
|
1817
|
-
const content = lines.join("\n");
|
|
1818
|
-
return createMcpResponse(memory, content, {
|
|
1819
|
-
contentSummary: content,
|
|
1820
|
-
includeSerializedStructuredContent: validated.structured
|
|
1821
|
-
});
|
|
1268
|
+
if (memory.scope?.repo) lines.push(`Repo: ${memory.scope.repo}`);
|
|
1269
|
+
if (memory.scope?.folder) lines.push(`Folder: ${memory.scope.folder}`);
|
|
1270
|
+
if (memory.content) {
|
|
1271
|
+
lines.push("", "--- Content ---", memory.content);
|
|
1272
|
+
}
|
|
1273
|
+
const content = lines.join("\n");
|
|
1274
|
+
return createMcpResponse(memory, content, {
|
|
1275
|
+
contentSummary: content,
|
|
1276
|
+
includeSerializedStructuredContent: validated.structured
|
|
1277
|
+
});
|
|
1822
1278
|
}
|
|
1823
1279
|
|
|
1824
1280
|
// src/mcp/tools/standard.store.ts
|
|
1825
|
-
import { randomUUID as
|
|
1826
|
-
var
|
|
1281
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1282
|
+
var UUID_REGEX3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1827
1283
|
function resolveStandardParentId(value, db2, owner, repo) {
|
|
1828
1284
|
if (!value) return null;
|
|
1829
|
-
if (
|
|
1285
|
+
if (UUID_REGEX3.test(value)) return value;
|
|
1830
1286
|
const standard = db2.standards.getByCode(value, owner, repo);
|
|
1831
1287
|
if (!standard) throw new Error(`parent_id: standard with code '${value}' not found`);
|
|
1832
1288
|
return standard.id;
|
|
@@ -1865,7 +1321,7 @@ async function storeSingleStandard(params, db2, vectors2) {
|
|
|
1865
1321
|
}
|
|
1866
1322
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1867
1323
|
const entry = {
|
|
1868
|
-
id:
|
|
1324
|
+
id: randomUUID2(),
|
|
1869
1325
|
code: generateNextCode(params.owner, params.repo || "__global__", "standard", db2),
|
|
1870
1326
|
title: params.name,
|
|
1871
1327
|
content: params.content,
|
|
@@ -1948,7 +1404,7 @@ async function handleStandardStore(params, db2, vectors2) {
|
|
|
1948
1404
|
const code = generateNextCode(validated.owner ?? "", standardRepo, "standard", db2, batchCodes);
|
|
1949
1405
|
batchCodes.add(code);
|
|
1950
1406
|
entries.push({
|
|
1951
|
-
id:
|
|
1407
|
+
id: randomUUID2(),
|
|
1952
1408
|
code,
|
|
1953
1409
|
title: std.name,
|
|
1954
1410
|
content: std.content,
|
|
@@ -2222,169 +1678,724 @@ async function handleStandardSearch(params, db2, vectors2) {
|
|
|
2222
1678
|
columns: [...COLUMNS],
|
|
2223
1679
|
rows
|
|
2224
1680
|
}
|
|
2225
|
-
},
|
|
2226
|
-
`Found ${results.length} coding standards matching your query`,
|
|
2227
|
-
{
|
|
2228
|
-
contentSummary,
|
|
2229
|
-
structuredContentPathHint: "results",
|
|
2230
|
-
includeSerializedStructuredContent: true
|
|
1681
|
+
},
|
|
1682
|
+
`Found ${results.length} coding standards matching your query`,
|
|
1683
|
+
{
|
|
1684
|
+
contentSummary,
|
|
1685
|
+
structuredContentPathHint: "results",
|
|
1686
|
+
includeSerializedStructuredContent: true
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// src/mcp/tools/standard.update.ts
|
|
1692
|
+
var UUID_REGEX4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1693
|
+
function resolveStandardParentId2(value, db2, owner, repo) {
|
|
1694
|
+
if (!value) return null;
|
|
1695
|
+
if (UUID_REGEX4.test(value)) return value;
|
|
1696
|
+
const standard = db2.standards.getByCode(value, owner, repo);
|
|
1697
|
+
if (!standard) throw new Error(`parent_id: standard with code '${value}' not found`);
|
|
1698
|
+
return standard.id;
|
|
1699
|
+
}
|
|
1700
|
+
async function handleStandardUpdate(params, db2, vectors2) {
|
|
1701
|
+
const validated = StandardUpdateSchema.parse(params);
|
|
1702
|
+
let resolvedId = validated.id;
|
|
1703
|
+
if (!resolvedId && validated.code) {
|
|
1704
|
+
const byCode = db2.standards.getByCode(validated.code, validated.owner, validated.repo);
|
|
1705
|
+
if (!byCode) throw new Error(`Coding standard not found: ${validated.code}`);
|
|
1706
|
+
resolvedId = byCode.id;
|
|
1707
|
+
} else if (!resolvedId) {
|
|
1708
|
+
throw new Error("Either id or code must be provided");
|
|
1709
|
+
}
|
|
1710
|
+
const existing = db2.standards.getById(resolvedId);
|
|
1711
|
+
if (!existing) {
|
|
1712
|
+
throw new Error(`Coding standard not found: ${resolvedId}`);
|
|
1713
|
+
}
|
|
1714
|
+
const updates = {};
|
|
1715
|
+
if (validated.name !== void 0) updates.title = validated.name;
|
|
1716
|
+
if (validated.content !== void 0) updates.content = validated.content;
|
|
1717
|
+
if (validated.parent_id !== void 0)
|
|
1718
|
+
updates.parent_id = resolveStandardParentId2(validated.parent_id, db2, existing.owner, existing.repo ?? void 0);
|
|
1719
|
+
if (validated.context !== void 0) updates.context = validated.context;
|
|
1720
|
+
if (validated.version !== void 0) updates.version = validated.version;
|
|
1721
|
+
if (validated.language !== void 0) updates.language = validated.language;
|
|
1722
|
+
if (validated.stack !== void 0) updates.stack = validated.stack;
|
|
1723
|
+
if (validated.repo !== void 0) updates.repo = validated.repo;
|
|
1724
|
+
if (validated.is_global !== void 0) updates.is_global = validated.is_global;
|
|
1725
|
+
if (validated.tags !== void 0) updates.tags = validated.tags;
|
|
1726
|
+
if (validated.metadata !== void 0) updates.metadata = validated.metadata;
|
|
1727
|
+
if (validated.agent !== void 0) updates.agent = validated.agent;
|
|
1728
|
+
if (validated.model !== void 0) updates.model = validated.model;
|
|
1729
|
+
db2.standards.update(resolvedId, updates);
|
|
1730
|
+
const merged = {
|
|
1731
|
+
...existing,
|
|
1732
|
+
...updates,
|
|
1733
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1734
|
+
};
|
|
1735
|
+
if (validated.name !== void 0 || validated.content !== void 0 || validated.context !== void 0 || validated.version !== void 0 || validated.language !== void 0 || validated.stack !== void 0 || validated.tags !== void 0 || validated.metadata !== void 0) {
|
|
1736
|
+
await vectors2.upsert(resolvedId, buildStandardVectorText(merged), "standard");
|
|
1737
|
+
}
|
|
1738
|
+
logger.info("[Tool] standard.update - updated coding standard", {
|
|
1739
|
+
standardId: resolvedId,
|
|
1740
|
+
fields: Object.keys(updates)
|
|
1741
|
+
});
|
|
1742
|
+
return createMcpResponse(
|
|
1743
|
+
{
|
|
1744
|
+
success: true,
|
|
1745
|
+
id: resolvedId,
|
|
1746
|
+
code: existing.code,
|
|
1747
|
+
updatedFields: Object.keys(updates)
|
|
1748
|
+
},
|
|
1749
|
+
`Updated coding standard [${existing.code}] in repo "${existing.repo || "global"}". Fields: ${Object.keys(updates).join(", ") || "none"}.`,
|
|
1750
|
+
{
|
|
1751
|
+
structuredContentPathHint: "updatedFields",
|
|
1752
|
+
includeSerializedStructuredContent: true
|
|
1753
|
+
}
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// src/mcp/tools/standard.detail.ts
|
|
1758
|
+
async function handleStandardDetail(args, storage) {
|
|
1759
|
+
const validated = StandardDetailSchema.parse(args);
|
|
1760
|
+
const standard = validated.id ? storage.standards.getById(validated.id) : storage.standards.getByCode(validated.code, validated.owner, validated.repo);
|
|
1761
|
+
if (!standard) {
|
|
1762
|
+
const identifier = validated.id ?? validated.code;
|
|
1763
|
+
throw new Error(`Coding standard not found: ${identifier}`);
|
|
1764
|
+
}
|
|
1765
|
+
storage.standards.incrementHitCounts([standard.id]);
|
|
1766
|
+
const lines = [
|
|
1767
|
+
`ID: ${standard.id}`,
|
|
1768
|
+
...standard.code ? [`Code: ${standard.code}`] : [],
|
|
1769
|
+
`Title: ${standard.title}`,
|
|
1770
|
+
`Parent ID: ${standard.parent_id || "-"}`,
|
|
1771
|
+
`Context: ${standard.context}`,
|
|
1772
|
+
`Version: ${standard.version}`,
|
|
1773
|
+
`Language: ${standard.language || "-"}`,
|
|
1774
|
+
`Scope: ${standard.is_global ? "global" : standard.repo || "-"}`,
|
|
1775
|
+
`Created: ${standard.created_at}`,
|
|
1776
|
+
`Updated: ${standard.updated_at}`
|
|
1777
|
+
];
|
|
1778
|
+
if (standard.stack.length > 0) lines.push(`Stack: ${standard.stack.join(", ")}`);
|
|
1779
|
+
if (standard.tags.length > 0) lines.push(`Tags: ${standard.tags.join(", ")}`);
|
|
1780
|
+
if (Object.keys(standard.metadata).length > 0) lines.push(`Metadata: ${JSON.stringify(standard.metadata)}`);
|
|
1781
|
+
if (standard.content) {
|
|
1782
|
+
lines.push("", "--- Content ---", standard.content);
|
|
1783
|
+
}
|
|
1784
|
+
const content = lines.join("\n");
|
|
1785
|
+
return createMcpResponse(standard, content, {
|
|
1786
|
+
contentSummary: content,
|
|
1787
|
+
includeSerializedStructuredContent: validated.structured
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// src/mcp/tools/standard.delete.ts
|
|
1792
|
+
async function handleStandardDelete(params, db2, vectors2) {
|
|
1793
|
+
const validated = StandardDeleteSchema.parse(params);
|
|
1794
|
+
const { id, ids, code, codes, owner, repo, structured } = validated;
|
|
1795
|
+
const resolvedIds = [];
|
|
1796
|
+
if (ids) resolvedIds.push(...ids);
|
|
1797
|
+
if (id) resolvedIds.push(id);
|
|
1798
|
+
if (code) {
|
|
1799
|
+
const entry = db2.standards.getByCode(code, owner, repo);
|
|
1800
|
+
if (!entry) throw new Error(`Coding standard not found: ${code}`);
|
|
1801
|
+
resolvedIds.push(entry.id);
|
|
1802
|
+
}
|
|
1803
|
+
if (codes) {
|
|
1804
|
+
for (const c of codes) {
|
|
1805
|
+
const entry = db2.standards.getByCode(c, owner, repo);
|
|
1806
|
+
if (!entry) throw new Error(`Coding standard not found: ${c}`);
|
|
1807
|
+
resolvedIds.push(entry.id);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
if (resolvedIds.length === 0) {
|
|
1811
|
+
throw new Error("Either 'id', 'ids', 'code', or 'codes' must be provided for deletion");
|
|
1812
|
+
}
|
|
1813
|
+
const targetIds = resolvedIds;
|
|
1814
|
+
let deletedCount = 0;
|
|
1815
|
+
const deletedTitles = [];
|
|
1816
|
+
let lastRepo = repo || "unknown";
|
|
1817
|
+
for (const targetId of targetIds) {
|
|
1818
|
+
const existing = db2.standards.getById(targetId);
|
|
1819
|
+
if (existing) {
|
|
1820
|
+
lastRepo = existing.repo || (existing.is_global ? "global" : lastRepo);
|
|
1821
|
+
deletedTitles.push(existing.title);
|
|
1822
|
+
db2.standards.delete(targetId);
|
|
1823
|
+
await vectors2.remove(targetId, "standard");
|
|
1824
|
+
deletedCount++;
|
|
1825
|
+
} else if (id) {
|
|
1826
|
+
throw new Error(`Coding standard not found: ${targetId}`);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
logger.info("[Tool] standard.delete", { repo: lastRepo, count: deletedCount });
|
|
1830
|
+
return createMcpResponse(
|
|
1831
|
+
{
|
|
1832
|
+
success: true,
|
|
1833
|
+
id: id || void 0,
|
|
1834
|
+
ids: ids || void 0,
|
|
1835
|
+
repo: lastRepo,
|
|
1836
|
+
deletedCount,
|
|
1837
|
+
deletedTitles: deletedTitles.length > 10 ? [...deletedTitles.slice(0, 10), "..."] : deletedTitles
|
|
1838
|
+
},
|
|
1839
|
+
`Deleted ${deletedCount} coding standard(s) from "${lastRepo}".`,
|
|
1840
|
+
{
|
|
1841
|
+
structuredContentPathHint: "deletedCount",
|
|
1842
|
+
includeSerializedStructuredContent: structured
|
|
1843
|
+
}
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
// src/mcp/tools/task.create.ts
|
|
1848
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1849
|
+
|
|
1850
|
+
// src/mcp/tools/task.helpers.ts
|
|
1851
|
+
var UUID_REGEX5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1852
|
+
function resolveParentId(value, owner, repo, storage, localCodeMap) {
|
|
1853
|
+
if (!value) return null;
|
|
1854
|
+
if (UUID_REGEX5.test(value)) return value;
|
|
1855
|
+
if (localCodeMap?.has(value)) return localCodeMap.get(value);
|
|
1856
|
+
const parent = storage.tasks.getTaskByCode(owner, repo, value);
|
|
1857
|
+
if (!parent) throw new Error(`parent_id: task with code '${value}' not found in repo '${repo}'`);
|
|
1858
|
+
return parent.id;
|
|
1859
|
+
}
|
|
1860
|
+
function resolveDependsOn(value, owner, repo, storage, localCodeMap) {
|
|
1861
|
+
if (!value) return null;
|
|
1862
|
+
if (UUID_REGEX5.test(value)) return value;
|
|
1863
|
+
if (localCodeMap?.has(value)) return localCodeMap.get(value);
|
|
1864
|
+
const task = storage.tasks.getTaskByCode(owner, repo, value);
|
|
1865
|
+
if (!task) throw new Error(`depends_on: task with code '${value}' not found in repo '${repo}'`);
|
|
1866
|
+
return task.id;
|
|
1867
|
+
}
|
|
1868
|
+
function deriveTaskStatusTimestamps(status, now, existingTask) {
|
|
1869
|
+
const timestamps = {
|
|
1870
|
+
in_progress_at: null,
|
|
1871
|
+
finished_at: null,
|
|
1872
|
+
canceled_at: null
|
|
1873
|
+
};
|
|
1874
|
+
if (status === "in_progress" && existingTask?.status !== "in_progress") {
|
|
1875
|
+
timestamps.in_progress_at = now;
|
|
1876
|
+
}
|
|
1877
|
+
if (status === "completed") {
|
|
1878
|
+
timestamps.finished_at = now;
|
|
1879
|
+
}
|
|
1880
|
+
if (status === "canceled") {
|
|
1881
|
+
timestamps.canceled_at = now;
|
|
1882
|
+
}
|
|
1883
|
+
return timestamps;
|
|
1884
|
+
}
|
|
1885
|
+
async function archiveTaskToMemory(taskId, repo, storage, vectors2) {
|
|
1886
|
+
const task = storage.tasks.getTaskById(taskId);
|
|
1887
|
+
if (!task) return;
|
|
1888
|
+
const comments = storage.taskComments.getTaskCommentsByTaskId(taskId);
|
|
1889
|
+
let content = `Task: [${task.task_code}] ${task.title}
|
|
1890
|
+
`;
|
|
1891
|
+
content += `Phase: ${task.phase}
|
|
1892
|
+
`;
|
|
1893
|
+
content += `Description: ${task.description || "No description"}
|
|
1894
|
+
`;
|
|
1895
|
+
content += `Commit: ${task.commit_id || "N/A"}
|
|
1896
|
+
`;
|
|
1897
|
+
if (task.changed_files && task.changed_files.length > 0) {
|
|
1898
|
+
content += `Files changed:
|
|
1899
|
+
`;
|
|
1900
|
+
for (const f of task.changed_files) {
|
|
1901
|
+
content += ` - ${f}
|
|
1902
|
+
`;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
if (comments && comments.length > 0) {
|
|
1906
|
+
content += `
|
|
1907
|
+
Comments & History:
|
|
1908
|
+
`;
|
|
1909
|
+
const chronComments = [...comments].reverse();
|
|
1910
|
+
for (const c of chronComments) {
|
|
1911
|
+
const statusInfo = c.next_status ? ` (Status: ${c.previous_status} -> ${c.next_status})` : "";
|
|
1912
|
+
content += `- [${c.created_at}] ${c.agent}${statusInfo}: ${c.comment}
|
|
1913
|
+
`;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
const metadata = {
|
|
1917
|
+
task_id: taskId,
|
|
1918
|
+
task_code: task.task_code,
|
|
1919
|
+
original_metadata: task.metadata
|
|
1920
|
+
};
|
|
1921
|
+
const title = `Completed Task: ${task.title}`;
|
|
1922
|
+
const truncatedTitle = title.length > 100 ? title.substring(0, 97) + "..." : title;
|
|
1923
|
+
try {
|
|
1924
|
+
await handleMemoryStore(
|
|
1925
|
+
{
|
|
1926
|
+
type: "task_archive",
|
|
1927
|
+
title: truncatedTitle,
|
|
1928
|
+
content,
|
|
1929
|
+
importance: Math.min(5, task.priority + 1),
|
|
1930
|
+
agent: task.agent || "system",
|
|
1931
|
+
role: task.role || "unknown",
|
|
1932
|
+
model: "system",
|
|
1933
|
+
scope: { repo, owner: task.owner || "" },
|
|
1934
|
+
tags: ["task-archive", ...task.tags],
|
|
1935
|
+
metadata
|
|
1936
|
+
},
|
|
1937
|
+
storage,
|
|
1938
|
+
vectors2
|
|
1939
|
+
);
|
|
1940
|
+
} catch (error) {
|
|
1941
|
+
logger.error("Failed to archive task to memory", { error: String(error) });
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// src/mcp/tools/task.create.ts
|
|
1946
|
+
async function handleTaskCreate(args, storage) {
|
|
1947
|
+
const parsed = TaskCreateSchema.parse(args);
|
|
1948
|
+
const { owner, repo, tasks: bulkTasks, ...singleTask } = parsed;
|
|
1949
|
+
if (bulkTasks) {
|
|
1950
|
+
const createdTasks = [];
|
|
1951
|
+
const tasksToInsert = [];
|
|
1952
|
+
const now2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
1953
|
+
const codesInRequest = /* @__PURE__ */ new Set();
|
|
1954
|
+
const batchCodes = /* @__PURE__ */ new Set();
|
|
1955
|
+
for (const taskData of bulkTasks) {
|
|
1956
|
+
if (!taskData.task_code) {
|
|
1957
|
+
taskData.task_code = generateNextCode(owner ?? "", repo, "task", storage, batchCodes);
|
|
1958
|
+
}
|
|
1959
|
+
batchCodes.add(taskData.task_code);
|
|
1960
|
+
}
|
|
1961
|
+
const allCodes = bulkTasks.map((t) => t.task_code);
|
|
1962
|
+
const existingCodes = storage.tasks.getExistingTaskCodes(owner, repo, allCodes);
|
|
1963
|
+
const initialStats = storage.taskStats.getTaskStats(owner, repo);
|
|
1964
|
+
let pendingInRequestCount = 0;
|
|
1965
|
+
const localCodeMap = /* @__PURE__ */ new Map();
|
|
1966
|
+
for (const taskData of bulkTasks) {
|
|
1967
|
+
localCodeMap.set(taskData.task_code, randomUUID3());
|
|
1968
|
+
}
|
|
1969
|
+
for (const taskData of bulkTasks) {
|
|
1970
|
+
const code = taskData.task_code;
|
|
1971
|
+
if (codesInRequest.has(code)) {
|
|
1972
|
+
throw new Error(`Duplicate task_code in request: '${code}'`);
|
|
1973
|
+
}
|
|
1974
|
+
if (existingCodes.has(code)) {
|
|
1975
|
+
throw new Error(`Duplicate task_code: '${code}' already exists in repository '${repo}'`);
|
|
1976
|
+
}
|
|
1977
|
+
codesInRequest.add(code);
|
|
1978
|
+
const normalizedStatus = taskData.status || "backlog";
|
|
1979
|
+
if (normalizedStatus !== "backlog" && normalizedStatus !== "pending") {
|
|
1980
|
+
throw new Error(`New tasks must be 'backlog' or 'pending'. Task '${code}' has status '${normalizedStatus}'.`);
|
|
1981
|
+
}
|
|
1982
|
+
if (normalizedStatus === "pending") {
|
|
1983
|
+
if (initialStats.todo + pendingInRequestCount >= 10) {
|
|
1984
|
+
throw new Error(
|
|
1985
|
+
`Cannot create task '${code}' as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
const statusTimestamps2 = deriveTaskStatusTimestamps(normalizedStatus, now2);
|
|
1990
|
+
const tags = [...taskData.tags || []];
|
|
1991
|
+
const phaseTag2 = `phase:${taskData.phase}`;
|
|
1992
|
+
if (!tags.includes(phaseTag2)) {
|
|
1993
|
+
tags.push(phaseTag2);
|
|
1994
|
+
}
|
|
1995
|
+
const taskId2 = localCodeMap.get(code);
|
|
1996
|
+
const task2 = {
|
|
1997
|
+
id: taskId2,
|
|
1998
|
+
owner,
|
|
1999
|
+
repo,
|
|
2000
|
+
task_code: code,
|
|
2001
|
+
phase: taskData.phase,
|
|
2002
|
+
title: taskData.title,
|
|
2003
|
+
description: taskData.description,
|
|
2004
|
+
status: normalizedStatus,
|
|
2005
|
+
priority: taskData.priority || 3,
|
|
2006
|
+
agent: taskData.agent || singleTask.agent || "unknown",
|
|
2007
|
+
role: taskData.role || singleTask.role || "unknown",
|
|
2008
|
+
doc_path: taskData.doc_path || null,
|
|
2009
|
+
created_at: now2,
|
|
2010
|
+
updated_at: now2,
|
|
2011
|
+
in_progress_at: statusTimestamps2.in_progress_at,
|
|
2012
|
+
finished_at: statusTimestamps2.finished_at,
|
|
2013
|
+
canceled_at: statusTimestamps2.canceled_at,
|
|
2014
|
+
est_tokens: taskData.est_tokens ?? 0,
|
|
2015
|
+
tags,
|
|
2016
|
+
suggested_skills: taskData.suggested_skills || [],
|
|
2017
|
+
commit_id: null,
|
|
2018
|
+
changed_files: [],
|
|
2019
|
+
metadata: taskData.metadata || {},
|
|
2020
|
+
parent_id: resolveParentId(taskData.parent_id, owner, repo, storage, localCodeMap),
|
|
2021
|
+
depends_on: resolveDependsOn(taskData.depends_on, owner, repo, storage, localCodeMap)
|
|
2022
|
+
};
|
|
2023
|
+
tasksToInsert.push(task2);
|
|
2024
|
+
createdTasks.push(code);
|
|
2025
|
+
if (normalizedStatus === "pending") {
|
|
2026
|
+
pendingInRequestCount++;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
storage.tasks.bulkInsertTasks(tasksToInsert);
|
|
2030
|
+
const taskCodesStr = createdTasks.length > 0 ? `: ${createdTasks.join(", ")}` : "";
|
|
2031
|
+
return createMcpResponse(
|
|
2032
|
+
{ success: true, repo, createdCount: bulkTasks.length, taskCodes: createdTasks },
|
|
2033
|
+
`Created ${bulkTasks.length} tasks in repo "${repo}"${taskCodesStr}.`,
|
|
2034
|
+
{ includeSerializedStructuredContent: parsed.structured || false }
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
const {
|
|
2038
|
+
task_code,
|
|
2039
|
+
phase,
|
|
2040
|
+
title,
|
|
2041
|
+
description,
|
|
2042
|
+
status,
|
|
2043
|
+
priority,
|
|
2044
|
+
agent,
|
|
2045
|
+
role,
|
|
2046
|
+
doc_path,
|
|
2047
|
+
metadata,
|
|
2048
|
+
parent_id,
|
|
2049
|
+
depends_on,
|
|
2050
|
+
est_tokens
|
|
2051
|
+
} = singleTask;
|
|
2052
|
+
if (!phase || !title || !description) {
|
|
2053
|
+
throw new Error("Missing required fields for single task creation (phase, title, description)");
|
|
2054
|
+
}
|
|
2055
|
+
const resolvedCode = task_code || generateNextCode(owner ?? "", repo, "task", storage);
|
|
2056
|
+
if (storage.tasks.isTaskCodeDuplicate(owner, repo, resolvedCode)) {
|
|
2057
|
+
throw new Error(`Duplicate task_code: '${resolvedCode}' already exists in repository '${repo}'`);
|
|
2058
|
+
}
|
|
2059
|
+
if (status !== "backlog" && status !== "pending" && status !== void 0) {
|
|
2060
|
+
throw new Error("New tasks must be created with status 'backlog' or 'pending'.");
|
|
2061
|
+
}
|
|
2062
|
+
if (status === "pending") {
|
|
2063
|
+
const stats = storage.taskStats.getTaskStats(owner, repo);
|
|
2064
|
+
if (stats.todo >= 10) {
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
`Cannot create task as 'pending'. Maximum of 10 pending tasks reached. Please use status 'backlog' for new tasks instead.`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
const taskId = randomUUID3();
|
|
2071
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2072
|
+
const statusTimestamps = deriveTaskStatusTimestamps(status || "backlog", now);
|
|
2073
|
+
const finalTags = [...singleTask.tags || []];
|
|
2074
|
+
const phaseTag = `phase:${phase}`;
|
|
2075
|
+
if (!finalTags.includes(phaseTag)) {
|
|
2076
|
+
finalTags.push(phaseTag);
|
|
2077
|
+
}
|
|
2078
|
+
const task = {
|
|
2079
|
+
id: taskId,
|
|
2080
|
+
owner,
|
|
2081
|
+
repo,
|
|
2082
|
+
task_code: resolvedCode,
|
|
2083
|
+
phase,
|
|
2084
|
+
title,
|
|
2085
|
+
description,
|
|
2086
|
+
status: status || "backlog",
|
|
2087
|
+
priority: priority || 3,
|
|
2088
|
+
agent: agent || "unknown",
|
|
2089
|
+
role: role || "unknown",
|
|
2090
|
+
doc_path: doc_path || null,
|
|
2091
|
+
created_at: now,
|
|
2092
|
+
updated_at: now,
|
|
2093
|
+
in_progress_at: statusTimestamps.in_progress_at,
|
|
2094
|
+
finished_at: statusTimestamps.finished_at,
|
|
2095
|
+
canceled_at: statusTimestamps.canceled_at,
|
|
2096
|
+
est_tokens: est_tokens ?? 0,
|
|
2097
|
+
tags: finalTags,
|
|
2098
|
+
suggested_skills: singleTask.suggested_skills || [],
|
|
2099
|
+
commit_id: null,
|
|
2100
|
+
changed_files: [],
|
|
2101
|
+
metadata: metadata || {},
|
|
2102
|
+
parent_id: resolveParentId(parent_id, owner, repo, storage),
|
|
2103
|
+
depends_on: resolveDependsOn(depends_on, owner, repo, storage)
|
|
2104
|
+
};
|
|
2105
|
+
storage.tasks.insertTask(task);
|
|
2106
|
+
return createMcpResponse(
|
|
2107
|
+
{
|
|
2108
|
+
success: true,
|
|
2109
|
+
id: task.id,
|
|
2110
|
+
repo: task.repo,
|
|
2111
|
+
task_code: task.task_code,
|
|
2112
|
+
phase: task.phase,
|
|
2113
|
+
title: task.title,
|
|
2114
|
+
status: task.status,
|
|
2115
|
+
priority: task.priority,
|
|
2116
|
+
depends_on: task.depends_on
|
|
2117
|
+
},
|
|
2118
|
+
`Created task [${task.task_code}] ${task.title} in repo "${task.repo}" with status "${task.status}".`,
|
|
2119
|
+
{ includeSerializedStructuredContent: parsed.structured || false }
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
async function handleTaskCreateInteractive(args, storage, options = {}) {
|
|
2123
|
+
const partialTaskData = TaskCreateInteractiveSchema.parse(args ?? {});
|
|
2124
|
+
const inferredRepo = partialTaskData.repo ?? inferRepoFromSession(options.session);
|
|
2125
|
+
const draft = {
|
|
2126
|
+
...partialTaskData,
|
|
2127
|
+
repo: inferredRepo
|
|
2128
|
+
};
|
|
2129
|
+
const requestedSchema = buildMissingTaskSchema(draft);
|
|
2130
|
+
let completedDraft = draft;
|
|
2131
|
+
if (Object.keys(requestedSchema.properties).length > 0) {
|
|
2132
|
+
if (!options.session?.supportsElicitationForm || !options.elicit) {
|
|
2133
|
+
throw new Error("Client does not advertise MCP elicitation form support");
|
|
2134
|
+
}
|
|
2135
|
+
const elicited = extractAcceptedElicitationContent(
|
|
2136
|
+
await options.elicit({
|
|
2137
|
+
mode: "form",
|
|
2138
|
+
message: "Please complete the missing task details to create a new task.",
|
|
2139
|
+
requestedSchema
|
|
2140
|
+
})
|
|
2141
|
+
);
|
|
2142
|
+
completedDraft = {
|
|
2143
|
+
...draft,
|
|
2144
|
+
...elicited
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
return await handleTaskCreate(
|
|
2148
|
+
{
|
|
2149
|
+
...completedDraft,
|
|
2150
|
+
status: completedDraft.status ?? "backlog",
|
|
2151
|
+
priority: completedDraft.priority ?? 3,
|
|
2152
|
+
structured: true
|
|
2153
|
+
},
|
|
2154
|
+
storage
|
|
2155
|
+
);
|
|
2156
|
+
}
|
|
2157
|
+
function buildMissingTaskSchema(task) {
|
|
2158
|
+
const properties = {};
|
|
2159
|
+
const required = [];
|
|
2160
|
+
addRequiredStringField(properties, required, task, "repo", {
|
|
2161
|
+
title: "Repository",
|
|
2162
|
+
description: "Name of the repository for this task.",
|
|
2163
|
+
minLength: 1
|
|
2164
|
+
});
|
|
2165
|
+
addRequiredStringField(properties, required, task, "phase", {
|
|
2166
|
+
title: "Phase",
|
|
2167
|
+
description: "Project phase or milestone for this task.",
|
|
2168
|
+
minLength: 1
|
|
2169
|
+
});
|
|
2170
|
+
addRequiredStringField(properties, required, task, "title", {
|
|
2171
|
+
title: "Title",
|
|
2172
|
+
description: "Short task title.",
|
|
2173
|
+
minLength: 3,
|
|
2174
|
+
maxLength: 100
|
|
2175
|
+
});
|
|
2176
|
+
addRequiredStringField(properties, required, task, "description", {
|
|
2177
|
+
title: "Description",
|
|
2178
|
+
description: "Detailed description. MUST follow format: 1. Context & Analysis, 2. Step & Implementation, 3. Acceptance & Verification",
|
|
2179
|
+
minLength: 1
|
|
2180
|
+
});
|
|
2181
|
+
if (!task.status) {
|
|
2182
|
+
properties.status = {
|
|
2183
|
+
type: "string",
|
|
2184
|
+
title: "Status",
|
|
2185
|
+
description: "Initial task status.",
|
|
2186
|
+
enum: ["backlog", "pending"],
|
|
2187
|
+
default: "backlog"
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
if (task.priority === void 0) {
|
|
2191
|
+
properties.priority = {
|
|
2192
|
+
type: "integer",
|
|
2193
|
+
title: "Priority",
|
|
2194
|
+
description: "Task priority from 1 to 5.",
|
|
2195
|
+
minimum: 1,
|
|
2196
|
+
maximum: 5,
|
|
2197
|
+
default: 3
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
return {
|
|
2201
|
+
type: "object",
|
|
2202
|
+
properties,
|
|
2203
|
+
required
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
function addRequiredStringField(properties, required, task, field, schema) {
|
|
2207
|
+
if (typeof task[field] === "string" && task[field].trim()) {
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
properties[field] = {
|
|
2211
|
+
type: "string",
|
|
2212
|
+
...schema
|
|
2213
|
+
};
|
|
2214
|
+
required.push(field);
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
// src/mcp/tools/task.update.ts
|
|
2218
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2219
|
+
async function handleTaskUpdate(args, storage, vectors2) {
|
|
2220
|
+
const updateData = TaskUpdateSchema.parse(args);
|
|
2221
|
+
const { owner, repo, id, ids, comment, force, ...updates } = updateData;
|
|
2222
|
+
let resolvedId = id;
|
|
2223
|
+
if (!resolvedId && !ids && updates.task_code) {
|
|
2224
|
+
const found = storage.tasks.getTaskByCode(owner, repo, updates.task_code);
|
|
2225
|
+
if (!found) throw new Error(`Task not found: ${updates.task_code}`);
|
|
2226
|
+
resolvedId = found.id;
|
|
2227
|
+
}
|
|
2228
|
+
const targetIds = ids || (resolvedId ? [resolvedId] : []);
|
|
2229
|
+
if (targetIds.length === 0) {
|
|
2230
|
+
throw new Error("Either 'id' or 'ids' must be provided for update");
|
|
2231
|
+
}
|
|
2232
|
+
let updatedCount = 0;
|
|
2233
|
+
const updatedTasks = [];
|
|
2234
|
+
const completedTaskIds = [];
|
|
2235
|
+
let releasedClaims = 0;
|
|
2236
|
+
let expiredHandoffs = 0;
|
|
2237
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2238
|
+
const isStatusChangingGlobal = updates.status !== void 0;
|
|
2239
|
+
const existingTasks = storage.tasks.getTasksByIds(targetIds);
|
|
2240
|
+
const taskMap = new Map(existingTasks.map((t) => [t.id, t]));
|
|
2241
|
+
for (const targetId of targetIds) {
|
|
2242
|
+
const existingTask = taskMap.get(targetId);
|
|
2243
|
+
if (!existingTask) {
|
|
2244
|
+
if (id) throw new Error(`Task not found: ${targetId}`);
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
const isStatusChanging = isStatusChangingGlobal && updates.status !== existingTask.status;
|
|
2248
|
+
if (isStatusChanging && !force) {
|
|
2249
|
+
if (!comment || comment.trim() === "") {
|
|
2250
|
+
throw new Error("comment is required when changing task status");
|
|
2251
|
+
}
|
|
2252
|
+
const isStartable = existingTask.status === "backlog" || existingTask.status === "pending" || existingTask.status === "blocked";
|
|
2253
|
+
if (isStartable && updates.status === "completed") {
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
`Cannot transition task ${targetId} from '${existingTask.status}' directly to 'completed'. Must be 'in_progress' first.`
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2231
2258
|
}
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
const
|
|
2249
|
-
if (
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2259
|
+
if (updates.status === "completed" && isStatusChanging) {
|
|
2260
|
+
if (updates.est_tokens === void 0) {
|
|
2261
|
+
throw new Error("est_tokens is required when changing task status to completed");
|
|
2262
|
+
}
|
|
2263
|
+
const children = storage.tasks.getChildrenByParentId(targetId);
|
|
2264
|
+
const incompleteChildren = children.filter((c) => c.status !== "completed");
|
|
2265
|
+
if (incompleteChildren.length > 0) {
|
|
2266
|
+
const childList = incompleteChildren.map((c) => `[${c.task_code}] ${c.title} (${c.status})`).join("; ");
|
|
2267
|
+
throw new Error(
|
|
2268
|
+
`Cannot complete task [${existingTask.task_code}] "${existingTask.title}" \u2014 it has ${incompleteChildren.length} incomplete child task(s). Complete the following child task(s) first: ${childList}`
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
if (updates.task_code && storage.tasks.isTaskCodeDuplicate(owner, repo, updates.task_code, targetId)) {
|
|
2273
|
+
throw new Error(`Duplicate task_code: '${updates.task_code}' already exists`);
|
|
2274
|
+
}
|
|
2275
|
+
const finalUpdates = { ...updates };
|
|
2276
|
+
if (updates.parent_id !== void 0) {
|
|
2277
|
+
finalUpdates.parent_id = resolveParentId(updates.parent_id, owner, repo, storage);
|
|
2278
|
+
}
|
|
2279
|
+
if (updates.depends_on !== void 0) {
|
|
2280
|
+
finalUpdates.depends_on = resolveDependsOn(updates.depends_on, owner, repo, storage);
|
|
2281
|
+
}
|
|
2282
|
+
if (updates.phase !== void 0 || updates.tags !== void 0) {
|
|
2283
|
+
let currentTags = updates.tags || existingTask.tags || [];
|
|
2284
|
+
currentTags = currentTags.filter((t) => !t.startsWith("phase:"));
|
|
2285
|
+
const finalPhase = updates.phase !== void 0 ? updates.phase : existingTask.phase;
|
|
2286
|
+
if (finalPhase) {
|
|
2287
|
+
const phaseTag = `phase:${finalPhase}`;
|
|
2288
|
+
if (!currentTags.includes(phaseTag)) {
|
|
2289
|
+
currentTags.push(phaseTag);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
finalUpdates.tags = currentTags;
|
|
2293
|
+
}
|
|
2294
|
+
if (updates.status === "completed") {
|
|
2295
|
+
finalUpdates.finished_at = now;
|
|
2296
|
+
finalUpdates.commit_id = updates.commit_id;
|
|
2297
|
+
finalUpdates.changed_files = updates.changed_files;
|
|
2298
|
+
} else if (updates.status === "canceled") finalUpdates.canceled_at = now;
|
|
2299
|
+
else if (updates.status === "in_progress" && existingTask.status !== "in_progress")
|
|
2300
|
+
finalUpdates.in_progress_at = now;
|
|
2301
|
+
storage.tasks.updateTask(targetId, finalUpdates);
|
|
2302
|
+
if (comment !== void 0 || isStatusChanging) {
|
|
2303
|
+
storage.taskComments.insertTaskComment({
|
|
2304
|
+
id: randomUUID4(),
|
|
2305
|
+
task_id: targetId,
|
|
2306
|
+
owner,
|
|
2307
|
+
repo,
|
|
2308
|
+
comment: comment || `Status updated to ${updates.status}`,
|
|
2309
|
+
agent: updates.agent || existingTask.agent || "unknown",
|
|
2310
|
+
role: updates.role || existingTask.role || "unknown",
|
|
2311
|
+
model: updates.model || "unknown",
|
|
2312
|
+
previous_status: isStatusChanging ? existingTask.status : null,
|
|
2313
|
+
next_status: isStatusChanging ? updates.status : null,
|
|
2314
|
+
created_at: now
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
if (updates.status === "completed" && existingTask.status !== "completed") {
|
|
2318
|
+
completedTaskIds.push(targetId);
|
|
2319
|
+
}
|
|
2320
|
+
if (isStatusChanging && (updates.status === "completed" || updates.status === "canceled")) {
|
|
2321
|
+
releasedClaims += storage.handoffs.releaseClaimsForTask(targetId);
|
|
2322
|
+
expiredHandoffs += storage.handoffs.updatePendingHandoffsForTask(targetId, "expired");
|
|
2323
|
+
}
|
|
2324
|
+
updatedTasks.push({ id: targetId, code: updates.task_code || existingTask.task_code });
|
|
2325
|
+
updatedCount++;
|
|
2257
2326
|
}
|
|
2258
|
-
const
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
if (
|
|
2264
|
-
|
|
2265
|
-
if (validated.language !== void 0) updates.language = validated.language;
|
|
2266
|
-
if (validated.stack !== void 0) updates.stack = validated.stack;
|
|
2267
|
-
if (validated.repo !== void 0) updates.repo = validated.repo;
|
|
2268
|
-
if (validated.is_global !== void 0) updates.is_global = validated.is_global;
|
|
2269
|
-
if (validated.tags !== void 0) updates.tags = validated.tags;
|
|
2270
|
-
if (validated.metadata !== void 0) updates.metadata = validated.metadata;
|
|
2271
|
-
if (validated.agent !== void 0) updates.agent = validated.agent;
|
|
2272
|
-
if (validated.model !== void 0) updates.model = validated.model;
|
|
2273
|
-
db2.standards.update(resolvedId, updates);
|
|
2274
|
-
const merged = {
|
|
2275
|
-
...existing,
|
|
2276
|
-
...updates,
|
|
2277
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2278
|
-
};
|
|
2279
|
-
if (validated.name !== void 0 || validated.content !== void 0 || validated.context !== void 0 || validated.version !== void 0 || validated.language !== void 0 || validated.stack !== void 0 || validated.tags !== void 0 || validated.metadata !== void 0) {
|
|
2280
|
-
await vectors2.upsert(resolvedId, buildStandardVectorText(merged), "standard");
|
|
2327
|
+
const taskCodesStr = updatedTasks.length > 0 ? ` Tasks: ${updatedTasks.map((t) => t.code).join(", ")}.` : "";
|
|
2328
|
+
const fieldsStr = Object.keys(updates).length > 0 ? ` Fields: ${Object.keys(updates).join(", ")}.` : "";
|
|
2329
|
+
const isCompleted = updates.status === "completed" && updatedCount > 0;
|
|
2330
|
+
let summaryText = isCompleted ? `Updated ${updatedCount} task(s) in repo "${repo}". Task marked as completed with commit ${updates.commit_id} (${(updates.changed_files || []).length} files changed).` : `Updated ${updatedCount} task(s) in repo "${repo}".`;
|
|
2331
|
+
summaryText += `${taskCodesStr}${fieldsStr}`;
|
|
2332
|
+
if (releasedClaims || expiredHandoffs) {
|
|
2333
|
+
summaryText += ` Auto-closed coordination: released ${releasedClaims} claim(s), expired ${expiredHandoffs} handoff(s).`;
|
|
2281
2334
|
}
|
|
2282
|
-
|
|
2283
|
-
standardId: resolvedId,
|
|
2284
|
-
fields: Object.keys(updates)
|
|
2285
|
-
});
|
|
2286
|
-
return createMcpResponse(
|
|
2335
|
+
const response = createMcpResponse(
|
|
2287
2336
|
{
|
|
2288
2337
|
success: true,
|
|
2289
|
-
id:
|
|
2290
|
-
|
|
2291
|
-
|
|
2338
|
+
id: id || void 0,
|
|
2339
|
+
ids: ids || void 0,
|
|
2340
|
+
repo,
|
|
2341
|
+
status: updates.status,
|
|
2342
|
+
updatedCount,
|
|
2343
|
+
updatedFields: Object.keys(updates),
|
|
2344
|
+
coordinationCleanup: {
|
|
2345
|
+
releasedClaims,
|
|
2346
|
+
expiredHandoffs
|
|
2347
|
+
}
|
|
2292
2348
|
},
|
|
2293
|
-
|
|
2294
|
-
{
|
|
2295
|
-
structuredContentPathHint: "updatedFields",
|
|
2296
|
-
includeSerializedStructuredContent: true
|
|
2297
|
-
}
|
|
2349
|
+
summaryText,
|
|
2350
|
+
{ includeSerializedStructuredContent: updateData.structured }
|
|
2298
2351
|
);
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
storage.standards.incrementHitCounts([standard.id]);
|
|
2310
|
-
const lines = [
|
|
2311
|
-
`ID: ${standard.id}`,
|
|
2312
|
-
...standard.code ? [`Code: ${standard.code}`] : [],
|
|
2313
|
-
`Title: ${standard.title}`,
|
|
2314
|
-
`Parent ID: ${standard.parent_id || "-"}`,
|
|
2315
|
-
`Context: ${standard.context}`,
|
|
2316
|
-
`Version: ${standard.version}`,
|
|
2317
|
-
`Language: ${standard.language || "-"}`,
|
|
2318
|
-
`Scope: ${standard.is_global ? "global" : standard.repo || "-"}`,
|
|
2319
|
-
`Created: ${standard.created_at}`,
|
|
2320
|
-
`Updated: ${standard.updated_at}`
|
|
2321
|
-
];
|
|
2322
|
-
if (standard.stack.length > 0) lines.push(`Stack: ${standard.stack.join(", ")}`);
|
|
2323
|
-
if (standard.tags.length > 0) lines.push(`Tags: ${standard.tags.join(", ")}`);
|
|
2324
|
-
if (Object.keys(standard.metadata).length > 0) lines.push(`Metadata: ${JSON.stringify(standard.metadata)}`);
|
|
2325
|
-
if (standard.content) {
|
|
2326
|
-
lines.push("", "--- Content ---", standard.content);
|
|
2352
|
+
if (completedTaskIds.length > 0) {
|
|
2353
|
+
setImmediate(async () => {
|
|
2354
|
+
for (const taskId of completedTaskIds) {
|
|
2355
|
+
try {
|
|
2356
|
+
await archiveTaskToMemory(taskId, repo, storage, vectors2);
|
|
2357
|
+
} catch (err) {
|
|
2358
|
+
logger.error("Failed to archive task to memory", { taskId, error: String(err) });
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2327
2362
|
}
|
|
2328
|
-
|
|
2329
|
-
return createMcpResponse(standard, content, {
|
|
2330
|
-
contentSummary: content,
|
|
2331
|
-
includeSerializedStructuredContent: validated.structured
|
|
2332
|
-
});
|
|
2363
|
+
return response;
|
|
2333
2364
|
}
|
|
2334
2365
|
|
|
2335
|
-
// src/mcp/tools/
|
|
2336
|
-
async function
|
|
2337
|
-
const validated =
|
|
2338
|
-
const {
|
|
2366
|
+
// src/mcp/tools/task.delete.ts
|
|
2367
|
+
async function handleTaskDelete(args, storage) {
|
|
2368
|
+
const validated = TaskDeleteSchema.parse(args);
|
|
2369
|
+
const { owner, repo, id, ids, task_code } = validated;
|
|
2339
2370
|
const resolvedIds = [];
|
|
2340
2371
|
if (ids) resolvedIds.push(...ids);
|
|
2341
2372
|
if (id) resolvedIds.push(id);
|
|
2342
|
-
if (
|
|
2343
|
-
const
|
|
2344
|
-
if (!
|
|
2345
|
-
resolvedIds.push(
|
|
2346
|
-
}
|
|
2347
|
-
if (codes) {
|
|
2348
|
-
for (const c of codes) {
|
|
2349
|
-
const entry = db2.standards.getByCode(c, owner, repo);
|
|
2350
|
-
if (!entry) throw new Error(`Coding standard not found: ${c}`);
|
|
2351
|
-
resolvedIds.push(entry.id);
|
|
2352
|
-
}
|
|
2373
|
+
if (task_code) {
|
|
2374
|
+
const task = storage.tasks.getTaskByCode(owner, repo, task_code);
|
|
2375
|
+
if (!task) throw new Error(`Task not found: ${task_code}`);
|
|
2376
|
+
resolvedIds.push(task.id);
|
|
2353
2377
|
}
|
|
2354
2378
|
if (resolvedIds.length === 0) {
|
|
2355
|
-
throw new Error("Either 'id', 'ids',
|
|
2379
|
+
throw new Error("Either 'id', 'ids', or 'task_code' must be provided for deletion");
|
|
2356
2380
|
}
|
|
2357
2381
|
const targetIds = resolvedIds;
|
|
2358
|
-
|
|
2359
|
-
const
|
|
2360
|
-
let lastRepo = repo || "unknown";
|
|
2382
|
+
const tasksToDelete = storage.tasks.getTasksByIds(targetIds);
|
|
2383
|
+
const deletedCodes = tasksToDelete.map((t) => t.task_code);
|
|
2361
2384
|
for (const targetId of targetIds) {
|
|
2362
|
-
|
|
2363
|
-
if (existing) {
|
|
2364
|
-
lastRepo = existing.repo || (existing.is_global ? "global" : lastRepo);
|
|
2365
|
-
deletedTitles.push(existing.title);
|
|
2366
|
-
db2.standards.delete(targetId);
|
|
2367
|
-
await vectors2.remove(targetId, "standard");
|
|
2368
|
-
deletedCount++;
|
|
2369
|
-
} else if (id) {
|
|
2370
|
-
throw new Error(`Coding standard not found: ${targetId}`);
|
|
2371
|
-
}
|
|
2385
|
+
storage.tasks.deleteTask(targetId);
|
|
2372
2386
|
}
|
|
2373
|
-
|
|
2387
|
+
const deletedCodesStr = deletedCodes.length > 0 ? `: ${deletedCodes.join(", ")}` : "";
|
|
2374
2388
|
return createMcpResponse(
|
|
2375
2389
|
{
|
|
2376
2390
|
success: true,
|
|
2377
2391
|
id: id || void 0,
|
|
2378
2392
|
ids: ids || void 0,
|
|
2379
|
-
repo
|
|
2380
|
-
deletedCount,
|
|
2381
|
-
|
|
2393
|
+
repo,
|
|
2394
|
+
deletedCount: targetIds.length,
|
|
2395
|
+
deletedCodes
|
|
2382
2396
|
},
|
|
2383
|
-
`Deleted ${
|
|
2384
|
-
{
|
|
2385
|
-
structuredContentPathHint: "deletedCount",
|
|
2386
|
-
includeSerializedStructuredContent: structured
|
|
2387
|
-
}
|
|
2397
|
+
`Deleted ${targetIds.length} task(s) from repo "${repo}"${deletedCodesStr}.`,
|
|
2398
|
+
{ includeSerializedStructuredContent: validated.structured }
|
|
2388
2399
|
);
|
|
2389
2400
|
}
|
|
2390
2401
|
|
|
@@ -2820,7 +2831,7 @@ function normalizeToolArguments(args, session2) {
|
|
|
2820
2831
|
const parsed = parseRepoInput(repoVal2, void 0);
|
|
2821
2832
|
nextArgs.owner = parsed.owner || inferOwnerFromSession(session2) || "";
|
|
2822
2833
|
if (nextArgs.owner && !repoVal2.includes("/")) {
|
|
2823
|
-
|
|
2834
|
+
logger.warn(
|
|
2824
2835
|
`[router] owner inferred from session (${nextArgs.owner}) \u2014 may be incorrect. Agents should pass explicit owner/repo.`
|
|
2825
2836
|
);
|
|
2826
2837
|
}
|