mirror of
https://github.com/cjo4m06/mcp-shrimp-task-manager.git
synced 2025-07-26 07:52:25 +08:00
重構工具架構
This commit is contained in:
parent
3037d4eba1
commit
04f55cb2e3
12
src/index.ts
12
src/index.ts
@ -15,7 +15,7 @@ import fs from "fs";
|
||||
import fsPromises from "fs/promises";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
// 導入工具函數
|
||||
// 導入所有工具函數和 schema
|
||||
import {
|
||||
planTask,
|
||||
planTaskSchema,
|
||||
@ -41,19 +41,11 @@ import {
|
||||
queryTaskSchema,
|
||||
getTaskDetail,
|
||||
getTaskDetailSchema,
|
||||
} from "./tools/taskTools.js";
|
||||
|
||||
// 導入思維鏈工具
|
||||
import {
|
||||
processThought,
|
||||
processThoughtSchema,
|
||||
} from "./tools/thoughtChainTools.js";
|
||||
|
||||
// 導入專案工具
|
||||
import {
|
||||
initProjectRules,
|
||||
initProjectRulesSchema,
|
||||
} from "./tools/projectTools.js";
|
||||
} from "./tools/index.js";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
|
8
src/tools/index.ts
Normal file
8
src/tools/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// 導出所有任務工具
|
||||
export * from "./task/index.js";
|
||||
|
||||
// 導出所有專案工具
|
||||
export * from "./project/index.js";
|
||||
|
||||
// 導出所有思維鏈工具
|
||||
export * from "./thought/index.js";
|
7
src/tools/project/index.ts
Normal file
7
src/tools/project/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// 導出所有專案工具
|
||||
|
||||
// initProjectRules
|
||||
export {
|
||||
initProjectRules,
|
||||
initProjectRulesSchema,
|
||||
} from "./initProjectRules.js";
|
@ -1,6 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { getInitProjectRulesPrompt } from "../prompts/index.js";
|
||||
import { getRulesFilePath, ensureRulesFileExists } from "../utils/pathUtils.js";
|
||||
import { getInitProjectRulesPrompt } from "../../prompts/index.js";
|
||||
import {
|
||||
getRulesFilePath,
|
||||
ensureRulesFileExists,
|
||||
} from "../../utils/pathUtils.js";
|
||||
|
||||
// 定義schema
|
||||
export const initProjectRulesSchema = z.object({});
|
49
src/tools/task/analyzeTask.ts
Normal file
49
src/tools/task/analyzeTask.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { z } from "zod";
|
||||
import { getAnalyzeTaskPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 分析問題工具
|
||||
export const analyzeTaskSchema = z.object({
|
||||
summary: z
|
||||
.string()
|
||||
.min(10, {
|
||||
message: "任務摘要不能少於10個字符,請提供更詳細的描述以確保任務目標明確",
|
||||
})
|
||||
.describe(
|
||||
"結構化的任務摘要,包含任務目標、範圍與關鍵技術挑戰,最少10個字符"
|
||||
),
|
||||
initialConcept: z
|
||||
.string()
|
||||
.min(50, {
|
||||
message:
|
||||
"初步解答構想不能少於50個字符,請提供更詳細的內容確保技術方案清晰",
|
||||
})
|
||||
.describe(
|
||||
"最少50個字符的初步解答構想,包含技術方案、架構設計和實施策略,如果需要提供程式碼請使用 pseudocode 格式且僅提供高級邏輯流程和關鍵步驟避免完整代碼"
|
||||
),
|
||||
previousAnalysis: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("前次迭代的分析結果,用於持續改進方案(僅在重新分析時需提供)"),
|
||||
});
|
||||
|
||||
export async function analyzeTask({
|
||||
summary,
|
||||
initialConcept,
|
||||
previousAnalysis,
|
||||
}: z.infer<typeof analyzeTaskSchema>) {
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getAnalyzeTaskPrompt({
|
||||
summary,
|
||||
initialConcept,
|
||||
previousAnalysis,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
63
src/tools/task/clearAllTasks.ts
Normal file
63
src/tools/task/clearAllTasks.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getAllTasks,
|
||||
clearAllTasks as modelClearAllTasks,
|
||||
} from "../../models/taskModel.js";
|
||||
import { getClearAllTasksPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 清除所有任務工具
|
||||
export const clearAllTasksSchema = z.object({
|
||||
confirm: z
|
||||
.boolean()
|
||||
.refine((val) => val === true, {
|
||||
message:
|
||||
"必須明確確認清除操作,請將 confirm 參數設置為 true 以確認此危險操作",
|
||||
})
|
||||
.describe("確認刪除所有未完成的任務(此操作不可逆)"),
|
||||
});
|
||||
|
||||
export async function clearAllTasks({
|
||||
confirm,
|
||||
}: z.infer<typeof clearAllTasksSchema>) {
|
||||
// 安全檢查:如果沒有確認,則拒絕操作
|
||||
if (!confirm) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getClearAllTasksPrompt({ confirm: false }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 檢查是否真的有任務需要清除
|
||||
const allTasks = await getAllTasks();
|
||||
if (allTasks.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getClearAllTasksPrompt({ isEmpty: true }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 執行清除操作
|
||||
const result = await modelClearAllTasks();
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getClearAllTasksPrompt({
|
||||
success: result.success,
|
||||
message: result.message,
|
||||
backupFile: result.backupFile,
|
||||
}),
|
||||
},
|
||||
],
|
||||
isError: !result.success,
|
||||
};
|
||||
}
|
60
src/tools/task/deleteTask.ts
Normal file
60
src/tools/task/deleteTask.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getTaskById,
|
||||
deleteTask as modelDeleteTask,
|
||||
} from "../../models/taskModel.js";
|
||||
import { TaskStatus } from "../../types/index.js";
|
||||
import { getDeleteTaskPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 刪除任務工具
|
||||
export const deleteTaskSchema = z.object({
|
||||
taskId: z
|
||||
.string()
|
||||
.uuid({ message: "任務ID格式無效,請提供有效的UUID格式" })
|
||||
.describe("待刪除任務的唯一標識符,必須是系統中存在且未完成的任務ID"),
|
||||
});
|
||||
|
||||
export async function deleteTask({ taskId }: z.infer<typeof deleteTaskSchema>) {
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getDeleteTaskPrompt({ taskId }),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (task.status === TaskStatus.COMPLETED) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getDeleteTaskPrompt({ taskId, task, isTaskCompleted: true }),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await modelDeleteTask(taskId);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getDeleteTaskPrompt({
|
||||
taskId,
|
||||
task,
|
||||
success: result.success,
|
||||
message: result.message,
|
||||
}),
|
||||
},
|
||||
],
|
||||
isError: !result.success,
|
||||
};
|
||||
}
|
155
src/tools/task/executeTask.ts
Normal file
155
src/tools/task/executeTask.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getTaskById,
|
||||
updateTaskStatus,
|
||||
canExecuteTask,
|
||||
assessTaskComplexity,
|
||||
} from "../../models/taskModel.js";
|
||||
import { TaskStatus, Task } from "../../types/index.js";
|
||||
import { getExecuteTaskPrompt } from "../../prompts/index.js";
|
||||
import { loadTaskRelatedFiles } from "../../utils/fileLoader.js";
|
||||
|
||||
// 執行任務工具
|
||||
export const executeTaskSchema = z.object({
|
||||
taskId: z
|
||||
.string()
|
||||
.uuid({
|
||||
message: "任務ID必須是有效的UUID格式",
|
||||
})
|
||||
.describe("待執行任務的唯一標識符,必須是系統中存在的有效任務ID"),
|
||||
});
|
||||
|
||||
export async function executeTask({
|
||||
taskId,
|
||||
}: z.infer<typeof executeTaskSchema>) {
|
||||
try {
|
||||
// 檢查任務是否存在
|
||||
const task = await getTaskById(taskId);
|
||||
if (!task) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `找不到ID為 \`${taskId}\` 的任務。請確認ID是否正確。`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 檢查任務是否可以執行(依賴任務都已完成)
|
||||
const executionCheck = await canExecuteTask(taskId);
|
||||
if (!executionCheck.canExecute) {
|
||||
const blockedByTasksText =
|
||||
executionCheck.blockedBy && executionCheck.blockedBy.length > 0
|
||||
? `被以下未完成的依賴任務阻擋: ${executionCheck.blockedBy.join(", ")}`
|
||||
: "無法確定阻擋原因";
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `任務 "${task.name}" (ID: \`${taskId}\`) 目前無法執行。${blockedByTasksText}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 如果任務已經標記為「進行中」,提示用戶
|
||||
if (task.status === TaskStatus.IN_PROGRESS) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `任務 "${task.name}" (ID: \`${taskId}\`) 已經處於進行中狀態。`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 如果任務已經標記為「已完成」,提示用戶
|
||||
if (task.status === TaskStatus.COMPLETED) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `任務 "${task.name}" (ID: \`${taskId}\`) 已經標記為完成。如需重新執行,請先使用 delete_task 刪除該任務並重新創建。`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 更新任務狀態為「進行中」
|
||||
await updateTaskStatus(taskId, TaskStatus.IN_PROGRESS);
|
||||
|
||||
// 評估任務複雜度
|
||||
const complexityResult = await assessTaskComplexity(taskId);
|
||||
|
||||
// 將複雜度結果轉換為適當的格式
|
||||
const complexityAssessment = complexityResult
|
||||
? {
|
||||
level: complexityResult.level,
|
||||
metrics: {
|
||||
descriptionLength: complexityResult.metrics.descriptionLength,
|
||||
dependenciesCount: complexityResult.metrics.dependenciesCount,
|
||||
},
|
||||
recommendations: complexityResult.recommendations,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// 獲取依賴任務,用於顯示完成摘要
|
||||
const dependencyTasks: Task[] = [];
|
||||
if (task.dependencies && task.dependencies.length > 0) {
|
||||
for (const dep of task.dependencies) {
|
||||
const depTask = await getTaskById(dep.taskId);
|
||||
if (depTask) {
|
||||
dependencyTasks.push(depTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加載任務相關的文件內容
|
||||
let relatedFilesSummary = "";
|
||||
if (task.relatedFiles && task.relatedFiles.length > 0) {
|
||||
try {
|
||||
const relatedFilesResult = await loadTaskRelatedFiles(
|
||||
task.relatedFiles
|
||||
);
|
||||
relatedFilesSummary =
|
||||
typeof relatedFilesResult === "string"
|
||||
? relatedFilesResult
|
||||
: relatedFilesResult.summary || "";
|
||||
} catch (error) {
|
||||
relatedFilesSummary =
|
||||
"Error loading related files, please check the files manually.";
|
||||
}
|
||||
}
|
||||
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getExecuteTaskPrompt({
|
||||
task,
|
||||
complexityAssessment,
|
||||
relatedFilesSummary,
|
||||
dependencyTasks,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `執行任務時發生錯誤: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
70
src/tools/task/getTaskDetail.ts
Normal file
70
src/tools/task/getTaskDetail.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { z } from "zod";
|
||||
import { searchTasksWithCommand } from "../../models/taskModel.js";
|
||||
import { getGetTaskDetailPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 取得完整任務詳情的參數
|
||||
export const getTaskDetailSchema = z.object({
|
||||
taskId: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "任務ID不能為空,請提供有效的任務ID",
|
||||
})
|
||||
.describe("欲檢視詳情的任務ID"),
|
||||
});
|
||||
|
||||
// 取得任務完整詳情
|
||||
export async function getTaskDetail({
|
||||
taskId,
|
||||
}: z.infer<typeof getTaskDetailSchema>) {
|
||||
try {
|
||||
// 使用 searchTasksWithCommand 替代 getTaskById,實現記憶區任務搜索
|
||||
// 設置 isId 為 true,表示按 ID 搜索;頁碼為 1,每頁大小為 1
|
||||
const result = await searchTasksWithCommand(taskId, true, 1, 1);
|
||||
|
||||
// 檢查是否找到任務
|
||||
if (result.tasks.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請確認任務ID是否正確。`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
// 獲取找到的任務(第一個也是唯一的一個)
|
||||
const task = result.tasks[0];
|
||||
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getGetTaskDetailPrompt({
|
||||
taskId,
|
||||
task,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
// 使用prompt生成器獲取錯誤訊息
|
||||
const errorPrompt = getGetTaskDetailPrompt({
|
||||
taskId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: errorPrompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
37
src/tools/task/index.ts
Normal file
37
src/tools/task/index.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// 導出所有任務工具
|
||||
|
||||
// planTask
|
||||
export { planTask, planTaskSchema } from "./planTask.js";
|
||||
|
||||
// analyzeTask
|
||||
export { analyzeTask, analyzeTaskSchema } from "./analyzeTask.js";
|
||||
|
||||
// reflectTask
|
||||
export { reflectTask, reflectTaskSchema } from "./reflectTask.js";
|
||||
|
||||
// splitTasks
|
||||
export { splitTasks, splitTasksSchema } from "./splitTasks.js";
|
||||
|
||||
// listTasks
|
||||
export { listTasks, listTasksSchema } from "./listTasks.js";
|
||||
|
||||
// executeTask
|
||||
export { executeTask, executeTaskSchema } from "./executeTask.js";
|
||||
|
||||
// verifyTask
|
||||
export { verifyTask, verifyTaskSchema } from "./verifyTask.js";
|
||||
|
||||
// deleteTask
|
||||
export { deleteTask, deleteTaskSchema } from "./deleteTask.js";
|
||||
|
||||
// clearAllTasks
|
||||
export { clearAllTasks, clearAllTasksSchema } from "./clearAllTasks.js";
|
||||
|
||||
// updateTaskContent
|
||||
export { updateTaskContent, updateTaskContentSchema } from "./updateTask.js";
|
||||
|
||||
// queryTask
|
||||
export { queryTask, queryTaskSchema } from "./queryTask.js";
|
||||
|
||||
// getTaskDetail
|
||||
export { getTaskDetail, getTaskDetailSchema } from "./getTaskDetail.js";
|
72
src/tools/task/listTasks.ts
Normal file
72
src/tools/task/listTasks.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { z } from "zod";
|
||||
import { getAllTasks } from "../../models/taskModel.js";
|
||||
import { TaskStatus } from "../../types/index.js";
|
||||
import { getListTasksPrompt } from "../../prompts/index.js";
|
||||
|
||||
export const listTasksSchema = z.object({
|
||||
status: z
|
||||
.enum(["all", "pending", "in_progress", "completed"])
|
||||
.describe("要列出的任務狀態,可選擇 'all' 列出所有任務,或指定具體狀態"),
|
||||
});
|
||||
|
||||
// 列出任務工具
|
||||
export async function listTasks({ status }: z.infer<typeof listTasksSchema>) {
|
||||
const tasks = await getAllTasks();
|
||||
let filteredTasks = tasks;
|
||||
switch (status) {
|
||||
case "all":
|
||||
break;
|
||||
case "pending":
|
||||
filteredTasks = tasks.filter(
|
||||
(task) => task.status === TaskStatus.PENDING
|
||||
);
|
||||
break;
|
||||
case "in_progress":
|
||||
filteredTasks = tasks.filter(
|
||||
(task) => task.status === TaskStatus.IN_PROGRESS
|
||||
);
|
||||
break;
|
||||
case "completed":
|
||||
filteredTasks = tasks.filter(
|
||||
(task) => task.status === TaskStatus.COMPLETED
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (filteredTasks.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 系統通知\n\n目前系統中沒有${
|
||||
status === "all" ? "任何" : `任何 ${status} 的`
|
||||
}任務。請查詢其他狀態任務或先使用「split_tasks」工具創建任務結構,再進行後續操作。`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const tasksByStatus = tasks.reduce((acc, task) => {
|
||||
if (!acc[task.status]) {
|
||||
acc[task.status] = [];
|
||||
}
|
||||
acc[task.status].push(task);
|
||||
return acc;
|
||||
}, {} as Record<string, typeof tasks>);
|
||||
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getListTasksPrompt({
|
||||
status,
|
||||
tasks: tasksByStatus,
|
||||
allTasks: filteredTasks,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
76
src/tools/task/planTask.ts
Normal file
76
src/tools/task/planTask.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { z } from "zod";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { getAllTasks } from "../../models/taskModel.js";
|
||||
import { TaskStatus, Task } from "../../types/index.js";
|
||||
import { getPlanTaskPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 開始規劃工具
|
||||
export const planTaskSchema = z.object({
|
||||
description: z
|
||||
.string()
|
||||
.min(10, {
|
||||
message: "任務描述不能少於10個字符,請提供更詳細的描述以確保任務目標明確",
|
||||
})
|
||||
.describe("完整詳細的任務問題描述,應包含任務目標、背景及預期成果"),
|
||||
requirements: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("任務的特定技術要求、業務約束條件或品質標準(選填)"),
|
||||
existingTasksReference: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe("是否參考現有任務作為規劃基礎,用於任務調整和延續性規劃"),
|
||||
});
|
||||
|
||||
export async function planTask({
|
||||
description,
|
||||
requirements,
|
||||
existingTasksReference = false,
|
||||
}: z.infer<typeof planTaskSchema>) {
|
||||
// 獲取基礎目錄路徑
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const PROJECT_ROOT = path.resolve(__dirname, "../../..");
|
||||
const DATA_DIR = process.env.DATA_DIR || path.join(PROJECT_ROOT, "data");
|
||||
const MEMORY_DIR = path.join(DATA_DIR, "memory");
|
||||
|
||||
// 準備所需參數
|
||||
let completedTasks: Task[] = [];
|
||||
let pendingTasks: Task[] = [];
|
||||
|
||||
// 當 existingTasksReference 為 true 時,從數據庫中載入所有任務作為參考
|
||||
if (existingTasksReference) {
|
||||
try {
|
||||
const allTasks = await getAllTasks();
|
||||
|
||||
// 將任務分為已完成和未完成兩類
|
||||
completedTasks = allTasks.filter(
|
||||
(task) => task.status === TaskStatus.COMPLETED
|
||||
);
|
||||
pendingTasks = allTasks.filter(
|
||||
(task) => task.status !== TaskStatus.COMPLETED
|
||||
);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getPlanTaskPrompt({
|
||||
description,
|
||||
requirements,
|
||||
existingTasksReference,
|
||||
completedTasks,
|
||||
pendingTasks,
|
||||
memoryDir: MEMORY_DIR,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
78
src/tools/task/queryTask.ts
Normal file
78
src/tools/task/queryTask.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { z } from "zod";
|
||||
import { searchTasksWithCommand } from "../../models/taskModel.js";
|
||||
import { getQueryTaskPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 查詢任務工具
|
||||
export const queryTaskSchema = z.object({
|
||||
query: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "查詢內容不能為空,請提供任務ID或搜尋關鍵字",
|
||||
})
|
||||
.describe("搜尋查詢文字,可以是任務ID或多個關鍵字(空格分隔)"),
|
||||
isId: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe("指定是否為ID查詢模式,默認為否(關鍵字模式)"),
|
||||
page: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.default(1)
|
||||
.describe("分頁頁碼,默認為第1頁"),
|
||||
pageSize: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.min(1)
|
||||
.max(20)
|
||||
.optional()
|
||||
.default(5)
|
||||
.describe("每頁顯示的任務數量,默認為5筆,最大20筆"),
|
||||
});
|
||||
|
||||
export async function queryTask({
|
||||
query,
|
||||
isId = false,
|
||||
page = 1,
|
||||
pageSize = 3,
|
||||
}: z.infer<typeof queryTaskSchema>) {
|
||||
try {
|
||||
// 使用系統指令搜尋函數
|
||||
const results = await searchTasksWithCommand(query, isId, page, pageSize);
|
||||
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getQueryTaskPrompt({
|
||||
query,
|
||||
isId,
|
||||
tasks: results.tasks,
|
||||
totalTasks: results.pagination.totalResults,
|
||||
page: results.pagination.currentPage,
|
||||
pageSize,
|
||||
totalPages: results.pagination.totalPages,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 系統錯誤\n\n查詢任務時發生錯誤: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
}
|
40
src/tools/task/reflectTask.ts
Normal file
40
src/tools/task/reflectTask.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { z } from "zod";
|
||||
import { getReflectTaskPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 反思構想工具
|
||||
export const reflectTaskSchema = z.object({
|
||||
summary: z
|
||||
.string()
|
||||
.min(10, {
|
||||
message: "任務摘要不能少於10個字符,請提供更詳細的描述以確保任務目標明確",
|
||||
})
|
||||
.describe("結構化的任務摘要,保持與分析階段一致以確保連續性"),
|
||||
analysis: z
|
||||
.string()
|
||||
.min(100, {
|
||||
message: "技術分析內容不夠詳盡,請提供完整的技術分析和實施方案",
|
||||
})
|
||||
.describe(
|
||||
"完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案,如果需要提供程式碼請使用 pseudocode 格式且僅提供高級邏輯流程和關鍵步驟避免完整代碼"
|
||||
),
|
||||
});
|
||||
|
||||
export async function reflectTask({
|
||||
summary,
|
||||
analysis,
|
||||
}: z.infer<typeof reflectTaskSchema>) {
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getReflectTaskPrompt({
|
||||
summary,
|
||||
analysis,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
247
src/tools/task/splitTasks.ts
Normal file
247
src/tools/task/splitTasks.ts
Normal file
@ -0,0 +1,247 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getAllTasks,
|
||||
batchCreateOrUpdateTasks,
|
||||
clearAllTasks as modelClearAllTasks,
|
||||
} from "../../models/taskModel.js";
|
||||
import { RelatedFileType, Task } from "../../types/index.js";
|
||||
import { getSplitTasksPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 拆分任務工具
|
||||
export const splitTasksSchema = z.object({
|
||||
updateMode: z
|
||||
.enum(["append", "overwrite", "selective", "clearAllTasks"])
|
||||
.describe(
|
||||
"任務更新模式選擇:'append'(保留所有現有任務並添加新任務)、'overwrite'(清除所有未完成任務並完全替換,保留已完成任務)、'selective'(智能更新:根據任務名稱匹配更新現有任務,保留不在列表中的任務,推薦用於任務微調)、'clearAllTasks'(清除所有任務並創建備份)。\n預設為'clearAllTasks'模式,只有用戶要求變更或修改計劃內容才使用其他模式"
|
||||
),
|
||||
tasks: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z
|
||||
.string()
|
||||
.max(100, {
|
||||
message: "任務名稱過長,請限制在100個字符以內",
|
||||
})
|
||||
.describe("簡潔明確的任務名稱,應能清晰表達任務目的"),
|
||||
description: z
|
||||
.string()
|
||||
.min(10, {
|
||||
message: "任務描述過短,請提供更詳細的內容以確保理解",
|
||||
})
|
||||
.describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"),
|
||||
implementationGuide: z
|
||||
.string()
|
||||
.describe(
|
||||
"此特定任務的具體實現方法和步驟,請參考之前的分析結果提供精簡pseudocode"
|
||||
),
|
||||
dependencies: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe(
|
||||
"此任務依賴的前置任務ID或任務名稱列表,支持兩種引用方式,名稱引用更直觀,是一個字串陣列"
|
||||
),
|
||||
notes: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("補充說明、特殊處理要求或實施建議(選填)"),
|
||||
relatedFiles: z
|
||||
.array(
|
||||
z.object({
|
||||
path: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "文件路徑不能為空",
|
||||
})
|
||||
.describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"),
|
||||
type: z
|
||||
.nativeEnum(RelatedFileType)
|
||||
.describe(
|
||||
"文件類型 (TO_MODIFY: 待修改, REFERENCE: 參考資料, CREATE: 待建立, DEPENDENCY: 依賴文件, OTHER: 其他)"
|
||||
),
|
||||
description: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: "文件描述不能為空",
|
||||
})
|
||||
.describe("文件描述,用於說明文件的用途和內容"),
|
||||
lineStart: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe("相關代碼區塊的起始行(選填)"),
|
||||
lineEnd: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe("相關代碼區塊的結束行(選填)"),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.describe(
|
||||
"與任務相關的文件列表,用於記錄與任務相關的代碼文件、參考資料、要建立的文件等(選填)"
|
||||
),
|
||||
verificationCriteria: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("此特定任務的驗證標準和檢驗方法"),
|
||||
})
|
||||
)
|
||||
.min(1, {
|
||||
message: "請至少提供一個任務",
|
||||
})
|
||||
.describe(
|
||||
"結構化的任務清單,每個任務應保持原子性且有明確的完成標準,避免過於簡單的任務,簡單修改可與其他任務整合,避免任務過多"
|
||||
),
|
||||
globalAnalysisResult: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"全局分析結果:來自 reflect_task 的完整分析結果,適用於所有任務的通用部分"
|
||||
),
|
||||
});
|
||||
|
||||
export async function splitTasks({
|
||||
updateMode,
|
||||
tasks,
|
||||
globalAnalysisResult,
|
||||
}: z.infer<typeof splitTasksSchema>) {
|
||||
try {
|
||||
// 檢查 tasks 裡面的 name 是否有重複
|
||||
const nameSet = new Set();
|
||||
for (const task of tasks) {
|
||||
if (nameSet.has(task.name)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: "tasks 參數中存在重複的任務名稱,請確保每個任務名稱是唯一的",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
nameSet.add(task.name);
|
||||
}
|
||||
|
||||
// 根據不同的更新模式處理任務
|
||||
let message = "";
|
||||
let actionSuccess = true;
|
||||
let backupFile = null;
|
||||
let createdTasks: Task[] = [];
|
||||
let allTasks: Task[] = [];
|
||||
|
||||
// 將任務資料轉換為符合batchCreateOrUpdateTasks的格式
|
||||
const convertedTasks = tasks.map((task) => ({
|
||||
name: task.name,
|
||||
description: task.description,
|
||||
notes: task.notes,
|
||||
dependencies: task.dependencies,
|
||||
implementationGuide: task.implementationGuide,
|
||||
verificationCriteria: task.verificationCriteria,
|
||||
relatedFiles: task.relatedFiles?.map((file) => ({
|
||||
path: file.path,
|
||||
type: file.type as RelatedFileType,
|
||||
description: file.description,
|
||||
lineStart: file.lineStart,
|
||||
lineEnd: file.lineEnd,
|
||||
})),
|
||||
}));
|
||||
|
||||
// 處理 clearAllTasks 模式
|
||||
if (updateMode === "clearAllTasks") {
|
||||
const clearResult = await modelClearAllTasks();
|
||||
|
||||
if (clearResult.success) {
|
||||
message = clearResult.message;
|
||||
backupFile = clearResult.backupFile;
|
||||
|
||||
try {
|
||||
// 清空任務後再創建新任務
|
||||
createdTasks = await batchCreateOrUpdateTasks(
|
||||
convertedTasks,
|
||||
"append",
|
||||
globalAnalysisResult
|
||||
);
|
||||
message += `\n成功創建了 ${createdTasks.length} 個新任務。`;
|
||||
} catch (error) {
|
||||
actionSuccess = false;
|
||||
message += `\n創建新任務時發生錯誤: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`;
|
||||
}
|
||||
} else {
|
||||
actionSuccess = false;
|
||||
message = clearResult.message;
|
||||
}
|
||||
} else {
|
||||
// 對於其他模式,直接使用 batchCreateOrUpdateTasks
|
||||
try {
|
||||
createdTasks = await batchCreateOrUpdateTasks(
|
||||
convertedTasks,
|
||||
updateMode,
|
||||
globalAnalysisResult
|
||||
);
|
||||
|
||||
// 根據不同的更新模式生成消息
|
||||
switch (updateMode) {
|
||||
case "append":
|
||||
message = `成功追加了 ${createdTasks.length} 個新任務。`;
|
||||
break;
|
||||
case "overwrite":
|
||||
message = `成功清除未完成任務並創建了 ${createdTasks.length} 個新任務。`;
|
||||
break;
|
||||
case "selective":
|
||||
message = `成功選擇性更新/創建了 ${createdTasks.length} 個任務。`;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
actionSuccess = false;
|
||||
message = `任務創建失敗:${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取所有任務用於顯示依賴關係
|
||||
try {
|
||||
allTasks = await getAllTasks();
|
||||
} catch (error) {
|
||||
allTasks = [...createdTasks]; // 如果獲取失敗,至少使用剛創建的任務
|
||||
}
|
||||
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getSplitTasksPrompt({
|
||||
updateMode,
|
||||
createdTasks,
|
||||
allTasks,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
ephemeral: {
|
||||
taskCreationResult: {
|
||||
success: actionSuccess,
|
||||
message,
|
||||
backupFilePath: backupFile,
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text:
|
||||
"執行任務拆分時發生錯誤: " +
|
||||
(error instanceof Error ? error.message : String(error)),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
175
src/tools/task/updateTask.ts
Normal file
175
src/tools/task/updateTask.ts
Normal file
@ -0,0 +1,175 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getTaskById,
|
||||
updateTaskContent as modelUpdateTaskContent,
|
||||
} from "../../models/taskModel.js";
|
||||
import { RelatedFileType } from "../../types/index.js";
|
||||
import { getUpdateTaskContentPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 更新任務內容工具
|
||||
export const updateTaskContentSchema = z.object({
|
||||
taskId: z
|
||||
.string()
|
||||
.uuid({ message: "任務ID格式無效,請提供有效的UUID格式" })
|
||||
.describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"),
|
||||
name: z.string().optional().describe("任務的新名稱(選填)"),
|
||||
description: z.string().optional().describe("任務的新描述內容(選填)"),
|
||||
notes: z.string().optional().describe("任務的新補充說明(選填)"),
|
||||
dependencies: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe("任務的新依賴關係(選填)"),
|
||||
relatedFiles: z
|
||||
.array(
|
||||
z.object({
|
||||
path: z
|
||||
.string()
|
||||
.min(1, { message: "文件路徑不能為空,請提供有效的文件路徑" })
|
||||
.describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"),
|
||||
type: z
|
||||
.nativeEnum(RelatedFileType)
|
||||
.describe(
|
||||
"文件與任務的關係類型 (TO_MODIFY, REFERENCE, CREATE, DEPENDENCY, OTHER)"
|
||||
),
|
||||
description: z.string().optional().describe("文件的補充描述(選填)"),
|
||||
lineStart: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe("相關代碼區塊的起始行(選填)"),
|
||||
lineEnd: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe("相關代碼區塊的結束行(選填)"),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.describe(
|
||||
"與任務相關的文件列表,用於記錄與任務相關的代碼文件、參考資料、要建立的檔案等(選填)"
|
||||
),
|
||||
implementationGuide: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("任務的新實現指南(選填)"),
|
||||
verificationCriteria: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("任務的新驗證標準(選填)"),
|
||||
});
|
||||
|
||||
export async function updateTaskContent({
|
||||
taskId,
|
||||
name,
|
||||
description,
|
||||
notes,
|
||||
relatedFiles,
|
||||
dependencies,
|
||||
implementationGuide,
|
||||
verificationCriteria,
|
||||
}: z.infer<typeof updateTaskContentSchema>) {
|
||||
if (relatedFiles) {
|
||||
for (const file of relatedFiles) {
|
||||
if (
|
||||
(file.lineStart && !file.lineEnd) ||
|
||||
(!file.lineStart && file.lineEnd) ||
|
||||
(file.lineStart && file.lineEnd && file.lineStart > file.lineEnd)
|
||||
) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getUpdateTaskContentPrompt({
|
||||
taskId,
|
||||
validationError:
|
||||
"行號設置無效:必須同時設置起始行和結束行,且起始行必須小於結束行",
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!(
|
||||
name ||
|
||||
description ||
|
||||
notes ||
|
||||
dependencies ||
|
||||
implementationGuide ||
|
||||
verificationCriteria ||
|
||||
relatedFiles
|
||||
)
|
||||
) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getUpdateTaskContentPrompt({
|
||||
taskId,
|
||||
emptyUpdate: true,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// 獲取任務以檢查它是否存在
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getUpdateTaskContentPrompt({
|
||||
taskId,
|
||||
}),
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
// 記錄要更新的任務和內容
|
||||
let updateSummary = `準備更新任務:${task.name} (ID: ${task.id})`;
|
||||
if (name) updateSummary += `,新名稱:${name}`;
|
||||
if (description) updateSummary += `,更新描述`;
|
||||
if (notes) updateSummary += `,更新注記`;
|
||||
if (relatedFiles)
|
||||
updateSummary += `,更新相關文件 (${relatedFiles.length} 個)`;
|
||||
if (dependencies)
|
||||
updateSummary += `,更新依賴關係 (${dependencies.length} 個)`;
|
||||
if (implementationGuide) updateSummary += `,更新實現指南`;
|
||||
if (verificationCriteria) updateSummary += `,更新驗證標準`;
|
||||
|
||||
// 執行更新操作
|
||||
const result = await modelUpdateTaskContent(taskId, {
|
||||
name,
|
||||
description,
|
||||
notes,
|
||||
relatedFiles,
|
||||
dependencies,
|
||||
implementationGuide,
|
||||
verificationCriteria,
|
||||
});
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: getUpdateTaskContentPrompt({
|
||||
taskId,
|
||||
task,
|
||||
success: result.success,
|
||||
message: result.message,
|
||||
updatedTask: result.task,
|
||||
}),
|
||||
},
|
||||
],
|
||||
isError: !result.success,
|
||||
};
|
||||
}
|
79
src/tools/task/verifyTask.ts
Normal file
79
src/tools/task/verifyTask.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getTaskById,
|
||||
updateTaskStatus,
|
||||
updateTaskSummary,
|
||||
} from "../../models/taskModel.js";
|
||||
import { TaskStatus } from "../../types/index.js";
|
||||
import { getVerifyTaskPrompt } from "../../prompts/index.js";
|
||||
|
||||
// 檢驗任務工具
|
||||
export const verifyTaskSchema = z.object({
|
||||
taskId: z
|
||||
.string()
|
||||
.uuid({ message: "任務ID格式無效,請提供有效的UUID格式" })
|
||||
.describe("待驗證任務的唯一標識符,必須是系統中存在的有效任務ID"),
|
||||
summary: z
|
||||
.string()
|
||||
.min(30, {
|
||||
message: "最少30個字",
|
||||
})
|
||||
.describe(
|
||||
"當分數高於或等於 80分時代表任務完成摘要,簡潔描述實施結果和重要決策,當分數低於 80分時代表缺失或需要修正的部分說明,最少30個字"
|
||||
),
|
||||
score: z
|
||||
.number()
|
||||
.min(0, { message: "分數不能小於0" })
|
||||
.max(100, { message: "分數不能大於100" })
|
||||
.describe("針對任務的評分,當評分等於或超過80分時自動完成任務"),
|
||||
});
|
||||
|
||||
export async function verifyTask({
|
||||
taskId,
|
||||
summary,
|
||||
score,
|
||||
}: z.infer<typeof verifyTaskSchema>) {
|
||||
const task = await getTaskById(taskId);
|
||||
|
||||
if (!task) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (task.status !== TaskStatus.IN_PROGRESS) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `## 狀態錯誤\n\n任務 "${task.name}" (ID: \`${task.id}\`) 當前狀態為 "${task.status}",不處於進行中狀態,無法進行檢驗。\n\n只有狀態為「進行中」的任務才能進行檢驗。請先使用「execute_task」工具開始任務執行。`,
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (score >= 80) {
|
||||
// 更新任務狀態為已完成,並添加摘要
|
||||
await updateTaskSummary(taskId, summary);
|
||||
await updateTaskStatus(taskId, TaskStatus.COMPLETED);
|
||||
}
|
||||
|
||||
// 使用prompt生成器獲取最終prompt
|
||||
const prompt = getVerifyTaskPrompt({ task, score, summary });
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: prompt,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
4
src/tools/thought/index.ts
Normal file
4
src/tools/thought/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// 導出所有思維鏈工具
|
||||
|
||||
// processThought
|
||||
export { processThought, processThoughtSchema } from "./processThought.js";
|
@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
import {
|
||||
getProcessThoughtPrompt,
|
||||
ProcessThoughtPromptParams,
|
||||
} from "../prompts/generators/processThought.js";
|
||||
} from "../../prompts/generators/processThought.js";
|
||||
|
||||
/**
|
||||
* processThought工具的參數結構
|
Loading…
x
Reference in New Issue
Block a user