From 95628091fba3e816aac7b4a638d90a527b7cb585 Mon Sep 17 00:00:00 2001 From: siage Date: Sat, 12 Apr 2025 00:17:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20API=20=E5=8F=83=E8=80=83?= =?UTF-8?q?=E6=96=87=E6=AA=94=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=9B=AE=E9=8C=84?= =?UTF-8?q?=E5=8F=8A=E8=A9=B3=E7=B4=B0=E7=9A=84=E6=A0=B8=E5=BF=83=E4=BB=BB?= =?UTF-8?q?=E5=8B=99=E7=AE=A1=E7=90=86=20API=20=E8=AA=AA=E6=98=8E=EF=BC=8C?= =?UTF-8?q?=E5=8C=85=E6=8B=AC=E4=BB=BB=E5=8B=99=E8=A6=8F=E5=8A=83=E3=80=81?= =?UTF-8?q?=E5=88=86=E6=9E=90=E3=80=81=E5=8F=8D=E6=80=9D=E3=80=81=E6=8B=86?= =?UTF-8?q?=E5=88=86=E3=80=81=E5=88=97=E5=87=BA=E3=80=81=E5=9F=B7=E8=A1=8C?= =?UTF-8?q?=E5=8F=8A=E9=A9=97=E8=AD=89=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=B8=A6?= =?UTF-8?q?=E5=BC=B7=E5=8C=96=E5=8F=83=E6=95=B8=E9=A9=97=E8=AD=89=E8=A6=8F?= =?UTF-8?q?=E5=89=87=E4=BB=A5=E6=8F=90=E5=8D=87=E7=94=A8=E6=88=B6=E9=AB=94?= =?UTF-8?q?=E9=A9=97=E3=80=82=E5=90=8C=E6=99=82=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=87=BD=E6=95=B8=E4=BB=A5=E5=8A=A0=E5=85=A5?= =?UTF-8?q?=E9=8C=AF=E8=AA=A4=E8=99=95=E7=90=86=EF=BC=8C=E7=A2=BA=E4=BF=9D?= =?UTF-8?q?=E8=BC=B8=E5=85=A5=E7=9A=84=E6=9C=89=E6=95=88=E6=80=A7=E8=88=87?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api-reference.md | 226 ++++++++++++++++++++++-- src/index.ts | 39 ++++- src/tools/logTools.ts | 111 ++++++++---- src/tools/taskTools.ts | 378 +++++++++++++++++++++++++++++------------ 4 files changed, 593 insertions(+), 161 deletions(-) diff --git a/docs/api-reference.md b/docs/api-reference.md index 17bf0aa..06a77ed 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -2,7 +2,215 @@ 本文檔提供蝦米任務管理器的 API 參考,包含所有可用工具的詳細說明和參數列表。 -## 新增功能 +## 目錄 + +- [核心任務管理 API](#核心任務管理-api) +- [任務管理 API](#任務管理-api) +- [工作日誌功能](#工作日誌功能) + +## 核心任務管理 API + +### 1. 任務規劃 + +#### `plan_task` + +初始化並詳細規劃任務流程,建立明確的目標與成功標準。 + +**參數:** + +| 參數名 | 類型 | 必填 | 描述 | +| ------------ | ------ | ---- | ------------------------------------------------------ | +| description | string | 是 | 完整詳細的任務問題描述,應包含任務目標、背景及預期成果 | +| requirements | string | 否 | 任務的特定技術要求、業務約束條件或品質標準(選填) | + +**返回:** + +- 成功:返回結構化的任務規劃結果 +- 失敗:返回錯誤訊息,說明失敗原因 + +**使用範例:** + +```javascript +const planResult = await mcp.mcp_shrimp_task_manager.plan_task({ + description: + "開發一個用戶註冊功能,包含表單驗證、數據存儲和電子郵件確認流程。", + requirements: "必須符合GDPR數據保護規定,支持多語言,並通過所有安全測試。", +}); +``` + +### 2. 任務分析 + +#### `analyze_task` + +深入分析任務需求並系統性檢查代碼庫,評估技術可行性與潛在風險。 + +**參數:** + +| 參數名 | 類型 | 必填 | 描述 | +| ---------------- | ------ | ---- | ------------------------------------------------------------ | +| summary | string | 是 | 結構化的任務摘要,包含任務目標、範圍與關鍵技術挑戰 | +| initialConcept | string | 是 | 初步解答構想,包含技術方案、架構設計和實施策略 | +| previousAnalysis | string | 否 | 前次迭代的分析結果,用於持續改進方案(僅在重新分析時需提供) | + +**返回:** + +- 成功:返回詳細的任務分析結果 +- 失敗:返回錯誤訊息,說明失敗原因 + +**使用範例:** + +```javascript +const analysisResult = await mcp.mcp_shrimp_task_manager.analyze_task({ + summary: "開發用戶身份驗證模塊,包含登入、註冊和密碼重設功能", + initialConcept: "計劃使用JWT進行身份驗證,實現無狀態API設計...", +}); +``` + +### 3. 方案反思 + +#### `reflect_task` + +批判性審查分析結果,評估方案完整性並識別優化機會,確保解決方案符合最佳實踐。 + +**參數:** + +| 參數名 | 類型 | 必填 | 描述 | +| -------- | ------ | ---- | ------------------------------------------------------------ | +| summary | string | 是 | 結構化的任務摘要,保持與分析階段一致以確保連續性 | +| analysis | string | 是 | 完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案 | + +**返回:** + +- 成功:返回方案改進建議和優化機會 +- 失敗:返回錯誤訊息,說明失敗原因 + +**使用範例:** + +```javascript +const reflectionResult = await mcp.mcp_shrimp_task_manager.reflect_task({ + summary: "開發用戶身份驗證模塊,包含登入、註冊和密碼重設功能", + analysis: "詳細的技術分析結果,包括JWT實現方式、安全考量等...", +}); +``` + +### 4. 任務拆分 + +#### `split_tasks` + +將複雜任務分解為獨立且可追蹤的子任務,建立明確的依賴關係和優先順序。 + +**參數:** + +| 參數名 | 類型 | 必填 | 描述 | +| ----------- | ------- | ---- | --------------------------------------------------------------------------- | +| isOverwrite | boolean | 是 | 任務覆蓋模式選擇(true:清除並覆蓋所有現有任務;false:保留現有任務並新增) | +| tasks | array | 是 | 結構化的任務清單,每個任務應保持原子性且有明確的完成標準 | + +**tasks 對象屬性:** + +| 屬性名 | 類型 | 必填 | 描述 | +| ------------ | ------ | ---- | ------------------------------------------------ | +| name | string | 是 | 簡潔明確的任務名稱,應能清晰表達任務目的 | +| description | string | 是 | 詳細的任務描述,包含實施要點、技術細節和驗收標準 | +| notes | string | 否 | 補充說明、特殊處理要求或實施建議(選填) | +| dependencies | array | 否 | 此任務依賴的前置任務 ID 或任務名稱列表 | + +**返回:** + +- 成功:返回創建的任務列表 +- 失敗:返回錯誤訊息,說明失敗原因 + +**使用範例:** + +```javascript +const tasksResult = await mcp.mcp_shrimp_task_manager.split_tasks({ + isOverwrite: false, + tasks: [ + { + name: "設計用戶數據模型", + description: "定義用戶實體的數據結構和驗證規則...", + notes: "參考現有的權限模型", + dependencies: [], + }, + { + name: "實現用戶註冊API", + description: "開發註冊端點,處理表單驗證和數據存儲...", + dependencies: ["設計用戶數據模型"], + }, + ], +}); +``` + +### 5. 任務列表 + +#### `list_tasks` + +生成結構化任務清單,包含完整狀態追蹤、優先級和依賴關係。 + +**參數:** 無 + +**返回:** + +- 成功:返回系統中所有任務的結構化清單 +- 失敗:返回錯誤訊息,說明失敗原因 + +**使用範例:** + +```javascript +const tasksList = await mcp.mcp_shrimp_task_manager.list_tasks(); +``` + +### 6. 任務執行 + +#### `execute_task` + +按照預定義計劃執行特定任務,確保每個步驟的輸出符合質量標準。 + +**參數:** + +| 參數名 | 類型 | 必填 | 描述 | +| ------ | ------ | ---- | ----------------------------------------------------- | +| taskId | string | 是 | 待執行任務的唯一標識符,必須是系統中存在的有效任務 ID | + +**返回:** + +- 成功:返回任務執行指南和上下文 +- 失敗:返回錯誤訊息,說明失敗原因 + +**使用範例:** + +```javascript +const executeResult = await mcp.mcp_shrimp_task_manager.execute_task({ + taskId: "task-uuid-here", +}); +``` + +### 7. 任務驗證 + +#### `verify_task` + +全面驗證任務完成度,確保所有需求與技術標準都已滿足,並無遺漏細節。 + +**參數:** + +| 參數名 | 類型 | 必填 | 描述 | +| ------ | ------ | ---- | ----------------------------------------------------- | +| taskId | string | 是 | 待驗證任務的唯一標識符,必須是系統中存在的有效任務 ID | + +**返回:** + +- 成功:返回任務驗證評估結果 +- 失敗:返回錯誤訊息,說明失敗原因 + +**使用範例:** + +```javascript +const verifyResult = await mcp.mcp_shrimp_task_manager.verify_task({ + taskId: "task-uuid-here", +}); +``` + +## 任務管理 API ### 1. 刪除任務功能 @@ -269,18 +477,4 @@ const clearResult = await mcp.mcp_shrimp_task_manager.clear_conversation_log({ }); ``` -## 完整 API 列表 - -除了上述新增功能外,蝦米任務管理器還提供以下核心功能: - -1. **開始規劃**:`plan_task` -2. **分析問題**:`analyze_task` -3. **反思構想**:`reflect_task` -4. **拆分任務**:`split_tasks` -5. **列出任務**:`list_tasks` -6. **執行任務**:`execute_task` -7. **檢驗任務**:`verify_task` -8. **完成任務**:`complete_task` -9. **刪除任務**:`delete_task` -10. **查詢日誌**:`list_conversation_log` -11. **清除日誌**:`clear_conversation_log` +## 實用工具函數 diff --git a/src/index.ts b/src/index.ts index cf523d2..56da060 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,13 +58,17 @@ async function main() { version: "1.0.0", }); - // 註冊工具 - 使用已定義的schema物件 + // 註冊工具 - 使用已定義的schema物件,並添加內嵌錯誤處理 server.tool( "plan_task", "初始化並詳細規劃任務流程,建立明確的目標與成功標準", { description: z .string() + .min(10, { + message: + "任務描述不能少於10個字符,請提供更詳細的描述以確保任務目標明確", + }) .describe("完整詳細的任務問題描述,應包含任務目標、背景及預期成果"), requirements: z .string() @@ -82,9 +86,17 @@ async function main() { { summary: z .string() + .min(20, { + message: + "任務摘要太短,請提供更詳細的摘要,包含任務目標、範圍與關鍵技術挑戰", + }) .describe("結構化的任務摘要,包含任務目標、範圍與關鍵技術挑戰"), initialConcept: z .string() + .min(50, { + message: + "初步解答構想過於簡短,請提供更完整的技術方案和實施策略詳情", + }) .describe("初步解答構想,包含技術方案、架構設計和實施策略"), previousAnalysis: z .string() @@ -104,9 +116,17 @@ async function main() { { summary: z .string() + .min(20, { + message: + "任務摘要太短,請確保包含完整的任務目標和範圍以維持分析連續性", + }) .describe("結構化的任務摘要,保持與分析階段一致以確保連續性"), analysis: z .string() + .min(100, { + message: + "技術分析結果過於簡略,請提供更詳盡的技術細節、依賴組件和實施方案說明", + }) .describe( "完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案" ), @@ -130,9 +150,20 @@ async function main() { z.object({ name: z .string() + .min(5, { + message: + "任務名稱太短,請提供更清晰明確的名稱以便識別任務目的", + }) + .max(100, { + message: "任務名稱過長,請保持簡潔,不超過100個字符", + }) .describe("簡潔明確的任務名稱,應能清晰表達任務目的"), description: z .string() + .min(20, { + message: + "任務描述太短,請詳細說明實施要點、技術細節和驗收標準", + }) .describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"), notes: z .string() @@ -157,7 +188,7 @@ async function main() { "list_tasks", "生成結構化任務清單,包含完整狀態追蹤、優先級和依賴關係", {}, - async () => { + async (args) => { return await listTasks(); } ); @@ -347,7 +378,7 @@ async function main() { } ); - // 註冊提示 + // 註冊提示 - 使用同樣的錯誤處理模式 server.prompt( "plan_task_prompt", "生成結構化的新任務規劃,包含明確目標、評估標準與執行步驟", @@ -364,7 +395,7 @@ async function main() { .describe("相關代碼片段或文件路徑(選填)"), }, async (args) => { - return planTaskPrompt(args); + return await planTaskPrompt(args); } ); diff --git a/src/tools/logTools.ts b/src/tools/logTools.ts index 92673d3..2acf9f3 100644 --- a/src/tools/logTools.ts +++ b/src/tools/logTools.ts @@ -11,30 +11,70 @@ import { getTaskById } from "../models/taskModel.js"; import { ListConversationLogArgs } from "../types/index.js"; // 列出對話日誌工具 -export const listConversationLogSchema = z.object({ - taskId: z.string().optional().describe("按任務 ID 過濾對話記錄(選填)"), - startDate: z - .string() - .optional() - .describe("起始日期過濾,格式為 ISO 日期字串(選填)"), - endDate: z - .string() - .optional() - .describe("結束日期過濾,格式為 ISO 日期字串(選填)"), - limit: z - .number() - .int() - .positive() - .max(100) - .default(20) - .describe("返回結果數量限制,最大 100(預設:20)"), - offset: z - .number() - .int() - .nonnegative() - .default(0) - .describe("分頁偏移量(預設:0)"), -}); +export const listConversationLogSchema = z + .object({ + taskId: z + .string() + .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) + .optional() + .describe("按任務 ID 過濾對話記錄(選填)"), + startDate: z + .string() + .refine( + (val) => { + const date = new Date(val); + return !isNaN(date.getTime()); + }, + { + message: + "起始日期格式無效,請使用ISO日期格式,例如:2025-04-11T12:13:49.751Z", + } + ) + .optional() + .describe("起始日期過濾,格式為 ISO 日期字串(選填)"), + endDate: z + .string() + .refine( + (val) => { + const date = new Date(val); + return !isNaN(date.getTime()); + }, + { + message: + "結束日期格式無效,請使用ISO日期格式,例如:2025-04-11T12:13:49.751Z", + } + ) + .optional() + .describe("結束日期過濾,格式為 ISO 日期字串(選填)"), + limit: z + .number() + .int({ message: "限制必須是整數" }) + .positive({ message: "限制必須是正數" }) + .max(100, { message: "限制不能超過100條記錄" }) + .default(20) + .describe("返回結果數量限制,最大 100(預設:20)"), + offset: z + .number() + .int({ message: "偏移量必須是整數" }) + .nonnegative({ message: "偏移量不能為負數" }) + .default(0) + .describe("分頁偏移量(預設:0)"), + }) + .refine( + (data) => { + // 驗證起始日期和結束日期的順序 + if (data.startDate && data.endDate) { + const start = new Date(data.startDate); + const end = new Date(data.endDate); + return start <= end; + } + return true; + }, + { + message: "起始日期必須早於或等於結束日期", + path: ["endDate"], + } + ); export async function listConversationLog({ taskId, @@ -171,7 +211,13 @@ export async function listConversationLog({ // 清除所有對話日誌工具 export const clearConversationLogSchema = z.object({ - confirm: z.boolean().describe("確認刪除所有日誌記錄(此操作不可逆)"), + confirm: z + .boolean() + .refine((val) => val === true, { + message: + "必須明確確認清除操作,請將 confirm 參數設置為 true 以確認此危險操作", + }) + .describe("確認刪除所有日誌記錄(此操作不可逆)"), }); export async function clearConversationLog({ @@ -314,18 +360,23 @@ export async function listArchivedLogs({ export const readArchivedLogSchema = z.object({ filename: z .string() + .min(1, { message: "文件名不能為空" }) + .refine((val) => val.match(/^conversation_log_[\d-]+T[\d-]+\.json$/), { + message: + "無效的歸檔日誌文件名,正確格式為 'conversation_log_[timestamp].json'", + }) .describe("歸檔日誌文件名,格式為 'conversation_log_[timestamp].json'"), limit: z .number() - .int() - .positive() - .max(100) + .int({ message: "限制必須是整數" }) + .positive({ message: "限制必須是正數" }) + .max(100, { message: "限制不能超過100條記錄" }) .default(50) .describe("返回結果數量限制,最大 100(預設:50)"), offset: z .number() - .int() - .nonnegative() + .int({ message: "偏移量必須是整數" }) + .nonnegative({ message: "偏移量不能為負數" }) .default(0) .describe("分頁起始位置(預設:0)"), }); diff --git a/src/tools/taskTools.ts b/src/tools/taskTools.ts index 4782eb2..58beed1 100644 --- a/src/tools/taskTools.ts +++ b/src/tools/taskTools.ts @@ -35,6 +35,9 @@ import { loadTaskRelatedFiles } from "../utils/fileLoader.js"; export const planTaskSchema = z.object({ description: z .string() + .min(10, { + message: "任務描述不能少於10個字符,請提供更詳細的描述以確保任務目標明確", + }) .describe("完整詳細的任務問題描述,應包含任務目標、背景及預期成果"), requirements: z .string() @@ -89,9 +92,16 @@ export async function planTask({ export const analyzeTaskSchema = z.object({ summary: z .string() + .min(20, { + message: + "任務摘要太短,請提供更詳細的摘要,包含任務目標、範圍與關鍵技術挑戰", + }) .describe("結構化的任務摘要,包含任務目標、範圍與關鍵技術挑戰"), initialConcept: z .string() + .min(50, { + message: "初步解答構想過於簡短,請提供更完整的技術方案和實施策略詳情", + }) .describe("初步解答構想,包含技術方案、架構設計和實施策略"), previousAnalysis: z .string() @@ -147,14 +157,27 @@ export async function analyzeTask({ } // 反思構想工具 -export const reflectTaskSchema = z.object({ - summary: z - .string() - .describe("結構化的任務摘要,保持與分析階段一致以確保連續性"), - analysis: z - .string() - .describe("完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案"), -}); +export const reflectTaskSchema = z + .object({ + summary: z + .string() + .min(20, { + message: "任務摘要太短,請確保包含完整的任務目標和範圍以維持分析連續性", + }) + .describe("結構化的任務摘要,保持與分析階段一致以確保連續性"), + analysis: z + .string() + .min(100, { + message: + "技術分析結果過於簡略,請提供更詳盡的技術細節、依賴組件和實施方案說明", + }) + .describe("完整詳盡的技術分析結果,包括所有技術細節、依賴組件和實施方案"), + }) + .refine((data) => data.summary.length * 3 <= data.analysis.length, { + message: + "分析內容應該比摘要更詳細,建議分析部分至少是摘要長度的3倍以提供足夠的技術深度", + path: ["analysis"], + }); export async function reflectTask({ summary, @@ -204,33 +227,62 @@ export async function reflectTask({ } // 拆分任務工具 -export const splitTasksSchema = z.object({ - isOverwrite: z - .boolean() - .describe( - "任務覆蓋模式選擇(true:清除並覆蓋所有現有任務;false:保留現有任務並新增)" - ), - tasks: z - .array( - z.object({ - name: z.string().describe("簡潔明確的任務名稱,應能清晰表達任務目的"), - description: z - .string() - .describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"), - notes: z - .string() - .optional() - .describe("補充說明、特殊處理要求或實施建議(選填)"), - dependencies: z - .array(z.string()) - .optional() - .describe( - "此任務依賴的前置任務ID或任務名稱列表,支持兩種引用方式,名稱引用更直觀" - ), - }) - ) - .describe("結構化的任務清單,每個任務應保持原子性且有明確的完成標準"), -}); +export const splitTasksSchema = z + .object({ + isOverwrite: z + .boolean() + .describe( + "任務覆蓋模式選擇(true:清除並覆蓋所有現有任務;false:保留現有任務並新增)" + ), + tasks: z + .array( + z.object({ + name: z + .string() + .min(5, { + message: "任務名稱太短,請提供更清晰明確的名稱以便識別任務目的", + }) + .max(100, { message: "任務名稱過長,請保持簡潔,不超過100個字符" }) + .describe("簡潔明確的任務名稱,應能清晰表達任務目的"), + description: z + .string() + .min(20, { + message: + "任務描述太簡短,請提供更詳細的描述,包含實施要點和驗收標準", + }) + .describe("詳細的任務描述,包含實施要點、技術細節和驗收標準"), + notes: z + .string() + .optional() + .describe("補充說明、特殊處理要求或實施建議(選填)"), + dependencies: z + .array(z.string()) + .optional() + .describe( + "此任務依賴的前置任務ID或任務名稱列表,支持兩種引用方式,名稱引用更直觀" + ), + }) + ) + .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"], + } + ); export async function splitTasks({ isOverwrite, @@ -398,6 +450,7 @@ export async function listTasks() { export const executeTaskSchema = z.object({ taskId: z .string() + .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe("待執行任務的唯一標識符,必須是系統中存在的有效任務ID"), }); @@ -811,6 +864,7 @@ export async function executeTask({ export const verifyTaskSchema = z.object({ taskId: z .string() + .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe("待驗證任務的唯一標識符,必須是狀態為「進行中」的有效任務ID"), }); @@ -911,11 +965,15 @@ export async function verifyTask({ taskId }: z.infer) { export const completeTaskSchema = z.object({ taskId: z .string() + .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe( "待標記為完成的任務唯一標識符,必須是狀態為「進行中」的有效任務ID" ), summary: z .string() + .min(30, { + message: "任務摘要太簡短,請提供更詳細的完成報告,包含實施結果和主要決策", + }) .optional() .describe( "任務完成摘要,簡潔描述實施結果和重要決策(選填,如未提供將自動生成)" @@ -1021,6 +1079,7 @@ export async function completeTask({ export const deleteTaskSchema = z.object({ taskId: z .string() + .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) .describe("待刪除任務的唯一標識符,必須是系統中存在且未完成的任務ID"), }); @@ -1122,7 +1181,13 @@ export async function deleteTask({ taskId }: z.infer) { // 清除所有任務工具 export const clearAllTasksSchema = z.object({ - confirm: z.boolean().describe("確認刪除所有未完成的任務(此操作不可逆)"), + confirm: z + .boolean() + .refine((val) => val === true, { + message: + "必須明確確認清除操作,請將 confirm 參數設置為 true 以確認此危險操作", + }) + .describe("確認刪除所有未完成的任務(此操作不可逆)"), }); export async function clearAllTasks({ @@ -1212,46 +1277,93 @@ export async function clearAllTasks({ } // 更新任務內容工具 -export const updateTaskContentSchema = z.object({ - taskId: z - .string() - .describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"), - name: z.string().optional().describe("任務的新名稱(選填)"), - description: z.string().optional().describe("任務的新描述內容(選填)"), - notes: z.string().optional().describe("任務的新補充說明(選填)"), - relatedFiles: z - .array( - z.object({ - path: z - .string() - .describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"), - type: z - .enum([ - RelatedFileType.TO_MODIFY, - RelatedFileType.REFERENCE, - RelatedFileType.OUTPUT, - RelatedFileType.DEPENDENCY, - RelatedFileType.OTHER, - ]) - .describe("文件與任務的關係類型"), - description: z.string().optional().describe("文件的補充描述(選填)"), - lineStart: z - .number() - .int() - .positive() - .optional() - .describe("相關代碼區塊的起始行(選填)"), - lineEnd: z - .number() - .int() - .positive() - .optional() - .describe("相關代碼區塊的結束行(選填)"), +export const updateTaskContentSchema = z + .object({ + taskId: z + .string() + .uuid({ message: "任務ID格式無效,請提供有效的UUID格式" }) + .describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"), + name: z + .string() + .min(5, { + message: "任務名稱太短,請提供更清晰明確的名稱以便識別任務目的", }) - ) - .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.OUTPUT, + 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("與任務相關的文件列表(選填)"), + }) + .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.relatedFiles && data.relatedFiles.length > 0) + ); + }, + { + message: + "更新請求無效:1. 行號設置必須同時包含起始行和結束行,且起始行必須小於結束行;2. 至少需要更新一個字段(名稱、描述、注記或相關文件)", + path: ["relatedFiles"], + } + ); export async function updateTaskContent({ taskId, @@ -1385,42 +1497,86 @@ export async function updateTaskContent({ } // 更新任務相關文件工具 -export const updateTaskRelatedFilesSchema = z.object({ - taskId: z - .string() - .describe("待更新任務的唯一標識符,必須是系統中存在且未完成的任務ID"), - relatedFiles: z - .array( - z.object({ - path: z - .string() - .describe("文件路徑,可以是相對於項目根目錄的路徑或絕對路徑"), - type: z - .enum([ - RelatedFileType.TO_MODIFY, - RelatedFileType.REFERENCE, - RelatedFileType.OUTPUT, - 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("與任務相關的文件列表"), -}); +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.OUTPUT, + 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 async function updateTaskRelatedFiles({ taskId,