mcp-shrimp-task-manager
Version:
Shrimp Task Manager is a task tool built for AI Agents, emphasizing chain-of-thought, reflection, and style consistency. It converts natural language into structured dev tasks with dependency tracking and iterative refinement, enabling agent-like develope
743 lines • 31.6 kB
JavaScript
import { TaskStatus, TaskComplexityLevel, TaskComplexityThresholds, } from "../types/index.js";
import fs from "fs/promises";
import path from "path";
import { v4 as uuidv4 } from "uuid";
import { fileURLToPath } from "url";
import { exec } from "child_process";
import { promisify } from "util";
import { getDataDir, getTasksFilePath, getMemoryDir } from "../utils/paths.js";
// 確保獲取專案資料夾路徑
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PROJECT_ROOT = path.resolve(__dirname, "../..");
// 數據文件路徑(改為異步獲取)
// const DATA_DIR = getDataDir();
// const TASKS_FILE = getTasksFilePath();
// 將exec轉換為Promise形式
const execPromise = promisify(exec);
// 確保數據目錄存在
async function ensureDataDir() {
const DATA_DIR = await getDataDir();
const TASKS_FILE = await getTasksFilePath();
try {
await fs.access(DATA_DIR);
}
catch (error) {
await fs.mkdir(DATA_DIR, { recursive: true });
}
try {
await fs.access(TASKS_FILE);
}
catch (error) {
await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks: [] }));
}
}
// 讀取所有任務
async function readTasks() {
await ensureDataDir();
const TASKS_FILE = await getTasksFilePath();
const data = await fs.readFile(TASKS_FILE, "utf-8");
const tasks = JSON.parse(data).tasks;
// 將日期字串轉換回 Date 物件
return tasks.map((task) => ({
...task,
createdAt: task.createdAt ? new Date(task.createdAt) : new Date(),
updatedAt: task.updatedAt ? new Date(task.updatedAt) : new Date(),
completedAt: task.completedAt ? new Date(task.completedAt) : undefined,
}));
}
// 寫入所有任務
async function writeTasks(tasks) {
await ensureDataDir();
const TASKS_FILE = await getTasksFilePath();
await fs.writeFile(TASKS_FILE, JSON.stringify({ tasks }, null, 2));
}
// 獲取所有任務
export async function getAllTasks() {
return await readTasks();
}
// 根據ID獲取任務
export async function getTaskById(taskId) {
const tasks = await readTasks();
return tasks.find((task) => task.id === taskId) || null;
}
// 創建新任務
export async function createTask(name, description, notes, dependencies = [], relatedFiles) {
const tasks = await readTasks();
const dependencyObjects = dependencies.map((taskId) => ({
taskId,
}));
const newTask = {
id: uuidv4(),
name,
description,
notes,
status: TaskStatus.PENDING,
dependencies: dependencyObjects,
createdAt: new Date(),
updatedAt: new Date(),
relatedFiles,
};
tasks.push(newTask);
await writeTasks(tasks);
return newTask;
}
// 更新任務
export async function updateTask(taskId, updates) {
const tasks = await readTasks();
const taskIndex = tasks.findIndex((task) => task.id === taskId);
if (taskIndex === -1) {
return null;
}
// 檢查任務是否已完成,已完成的任務不允許更新(除非是明確允許的欄位)
if (tasks[taskIndex].status === TaskStatus.COMPLETED) {
// 僅允許更新 summary 欄位(任務摘要)和 relatedFiles 欄位
const allowedFields = ["summary", "relatedFiles"];
const attemptedFields = Object.keys(updates);
const disallowedFields = attemptedFields.filter((field) => !allowedFields.includes(field));
if (disallowedFields.length > 0) {
return null;
}
}
tasks[taskIndex] = {
...tasks[taskIndex],
...updates,
updatedAt: new Date(),
};
await writeTasks(tasks);
return tasks[taskIndex];
}
// 更新任務狀態
export async function updateTaskStatus(taskId, status) {
const updates = { status };
if (status === TaskStatus.COMPLETED) {
updates.completedAt = new Date();
}
return await updateTask(taskId, updates);
}
// 更新任務摘要
export async function updateTaskSummary(taskId, summary) {
return await updateTask(taskId, { summary });
}
// 更新任務內容
export async function updateTaskContent(taskId, updates) {
// 獲取任務並檢查是否存在
const task = await getTaskById(taskId);
if (!task) {
return { success: false, message: "找不到指定任務" };
}
// 檢查任務是否已完成
if (task.status === TaskStatus.COMPLETED) {
return { success: false, message: "無法更新已完成的任務" };
}
// 構建更新物件,只包含實際需要更新的欄位
const updateObj = {};
if (updates.name !== undefined) {
updateObj.name = updates.name;
}
if (updates.description !== undefined) {
updateObj.description = updates.description;
}
if (updates.notes !== undefined) {
updateObj.notes = updates.notes;
}
if (updates.relatedFiles !== undefined) {
updateObj.relatedFiles = updates.relatedFiles;
}
if (updates.dependencies !== undefined) {
updateObj.dependencies = updates.dependencies.map((dep) => ({
taskId: dep,
}));
}
if (updates.implementationGuide !== undefined) {
updateObj.implementationGuide = updates.implementationGuide;
}
if (updates.verificationCriteria !== undefined) {
updateObj.verificationCriteria = updates.verificationCriteria;
}
// 如果沒有要更新的內容,提前返回
if (Object.keys(updateObj).length === 0) {
return { success: true, message: "沒有提供需要更新的內容", task };
}
// 執行更新
const updatedTask = await updateTask(taskId, updateObj);
if (!updatedTask) {
return { success: false, message: "更新任務時發生錯誤" };
}
return {
success: true,
message: "任務內容已成功更新",
task: updatedTask,
};
}
// 更新任務相關文件
export async function updateTaskRelatedFiles(taskId, relatedFiles) {
// 獲取任務並檢查是否存在
const task = await getTaskById(taskId);
if (!task) {
return { success: false, message: "找不到指定任務" };
}
// 檢查任務是否已完成
if (task.status === TaskStatus.COMPLETED) {
return { success: false, message: "無法更新已完成的任務" };
}
// 執行更新
const updatedTask = await updateTask(taskId, { relatedFiles });
if (!updatedTask) {
return { success: false, message: "更新任務相關文件時發生錯誤" };
}
return {
success: true,
message: `已成功更新任務相關文件,共 ${relatedFiles.length} 個文件`,
task: updatedTask,
};
}
// 批量創建或更新任務
export async function batchCreateOrUpdateTasks(taskDataList, updateMode, // 必填參數,指定任務更新策略
globalAnalysisResult // 新增:全局分析結果
) {
// 讀取現有的所有任務
const existingTasks = await readTasks();
// 根據更新模式處理現有任務
let tasksToKeep = [];
if (updateMode === "append") {
// 追加模式:保留所有現有任務
tasksToKeep = [...existingTasks];
}
else if (updateMode === "overwrite") {
// 覆蓋模式:僅保留已完成的任務,清除所有未完成任務
tasksToKeep = existingTasks.filter((task) => task.status === TaskStatus.COMPLETED);
}
else if (updateMode === "selective") {
// 選擇性更新模式:根據任務名稱選擇性更新,保留未在更新列表中的任務
// 1. 提取待更新任務的名稱清單
const updateTaskNames = new Set(taskDataList.map((task) => task.name));
// 2. 保留所有沒有出現在更新列表中的任務
tasksToKeep = existingTasks.filter((task) => !updateTaskNames.has(task.name));
}
else if (updateMode === "clearAllTasks") {
// 清除所有任務模式:清空任務列表
tasksToKeep = [];
}
// 這個映射將用於存儲名稱到任務ID的映射,用於支持通過名稱引用任務
const taskNameToIdMap = new Map();
// 對於選擇性更新模式,先將現有任務的名稱和ID記錄下來
if (updateMode === "selective") {
existingTasks.forEach((task) => {
taskNameToIdMap.set(task.name, task.id);
});
}
// 記錄所有任務的名稱和ID,無論是要保留的任務還是新建的任務
// 這將用於稍後解析依賴關係
tasksToKeep.forEach((task) => {
taskNameToIdMap.set(task.name, task.id);
});
// 創建新任務的列表
const newTasks = [];
for (const taskData of taskDataList) {
// 檢查是否為選擇性更新模式且該任務名稱已存在
if (updateMode === "selective" && taskNameToIdMap.has(taskData.name)) {
// 獲取現有任務的ID
const existingTaskId = taskNameToIdMap.get(taskData.name);
// 查找現有任務
const existingTaskIndex = existingTasks.findIndex((task) => task.id === existingTaskId);
// 如果找到現有任務並且該任務未完成,則更新它
if (existingTaskIndex !== -1 &&
existingTasks[existingTaskIndex].status !== TaskStatus.COMPLETED) {
const taskToUpdate = existingTasks[existingTaskIndex];
// 更新任務的基本信息,但保留原始ID、創建時間等
const updatedTask = {
...taskToUpdate,
name: taskData.name,
description: taskData.description,
notes: taskData.notes,
// 後面會處理 dependencies
updatedAt: new Date(),
// 新增:保存實現指南(如果有)
implementationGuide: taskData.implementationGuide,
// 新增:保存驗證標準(如果有)
verificationCriteria: taskData.verificationCriteria,
// 新增:保存全局分析結果(如果有)
analysisResult: globalAnalysisResult,
};
// 處理相關文件(如果有)
if (taskData.relatedFiles) {
updatedTask.relatedFiles = taskData.relatedFiles;
}
// 將更新後的任務添加到新任務列表
newTasks.push(updatedTask);
// 從tasksToKeep中移除此任務,因為它已經被更新並添加到newTasks中了
tasksToKeep = tasksToKeep.filter((task) => task.id !== existingTaskId);
}
}
else {
// 創建新任務
const newTaskId = uuidv4();
// 將新任務的名稱和ID添加到映射中
taskNameToIdMap.set(taskData.name, newTaskId);
const newTask = {
id: newTaskId,
name: taskData.name,
description: taskData.description,
notes: taskData.notes,
status: TaskStatus.PENDING,
dependencies: [], // 後面會填充
createdAt: new Date(),
updatedAt: new Date(),
relatedFiles: taskData.relatedFiles,
// 新增:保存實現指南(如果有)
implementationGuide: taskData.implementationGuide,
// 新增:保存驗證標準(如果有)
verificationCriteria: taskData.verificationCriteria,
// 新增:保存全局分析結果(如果有)
analysisResult: globalAnalysisResult,
};
newTasks.push(newTask);
}
}
// 處理任務之間的依賴關係
for (let i = 0; i < taskDataList.length; i++) {
const taskData = taskDataList[i];
const newTask = newTasks[i];
// 如果存在依賴關係,處理它們
if (taskData.dependencies && taskData.dependencies.length > 0) {
const resolvedDependencies = [];
for (const dependencyName of taskData.dependencies) {
// 首先嘗試將依賴項解釋為任務ID
let dependencyTaskId = dependencyName;
// 如果依賴項看起來不像UUID,則嘗試將其解釋為任務名稱
if (!dependencyName.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
// 如果映射中存在此名稱,則使用對應的ID
if (taskNameToIdMap.has(dependencyName)) {
dependencyTaskId = taskNameToIdMap.get(dependencyName);
}
else {
continue; // 跳過此依賴
}
}
else {
// 是UUID格式,但需要確認此ID是否對應實際存在的任務
const idExists = [...tasksToKeep, ...newTasks].some((task) => task.id === dependencyTaskId);
if (!idExists) {
continue; // 跳過此依賴
}
}
resolvedDependencies.push({ taskId: dependencyTaskId });
}
newTask.dependencies = resolvedDependencies;
}
}
// 合併保留的任務和新任務
const allTasks = [...tasksToKeep, ...newTasks];
// 寫入更新後的任務列表
await writeTasks(allTasks);
return newTasks;
}
// 檢查任務是否可以執行(所有依賴都已完成)
export async function canExecuteTask(taskId) {
const task = await getTaskById(taskId);
if (!task) {
return { canExecute: false };
}
if (task.status === TaskStatus.COMPLETED) {
return { canExecute: false }; // 已完成的任務不需要再執行
}
if (task.dependencies.length === 0) {
return { canExecute: true }; // 沒有依賴的任務可以直接執行
}
const allTasks = await readTasks();
const blockedBy = [];
for (const dependency of task.dependencies) {
const dependencyTask = allTasks.find((t) => t.id === dependency.taskId);
if (!dependencyTask || dependencyTask.status !== TaskStatus.COMPLETED) {
blockedBy.push(dependency.taskId);
}
}
return {
canExecute: blockedBy.length === 0,
blockedBy: blockedBy.length > 0 ? blockedBy : undefined,
};
}
// 刪除任務
export async function deleteTask(taskId) {
const tasks = await readTasks();
const taskIndex = tasks.findIndex((task) => task.id === taskId);
if (taskIndex === -1) {
return { success: false, message: "找不到指定任務" };
}
// 檢查任務狀態,已完成的任務不允許刪除
if (tasks[taskIndex].status === TaskStatus.COMPLETED) {
return { success: false, message: "無法刪除已完成的任務" };
}
// 檢查是否有其他任務依賴此任務
const allTasks = tasks.filter((_, index) => index !== taskIndex);
const dependentTasks = allTasks.filter((task) => task.dependencies.some((dep) => dep.taskId === taskId));
if (dependentTasks.length > 0) {
const dependentTaskNames = dependentTasks
.map((task) => `"${task.name}" (ID: ${task.id})`)
.join(", ");
return {
success: false,
message: `無法刪除此任務,因為以下任務依賴於它: ${dependentTaskNames}`,
};
}
// 執行刪除操作
tasks.splice(taskIndex, 1);
await writeTasks(tasks);
return { success: true, message: "任務刪除成功" };
}
// 評估任務複雜度
export async function assessTaskComplexity(taskId) {
const task = await getTaskById(taskId);
if (!task) {
return null;
}
// 評估各項指標
const descriptionLength = task.description.length;
const dependenciesCount = task.dependencies.length;
const notesLength = task.notes ? task.notes.length : 0;
const hasNotes = !!task.notes;
// 基於各項指標評估複雜度級別
let level = TaskComplexityLevel.LOW;
// 描述長度評估
if (descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.VERY_HIGH) {
level = TaskComplexityLevel.VERY_HIGH;
}
else if (descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.HIGH) {
level = TaskComplexityLevel.HIGH;
}
else if (descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.MEDIUM) {
level = TaskComplexityLevel.MEDIUM;
}
// 依賴數量評估(取最高級別)
if (dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.VERY_HIGH) {
level = TaskComplexityLevel.VERY_HIGH;
}
else if (dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.HIGH &&
level !== TaskComplexityLevel.VERY_HIGH) {
level = TaskComplexityLevel.HIGH;
}
else if (dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.MEDIUM &&
level !== TaskComplexityLevel.HIGH &&
level !== TaskComplexityLevel.VERY_HIGH) {
level = TaskComplexityLevel.MEDIUM;
}
// 注記長度評估(取最高級別)
if (notesLength >= TaskComplexityThresholds.NOTES_LENGTH.VERY_HIGH) {
level = TaskComplexityLevel.VERY_HIGH;
}
else if (notesLength >= TaskComplexityThresholds.NOTES_LENGTH.HIGH &&
level !== TaskComplexityLevel.VERY_HIGH) {
level = TaskComplexityLevel.HIGH;
}
else if (notesLength >= TaskComplexityThresholds.NOTES_LENGTH.MEDIUM &&
level !== TaskComplexityLevel.HIGH &&
level !== TaskComplexityLevel.VERY_HIGH) {
level = TaskComplexityLevel.MEDIUM;
}
// 根據複雜度級別生成處理建議
const recommendations = [];
// 低複雜度任務建議
if (level === TaskComplexityLevel.LOW) {
recommendations.push("此任務複雜度較低,可直接執行");
recommendations.push("建議設定清晰的完成標準,確保驗收有明確依據");
}
// 中等複雜度任務建議
else if (level === TaskComplexityLevel.MEDIUM) {
recommendations.push("此任務具有一定複雜性,建議詳細規劃執行步驟");
recommendations.push("可分階段執行並定期檢查進度,確保理解準確且實施完整");
if (dependenciesCount > 0) {
recommendations.push("注意檢查所有依賴任務的完成狀態和輸出質量");
}
}
// 高複雜度任務建議
else if (level === TaskComplexityLevel.HIGH) {
recommendations.push("此任務複雜度較高,建議先進行充分的分析和規劃");
recommendations.push("考慮將任務拆分為較小的、可獨立執行的子任務");
recommendations.push("建立清晰的里程碑和檢查點,便於追蹤進度和品質");
if (dependenciesCount > TaskComplexityThresholds.DEPENDENCIES_COUNT.MEDIUM) {
recommendations.push("依賴任務較多,建議製作依賴關係圖,確保執行順序正確");
}
}
// 極高複雜度任務建議
else if (level === TaskComplexityLevel.VERY_HIGH) {
recommendations.push("⚠️ 此任務複雜度極高,強烈建議拆分為多個獨立任務");
recommendations.push("在執行前進行詳盡的分析和規劃,明確定義各子任務的範圍和介面");
recommendations.push("對任務進行風險評估,識別可能的阻礙因素並制定應對策略");
recommendations.push("建立具體的測試和驗證標準,確保每個子任務的輸出質量");
if (descriptionLength >= TaskComplexityThresholds.DESCRIPTION_LENGTH.VERY_HIGH) {
recommendations.push("任務描述非常長,建議整理關鍵點並建立結構化的執行清單");
}
if (dependenciesCount >= TaskComplexityThresholds.DEPENDENCIES_COUNT.HIGH) {
recommendations.push("依賴任務數量過多,建議重新評估任務邊界,確保任務切分合理");
}
}
return {
level,
metrics: {
descriptionLength,
dependenciesCount,
notesLength,
hasNotes,
},
recommendations,
};
}
// 清除所有任務
export async function clearAllTasks() {
try {
// 確保數據目錄存在
await ensureDataDir();
// 讀取現有任務
const allTasks = await readTasks();
// 如果沒有任務,直接返回
if (allTasks.length === 0) {
return { success: true, message: "沒有任務需要清除" };
}
// 篩選出已完成的任務
const completedTasks = allTasks.filter((task) => task.status === TaskStatus.COMPLETED);
// 創建備份文件名
const timestamp = new Date()
.toISOString()
.replace(/:/g, "-")
.replace(/\..+/, "");
const backupFileName = `tasks_memory_${timestamp}.json`;
// 確保 memory 目錄存在
const MEMORY_DIR = await getMemoryDir();
try {
await fs.access(MEMORY_DIR);
}
catch (error) {
await fs.mkdir(MEMORY_DIR, { recursive: true });
}
// 創建 memory 目錄下的備份路徑
const memoryFilePath = path.join(MEMORY_DIR, backupFileName);
// 同時寫入到 memory 目錄 (只包含已完成的任務)
await fs.writeFile(memoryFilePath, JSON.stringify({ tasks: completedTasks }, null, 2));
// 清空任務文件
await writeTasks([]);
return {
success: true,
message: `已成功清除所有任務,共 ${allTasks.length} 個任務被刪除,已備份 ${completedTasks.length} 個已完成的任務至 memory 目錄`,
backupFile: backupFileName,
};
}
catch (error) {
return {
success: false,
message: `清除任務時發生錯誤: ${error instanceof Error ? error.message : String(error)}`,
};
}
}
// 使用系統指令搜尋任務記憶
export async function searchTasksWithCommand(query, isId = false, page = 1, pageSize = 5) {
// 讀取當前任務檔案中的任務
const currentTasks = await readTasks();
let memoryTasks = [];
// 搜尋記憶資料夾中的任務
const MEMORY_DIR = await getMemoryDir();
try {
// 確保記憶資料夾存在
await fs.access(MEMORY_DIR);
// 生成搜尋命令
const cmd = generateSearchCommand(query, isId, MEMORY_DIR);
// 如果有搜尋命令,執行它
if (cmd) {
try {
const { stdout } = await execPromise(cmd, {
maxBuffer: 1024 * 1024 * 10,
});
if (stdout) {
// 解析搜尋結果,提取符合的檔案路徑
const matchedFiles = new Set();
stdout.split("\n").forEach((line) => {
if (line.trim()) {
// 格式通常是: 文件路徑:匹配內容
const filePath = line.split(":")[0];
if (filePath) {
matchedFiles.add(filePath);
}
}
});
// 限制讀取檔案數量
const MAX_FILES_TO_READ = 10;
const sortedFiles = Array.from(matchedFiles)
.sort()
.reverse()
.slice(0, MAX_FILES_TO_READ);
// 只處理符合條件的檔案
for (const filePath of sortedFiles) {
try {
const data = await fs.readFile(filePath, "utf-8");
const tasks = JSON.parse(data).tasks || [];
// 格式化日期字段
const formattedTasks = tasks.map((task) => ({
...task,
createdAt: task.createdAt
? new Date(task.createdAt)
: new Date(),
updatedAt: task.updatedAt
? new Date(task.updatedAt)
: new Date(),
completedAt: task.completedAt
? new Date(task.completedAt)
: undefined,
}));
// 進一步過濾任務確保符合條件
const filteredTasks = isId
? formattedTasks.filter((task) => task.id === query)
: formattedTasks.filter((task) => {
const keywords = query
.split(/\s+/)
.filter((k) => k.length > 0);
if (keywords.length === 0)
return true;
return keywords.every((keyword) => {
const lowerKeyword = keyword.toLowerCase();
return (task.name.toLowerCase().includes(lowerKeyword) ||
task.description.toLowerCase().includes(lowerKeyword) ||
(task.notes &&
task.notes.toLowerCase().includes(lowerKeyword)) ||
(task.implementationGuide &&
task.implementationGuide
.toLowerCase()
.includes(lowerKeyword)) ||
(task.summary &&
task.summary.toLowerCase().includes(lowerKeyword)));
});
});
memoryTasks.push(...filteredTasks);
}
catch (error) { }
}
}
}
catch (error) { }
}
}
catch (error) { }
// 從當前任務中過濾符合條件的任務
const filteredCurrentTasks = filterCurrentTasks(currentTasks, query, isId);
// 合併結果並去重
const taskMap = new Map();
// 當前任務優先
filteredCurrentTasks.forEach((task) => {
taskMap.set(task.id, task);
});
// 加入記憶任務,避免重複
memoryTasks.forEach((task) => {
if (!taskMap.has(task.id)) {
taskMap.set(task.id, task);
}
});
// 合併後的結果
const allTasks = Array.from(taskMap.values());
// 排序 - 按照更新或完成時間降序排列
allTasks.sort((a, b) => {
// 優先按完成時間排序
if (a.completedAt && b.completedAt) {
return b.completedAt.getTime() - a.completedAt.getTime();
}
else if (a.completedAt) {
return -1; // a完成了但b未完成,a排前面
}
else if (b.completedAt) {
return 1; // b完成了但a未完成,b排前面
}
// 否則按更新時間排序
return b.updatedAt.getTime() - a.updatedAt.getTime();
});
// 分頁處理
const totalResults = allTasks.length;
const totalPages = Math.ceil(totalResults / pageSize);
const safePage = Math.max(1, Math.min(page, totalPages || 1)); // 確保頁碼有效
const startIndex = (safePage - 1) * pageSize;
const endIndex = Math.min(startIndex + pageSize, totalResults);
const paginatedTasks = allTasks.slice(startIndex, endIndex);
return {
tasks: paginatedTasks,
pagination: {
currentPage: safePage,
totalPages: totalPages || 1,
totalResults,
hasMore: safePage < totalPages,
},
};
}
// 根據平台生成適當的搜尋命令
function generateSearchCommand(query, isId, memoryDir) {
// 安全地轉義用戶輸入
const safeQuery = escapeShellArg(query);
const keywords = safeQuery.split(/\s+/).filter((k) => k.length > 0);
// 檢測操作系統類型
const isWindows = process.platform === "win32";
if (isWindows) {
// Windows環境,使用findstr命令
if (isId) {
// ID搜尋
return `findstr /s /i /c:"${safeQuery}" "${memoryDir}\\*.json"`;
}
else if (keywords.length === 1) {
// 單一關鍵字
return `findstr /s /i /c:"${safeQuery}" "${memoryDir}\\*.json"`;
}
else {
// 多關鍵字搜尋 - Windows中使用PowerShell
const keywordPatterns = keywords.map((k) => `'${k}'`).join(" -and ");
return `powershell -Command "Get-ChildItem -Path '${memoryDir}' -Filter *.json -Recurse | Select-String -Pattern ${keywordPatterns} | ForEach-Object { $_.Path }"`;
}
}
else {
// Unix/Linux/MacOS環境,使用grep命令
if (isId) {
return `grep -r --include="*.json" "${safeQuery}" "${memoryDir}"`;
}
else if (keywords.length === 1) {
return `grep -r --include="*.json" "${safeQuery}" "${memoryDir}"`;
}
else {
// 多關鍵字用管道連接多個grep命令
const firstKeyword = escapeShellArg(keywords[0]);
const otherKeywords = keywords.slice(1).map((k) => escapeShellArg(k));
let cmd = `grep -r --include="*.json" "${firstKeyword}" "${memoryDir}"`;
for (const keyword of otherKeywords) {
cmd += ` | grep "${keyword}"`;
}
return cmd;
}
}
}
/**
* 安全地轉義shell參數,防止命令注入
*/
function escapeShellArg(arg) {
if (!arg)
return "";
// 移除所有控制字符和特殊字符
return arg
.replace(/[\x00-\x1F\x7F]/g, "") // 控制字符
.replace(/[&;`$"'<>|]/g, ""); // Shell 特殊字符
}
// 過濾當前任務列表
function filterCurrentTasks(tasks, query, isId) {
if (isId) {
return tasks.filter((task) => task.id === query);
}
else {
const keywords = query.split(/\s+/).filter((k) => k.length > 0);
if (keywords.length === 0)
return tasks;
return tasks.filter((task) => {
return keywords.every((keyword) => {
const lowerKeyword = keyword.toLowerCase();
return (task.name.toLowerCase().includes(lowerKeyword) ||
task.description.toLowerCase().includes(lowerKeyword) ||
(task.notes && task.notes.toLowerCase().includes(lowerKeyword)) ||
(task.implementationGuide &&
task.implementationGuide.toLowerCase().includes(lowerKeyword)) ||
(task.summary && task.summary.toLowerCase().includes(lowerKeyword)));
});
});
}
}
//# sourceMappingURL=taskModel.js.map