1571 lines
44 KiB
TypeScript
Raw Normal View History

2025-04-11 15:45:35 +08:00
import { z } from "zod";
import path from "path";
import { fileURLToPath } from "url";
2025-04-11 15:45:35 +08:00
import {
getAllTasks,
getTaskById,
updateTaskStatus,
canExecuteTask,
batchCreateOrUpdateTasks,
deleteTask as modelDeleteTask,
updateTaskSummary,
assessTaskComplexity,
clearAllTasks as modelClearAllTasks,
updateTaskContent as modelUpdateTaskContent,
updateTaskRelatedFiles as modelUpdateTaskRelatedFiles,
searchTasksWithCommand,
2025-04-11 15:45:35 +08:00
} from "../models/taskModel.js";
import {
TaskStatus,
TaskComplexityLevel,
RelatedFileType,
RelatedFile,
Task,
TaskDependency,
} from "../types/index.js";
import {
extractSummary,
generateTaskSummary,
} from "../utils/summaryExtractor.js";
import { loadTaskRelatedFiles } from "../utils/fileLoader.js";
// 導入prompt生成器
import {
getPlanTaskPrompt,
getAnalyzeTaskPrompt,
getReflectTaskPrompt,
getSplitTasksPrompt,
getExecuteTaskPrompt,
getVerifyTaskPrompt,
getCompleteTaskPrompt,
getListTasksPrompt,
getQueryTaskPrompt,
getGetTaskDetailPrompt,
} from "../prompts/index.js";
2025-04-11 15:45:35 +08:00
/**
*
*/
function getTaskStatusDisplay(status: TaskStatus): string {
switch (status) {
case TaskStatus.PENDING:
return "待處理";
case TaskStatus.IN_PROGRESS:
return "進行中";
case TaskStatus.COMPLETED:
return "已完成";
default:
return status;
}
}
/**
*
*/
function formatDate(date: Date): string {
return date.toLocaleString("zh-TW", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
}
2025-04-11 15:45:35 +08:00
// 開始規劃工具
export const planTaskSchema = z.object({
description: z
.string()
.min(10, {
message: "任務描述不能少於10個字符請提供更詳細的描述以確保任務目標明確",
})
2025-04-11 15:45:35 +08:00
.describe("完整詳細的任務問題描述,應包含任務目標、背景及預期成果"),
requirements: z
.string()
.optional()
.describe("任務的特定技術要求、業務約束條件或品質標準(選填)"),
existingTasksReference: z
.boolean()
.optional()
.default(false)
.describe("是否參考現有任務作為規劃基礎,用於任務調整和延續性規劃"),
2025-04-11 15:45:35 +08:00
});
export async function planTask({
description,
requirements,
existingTasksReference = false,
2025-04-11 15:45:35 +08:00
}: 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");
2025-04-11 15:45:35 +08:00
// 準備所需參數
let completedTasks: Task[] = [];
let pendingTasks: Task[] = [];
2025-04-11 15:45:35 +08:00
// 當 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) {
console.error("載入現有任務時發生錯誤:", error);
}
}
// 使用prompt生成器獲取最終prompt
const prompt = getPlanTaskPrompt({
description,
requirements,
existingTasksReference,
completedTasks,
pendingTasks,
memoryDir: MEMORY_DIR,
});
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text: prompt,
},
],
};
}
// 分析問題工具
export const analyzeTaskSchema = z.object({
summary: z
.string()
.min(20, {
message: "任務摘要不能少於20個字符請提供更詳細的描述以確保任務目標明確",
})
2025-04-11 15:45:35 +08:00
.describe("結構化的任務摘要,包含任務目標、範圍與關鍵技術挑戰"),
initialConcept: z
.string()
.min(50, {
message:
"初步解答構想不能少於50個字符請提供更詳細的內容確保技術方案清晰",
})
.describe(
"初步解答構想,包含技術方案、架構設計和實施策略,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分"
),
2025-04-11 15:45:35 +08:00
previousAnalysis: z
.string()
.optional()
.describe("前次迭代的分析結果,用於持續改進方案(僅在重新分析時需提供)"),
});
export async function analyzeTask({
summary,
initialConcept,
previousAnalysis,
}: z.infer<typeof analyzeTaskSchema>) {
// 使用prompt生成器獲取最終prompt
const prompt = getAnalyzeTaskPrompt({
summary,
initialConcept,
previousAnalysis,
});
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text: prompt,
},
],
};
}
// 反思構想工具
export const reflectTaskSchema = z.object({
summary: z
.string()
.min(20, {
message: "任務摘要不能少於20個字符請提供更詳細的描述以確保任務目標明確",
})
.describe("結構化的任務摘要,保持與分析階段一致以確保連續性"),
analysis: z
.string()
.min(100, {
message: "技術分析內容不夠詳盡,請提供完整的技術分析和實施方案",
})
.describe(
"完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分"
),
});
2025-04-11 15:45:35 +08:00
export async function reflectTask({
summary,
analysis,
}: z.infer<typeof reflectTaskSchema>) {
// 使用prompt生成器獲取最終prompt
const prompt = getReflectTaskPrompt({
summary,
analysis,
});
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text: prompt,
},
],
};
}
// 拆分任務工具
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
.enum(["待修改", "參考資料", "待建立", "依賴文件", "其他"])
.describe("文件類型,用於區分不同類型的文件"),
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 的完整分析結果,適用於所有任務的通用部分"
),
});
2025-04-11 15:45:35 +08:00
export async function splitTasks({
updateMode,
2025-04-11 15:45:35 +08:00
tasks,
globalAnalysisResult,
2025-04-11 15:45:35 +08:00
}: 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);
}
2025-04-11 15:45:35 +08:00
// 根據不同的更新模式處理任務
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) {
console.error("獲取所有任務時發生錯誤:", 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) {
console.error("執行任務拆分時發生錯誤:", error);
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text:
"執行任務拆分時發生錯誤: " +
(error instanceof Error ? error.message : String(error)),
},
],
ephemeral: {
taskCreationResult: {
success: false,
message: `任務創建失敗:${
error instanceof Error ? error.message : String(error)
}`,
},
2025-04-11 15:45:35 +08:00
},
};
}
2025-04-11 15:45:35 +08:00
}
export const listTasksSchema = z.object({
status: z
.enum(["all", "pending", "in_progress", "completed"])
.describe("要列出的任務狀態,可選擇 'all' 列出所有任務,或指定具體狀態"),
});
2025-04-11 15:45:35 +08:00
// 列出任務工具
export async function listTasks({ status }: z.infer<typeof listTasksSchema>) {
2025-04-11 15:45:35 +08:00
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;
}
2025-04-11 15:45:35 +08:00
if (filteredTasks.length === 0) {
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text: `## 系統通知\n\n目前系統中沒有${
status === "all" ? "任何" : `任何 ${status}`
}使split_tasks`,
2025-04-11 15:45:35 +08:00
},
],
};
}
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,
});
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text: prompt,
2025-04-11 15:45:35 +08:00
},
],
};
}
// 執行任務工具
export const executeTaskSchema = z.object({
taskId: z
.string()
.uuid({
message: "任務ID必須是有效的UUID格式",
})
2025-04-11 15:45:35 +08:00
.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) {
console.error("載入相關文件時發生錯誤:", error);
relatedFilesSummary = "載入相關文件時發生錯誤,請手動查看文件。";
}
}
// 嘗試自動發現相關文件
let potentialFiles: string[] = [];
if (!task.relatedFiles || task.relatedFiles.length === 0) {
// 基於任務名稱和描述關鍵詞,嘗試推測可能相關的文件
const taskWords = [
...task.name.split(/[\s,.;:]+/),
...task.description.split(/[\s,.;:]+/),
]
.filter((word) => word.length > 3)
.map((word) => word.toLowerCase());
// 從關鍵詞中提取可能的文件名或路徑片段
potentialFiles = taskWords.filter(
(word) =>
/^[a-z0-9]+$/i.test(word) &&
![
"task",
"function",
"model",
"index",
"with",
"from",
"this",
].includes(word.toLowerCase())
);
// 只保留前5個
potentialFiles = potentialFiles.slice(0, 5);
}
// 使用prompt生成器獲取最終prompt
const prompt = getExecuteTaskPrompt({
task,
complexityAssessment,
relatedFilesSummary,
dependencyTasks,
potentialFiles,
});
return {
content: [
{
type: "text" as const,
text: prompt,
},
],
};
} catch (error) {
console.error("執行任務時發生錯誤:", error);
return {
content: [
{
type: "text" as const,
text: `執行任務時發生錯誤: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
2025-04-11 15:45:35 +08:00
}
// 檢驗任務工具
export const verifyTaskSchema = z.object({
taskId: z
.string()
.uuid({ message: "任務ID格式無效請提供有效的UUID格式" })
.describe("待驗證任務的唯一標識符必須是系統中存在的有效任務ID"),
2025-04-11 15:45:35 +08:00
});
export async function verifyTask({ taskId }: 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,
};
}
// 使用prompt生成器獲取最終prompt
const prompt = getVerifyTaskPrompt({ task });
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text: prompt,
},
],
};
}
// 完成任務工具
export const completeTaskSchema = z.object({
taskId: z
.string()
.uuid({ message: "任務ID格式無效請提供有效的UUID格式" })
2025-04-11 15:45:35 +08:00
.describe(
"待標記為完成的任務唯一標識符必須是狀態為「進行中」的有效任務ID"
),
summary: z
.string()
.min(30, {
message: "任務摘要太簡短,請提供更詳細的完成報告,包含實施結果和主要決策",
})
.optional()
.describe(
"任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)"
),
2025-04-11 15:45:35 +08:00
});
export async function completeTask({
taskId,
summary,
2025-04-11 15:45:35 +08:00
}: z.infer<typeof completeTaskSchema>) {
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,
};
}
// 處理摘要信息
let taskSummary = summary;
if (!taskSummary) {
// 自動生成摘要
taskSummary = generateTaskSummary(task.name, task.description);
}
// 更新任務狀態為已完成,並添加摘要
2025-04-11 15:45:35 +08:00
await updateTaskStatus(taskId, TaskStatus.COMPLETED);
await updateTaskSummary(taskId, taskSummary);
2025-04-11 15:45:35 +08:00
// 使用prompt生成器獲取最終prompt
const prompt = getCompleteTaskPrompt({
task,
completionTime: new Date().toISOString(),
});
2025-04-11 15:45:35 +08:00
return {
content: [
{
type: "text" as const,
text: prompt,
2025-04-11 15:45:35 +08:00
},
],
};
}
// 刪除任務工具
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: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`,
},
],
isError: true,
};
}
if (task.status === TaskStatus.COMPLETED) {
return {
content: [
{
type: "text" as const,
text: `## 操作被拒絕\n\n任務 "${task.name}" (ID: \`${task.id}\`) 已完成,不允許刪除已完成的任務。`,
},
],
isError: true,
};
}
const result = await modelDeleteTask(taskId);
return {
content: [
{
type: "text" as const,
text: `## ${result.success ? "操作成功" : "操作失敗"}\n\n${
result.message
}`,
},
],
isError: !result.success,
};
}
// 清除所有任務工具
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: `## 操作取消\n\n未確認清除操作。如要清除所有任務請將 confirm 參數設為 true。\n\n⚠ 此操作將刪除所有未完成的任務且無法恢復。`,
},
],
};
}
// 檢查是否真的有任務需要清除
const allTasks = await getAllTasks();
if (allTasks.length === 0) {
return {
content: [
{
type: "text" as const,
text: `## 操作提示\n\n系統中沒有任何任務需要清除。`,
},
],
};
}
// 執行清除操作
const result = await modelClearAllTasks();
return {
content: [
{
type: "text" as const,
text: `## ${result.success ? "操作成功" : "操作失敗"}\n\n${
result.message
}${
result.backupFile
? `\n\n已自動創建備份: \`${result.backupFile}\``
: ""
}`,
},
],
isError: !result.success,
};
}
// 更新任務內容工具
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("文件與任務的關係類型"),
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: `## 操作失敗\n\n行號設置無效必須同時設置起始行和結束行且起始行必須小於結束行`,
},
],
};
}
}
}
if (
!(
name ||
description ||
notes ||
dependencies ||
implementationGuide ||
verificationCriteria ||
relatedFiles
)
) {
return {
content: [
{
type: "text" as const,
text: `## 操作失敗\n\n至少需要更新一個字段名稱、描述、注記或相關文件`,
},
],
};
}
// 獲取任務以檢查它是否存在
const task = await getTaskById(taskId);
if (!task) {
return {
content: [
{
type: "text" as const,
text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`,
},
],
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,
});
// 構建響應消息
const responseTitle = result.success ? "操作成功" : "操作失敗";
let responseMessage = result.message;
if (result.success && result.task) {
// 顯示更新後的任務詳情
responseMessage += "\n\n### 更新後的任務詳情\n";
responseMessage += `- **名稱:** ${result.task.name}\n`;
responseMessage += `- **描述:** ${result.task.description.substring(
0,
100
)}${result.task.description.length > 100 ? "..." : ""}\n`;
if (result.task.notes) {
responseMessage += `- **注記:** ${result.task.notes.substring(0, 100)}${
result.task.notes.length > 100 ? "..." : ""
}\n`;
}
responseMessage += `- **狀態:** ${result.task.status}\n`;
responseMessage += `- **更新時間:** ${new Date(
result.task.updatedAt
).toISOString()}\n`;
// 顯示相關文件信息
if (result.task.relatedFiles && result.task.relatedFiles.length > 0) {
responseMessage += `- **相關文件:** ${result.task.relatedFiles.length}\n`;
// 按文件類型分組
const filesByType = result.task.relatedFiles.reduce((acc, file) => {
if (!acc[file.type]) {
acc[file.type] = [];
}
acc[file.type].push(file);
return acc;
}, {} as Record<string, RelatedFile[]>);
for (const [type, files] of Object.entries(filesByType)) {
responseMessage += ` - ${type} (${files.length} 個): `;
responseMessage += files.map((file) => `\`${file.path}\``).join(", ");
responseMessage += "\n";
}
}
}
return {
content: [
{
type: "text" as const,
text: `## ${responseTitle}\n\n${responseMessage}`,
},
],
isError: !result.success,
};
}
// 更新任務相關文件工具
export const updateTaskRelatedFilesSchema = z.object({
taskId: z
.string()
.uuid({ message: "任務ID格式無效請提供有效的UUID格式" })
.describe("待更新任務的唯一標識符必須是系統中存在且未完成的任務ID"),
relatedFiles: z
.array(
z.object({
path: z
.string()
.min(1, { message: "文件路徑不能為空,請提供有效的文件路徑" })
.describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"),
type: z
.enum([
RelatedFileType.TO_MODIFY,
RelatedFileType.REFERENCE,
RelatedFileType.CREATE,
RelatedFileType.DEPENDENCY,
RelatedFileType.OTHER,
])
.describe("文件與任務的關係類型"),
description: z.string().optional().describe("文件的補充描述(選填)"),
lineStart: z
.number()
.int()
.positive()
.optional()
.describe("相關代碼區塊的起始行(選填)"),
lineEnd: z
.number()
.int()
.positive()
.optional()
.describe("相關代碼區塊的結束行(選填)"),
})
)
.min(1, { message: "至少需要提供一個相關文件,請確保文件列表不為空" })
.describe("與任務相關的文件列表"),
});
export async function updateTaskRelatedFiles({
taskId,
relatedFiles,
}: z.infer<typeof updateTaskRelatedFilesSchema>) {
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: `## 操作失敗\n\n行號設置無效必須同時設置起始行和結束行且起始行必須小於結束行`,
},
],
};
}
}
}
// 獲取任務以檢查它是否存在
const task = await getTaskById(taskId);
if (!task) {
return {
content: [
{
type: "text" as const,
text: `## 系統錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請使用「list_tasks」工具確認有效的任務ID後再試。`,
},
],
isError: true,
};
}
// 記錄要更新的任務和相關文件
const fileTypeCount = relatedFiles.reduce((acc, file) => {
acc[file.type] = (acc[file.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
const fileTypeSummary = Object.entries(fileTypeCount)
.map(([type, count]) => `${type} ${count}`)
.join("");
// 執行更新操作
const result = await modelUpdateTaskRelatedFiles(taskId, relatedFiles);
// 構建響應消息
const responseTitle = result.success ? "操作成功" : "操作失敗";
let responseMessage = result.message;
if (result.success && result.task && result.task.relatedFiles) {
// 顯示更新後的相關文件列表
responseMessage += "\n\n### 任務相關文件列表\n";
// 按文件類型分組顯示
const filesByType = result.task.relatedFiles.reduce((acc, file) => {
acc[file.type] = acc[file.type] || [];
acc[file.type].push(file);
return acc;
}, {} as Record<string, RelatedFile[]>);
for (const [type, files] of Object.entries(filesByType)) {
responseMessage += `\n#### ${type} (${files.length} 個)\n`;
files.forEach((file, index) => {
responseMessage += `${index + 1}. \`${file.path}\`${
file.description ? ` - ${file.description}` : ""
}${
file.lineStart && file.lineEnd
? ` (行 ${file.lineStart}-${file.lineEnd})`
: ""
}\n`;
});
}
}
return {
content: [
{
type: "text" as const,
text: `## ${responseTitle}\n\n${responseMessage}`,
},
],
isError: !result.success,
};
}
// 格式化單個任務的詳細資訊
const formatTaskDetails = (task: Task) => {
let details = `### ${task.name}\n**ID:** \`${task.id}\`\n**描述:** ${task.description}\n`;
if (task.notes) {
details += `**注記:** ${task.notes}\n`;
}
details += `**狀態:** ${task.status}\n`;
if (task.dependencies.length > 0) {
const depIds = task.dependencies
.map((dep: TaskDependency) => `\`${dep.taskId}\``)
.join(", ");
details += `**依賴任務:** ${depIds}\n`;
}
// 添加相關文件信息
if (task.relatedFiles && task.relatedFiles.length > 0) {
details += `**相關文件:** ${task.relatedFiles.length}\n`;
// 按文件類型分組
const filesByType = task.relatedFiles.reduce(
(acc: Record<string, RelatedFile[]>, file: RelatedFile) => {
if (!acc[file.type]) {
acc[file.type] = [];
}
acc[file.type].push(file);
return acc;
},
{} as Record<string, RelatedFile[]>
);
for (const [type, files] of Object.entries(filesByType)) {
details += ` - ${type} (${files.length} 個): `;
details += files
.map((file: RelatedFile) => `\`${file.path}\``)
.join(", ");
details += "\n";
}
}
details += `**創建時間:** ${new Date(task.createdAt).toISOString()}\n`;
details += `**更新時間:** ${new Date(task.updatedAt).toISOString()}\n`;
if (task.completedAt) {
details += `**完成時間:** ${new Date(task.completedAt).toISOString()}\n`;
}
if (task.summary) {
details += `**完成摘要:** ${task.summary}\n`;
}
return details;
};
// 查詢任務工具
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) {
console.error("查詢任務時發生錯誤:", error);
return {
content: [
{
type: "text" as const,
text: `## 系統錯誤\n\n查詢任務時發生錯誤: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
isError: true,
};
}
}
// 格式化任務顯示內容的輔助函數
function formatTaskForDisplay(task: Task): string {
let taskInfo = `### ${task.name}\n**ID:** \`${task.id}\`\n**狀態:** ${task.status}\n**描述:** ${task.description}\n`;
if (task.notes) {
taskInfo += `**注記:** ${task.notes}\n`;
}
if (task.implementationGuide) {
taskInfo += `**實現指南:** ${
task.implementationGuide.length > 300
? task.implementationGuide.substring(0, 300) + "..."
: task.implementationGuide
}\n`;
}
if (task.verificationCriteria) {
taskInfo += `**驗證標準:** ${
task.verificationCriteria.length > 300
? task.verificationCriteria.substring(0, 300) + "..."
: task.verificationCriteria
}\n`;
}
if (task.summary) {
taskInfo += `**完成摘要:** ${task.summary}\n`;
}
taskInfo += `**創建時間:** ${new Date(task.createdAt).toLocaleString(
"zh-TW"
)}\n`;
taskInfo += `**更新時間:** ${new Date(task.updatedAt).toLocaleString(
"zh-TW"
)}\n`;
if (task.completedAt) {
taskInfo += `**完成時間:** ${new Date(task.completedAt).toLocaleString(
"zh-TW"
)}\n`;
}
taskInfo += `**詳細內容:** 請使用「get_task_detail」工具查看 ${task.id} 完整任務詳情`;
return taskInfo;
}
// 取得完整任務詳情的參數
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 {
// 檢查任務是否存在
const task = await getTaskById(taskId);
if (!task) {
return {
content: [
{
type: "text" as const,
text: `## 錯誤\n\n找不到ID為 \`${taskId}\` 的任務。請確認任務ID是否正確。`,
},
],
isError: true,
};
}
// 使用prompt生成器獲取最終prompt
const prompt = getGetTaskDetailPrompt({
taskId,
task,
});
return {
content: [
{
type: "text" as const,
text: prompt,
},
],
};
} catch (error) {
console.error("取得任務詳情時發生錯誤:", error);
// 使用prompt生成器獲取錯誤訊息
const errorPrompt = getGetTaskDetailPrompt({
taskId,
error: error instanceof Error ? error.message : String(error),
});
return {
content: [
{
type: "text" as const,
text: errorPrompt,
},
],
};
}
}
// 格式化完整任務詳情的輔助函數,不截斷長內容
function formatFullTaskDetail(task: Task): string {
let taskDetail = `### ${task.name}\n\n**ID:** \`${task.id}\`\n\n**狀態:** ${task.status}\n\n**描述:**\n${task.description}\n\n`;
if (task.notes) {
taskDetail += `**注記:**\n${task.notes}\n\n`;
}
if (task.dependencies && task.dependencies.length > 0) {
taskDetail += `**依賴任務:** ${task.dependencies
.map((dep) => `\`${dep.taskId}\``)
.join(", ")}\n\n`;
}
if (task.implementationGuide) {
taskDetail += `**實現指南:**\n\`\`\`\n${task.implementationGuide}\n\`\`\`\n\n`;
}
if (task.verificationCriteria) {
taskDetail += `**驗證標準:**\n\`\`\`\n${task.verificationCriteria}\n\`\`\`\n\n`;
}
if (task.relatedFiles && task.relatedFiles.length > 0) {
taskDetail += `**相關文件:**\n`;
for (const file of task.relatedFiles) {
taskDetail += `- \`${file.path}\` (${file.type})${
file.description ? `: ${file.description}` : ""
}\n`;
}
taskDetail += `\n`;
}
taskDetail += `**創建時間:** ${new Date(task.createdAt).toLocaleString(
"zh-TW"
)}\n`;
taskDetail += `**更新時間:** ${new Date(task.updatedAt).toLocaleString(
"zh-TW"
)}\n`;
if (task.completedAt) {
taskDetail += `**完成時間:** ${new Date(task.completedAt).toLocaleString(
"zh-TW"
)}\n\n`;
}
if (task.summary) {
taskDetail += `**完成摘要:**\n${task.summary}\n\n`;
}
return taskDetail;
}