From 5bd4be7489ef8ae8f9f850e238c20b482107e525 Mon Sep 17 00:00:00 2001 From: siage Date: Tue, 15 Apr 2025 15:17:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E5=90=88=E4=BB=BB=E5=8B=99=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=9A=84=E6=9E=B6=E6=A7=8B=EF=BC=8C=E5=B0=87=E5=90=84?= =?UTF-8?q?=E9=A0=85=E4=BB=BB=E5=8B=99=E5=B7=A5=E5=85=B7=E7=9A=84=E5=8F=83?= =?UTF-8?q?=E6=95=B8=E7=B5=90=E6=A7=8B=E6=9B=B4=E6=96=B0=E7=82=BA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20schema=20=E9=80=B2=E8=A1=8C=E5=AE=9A=E7=BE=A9?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A2=BC=E5=8F=AF=E8=AE=80?= =?UTF-8?q?=E6=80=A7=E8=88=87=E7=B6=AD=E8=AD=B7=E6=80=A7=E3=80=82=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=BB=BB=E5=8B=99=E5=88=97=E8=A1=A8=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E6=A0=B9=E6=93=9A=E7=8B=80=E6=85=8B?= =?UTF-8?q?=E9=81=8E=E6=BF=BE=E4=BB=BB=E5=8B=99=EF=BC=8C=E4=B8=A6=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=9B=B8=E9=97=9C=E7=9A=84=E9=A9=97=E8=AD=89=E6=A8=99?= =?UTF-8?q?=E6=BA=96=E8=88=87=E6=8F=8F=E8=BF=B0=EF=BC=8C=E7=A2=BA=E4=BF=9D?= =?UTF-8?q?=E7=94=A8=E6=88=B6=E8=83=BD=E5=A4=A0=E6=9B=B4=E6=B8=85=E6=99=B0?= =?UTF-8?q?=E5=9C=B0=E7=90=86=E8=A7=A3=E4=BB=BB=E5=8B=99=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 312 ++------------------- src/tools/taskTools.ts | 599 ++++++++++++++++++++--------------------- 2 files changed, 315 insertions(+), 596 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1833ae0..ffac9e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import { reflectTaskSchema, splitTasks, splitTasksSchema, + listTasksSchema, listTasks, executeTask, executeTaskSchema, @@ -48,24 +49,7 @@ async function main() { server.tool( "plan_task", "初始化並詳細規劃任務流程,建立明確的目標與成功標準,可選擇參考現有任務進行延續規劃", - { - description: z - .string() - .min(10, { - message: - "任務描述不能少於10個字符,請提供更詳細的描述以確保任務目標明確", - }) - .describe("完整詳細的任務問題描述,應包含任務目標、背景及預期成果"), - requirements: z - .string() - .optional() - .describe("任務的特定技術要求、業務約束條件或品質標準(選填)"), - existingTasksReference: z - .boolean() - .optional() - .default(false) - .describe("是否參考現有任務作為規劃基礎,用於任務調整和延續性規劃"), - }, + planTaskSchema.shape, async (args) => { return await planTask(args); } @@ -74,30 +58,7 @@ async function main() { server.tool( "analyze_task", "深入分析任務需求並系統性檢查代碼庫,評估技術可行性與潛在風險,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分", - { - summary: z - .string() - .min(20, { - message: - "任務摘要太短,請提供更詳細的摘要,包含任務目標、範圍與關鍵技術挑戰", - }) - .describe("結構化的任務摘要,包含任務目標、範圍與關鍵技術挑戰"), - initialConcept: z - .string() - .min(50, { - message: - "初步解答構想過於簡短,請提供更完整的技術方案和實施策略詳情", - }) - .describe( - "初步解答構想,包含技術方案、架構設計和實施策略,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分" - ), - previousAnalysis: z - .string() - .optional() - .describe( - "前次迭代的分析結果,用於持續改進方案(僅在重新分析時需提供)" - ), - }, + analyzeTaskSchema.shape, async (args) => { return await analyzeTask(args); } @@ -106,24 +67,7 @@ async function main() { server.tool( "reflect_task", "批判性審查分析結果,評估方案完整性並識別優化機會,確保解決方案符合最佳實踐,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分", - { - summary: z - .string() - .min(20, { - message: - "任務摘要太短,請確保包含完整的任務目標和範圍以維持分析連續性", - }) - .describe("結構化的任務摘要,保持與分析階段一致以確保連續性"), - analysis: z - .string() - .min(100, { - message: - "技術分析結果過於簡略,請提供更詳盡的技術細節、依賴組件和實施方案說明", - }) - .describe( - "完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分" - ), - }, + reflectTaskSchema.shape, async (args) => { return await reflectTask(args); } @@ -132,77 +76,7 @@ async function main() { server.tool( "split_tasks", "將複雜任務分解為獨立且可追蹤的子任務,建立明確的依賴關係和優先順序。支援四種任務更新模式:追加(append)、覆蓋(overwrite)、選擇性更新(selective)和清除所有任務(clearAllTasks),其中覆蓋模式只會刪除未完成的任務並保留已完成任務,選擇性更新模式可根據任務名稱智能匹配更新現有任務,同時保留其他任務,如果你需要規劃全新的任務請使用清除所有任務模式會清除所有任務並創建備份。請優先使用清除所有任務模式,只有用戶要求變更或修改計畫內容才使用其他模式。\n\n**請參考之前的分析結果提供 pseudocode\n\n**如果任務太多或內容過長,請分批使用「split_tasks」工具,每次只提交一小部分任務", - { - updateMode: z - .enum(["append", "overwrite", "selective", "clearAllTasks"]) - .describe( - "任務更新模式選擇:'append'(保留所有現有任務並添加新任務)、'overwrite'(清除所有未完成任務並完全替換,保留已完成任務)、'selective'(智能更新:根據任務名稱匹配更新現有任務,保留不在列表中的任務,推薦用於任務微調)、'clearAllTasks'(清除所有任務並創建備份)。\n預設為'clearAllTasks'模式,只有用戶要求變更或修改計畫內容才使用其他模式" - ), - globalAnalysisResult: z - .string() - .optional() - .describe( - "全局分析結果:來自 reflect_task 的完整分析結果,適用於所有任務的通用部分" - ), - tasks: z - .array( - z.object({ - name: z - .string() - .max(100, { - message: "任務名稱過長,請保持簡潔,不超過100個字符", - }) - .describe("簡潔明確的任務名稱,應能清晰表達任務目的"), - description: z - .string() - .min(10, { - message: - "任務描述太短,請詳細說明實施要點、技術細節和驗收標準", - }) - .describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"), - notes: z - .string() - .optional() - .describe("補充說明、特殊處理要求或實施建議(選填)"), - dependencies: z - .array(z.string(), { - message: "必須是字串陣列,支援任務名稱或任務ID(UUID)", - }) - .optional() - .describe( - "此任務依賴的前置任務ID或任務名稱列表,支持兩種引用方式,名稱引用更直觀,是一個字串陣列" - ), - relatedFiles: z - .array( - z.object({ - path: z - .string() - .min(1, { message: "檔案路徑不能為空" }) - .describe("檔案路徑,相對於專案根目錄"), - type: z - .nativeEnum(RelatedFileType) - .describe("檔案類型,用於區分不同類型的檔案"), - description: z - .string() - .min(1, { message: "檔案描述不能為空" }) - .describe("檔案描述,用於說明檔案的用途和內容"), - }) - ) - .optional() - .describe("與任務相關的檔案列表,包含檔案路徑、類型和描述"), - implementationGuide: z - .string() - .describe( - "此特定任務的具體實現方法和步驟,請參考之前的分析結果提供 pseudocode" - ), - verificationCriteria: z - .string() - .optional() - .describe("此特定任務的驗證標準和檢驗方法"), - }) - ) - .describe("結構化的任務清單,每個任務應保持原子性且有明確的完成標準"), - }, + splitTasksSchema.shape, async (args) => { return await splitTasks(args); } @@ -211,20 +85,16 @@ async function main() { server.tool( "list_tasks", "生成結構化任務清單,包含完整狀態追蹤、優先級和依賴關係", - {}, + listTasksSchema.shape, async (args) => { - return await listTasks(); + return await listTasks(args); } ); server.tool( "execute_task", "按照預定義計劃執行特定任務,確保每個步驟的輸出符合質量標準", - { - taskId: z - .string() - .describe("待執行任務的唯一標識符,必須是系統中存在的有效任務ID"), - }, + executeTaskSchema.shape, async (args) => { return await executeTask(args); } @@ -233,11 +103,7 @@ async function main() { server.tool( "verify_task", "全面驗證任務完成度,確保所有需求與技術標準都已滿足,並無遺漏細節", - { - taskId: z - .string() - .describe("待驗證任務的唯一標識符,必須是系統中存在的有效任務ID"), - }, + verifyTaskSchema.shape, async (args) => { return await verifyTask(args); } @@ -246,17 +112,7 @@ async function main() { server.tool( "complete_task", "正式標記任務為完成狀態,生成詳細的完成報告,並更新關聯任務的依賴狀態", - { - taskId: z - .string() - .describe("待完成任務的唯一標識符,必須是系統中存在的有效任務ID"), - summary: z - .string() - .optional() - .describe( - "任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)" - ), - }, + completeTaskSchema.shape, async (args) => { return await completeTask(args); } @@ -265,11 +121,7 @@ async function main() { server.tool( "delete_task", "刪除未完成的任務,但不允許刪除已完成的任務,確保系統記錄的完整性", - { - taskId: z - .string() - .describe("待刪除任務的唯一標識符,必須是系統中存在且未完成的任務ID"), - }, + deleteTaskSchema.shape, async (args) => { return await deleteTask(args); } @@ -279,11 +131,7 @@ async function main() { server.tool( "clear_all_tasks", "刪除系統中所有未完成的任務,該指令必須由用戶明確確認才能執行", - { - confirm: z - .boolean() - .describe("確認刪除所有未完成的任務(此操作不可逆)"), - }, + clearAllTasksSchema.shape, async (args) => { return await clearAllTasks(args); } @@ -293,62 +141,7 @@ async function main() { server.tool( "update_task", "更新任務內容,包括名稱、描述和注記,但不允許修改已完成的任務", - { - taskId: z - .string() - .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 - .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("相關代碼區塊的結束行(選填)"), - }) - ) - .optional() - .describe("與任務相關的文件列表(選填)"), - implementationGuide: z - .string() - .optional() - .describe("任務的新實現指南(選填)"), - verificationCriteria: z - .string() - .optional() - .describe("任務的新驗證標準(選填)"), - }, + updateTaskContentSchema.shape, async (args) => { return await updateTaskContent(args); } @@ -358,45 +151,7 @@ async function main() { server.tool( "update_task_files", "更新任務相關文件列表,用於記錄與任務相關的代碼文件、參考資料等", - { - taskId: z - .string() - .describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"), - relatedFiles: z - .array( - z.object({ - path: z - .string() - .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("相關代碼區塊的結束行(選填)"), - }) - ) - .describe("與任務相關的文件列表"), - }, + updateTaskRelatedFilesSchema.shape, async (args) => { return await updateTaskRelatedFiles(args); } @@ -406,35 +161,7 @@ async function main() { server.tool( "query_task", "根據關鍵字或ID搜尋任務,顯示省略版的任務資訊", - { - 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筆"), - }, + queryTaskSchema.shape, async (args) => { return await queryTask(args); } @@ -444,14 +171,7 @@ async function main() { server.tool( "get_task_detail", "根據任務ID獲取任務的完整詳細信息,包括未截斷的實現指南和驗證標準等", - { - taskId: z - .string() - .min(1, { - message: "任務ID不能為空,請提供有效的任務ID", - }) - .describe("欲檢視詳情的任務ID"), - }, + getTaskDetailSchema.shape, async (args) => { return await getTaskDetail(args); } diff --git a/src/tools/taskTools.ts b/src/tools/taskTools.ts index 767a123..612adaf 100644 --- a/src/tools/taskTools.ts +++ b/src/tools/taskTools.ts @@ -260,10 +260,11 @@ export const analyzeTaskSchema = z.object({ initialConcept: z .string() .min(50, { - message: - "初步解答構想過於簡短,請提供更完整的技術方案和實施策略詳情,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分", + message: "初步解答構想過於簡短,請提供更完整的技術方案和實施策略詳情", }) - .describe("初步解答構想,包含技術方案、架構設計和實施策略"), + .describe( + "初步解答構想,包含技術方案、架構設計和實施策略,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分" + ), previousAnalysis: z .string() .optional() @@ -323,29 +324,23 @@ export async function analyzeTask({ } // 反思構想工具 -export const reflectTaskSchema = z - .object({ - summary: z - .string() - .min(20, { - message: "任務摘要太短,請確保包含完整的任務目標和範圍以維持分析連續性", - }) - .describe("結構化的任務摘要,保持與分析階段一致以確保連續性"), - analysis: z - .string() - .min(100, { - message: - "技術分析結果過於簡略,請提供更詳盡的技術細節、依賴組件和實施方案說明", - }) - .describe( - "完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分" - ), - }) - .refine((data) => data.summary.length * 3 <= data.analysis.length, { - message: - "分析內容應該比摘要更詳細,建議分析部分至少是摘要長度的3倍以提供足夠的技術深度", - path: ["analysis"], - }); +export const reflectTaskSchema = z.object({ + summary: z + .string() + .min(20, { + message: "任務摘要太短,請確保包含完整的任務目標和範圍以維持分析連續性", + }) + .describe("結構化的任務摘要,保持與分析階段一致以確保連續性"), + analysis: z + .string() + .min(100, { + message: + "技術分析結果過於簡略,請提供更詳盡的技術細節、依賴組件和實施方案說明", + }) + .describe( + "完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案,如果需要提供程式碼請使用 pseudocode 格式且盡量精簡只保留核心實現部分" + ), +}); export async function reflectTask({ summary, @@ -401,100 +396,112 @@ export async function reflectTask({ } // 拆分任務工具 -export const splitTasksSchema = z - .object({ - updateMode: z - .enum(["append", "overwrite", "selective", "clearAllTasks"]) - .describe( - "任務更新模式(必填):'append'(保留現有任務並新增)、'overwrite'(清除所有未完成任務並重建)、'selective'(根據名稱匹配更新現有任務,保留其餘任務)、'clearAllTasks'(清除所有任務並創建備份)" - ), - globalAnalysisResult: z - .string() - .optional() - .describe( - "全局分析結果:來自 reflect_task 的完整分析結果,適用於所有任務的通用部分" - ), - tasks: z - .array( - z.object({ - name: z - .string() - .max(100, { message: "任務名稱過長,請保持簡潔,不超過100個字符" }) - .describe("簡潔明確的任務名稱,應能清晰表達任務目的"), - description: z - .string() - .min(10, { - message: - "任務描述太簡短,請提供更詳細的描述,包含實施要點和驗收標準", +export const splitTasksSchema = z.object({ + updateMode: z + .enum(["append", "overwrite", "selective", "clearAllTasks"]) + .describe( + "任務更新模式選擇:'append'(保留所有現有任務並添加新任務)、'overwrite'(清除所有未完成任務並完全替換,保留已完成任務)、'selective'(智能更新:根據任務名稱匹配更新現有任務,保留不在列表中的任務,推薦用於任務微調)、'clearAllTasks'(清除所有任務並創建備份)。\n預設為'clearAllTasks'模式,只有用戶要求變更或修改計畫內容才使用其他模式" + ), + globalAnalysisResult: z + .string() + .optional() + .describe( + "全局分析結果:來自 reflect_task 的完整分析結果,適用於所有任務的通用部分" + ), + tasks: z + .array( + z.object({ + name: z + .string() + .max(100, { + message: "任務名稱過長,請保持簡潔,不超過100個字符", + }) + .describe("簡潔明確的任務名稱,應能清晰表達任務目的"), + description: z + .string() + .min(10, { + message: "任務描述太短,請詳細說明實施要點、技術細節和驗收標準", + }) + .describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"), + notes: z + .string() + .optional() + .describe("補充說明、特殊處理要求或實施建議(選填)"), + dependencies: z + .array(z.string(), { + message: "必須是字串陣列,支援任務名稱或任務ID(UUID)", + }) + .optional() + .describe( + "此任務依賴的前置任務ID或任務名稱列表,支持兩種引用方式,名稱引用更直觀,是一個字串陣列" + ), + relatedFiles: z + .array( + z.object({ + path: z + .string() + .min(1, { message: "檔案路徑不能為空" }) + .describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"), + type: z + .nativeEnum(RelatedFileType) + .describe("檔案類型,用於區分不同類型的檔案"), + description: z + .string() + .min(1, { message: "檔案描述不能為空" }) + .describe("檔案描述,用於說明檔案的用途和內容"), + lineStart: z + .number() + .int() + .positive() + .optional() + .describe("相關代碼區塊的起始行(選填)"), + lineEnd: z + .number() + .int() + .positive() + .optional() + .describe("相關代碼區塊的結束行(選填)"), }) - .describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"), - notes: z - .string() - .optional() - .describe("補充說明、特殊處理要求或實施建議(選填)"), - dependencies: z - .array(z.string(), { - message: "必須是字串陣列,支援任務名稱或任務ID(UUID)", - }) - .optional() - .describe( - "此任務依賴的前置任務ID或任務名稱列表,支持兩種引用方式,名稱引用更直觀" - ), - relatedFiles: z - .array( - z.object({ - path: z - .string() - .min(1, { message: "檔案路徑不能為空" }) - .describe("檔案路徑,相對於專案根目錄"), - type: z - .nativeEnum(RelatedFileType) - .describe("檔案類型,用於區分不同類型的檔案"), - description: z - .string() - .min(1, { message: "檔案描述不能為空" }) - .describe("檔案描述,用於說明檔案的用途和內容"), - }) - ) - .optional() - .describe("與任務相關的檔案列表,包含檔案路徑、類型和描述"), - implementationGuide: z - .string() - .describe( - "此特定任務的具體實現方法和步驟,請參考之前的分析結果提供 pseudocode" - ), - verificationCriteria: z - .string() - .optional() - .describe("此特定任務的驗證標準和檢驗方法"), - }) - ) - .min(1, { message: "至少需要提供一個任務,請確保任務列表不為空" }) - .describe("結構化的任務清單,每個任務應保持原子性且有明確的完成標準"), - }) - .refine( - (data) => { - // 檢查任務名稱是否有重複 - const nameSet = new Set(); - for (const task of data.tasks) { - if (nameSet.has(task.name)) { - return false; - } - nameSet.add(task.name); - } - return true; - }, - { - message: "任務列表中存在重複的任務名稱,請確保每個任務名稱是唯一的", - path: ["tasks"], - } - ); + ) + .optional() + .describe( + "與任務相關的檔案列表,用於記錄與任務相關的代碼文件、參考資料、要建立的檔案等(選填)" + ), + implementationGuide: z + .string() + .describe( + "此特定任務的具體實現方法和步驟,請參考之前的分析結果提供 pseudocode" + ), + verificationCriteria: z + .string() + .optional() + .describe("此特定任務的驗證標準和檢驗方法"), + }) + ) + .describe("結構化的任務清單,每個任務應保持原子性且有明確的完成標準"), +}); export async function splitTasks({ updateMode, tasks, globalAnalysisResult, }: z.infer) { + // 檢查 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); + } + // 處理 clearAllTasks 模式,直接調用 modelClearAllTasks 函數 if (updateMode === "clearAllTasks") { const clearResult = await modelClearAllTasks(); @@ -644,16 +651,44 @@ export async function splitTasks({ }; } -// 列出任務工具 -export async function listTasks() { - const tasks = await getAllTasks(); +export const listTasksSchema = z.object({ + status: z + .enum(["all", "pending", "in_progress", "completed"]) + .describe("要列出的任務狀態,可選擇 'all' 列出所有任務,或指定具體狀態"), +}); - if (tasks.length === 0) { +// 列出任務工具 +export async function listTasks({ status }: z.infer) { + 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目前系統中沒有註冊任何任務。請先使用「split_tasks」工具創建任務結構,再進行後續操作。", + text: `## 系統通知\n\n目前系統中沒有${ + status === "all" ? "任何" : `任何 ${status} 的` + }任務。請查詢其他狀態任務或先使用「split_tasks」工具創建任務結構,再進行後續操作。`, }, ], }; @@ -964,7 +999,7 @@ export const verifyTaskSchema = z.object({ taskId: z .string() .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) - .describe("待驗證任務的唯一標識符,必須是狀態為「進行中」的有效任務ID"), + .describe("待驗證任務的唯一標識符,必須是系統中存在的有效任務ID"), }); export async function verifyTask({ taskId }: z.infer) { @@ -1221,108 +1256,54 @@ export async function clearAllTasks({ } // 更新任務內容工具 -export const updateTaskContentSchema = z - .object({ - taskId: z - .string() - .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) - .describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"), - name: z - .string() - .min(5, { - message: "任務名稱太短,請提供更清晰明確的名稱以便識別任務目的", +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("相關代碼區塊的結束行(選填)"), }) - .max(100, { message: "任務名稱過長,請保持簡潔,不超過100個字符" }) - .optional() - .describe("任務的新名稱(選填)"), - description: z - .string() - .min(20, { - message: "任務描述太簡短,請提供更詳細的描述,包含實施要點和驗收標準", - }) - .optional() - .describe("任務的新描述內容(選填)"), - notes: z.string().optional().describe("任務的新補充說明(選填)"), - 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("相關代碼區塊的結束行(選填)"), - }) - ) - .optional() - .describe("與任務相關的文件列表(選填)"), - dependencies: z - .array(z.string()) - .optional() - .describe("任務的新依賴關係(選填)"), - implementationGuide: z - .string() - .optional() - .describe("任務的新實現指南(選填)"), - verificationCriteria: z - .string() - .optional() - .describe("任務的新驗證標準(選填)"), - }) - .refine( - (data) => { - // 確保行號有效:如果有起始行,就必須有結束行,反之亦然 - if (data.relatedFiles) { - for (const file of data.relatedFiles) { - if ( - (file.lineStart && !file.lineEnd) || - (!file.lineStart && file.lineEnd) - ) { - return false; - } - // 確保起始行小於結束行 - if (file.lineStart && file.lineEnd && file.lineStart > file.lineEnd) { - return false; - } - } - } - // 確保至少有一個字段被更新 - return !!( - data.name || - data.description || - data.notes || - data.dependencies || - data.implementationGuide || - data.verificationCriteria || - data.relatedFiles - ); - }, - { - message: - "更新請求無效:1. 行號設置必須同時包含起始行和結束行,且起始行必須小於結束行;2. 至少需要更新一個字段(名稱、描述、注記或相關文件)", - path: ["relatedFiles"], - } - ); + ) + .optional() + .describe( + "與任務相關的文件列表,用於記錄與任務相關的代碼文件、參考資料、要建立的檔案等(選填)" + ), + implementationGuide: z + .string() + .optional() + .describe("任務的新實現指南(選填)"), + verificationCriteria: z + .string() + .optional() + .describe("任務的新驗證標準(選填)"), +}); export async function updateTaskContent({ taskId, @@ -1334,6 +1315,46 @@ export async function updateTaskContent({ implementationGuide, verificationCriteria, }: z.infer) { + 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); @@ -1429,91 +1450,69 @@ export async function updateTaskContent({ } // 更新任務相關文件工具 -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("與任務相關的文件列表"), - }) - .refine( - (data) => { - // 檢查文件路徑是否有重複 - const pathSet = new Set(); - for (const file of data.relatedFiles) { - if (pathSet.has(file.path)) { - return false; - } - pathSet.add(file.path); - } - return true; - }, - { - message: "文件列表中存在重複的文件路徑,請確保每個文件路徑是唯一的", - path: ["relatedFiles"], - } - ) - .refine( - (data) => { - // 確保行號有效:如果有起始行,就必須有結束行,反之亦然 - for (const file of data.relatedFiles) { - if ( - (file.lineStart && !file.lineEnd) || - (!file.lineStart && file.lineEnd) - ) { - return false; - } - // 確保起始行小於結束行 - if (file.lineStart && file.lineEnd && file.lineStart > file.lineEnd) { - return false; - } - } - return true; - }, - { - message: - "行號設置無效:必須同時設置起始行和結束行,且起始行必須小於結束行", - path: ["relatedFiles"], - } - ); +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) { + 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);