chore: v0.17 features and improvements (#771)
* chore: task management and small bug fix. * chore: task management * feat: implement research command with enhanced context gathering - Add comprehensive research command with AI-powered queries - Implement ContextGatherer utility for reusable context extraction - Support multiple context types: tasks, files, custom text, project tree - Add fuzzy search integration for automatic task discovery - Implement detailed token breakdown display with syntax highlighting - Add enhanced UI with boxed output and code block formatting - Support different detail levels (low, medium, high) for responses - Include project-specific context for more relevant AI responses - Add token counting with gpt-tokens library integration - Create reusable patterns for future context-aware commands - Task 94.4 completed * docs: add context gathering rule and update existing rules - Create comprehensive context_gathering.mdc rule documenting ContextGatherer utility patterns, FuzzyTaskSearch integration, token breakdown display, code block syntax highlighting, and enhanced result display patterns - Update new_features.mdc to include context gathering step - Update commands.mdc with context-aware command pattern - Update ui.mdc with enhanced display patterns and syntax highlighting - Update utilities.mdc to document new context gathering utilities - Update glossary.mdc to include new context_gathering rule - Establishes standardized patterns for building intelligent, context-aware commands that can leverage project knowledge for better AI assistance * feat(fuzzy): improves fuzzy search to introspect into subtasks as well. might still need improvement. * fix(move): adjusts logic to prevent an issue when moving from parent to subtask if the target parent has no subtasks. * fix(move-task): Fix critical bugs in task move functionality - Fixed parent-to-parent task moves where original task would remain as duplicate - Fixed moving tasks to become subtasks of empty parents (validation errors) - Fixed moving subtasks between different parent tasks - Improved comma-separated batch moves with proper error handling - Updated MCP tool to use core logic instead of custom implementation - Resolves task duplication issues and enables proper task hierarchy reorganization * feat(research): Add subtasks to fuzzy search and follow-up questions - Enhanced fuzzy search to include subtasks in discovery - Added interactive follow-up question functionality using inquirer - Improved context discovery by including both tasks and subtasks - Follow-up option for research with default to 'n' for quick workflow * chore: removes task004 chat that had like 11k lines lol. * chore: formatting * feat(show): add comma-separated ID support for multi-task viewing - Enhanced get-task/show command to support comma-separated task IDs for efficient batch operations. - New features include multiple task retrieval, smart display logic, interactive action menu with batch operations, MCP array response for AI agent efficiency, and support for mixed parent tasks and subtasks. - Implementation includes updated CLI show command, enhanced MCP get_task tool, modified showTaskDirect function, and maintained full backward compatibility. - Documentation updated across all relevant files. Benefits include faster context gathering for AI agents, improved workflow with interactive batch operations, better UX with responsive layout, and enhanced API efficiency. * feat(research): Adds MCP tool for command - New MCP Tool: research tool enables AI-powered research with project context - Context Integration: Supports task IDs, file paths, custom context, and project tree - Fuzzy Task Discovery: Automatically finds relevant tasks using semantic search - Token Management: Detailed token counting and breakdown by context type - Multiple Detail Levels: Support for low, medium, and high detail research responses - Telemetry Integration: Full cost tracking and usage analytics - Direct Function: researchDirect with comprehensive parameter validation - Silent Mode: Prevents console output interference with MCP JSON responses - Error Handling: Robust error handling with proper MCP response formatting This completes subtasks 94.5 (Direct Function) and 94.6 (MCP Tool) for the research command implementation, providing a powerful research interface for integrated development environments like Cursor. Updated documentation across taskmaster.mdc, README.md, command-reference.md, examples.md, tutorial.md, and docs/README.md to highlight research capabilities and usage patterns. * chore: task management * chore: task management and removes mistakenly staged changes * fix(move): Fix move command bug that left duplicate tasks - Fixed logic in moveTaskToNewId function that was incorrectly treating task-to-task moves as subtask creation instead of task replacement - Updated moveTaskToNewId to properly handle replacing existing destination tasks instead of just placeholders - The move command now correctly replaces destination tasks and cleans up properly without leaving duplicates - Task Management: Moved task 93 (Google Vertex AI Provider) to position 88, Moved task 94 (Azure OpenAI Provider) to position 89, Updated task dependencies and regenerated task files, Cleaned up orphaned task files automatically - All important validations remain in place: Prevents moving tasks to themselves, Prevents moving parent tasks to their own subtasks, Prevents circular dependencies - Resolves the issue where moving tasks would leave both source and destination tasks in tasks.json and file system * chore: formatting * feat: Add .taskmaster directory (#619) * chore: apply requested changes from next branch (#629) * chore: rc version bump * chore: cleanup migration-guide * fix: bedrock set model and other fixes (#641) * Fix: MCP log errors (#648) * fix: projectRoot duplicate .taskmaster directory (#655) * Version Packages * chore: add package-lock.json * Version Packages * Version Packages * fix: markdown format (#622) * Version Packages * Version Packages * Fixed the Typo in cursor rules Issue:#675 (#677) Fixed the typo in the Api keys * Add one-click MCP server installation for Cursor (#671) * Update README.md - Remove trailing commas (#673) JSON doesn't allow for trailing commas, so these need to be removed in order for this to work * chore: rc version bump * fix: findTasksPath function * fix: update MCP tool * feat(ui): replace emoji complexity indicators with clean filled circle characters Replace 🟢, 🟡, 🔴 emojis with ● character in getComplexityWithColor function Update corresponding unit tests to expect ● instead of emojis Improves UI continuity * fix(ai-providers): change generateObject mode from 'tool' to 'auto' for better provider compatibility Fixes Perplexity research role failing with 'tool-mode object generation' error The hardcoded 'tool' mode was incompatible with providers like Perplexity that support structured JSON output but not function calling/tool use Using 'auto' mode allows the AI SDK to choose the best approach for each provider * Adds qwen3-235n-a22b:free to supported models. Closes #687) * chore: adds a warning when custom openrouter model is a free model which suffers from lower rate limits, restricted context, and, worst of all, no access to tool_use. * refactor: enhance add-task fuzzy search and fix duplicate banner display - **Remove hardcoded category system** in add-task that always matched 'Task management' - **Eliminate arbitrary limits** in fuzzy search results (5→25 high relevance, 3→10 medium relevance, 8→20 detailed tasks) - **Improve semantic weighting** in Fuse.js search (details=3, description=2, title=1.5) for better relevance - **Fix duplicate banner issue** by removing console.clear() and redundant displayBanner() calls from UI functions - **Enhance context generation** to rely on semantic similarity rather than rigid pattern matching - **Preserve terminal history** to address GitHub issue #553 about eating terminal lines - **Remove displayBanner() calls** from: displayHelp, displayNextTask, displayTaskById, displayComplexityReport, set-task-status, clear-subtasks, dependency-manager functions The add-task system now provides truly relevant task context based on semantic similarity rather than arbitrary categories and limits, while maintaining a cleaner terminal experience. Changes span: add-task.js, ui.js, set-task-status.js, clear-subtasks.js, list-tasks.js, dependency-manager.js Closes #553 * chore: changeset * chore: passes tests and linting * chore: more linting * ninja(sync): add sync-readme command for GitHub README export with UTM tracking and professional markdown formatting. Experimental * chore: changeset adjustment * docs: Auto-update and format models.md * chore: updates readme with npm download badges and mentions AI Jason who is joining the taskmaster core team. * chore: fixes urls in readme npm packages * chore: fixes urls in readme npm packages again * fix: readme typo * readme: fix twitter urls. * readme: removes the taskmaster list output which is too overwhelming given its size with subtasks. may re-add later. fixes likely issues in the json for manual config in cursor and windsurf in the readme. * chore: small readme nitpicks * chore: adjusts changeset from minor to patch to avoid version bump to 0.17 * readme: moves up the documentation links higher up in the readme. same with the cursor one-click install. * Fix Cursor deeplink installation with copy-paste instructions (#723) * solve merge conflics with next. not gonna deal with these much longer. * chore: update task files during rebase * chore: task management * feat: implement research command with enhanced context gathering - Add comprehensive research command with AI-powered queries - Implement ContextGatherer utility for reusable context extraction - Support multiple context types: tasks, files, custom text, project tree - Add fuzzy search integration for automatic task discovery - Implement detailed token breakdown display with syntax highlighting - Add enhanced UI with boxed output and code block formatting - Support different detail levels (low, medium, high) for responses - Include project-specific context for more relevant AI responses - Add token counting with gpt-tokens library integration - Create reusable patterns for future context-aware commands - Task 94.4 completed * fix(move): adjusts logic to prevent an issue when moving from parent to subtask if the target parent has no subtasks. * fix(move-task): Fix critical bugs in task move functionality - Fixed parent-to-parent task moves where original task would remain as duplicate - Fixed moving tasks to become subtasks of empty parents (validation errors) - Fixed moving subtasks between different parent tasks - Improved comma-separated batch moves with proper error handling - Updated MCP tool to use core logic instead of custom implementation - Resolves task duplication issues and enables proper task hierarchy reorganization * chore: removes task004 chat that had like 11k lines lol. * feat(show): add comma-separated ID support for multi-task viewing - Enhanced get-task/show command to support comma-separated task IDs for efficient batch operations. - New features include multiple task retrieval, smart display logic, interactive action menu with batch operations, MCP array response for AI agent efficiency, and support for mixed parent tasks and subtasks. - Implementation includes updated CLI show command, enhanced MCP get_task tool, modified showTaskDirect function, and maintained full backward compatibility. - Documentation updated across all relevant files. Benefits include faster context gathering for AI agents, improved workflow with interactive batch operations, better UX with responsive layout, and enhanced API efficiency. * feat(research): Adds MCP tool for command - New MCP Tool: research tool enables AI-powered research with project context - Context Integration: Supports task IDs, file paths, custom context, and project tree - Fuzzy Task Discovery: Automatically finds relevant tasks using semantic search - Token Management: Detailed token counting and breakdown by context type - Multiple Detail Levels: Support for low, medium, and high detail research responses - Telemetry Integration: Full cost tracking and usage analytics - Direct Function: researchDirect with comprehensive parameter validation - Silent Mode: Prevents console output interference with MCP JSON responses - Error Handling: Robust error handling with proper MCP response formatting This completes subtasks 94.5 (Direct Function) and 94.6 (MCP Tool) for the research command implementation, providing a powerful research interface for integrated development environments like Cursor. Updated documentation across taskmaster.mdc, README.md, command-reference.md, examples.md, tutorial.md, and docs/README.md to highlight research capabilities and usage patterns. * chore: task management * fix(move): Fix move command bug that left duplicate tasks - Fixed logic in moveTaskToNewId function that was incorrectly treating task-to-task moves as subtask creation instead of task replacement - Updated moveTaskToNewId to properly handle replacing existing destination tasks instead of just placeholders - The move command now correctly replaces destination tasks and cleans up properly without leaving duplicates - Task Management: Moved task 93 (Google Vertex AI Provider) to position 88, Moved task 94 (Azure OpenAI Provider) to position 89, Updated task dependencies and regenerated task files, Cleaned up orphaned task files automatically - All important validations remain in place: Prevents moving tasks to themselves, Prevents moving parent tasks to their own subtasks, Prevents circular dependencies - Resolves the issue where moving tasks would leave both source and destination tasks in tasks.json and file system * chore: moves to new task master config setup * feat: add comma-separated status filtering to list-tasks - supports multiple statuses like 'blocked,deferred' with comprehensive test coverage and backward compatibility - also adjusts biome.json to stop bitching about templating. * chore: linting ffs * fix(generate): Fix generate command creating tasks in legacy location - Update generate command default output directory from 'tasks' to '.taskmaster/tasks' - Fix path.dirname() usage to properly derive output directory from tasks file location - Update MCP tool description and documentation to reflect new structure - Disable Biome linting rules for noUnusedTemplateLiteral and useArrowFunction - Fixes issue where generate command was creating task files in the old 'tasks/' directory instead of the new '.taskmaster/tasks/' structure after the refactor * chore: task management * chore: task management some more * fix(get-task): makes the projectRoot argument required to prevent errors when getting tasks. * feat(tags): Implement tagged task lists migration system (Part 1/2) This commit introduces the foundational infrastructure for tagged task lists, enabling multi-context task management without remote storage to prevent merge conflicts. CORE ARCHITECTURE: • Silent migration system transforms tasks.json from old format { "tasks": [...] } to new tagged format { "master": { "tasks": [...] } } • Tag resolution layer provides complete backward compatibility - existing code continues to work • Automatic configuration and state management for seamless user experience SILENT MIGRATION SYSTEM: • Automatic detection and migration of legacy tasks.json format • Complete project migration: tasks.json + config.json + state.json • Transparent tag resolution returns old format to maintain compatibility • Zero breaking changes - all existing functionality preserved CONFIGURATION MANAGEMENT: • Added global.defaultTag setting (defaults to 'master') • New tags section with gitIntegration placeholders for future features • Automatic config.json migration during first run • Proper state.json creation with migration tracking USER EXPERIENCE: • Clean, one-time FYI notice after migration (no emojis, professional styling) • Notice appears after 'Suggested Next Steps' and is tracked in state.json • Silent operation - users unaware migration occurred unless explicitly shown TECHNICAL IMPLEMENTATION: • Enhanced readJSON() with automatic migration detection and processing • New utility functions: getCurrentTag(), resolveTag(), getTasksForTag(), setTasksForTag() • Complete migration orchestration via performCompleteTagMigration() • Robust error handling and fallback mechanisms BACKWARD COMPATIBILITY: • 100% backward compatibility maintained • Existing CLI commands and MCP tools continue to work unchanged • Legacy tasks.json format automatically upgraded on first read • All existing workflows preserved TESTING VERIFIED: • Complete migration from legacy state works correctly • Config.json properly updated with tagged system settings • State.json created with correct initial values • Migration notice system functions as designed • All existing functionality continues to work normally Part 2 will implement tag management commands (add-tag, use-tag, list-tags) and MCP tool updates for full tagged task system functionality. Related: Task 103 - Implement Tagged Task Lists System for Multi-Context Task Management * docs: Update documentation and rules for tagged task lists system - Updated task-structure.md with comprehensive tagged format explanation - Updated all .cursor/rules/*.mdc files to reflect tagged system - Completed subtask 103.16: Update Documentation for Tagged Task Lists System * feat(mcp): Add tagInfo to responses and integrate ContextGatherer Enhances the MCP server to include 'tagInfo' (currentTag, availableTags) in all tool responses, providing better client-side context. - Introduces a new 'ContextGatherer' utility to standardize the collection of file, task, and project context for AI-powered commands. This refactors several task-manager modules ('expand-task', 'research', 'update-task', etc.) to use the new utility. - Fixes an issue in 'get-task' and 'get-tasks' MCP tools where the 'projectRoot' was not being passed correctly, preventing tag information from being included in their responses. - Adds subtask '103.17' to track the implementation of the task template importing feature. - Updates documentation ('.cursor/rules', 'docs/') to align with the new tagged task system and context gatherer logic. * fix: include tagInfo in AI service responses for MCP tools - Update all core functions that call AI services to extract and return tagInfo - Update all direct functions to include tagInfo in MCP response data - Fixes issue where add_task, expand_task, and other AI commands were not including current tag and available tags information - tagInfo includes currentTag from state.json and availableTags list - Ensures tagged task lists system information is properly propagated through the full chain: AI service -> core function -> direct function -> MCP client * fix(move-task): Update move functionality for tagged task system compatibility - incorporate GitHub commit fixes and resolve readJSON data handling * feat(tagged-tasks): Complete core tag management system implementation - Implements comprehensive tagged task lists system for multi-context task management including core tag management functions (Task 103.11), MCP integration updates, and foundational infrastructure for tagged task operations. Features tag CRUD operations, validation, metadata tracking, deep task copying, and full backward compatibility. * fix(core): Fixed move-task.js writing _rawTaggedData directly, updated writeJSON to filter tag fields, fixed CLI move command missing projectRoot, added ensureTagMetadata utility * fix(tasks): ensure list tasks triggers silent migration if necessary. * feat(tags): Complete show and add-task command tag support - show command: Added --tag flag, fixed projectRoot passing to UI functions - add-task command: Already had proper tag support and projectRoot handling - Both commands now work correctly with tagged task lists system - Migration logic works properly when viewing and adding tasks - Updated subtask 103.5 with progress on high-priority command fixes * fix(tags): Clean up rogue created properties and fix taskCount calculation - Enhanced writeJSON to automatically filter rogue created/description properties from tag objects - Fixed tags command error by making taskCount calculation dynamic instead of hardcoded - Cleaned up existing rogue created property in master tag through forced write operation - All created properties now properly located in metadata objects only - Tags command working perfectly with proper task count display - Data integrity maintained with automatic cleanup during write operations * fix(tags): Resolve critical tag deletion and migration notice bugs Major Issues Fixed: 1. Tag Deletion Bug: Fixed critical issue where creating subtasks would delete other tags - Root cause: writeJSON function wasn't accepting projectRoot/tag parameters - Fixed writeJSON signature and logic to handle tagged data structure - Added proper merging of resolved tag data back into full tagged structure 2. Persistent Migration Notice: Fixed FYI notice showing after every command - Root cause: markMigrationForNotice was resetting migrationNoticeShown to false - Fixed migration logic to only trigger on actual legacy->tagged migrations - Added proper _rawTaggedData checks to prevent false migration detection 3. Data Corruption Prevention: Enhanced data integrity safeguards - Fixed writeJSON to filter out internal properties - Added automatic cleanup of rogue properties - Improved hasTaggedStructure detection logic Commands Fixed: add-subtask, remove-subtask, and all commands now preserve tags correctly * fix(tags): Resolve tag deletion bug in remove-task command Refactored the core 'removeTask' function to be fully tag-aware, preventing data corruption. - The function now correctly reads the full tagged data structure by prioritizing '_rawTaggedData' instead of operating on a resolved single-tag view. - All subsequent operations (task removal, dependency cleanup, file writing) now correctly reference the full multi-tag data object, preserving the integrity of 'tasks.json'. - This resolves the critical bug where removing a task would delete all other tags. * fix(tasks): Ensure new task IDs are sequential within the target tag Modified the ID generation logic in 'add-task.js' to calculate the next task ID based on the highest ID within the specified tag, rather than globally across all tags. This fixes a critical bug where creating a task in a new tag would result in a high, non-sequential ID, such as ID 105 for the first task in a tag. * fix(commands): Add missing context parameters to dependency and remove-subtask commands - Add projectRoot and tag context to all dependency commands - Add projectRoot and tag context to remove-subtask command - Add --tag option to remove-subtask command - Fixes critical bug where remove-subtask was deleting other tags due to missing context - All dependency and subtask commands now properly handle tagged task lists * feat(tags): Add --tag flag support to core commands for multi-context task management - parse-prd now supports creating tasks in specific contexts - Fixed tag preservation logic to prevent data loss - analyze-complexity generates tag-specific reports - Non-existent tags created automatically - Enables rapid prototyping and parallel development workflows * feat(tags): Complete tagged task lists system with enhanced use-tag command - Multi-context task management with full CLI support - Enhanced use-tag command shows next available task after switching - Universal --tag flag support across all commands - Seamless migration with zero disruption - Complete tag management suite (add, delete, rename, copy, list) - Smart confirmation logic and data integrity protection - State management and configuration integration - Real-world use cases for teams, features, and releases * feat(tags): Complete tag support for remaining CLI commands - Add --tag flag to update, move, and set-status commands - Ensure all task operation commands now support tag context - Fix missing tag context passing to core functions - Complete comprehensive tag-aware command coverage * feat(ui): add tag indicator to all CLI commands - shows 🏷️ tag: tagname for complete context visibility across 15+ commands * fix(ui): resolve dependency 'Not found' issue when filtering - now correctly displays dependencies that exist but are filtered out of view * feat(research): Add comprehensive AI-powered research command with interactive follow-ups, save functionality, intelligent context gathering, fuzzy task discovery, multi-source context support, enhanced display with syntax highlighting, clean inquirer menus, comprehensive help, and MCP integration with saveTo parameter * feat(tags): Implement full MCP support for Tagged Task Lists and update-task append mode * chore: task management * feat(research): Enhance research command with follow-up menu, save functionality, and fix ContextGatherer token counting * feat(git-workflow): Add automatic git branch-tag integration - Implement automatic tag creation when switching to new git branches - Add branch-tag mapping system for seamless context switching - Enable auto-switch of task contexts based on current git branch - Provide isolated task contexts per branch to prevent merge conflicts - Add configuration support for enabling/disabling git workflow features - Fix ES module compatibility issues in git-utils module - Maintain zero migration impact with automatic 'master' tag creation - Support parallel development with branch-specific task contexts The git workflow system automatically detects branch changes and creates corresponding empty task tags, enabling developers to maintain separate task contexts for different features/branches while preventing task-related merge conflicts during collaborative development. Resolves git workflow integration requirements for multi-context development. * feat(git-workflow): Simplify git integration with --from-branch option - Remove automatic git workflow and branch-tag switching - we are not ready for it yet - Add --from-branch option to add-tag command for manual tag creation from git branch - Remove git workflow configuration from config.json and assets - Disable automatic tag switching functions in git-utils.js - Add createTagFromBranch function for branch-based tag creation - Support both CLI and MCP interfaces for --from-branch functionality - Fix ES module imports in git-utils.js and utils.js - Maintain user control over tag contexts without forced automation The simplified approach allows users to create tags from their current git branch when desired, without the complexity and rigidity of automatic branch-tag synchronization. Users maintain full control over their tag contexts while having convenient tools for git-based workflows when needed. * docs: Update rule files to reflect simplified git integration approach - Remove automatic git workflow features, update to manual --from-branch option, change Part 2 references to completed status * fix(commands): Fix add-tag --from-branch requiring tagName argument - Made tagName optional when using --from-branch - Added validation for either tagName or --from-branch - Fixes 'missing required argument' error with --from-branch option * fix(mcp): Prevent tag deletion on subtask update Adds a safety net to the writeJSON utility to prevent data loss when updating subtasks via the MCP server. The MCP process was inadvertently causing the _rawTaggedData property, which holds the complete multi-tag structure, to be lost. When writeJSON received the data for only a single tag, it would overwrite the entire tasks.json file, deleting all other tags. This fix makes writeJSON more robust. If it receives data that looks like a single, resolved tag without the complete structure, it re-reads the full tasks.json file from disk. It then carefully merges the updated data back into the correct tag within the full structure, preserving all other tags. * fix: resolve all remaining test failures and improve test reliability - Fix clear-subtasks test by implementing deep copy of mock data to prevent mutation issues between tests - Fix add-task test by uncommenting and properly configuring generateTaskFiles call with correct parameters - Fix analyze-task-complexity tests by properly mocking fs.writeFileSync with shared mock function - Update test expectations to match actual function signatures and data structures - Improve mock setup consistency across all test suites - Ensure all tests now pass (329 total: 318 passed, 11 skipped, 0 failed) * chore: task management --------- Co-authored-by: Eyal Toledano <eyal@microangel.so> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ibrahim H. <bitsnaps@yahoo.fr> Co-authored-by: Saksham Goel <sakshamgoel1107@gmail.com> Co-authored-by: Joe Danziger <joe@ticc.net> Co-authored-by: Aaron Gabriel Neyer <ag@unforced.org>
This commit is contained in:
198
mcp-server/src/core/direct-functions/add-tag.js
Normal file
198
mcp-server/src/core/direct-functions/add-tag.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* add-tag.js
|
||||
* Direct function implementation for creating a new tag
|
||||
*/
|
||||
|
||||
import {
|
||||
createTag,
|
||||
createTagFromBranch
|
||||
} from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for creating a new tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.name - Name of the new tag to create
|
||||
* @param {boolean} [args.copyFromCurrent=false] - Whether to copy tasks from current tag
|
||||
* @param {string} [args.copyFromTag] - Specific tag to copy tasks from
|
||||
* @param {boolean} [args.fromBranch=false] - Create tag name from current git branch
|
||||
* @param {string} [args.description] - Optional description for the tag
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function addTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const {
|
||||
tasksJsonPath,
|
||||
name,
|
||||
copyFromCurrent = false,
|
||||
copyFromTag,
|
||||
fromBranch = false,
|
||||
description,
|
||||
projectRoot
|
||||
} = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('addTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handle --from-branch option
|
||||
if (fromBranch) {
|
||||
log.info('Creating tag from current git branch');
|
||||
|
||||
// Import git utilities
|
||||
const gitUtils = await import(
|
||||
'../../../../scripts/modules/utils/git-utils.js'
|
||||
);
|
||||
|
||||
// Check if we're in a git repository
|
||||
if (!(await gitUtils.isGitRepository(projectRoot))) {
|
||||
log.error('Not in a git repository');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NOT_GIT_REPO',
|
||||
message: 'Not in a git repository. Cannot use fromBranch option.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get current git branch
|
||||
const currentBranch = await gitUtils.getCurrentBranch(projectRoot);
|
||||
if (!currentBranch) {
|
||||
log.error('Could not determine current git branch');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NO_CURRENT_BRANCH',
|
||||
message: 'Could not determine current git branch.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Prepare options for branch-based tag creation
|
||||
const branchOptions = {
|
||||
copyFromCurrent,
|
||||
copyFromTag,
|
||||
description:
|
||||
description || `Tag created from git branch "${currentBranch}"`
|
||||
};
|
||||
|
||||
// Call the createTagFromBranch function
|
||||
const result = await createTagFromBranch(
|
||||
tasksJsonPath,
|
||||
currentBranch,
|
||||
branchOptions,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
branchName: result.branchName,
|
||||
tagName: result.tagName,
|
||||
created: result.created,
|
||||
mappingUpdated: result.mappingUpdated,
|
||||
message: `Successfully created tag "${result.tagName}" from git branch "${result.branchName}"`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Check required parameters for regular tag creation
|
||||
if (!name || typeof name !== 'string') {
|
||||
log.error('Missing required parameter: name');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Creating new tag: ${name}`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
copyFromCurrent,
|
||||
copyFromTag,
|
||||
description
|
||||
};
|
||||
|
||||
// Call the createTag function
|
||||
const result = await createTag(
|
||||
tasksJsonPath,
|
||||
name,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tagName: result.tagName,
|
||||
created: result.created,
|
||||
tasksCopied: result.tasksCopied,
|
||||
sourceTag: result.sourceTag,
|
||||
description: result.description,
|
||||
message: `Successfully created tag "${result.tagName}"`
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in addTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'ADD_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
let manualTaskData = null;
|
||||
let newTaskId;
|
||||
let telemetryData;
|
||||
let tagInfo;
|
||||
|
||||
if (isManualCreation) {
|
||||
// Create manual task data object
|
||||
@@ -129,6 +130,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
);
|
||||
newTaskId = result.newTaskId;
|
||||
telemetryData = result.telemetryData;
|
||||
tagInfo = result.tagInfo;
|
||||
} else {
|
||||
// AI-driven task creation
|
||||
log.info(
|
||||
@@ -154,6 +156,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
);
|
||||
newTaskId = result.newTaskId;
|
||||
telemetryData = result.telemetryData;
|
||||
tagInfo = result.tagInfo;
|
||||
}
|
||||
|
||||
// Restore normal logging
|
||||
@@ -164,7 +167,8 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
data: {
|
||||
taskId: newTaskId,
|
||||
message: `Successfully added new task #${newTaskId}`,
|
||||
telemetryData: telemetryData
|
||||
telemetryData: telemetryData,
|
||||
tagInfo: tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -196,7 +196,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
lowComplexityTasks
|
||||
},
|
||||
fullReport: coreResult.report,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (parseError) {
|
||||
|
||||
125
mcp-server/src/core/direct-functions/copy-tag.js
Normal file
125
mcp-server/src/core/direct-functions/copy-tag.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* copy-tag.js
|
||||
* Direct function implementation for copying a tag
|
||||
*/
|
||||
|
||||
import { copyTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for copying a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.sourceName - Name of the source tag to copy from
|
||||
* @param {string} args.targetName - Name of the new tag to create
|
||||
* @param {string} [args.description] - Optional description for the new tag
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function copyTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, sourceName, targetName, description, projectRoot } =
|
||||
args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('copyTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!sourceName || typeof sourceName !== 'string') {
|
||||
log.error('Missing required parameter: sourceName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Source tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!targetName || typeof targetName !== 'string') {
|
||||
log.error('Missing required parameter: targetName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Target tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Copying tag from "${sourceName}" to "${targetName}"`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
description
|
||||
};
|
||||
|
||||
// Call the copyTag function
|
||||
const result = await copyTag(
|
||||
tasksJsonPath,
|
||||
sourceName,
|
||||
targetName,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
sourceName: result.sourceName,
|
||||
targetName: result.targetName,
|
||||
copied: result.copied,
|
||||
tasksCopied: result.tasksCopied,
|
||||
description: result.description,
|
||||
message: `Successfully copied tag from "${result.sourceName}" to "${result.targetName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in copyTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'COPY_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
159
mcp-server/src/core/direct-functions/create-tag-from-branch.js
Normal file
159
mcp-server/src/core/direct-functions/create-tag-from-branch.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* create-tag-from-branch.js
|
||||
* Direct function implementation for creating tags from git branches
|
||||
*/
|
||||
|
||||
import { createTagFromBranch } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
getCurrentBranch,
|
||||
isGitRepository
|
||||
} from '../../../../scripts/modules/utils/git-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for creating tags from git branches with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.tasksJsonPath - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.branchName] - Git branch name (optional, uses current branch if not provided)
|
||||
* @param {boolean} [args.copyFromCurrent] - Copy tasks from current tag
|
||||
* @param {string} [args.copyFromTag] - Copy tasks from specific tag
|
||||
* @param {string} [args.description] - Custom description for the tag
|
||||
* @param {boolean} [args.autoSwitch] - Automatically switch to the new tag
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function createTagFromBranchDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const {
|
||||
tasksJsonPath,
|
||||
branchName,
|
||||
copyFromCurrent,
|
||||
copyFromTag,
|
||||
description,
|
||||
autoSwitch,
|
||||
projectRoot
|
||||
} = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('createTagFromBranchDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check if projectRoot was provided
|
||||
if (!projectRoot) {
|
||||
log.error('createTagFromBranchDirect called without projectRoot');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'projectRoot is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check if we're in a git repository
|
||||
if (!(await isGitRepository(projectRoot))) {
|
||||
log.error('Not in a git repository');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NOT_GIT_REPOSITORY',
|
||||
message: 'Not in a git repository. Cannot create tag from branch.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Determine branch name
|
||||
let targetBranch = branchName;
|
||||
if (!targetBranch) {
|
||||
targetBranch = await getCurrentBranch(projectRoot);
|
||||
if (!targetBranch) {
|
||||
log.error('Could not determine current git branch');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NO_CURRENT_BRANCH',
|
||||
message: 'Could not determine current git branch'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`Creating tag from git branch: ${targetBranch}`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
copyFromCurrent: copyFromCurrent || false,
|
||||
copyFromTag,
|
||||
description:
|
||||
description || `Tag created from git branch "${targetBranch}"`,
|
||||
autoSwitch: autoSwitch || false
|
||||
};
|
||||
|
||||
// Call the createTagFromBranch function
|
||||
const result = await createTagFromBranch(
|
||||
tasksJsonPath,
|
||||
targetBranch,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
branchName: result.branchName,
|
||||
tagName: result.tagName,
|
||||
created: result.created,
|
||||
mappingUpdated: result.mappingUpdated,
|
||||
autoSwitched: result.autoSwitched,
|
||||
message: `Successfully created tag "${result.tagName}" from branch "${result.branchName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in createTagFromBranchDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'CREATE_TAG_FROM_BRANCH_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
110
mcp-server/src/core/direct-functions/delete-tag.js
Normal file
110
mcp-server/src/core/direct-functions/delete-tag.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* delete-tag.js
|
||||
* Direct function implementation for deleting a tag
|
||||
*/
|
||||
|
||||
import { deleteTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for deleting a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.name - Name of the tag to delete
|
||||
* @param {boolean} [args.yes=false] - Skip confirmation prompts
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function deleteTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, name, yes = false, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('deleteTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!name || typeof name !== 'string') {
|
||||
log.error('Missing required parameter: name');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Deleting tag: ${name}`);
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
yes // For MCP, we always skip confirmation prompts
|
||||
};
|
||||
|
||||
// Call the deleteTag function
|
||||
const result = await deleteTag(
|
||||
tasksJsonPath,
|
||||
name,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tagName: result.tagName,
|
||||
deleted: result.deleted,
|
||||
tasksDeleted: result.tasksDeleted,
|
||||
wasCurrentTag: result.wasCurrentTag,
|
||||
switchedToMaster: result.switchedToMaster,
|
||||
message: `Successfully deleted tag "${result.tagName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in deleteTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'DELETE_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
|
||||
// Read tasks data
|
||||
log.info(`[expandTaskDirect] Attempting to read JSON from: ${tasksPath}`);
|
||||
const data = readJSON(tasksPath);
|
||||
const data = readJSON(tasksPath, projectRoot);
|
||||
log.info(
|
||||
`[expandTaskDirect] Result of readJSON: ${data ? 'Data read successfully' : 'readJSON returned null or undefined'}`
|
||||
);
|
||||
@@ -164,10 +164,6 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
// Tracking subtasks count before expansion
|
||||
const subtasksCountBefore = task.subtasks ? task.subtasks.length : 0;
|
||||
|
||||
// Create a backup of the tasks.json file
|
||||
const backupPath = path.join(path.dirname(tasksPath), 'tasks.json.bak');
|
||||
fs.copyFileSync(tasksPath, backupPath);
|
||||
|
||||
// Directly modify the data instead of calling the CLI function
|
||||
if (!task.subtasks) {
|
||||
task.subtasks = [];
|
||||
@@ -207,7 +203,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
if (!wasSilent && isSilentMode()) disableSilentMode();
|
||||
|
||||
// Read the updated data
|
||||
const updatedData = readJSON(tasksPath);
|
||||
const updatedData = readJSON(tasksPath, projectRoot);
|
||||
const updatedTask = updatedData.tasks.find((t) => t.id === taskId);
|
||||
|
||||
// Calculate how many subtasks were added
|
||||
@@ -225,7 +221,8 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
task: coreResult.task,
|
||||
subtasksAdded,
|
||||
hasExistingSubtasks,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
132
mcp-server/src/core/direct-functions/list-tags.js
Normal file
132
mcp-server/src/core/direct-functions/list-tags.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* list-tags.js
|
||||
* Direct function implementation for listing all tags
|
||||
*/
|
||||
|
||||
import { tags } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for listing all tags with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {boolean} [args.showMetadata=false] - Whether to include metadata in the output
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function listTagsDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, showMetadata = false, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('listTagsDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info('Listing all tags');
|
||||
|
||||
// Prepare options
|
||||
const options = {
|
||||
showMetadata
|
||||
};
|
||||
|
||||
// Call the tags function
|
||||
const result = await tags(
|
||||
tasksJsonPath,
|
||||
options,
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Transform the result to remove full task data and provide summary info
|
||||
const tagsSummary = result.tags.map((tag) => {
|
||||
const tasks = tag.tasks || [];
|
||||
|
||||
// Calculate status breakdown
|
||||
const statusBreakdown = tasks.reduce((acc, task) => {
|
||||
const status = task.status || 'pending';
|
||||
acc[status] = (acc[status] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Calculate subtask counts
|
||||
const subtaskCounts = tasks.reduce(
|
||||
(acc, task) => {
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
acc.totalSubtasks += task.subtasks.length;
|
||||
task.subtasks.forEach((subtask) => {
|
||||
const subStatus = subtask.status || 'pending';
|
||||
acc.subtasksByStatus[subStatus] =
|
||||
(acc.subtasksByStatus[subStatus] || 0) + 1;
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ totalSubtasks: 0, subtasksByStatus: {} }
|
||||
);
|
||||
|
||||
return {
|
||||
name: tag.name,
|
||||
isCurrent: tag.isCurrent,
|
||||
taskCount: tasks.length,
|
||||
completedTasks: tag.completedTasks,
|
||||
statusBreakdown,
|
||||
subtaskCounts,
|
||||
created: tag.created,
|
||||
description: tag.description
|
||||
};
|
||||
});
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tags: tagsSummary,
|
||||
currentTag: result.currentTag,
|
||||
totalTags: result.totalTags,
|
||||
message: `Found ${result.totalTags} tag(s)`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in listTagsDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'LIST_TAGS_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,10 @@ import {
|
||||
* @param {Object} log - Logger object.
|
||||
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }.
|
||||
*/
|
||||
export async function listTasksDirect(args, log) {
|
||||
export async function listTasksDirect(args, log, context = {}) {
|
||||
// Destructure the explicit tasksJsonPath from args
|
||||
const { tasksJsonPath, reportPath, status, withSubtasks } = args;
|
||||
const { tasksJsonPath, reportPath, status, withSubtasks, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('listTasksDirect called without tasksJsonPath');
|
||||
@@ -50,7 +51,9 @@ export async function listTasksDirect(args, log) {
|
||||
statusFilter,
|
||||
reportPath,
|
||||
withSubtasksFilter,
|
||||
'json'
|
||||
'json',
|
||||
null, // tag
|
||||
{ projectRoot, session } // context
|
||||
);
|
||||
|
||||
if (!resultData || !resultData.tasks) {
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
* Move a task or subtask to a new position
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file
|
||||
* @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2')
|
||||
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3')
|
||||
* @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2' or '5,6,7')
|
||||
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3' or '7,8,9')
|
||||
* @param {string} args.file - Alternative path to the tasks.json file
|
||||
* @param {string} args.projectRoot - Project root directory
|
||||
* @param {boolean} args.generateFiles - Whether to regenerate task files after moving (default: true)
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: Object}>}
|
||||
*/
|
||||
@@ -64,12 +65,13 @@ export async function moveTaskDirect(args, log, context = {}) {
|
||||
// Enable silent mode to prevent console output during MCP operation
|
||||
enableSilentMode();
|
||||
|
||||
// Call the core moveTask function, always generate files
|
||||
// Call the core moveTask function with file generation control
|
||||
const generateFiles = args.generateFiles !== false; // Default to true
|
||||
const result = await moveTask(
|
||||
tasksPath,
|
||||
args.sourceId,
|
||||
args.destinationId,
|
||||
true
|
||||
generateFiles
|
||||
);
|
||||
|
||||
// Restore console output
|
||||
@@ -78,7 +80,7 @@ export async function moveTaskDirect(args, log, context = {}) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
movedTask: result.movedTask,
|
||||
...result,
|
||||
message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function nextTaskDirect(args, log) {
|
||||
export async function nextTaskDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, reportPath } = args;
|
||||
const { tasksJsonPath, reportPath, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('nextTaskDirect called without tasksJsonPath');
|
||||
@@ -45,7 +46,7 @@ export async function nextTaskDirect(args, log) {
|
||||
log.info(`Finding next task from ${tasksJsonPath}`);
|
||||
|
||||
// Read tasks data using the provided path
|
||||
const data = readJSON(tasksJsonPath);
|
||||
const data = readJSON(tasksJsonPath, projectRoot);
|
||||
if (!data || !data.tasks) {
|
||||
disableSilentMode(); // Disable before return
|
||||
return {
|
||||
|
||||
@@ -170,7 +170,8 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
data: {
|
||||
message: successMsg,
|
||||
outputPath: result.tasksPath,
|
||||
telemetryData: result.telemetryData
|
||||
telemetryData: result.telemetryData,
|
||||
tagInfo: result.tagInfo
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -23,9 +23,10 @@ import {
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function removeTaskDirect(args, log) {
|
||||
export async function removeTaskDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, id } = args;
|
||||
const { tasksJsonPath, id, projectRoot } = args;
|
||||
const { session } = context;
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
@@ -59,7 +60,7 @@ export async function removeTaskDirect(args, log) {
|
||||
);
|
||||
|
||||
// Validate all task IDs exist before proceeding
|
||||
const data = readJSON(tasksJsonPath);
|
||||
const data = readJSON(tasksJsonPath, projectRoot);
|
||||
if (!data || !data.tasks) {
|
||||
return {
|
||||
success: false,
|
||||
|
||||
118
mcp-server/src/core/direct-functions/rename-tag.js
Normal file
118
mcp-server/src/core/direct-functions/rename-tag.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* rename-tag.js
|
||||
* Direct function implementation for renaming a tag
|
||||
*/
|
||||
|
||||
import { renameTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for renaming a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.oldName - Current name of the tag to rename
|
||||
* @param {string} args.newName - New name for the tag
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function renameTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, oldName, newName, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('renameTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!oldName || typeof oldName !== 'string') {
|
||||
log.error('Missing required parameter: oldName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Old tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!newName || typeof newName !== 'string') {
|
||||
log.error('Missing required parameter: newName');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'New tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Renaming tag from "${oldName}" to "${newName}"`);
|
||||
|
||||
// Call the renameTag function
|
||||
const result = await renameTag(
|
||||
tasksJsonPath,
|
||||
oldName,
|
||||
newName,
|
||||
{}, // options (empty for now)
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
oldName: result.oldName,
|
||||
newName: result.newName,
|
||||
renamed: result.renamed,
|
||||
taskCount: result.taskCount,
|
||||
wasCurrentTag: result.wasCurrentTag,
|
||||
message: `Successfully renamed tag from "${result.oldName}" to "${result.newName}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in renameTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'RENAME_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
249
mcp-server/src/core/direct-functions/research.js
Normal file
249
mcp-server/src/core/direct-functions/research.js
Normal file
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* research.js
|
||||
* Direct function implementation for AI-powered research queries
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { performResearch } from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for performing AI-powered research with project context.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.query - Research query/prompt (required)
|
||||
* @param {string} [args.taskIds] - Comma-separated list of task/subtask IDs for context
|
||||
* @param {string} [args.filePaths] - Comma-separated list of file paths for context
|
||||
* @param {string} [args.customContext] - Additional custom context text
|
||||
* @param {boolean} [args.includeProjectTree=false] - Include project file tree in context
|
||||
* @param {string} [args.detailLevel='medium'] - Detail level: 'low', 'medium', 'high'
|
||||
* @param {string} [args.saveTo] - Automatically save to task/subtask ID (e.g., "15" or "15.2")
|
||||
* @param {boolean} [args.saveToFile=false] - Save research results to .taskmaster/docs/research/ directory
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function researchDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const {
|
||||
query,
|
||||
taskIds,
|
||||
filePaths,
|
||||
customContext,
|
||||
includeProjectTree = false,
|
||||
detailLevel = 'medium',
|
||||
saveTo,
|
||||
saveToFile = false,
|
||||
projectRoot
|
||||
} = args;
|
||||
const { session } = context; // Destructure session from context
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check required parameters
|
||||
if (!query || typeof query !== 'string' || query.trim().length === 0) {
|
||||
log.error('Missing or invalid required parameter: query');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message:
|
||||
'The query parameter is required and must be a non-empty string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Parse comma-separated task IDs if provided
|
||||
const parsedTaskIds = taskIds
|
||||
? taskIds
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id.length > 0)
|
||||
: [];
|
||||
|
||||
// Parse comma-separated file paths if provided
|
||||
const parsedFilePaths = filePaths
|
||||
? filePaths
|
||||
.split(',')
|
||||
.map((path) => path.trim())
|
||||
.filter((path) => path.length > 0)
|
||||
: [];
|
||||
|
||||
// Validate detail level
|
||||
const validDetailLevels = ['low', 'medium', 'high'];
|
||||
if (!validDetailLevels.includes(detailLevel)) {
|
||||
log.error(`Invalid detail level: ${detailLevel}`);
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_PARAMETER',
|
||||
message: `Detail level must be one of: ${validDetailLevels.join(', ')}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Performing research query: "${query.substring(0, 100)}${query.length > 100 ? '...' : ''}", ` +
|
||||
`taskIds: [${parsedTaskIds.join(', ')}], ` +
|
||||
`filePaths: [${parsedFilePaths.join(', ')}], ` +
|
||||
`detailLevel: ${detailLevel}, ` +
|
||||
`includeProjectTree: ${includeProjectTree}, ` +
|
||||
`projectRoot: ${projectRoot}`
|
||||
);
|
||||
|
||||
// Prepare options for the research function
|
||||
const researchOptions = {
|
||||
taskIds: parsedTaskIds,
|
||||
filePaths: parsedFilePaths,
|
||||
customContext: customContext || '',
|
||||
includeProjectTree,
|
||||
detailLevel,
|
||||
projectRoot,
|
||||
saveToFile
|
||||
};
|
||||
|
||||
// Prepare context for the research function
|
||||
const researchContext = {
|
||||
session,
|
||||
mcpLog,
|
||||
commandName: 'research',
|
||||
outputType: 'mcp'
|
||||
};
|
||||
|
||||
// Call the performResearch function
|
||||
const result = await performResearch(
|
||||
query.trim(),
|
||||
researchOptions,
|
||||
researchContext,
|
||||
'json', // outputFormat - use 'json' to suppress CLI UI
|
||||
false // allowFollowUp - disable for MCP calls
|
||||
);
|
||||
|
||||
// Auto-save to task/subtask if requested
|
||||
if (saveTo) {
|
||||
try {
|
||||
const isSubtask = saveTo.includes('.');
|
||||
|
||||
// Format research content for saving
|
||||
const researchContent = `## Research Query: ${query.trim()}
|
||||
|
||||
**Detail Level:** ${result.detailLevel}
|
||||
**Context Size:** ${result.contextSize} characters
|
||||
**Timestamp:** ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}
|
||||
|
||||
### Results
|
||||
|
||||
${result.result}`;
|
||||
|
||||
if (isSubtask) {
|
||||
// Save to subtask
|
||||
const { updateSubtaskById } = await import(
|
||||
'../../../../scripts/modules/task-manager/update-subtask-by-id.js'
|
||||
);
|
||||
|
||||
const tasksPath = path.join(
|
||||
projectRoot,
|
||||
'.taskmaster',
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
await updateSubtaskById(
|
||||
tasksPath,
|
||||
saveTo,
|
||||
researchContent,
|
||||
false, // useResearch = false for simple append
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
commandName: 'research-save',
|
||||
outputType: 'mcp',
|
||||
projectRoot
|
||||
},
|
||||
'json'
|
||||
);
|
||||
|
||||
log.info(`Research saved to subtask ${saveTo}`);
|
||||
} else {
|
||||
// Save to task
|
||||
const updateTaskById = (
|
||||
await import(
|
||||
'../../../../scripts/modules/task-manager/update-task-by-id.js'
|
||||
)
|
||||
).default;
|
||||
|
||||
const taskIdNum = parseInt(saveTo, 10);
|
||||
const tasksPath = path.join(
|
||||
projectRoot,
|
||||
'.taskmaster',
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
await updateTaskById(
|
||||
tasksPath,
|
||||
taskIdNum,
|
||||
researchContent,
|
||||
false, // useResearch = false for simple append
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
commandName: 'research-save',
|
||||
outputType: 'mcp',
|
||||
projectRoot
|
||||
},
|
||||
'json',
|
||||
true // appendMode = true
|
||||
);
|
||||
|
||||
log.info(`Research saved to task ${saveTo}`);
|
||||
}
|
||||
} catch (saveError) {
|
||||
log.warn(`Error saving research to task/subtask: ${saveError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
query: result.query,
|
||||
result: result.result,
|
||||
contextSize: result.contextSize,
|
||||
contextTokens: result.contextTokens,
|
||||
tokenBreakdown: result.tokenBreakdown,
|
||||
systemPromptTokens: result.systemPromptTokens,
|
||||
userPromptTokens: result.userPromptTokens,
|
||||
totalInputTokens: result.totalInputTokens,
|
||||
detailLevel: result.detailLevel,
|
||||
telemetryData: result.telemetryData,
|
||||
tagInfo: result.tagInfo,
|
||||
savedFilePath: result.savedFilePath
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in researchDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'RESEARCH_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,15 @@ import { nextTaskDirect } from './next-task.js';
|
||||
/**
|
||||
* Direct function wrapper for setTaskStatus with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing id, status and tasksJsonPath.
|
||||
* @param {Object} args - Command arguments containing id, status, tasksJsonPath, and projectRoot.
|
||||
* @param {Object} log - Logger object.
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||
*/
|
||||
export async function setTaskStatusDirect(args, log) {
|
||||
// Destructure expected args, including the resolved tasksJsonPath
|
||||
const { tasksJsonPath, id, status, complexityReportPath } = args;
|
||||
export async function setTaskStatusDirect(args, log, context = {}) {
|
||||
// Destructure expected args, including the resolved tasksJsonPath and projectRoot
|
||||
const { tasksJsonPath, id, status, complexityReportPath, projectRoot } = args;
|
||||
const { session } = context;
|
||||
try {
|
||||
log.info(`Setting task status with args: ${JSON.stringify(args)}`);
|
||||
|
||||
@@ -67,7 +69,11 @@ export async function setTaskStatusDirect(args, log) {
|
||||
enableSilentMode(); // Enable silent mode before calling core function
|
||||
try {
|
||||
// Call the core function
|
||||
await setTaskStatus(tasksPath, taskId, newStatus, { mcpLog: log });
|
||||
await setTaskStatus(tasksPath, taskId, newStatus, {
|
||||
mcpLog: log,
|
||||
projectRoot,
|
||||
session
|
||||
});
|
||||
|
||||
log.info(`Successfully set task ${taskId} status to ${newStatus}`);
|
||||
|
||||
@@ -89,9 +95,11 @@ export async function setTaskStatusDirect(args, log) {
|
||||
const nextResult = await nextTaskDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
reportPath: complexityReportPath
|
||||
reportPath: complexityReportPath,
|
||||
projectRoot: projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (nextResult.success) {
|
||||
|
||||
@@ -24,8 +24,7 @@ import { findTasksPath } from '../utils/path-utils.js';
|
||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||
*/
|
||||
export async function showTaskDirect(args, log) {
|
||||
// Destructure session from context if needed later, otherwise ignore
|
||||
// const { session } = context;
|
||||
// This function doesn't need session context since it only reads data
|
||||
// Destructure projectRoot and other args. projectRoot is assumed normalized.
|
||||
const { id, file, reportPath, status, projectRoot } = args;
|
||||
|
||||
@@ -56,7 +55,7 @@ export async function showTaskDirect(args, log) {
|
||||
|
||||
// --- Rest of the function remains the same, using tasksJsonPath ---
|
||||
try {
|
||||
const tasksData = readJSON(tasksJsonPath);
|
||||
const tasksData = readJSON(tasksJsonPath, projectRoot);
|
||||
if (!tasksData || !tasksData.tasks) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -66,32 +65,91 @@ export async function showTaskDirect(args, log) {
|
||||
|
||||
const complexityReport = readComplexityReport(reportPath);
|
||||
|
||||
const { task, originalSubtaskCount } = findTaskById(
|
||||
tasksData.tasks,
|
||||
id,
|
||||
complexityReport,
|
||||
status
|
||||
);
|
||||
// Parse comma-separated IDs
|
||||
const taskIds = id
|
||||
.split(',')
|
||||
.map((taskId) => taskId.trim())
|
||||
.filter((taskId) => taskId.length > 0);
|
||||
|
||||
if (!task) {
|
||||
if (taskIds.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TASK_NOT_FOUND',
|
||||
message: `Task or subtask with ID ${id} not found`
|
||||
code: 'INVALID_TASK_ID',
|
||||
message: 'No valid task IDs provided'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Successfully retrieved task ${id}.`);
|
||||
// Handle single task ID (existing behavior)
|
||||
if (taskIds.length === 1) {
|
||||
const { task, originalSubtaskCount } = findTaskById(
|
||||
tasksData.tasks,
|
||||
taskIds[0],
|
||||
complexityReport,
|
||||
status
|
||||
);
|
||||
|
||||
const returnData = { ...task };
|
||||
if (originalSubtaskCount !== null) {
|
||||
returnData._originalSubtaskCount = originalSubtaskCount;
|
||||
returnData._subtaskFilter = status;
|
||||
if (!task) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TASK_NOT_FOUND',
|
||||
message: `Task or subtask with ID ${taskIds[0]} not found`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Successfully retrieved task ${taskIds[0]}.`);
|
||||
|
||||
const returnData = { ...task };
|
||||
if (originalSubtaskCount !== null) {
|
||||
returnData._originalSubtaskCount = originalSubtaskCount;
|
||||
returnData._subtaskFilter = status;
|
||||
}
|
||||
|
||||
return { success: true, data: returnData };
|
||||
}
|
||||
|
||||
return { success: true, data: returnData };
|
||||
// Handle multiple task IDs
|
||||
const foundTasks = [];
|
||||
const notFoundIds = [];
|
||||
|
||||
taskIds.forEach((taskId) => {
|
||||
const { task, originalSubtaskCount } = findTaskById(
|
||||
tasksData.tasks,
|
||||
taskId,
|
||||
complexityReport,
|
||||
status
|
||||
);
|
||||
|
||||
if (task) {
|
||||
const taskData = { ...task };
|
||||
if (originalSubtaskCount !== null) {
|
||||
taskData._originalSubtaskCount = originalSubtaskCount;
|
||||
taskData._subtaskFilter = status;
|
||||
}
|
||||
foundTasks.push(taskData);
|
||||
} else {
|
||||
notFoundIds.push(taskId);
|
||||
}
|
||||
});
|
||||
|
||||
log.info(
|
||||
`Successfully retrieved ${foundTasks.length} of ${taskIds.length} requested tasks.`
|
||||
);
|
||||
|
||||
// Return multiple tasks with metadata
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tasks: foundTasks,
|
||||
requestedIds: taskIds,
|
||||
foundCount: foundTasks.length,
|
||||
notFoundIds: notFoundIds,
|
||||
isMultiple: true
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(`Error showing task ${id}: ${error.message}`);
|
||||
return {
|
||||
|
||||
@@ -139,7 +139,8 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
subtask: coreResult.updatedSubtask,
|
||||
tasksPath,
|
||||
useResearch,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { createLogWrapper } from '../../tools/utils.js';
|
||||
* @param {string} args.id - Task ID (or subtask ID like "1.2").
|
||||
* @param {string} args.prompt - New information/context prompt.
|
||||
* @param {boolean} [args.research] - Whether to use research role.
|
||||
* @param {boolean} [args.append] - Whether to append timestamped information instead of full update.
|
||||
* @param {string} [args.projectRoot] - Project root path.
|
||||
* @param {Object} log - Logger object.
|
||||
* @param {Object} context - Context object containing session data.
|
||||
@@ -27,7 +28,7 @@ import { createLogWrapper } from '../../tools/utils.js';
|
||||
export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
// Destructure expected args, including projectRoot
|
||||
const { tasksJsonPath, id, prompt, research, projectRoot } = args;
|
||||
const { tasksJsonPath, id, prompt, research, append, projectRoot } = args;
|
||||
|
||||
const logWrapper = createLogWrapper(log);
|
||||
|
||||
@@ -76,7 +77,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
} else {
|
||||
// Parse as integer for main task IDs
|
||||
taskId = parseInt(id, 10);
|
||||
if (isNaN(taskId)) {
|
||||
if (Number.isNaN(taskId)) {
|
||||
const errorMessage = `Invalid task ID: ${id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
@@ -118,7 +119,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
commandName: 'update-task',
|
||||
outputType: 'mcp'
|
||||
},
|
||||
'json'
|
||||
'json',
|
||||
append || false
|
||||
);
|
||||
|
||||
// Check if the core function returned null or an object without success
|
||||
@@ -132,7 +134,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
message: message,
|
||||
taskId: taskId,
|
||||
updated: false,
|
||||
telemetryData: coreResult?.telemetryData
|
||||
telemetryData: coreResult?.telemetryData,
|
||||
tagInfo: coreResult?.tagInfo
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -149,7 +152,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
useResearch: useResearch,
|
||||
updated: true,
|
||||
updatedTask: coreResult.updatedTask,
|
||||
telemetryData: coreResult.telemetryData
|
||||
telemetryData: coreResult.telemetryData,
|
||||
tagInfo: coreResult.tagInfo
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
@@ -90,7 +90,8 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
message: `Successfully updated ${result.updatedTasks.length} tasks.`,
|
||||
tasksPath: tasksJsonPath,
|
||||
updatedCount: result.updatedTasks.length,
|
||||
telemetryData: result.telemetryData
|
||||
telemetryData: result.telemetryData,
|
||||
tagInfo: result.tagInfo
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
103
mcp-server/src/core/direct-functions/use-tag.js
Normal file
103
mcp-server/src/core/direct-functions/use-tag.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* use-tag.js
|
||||
* Direct function implementation for switching to a tag
|
||||
*/
|
||||
|
||||
import { useTag } from '../../../../scripts/modules/task-manager/tag-management.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for switching to a tag with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.name - Name of the tag to switch to
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {string} [args.projectRoot] - Project root path
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function useTagDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, name, projectRoot } = args;
|
||||
const { session } = context;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('useTagDirect called without tasksJsonPath');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!name || typeof name !== 'string') {
|
||||
log.error('Missing required parameter: name');
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_PARAMETER',
|
||||
message: 'Tag name is required and must be a string'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Switching to tag: ${name}`);
|
||||
|
||||
// Call the useTag function
|
||||
const result = await useTag(
|
||||
tasksJsonPath,
|
||||
name,
|
||||
{}, // options (empty for now)
|
||||
{
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot
|
||||
},
|
||||
'json' // outputFormat - use 'json' to suppress CLI UI
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tagName: result.currentTag,
|
||||
switched: result.switched,
|
||||
previousTag: result.previousTag,
|
||||
taskCount: result.taskCount,
|
||||
message: `Successfully switched to tag "${result.currentTag}"`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in useTagDirect: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'USE_TAG_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,13 @@ import { removeTaskDirect } from './direct-functions/remove-task.js';
|
||||
import { initializeProjectDirect } from './direct-functions/initialize-project.js';
|
||||
import { modelsDirect } from './direct-functions/models.js';
|
||||
import { moveTaskDirect } from './direct-functions/move-task.js';
|
||||
import { researchDirect } from './direct-functions/research.js';
|
||||
import { addTagDirect } from './direct-functions/add-tag.js';
|
||||
import { deleteTagDirect } from './direct-functions/delete-tag.js';
|
||||
import { listTagsDirect } from './direct-functions/list-tags.js';
|
||||
import { useTagDirect } from './direct-functions/use-tag.js';
|
||||
import { renameTagDirect } from './direct-functions/rename-tag.js';
|
||||
import { copyTagDirect } from './direct-functions/copy-tag.js';
|
||||
|
||||
// Re-export utility functions
|
||||
export { findTasksPath } from './utils/path-utils.js';
|
||||
@@ -62,7 +69,14 @@ export const directFunctions = new Map([
|
||||
['removeTaskDirect', removeTaskDirect],
|
||||
['initializeProjectDirect', initializeProjectDirect],
|
||||
['modelsDirect', modelsDirect],
|
||||
['moveTaskDirect', moveTaskDirect]
|
||||
['moveTaskDirect', moveTaskDirect],
|
||||
['researchDirect', researchDirect],
|
||||
['addTagDirect', addTagDirect],
|
||||
['deleteTagDirect', deleteTagDirect],
|
||||
['listTagsDirect', listTagsDirect],
|
||||
['useTagDirect', useTagDirect],
|
||||
['renameTagDirect', renameTagDirect],
|
||||
['copyTagDirect', copyTagDirect]
|
||||
]);
|
||||
|
||||
// Re-export all direct function implementations
|
||||
@@ -92,5 +106,12 @@ export {
|
||||
removeTaskDirect,
|
||||
initializeProjectDirect,
|
||||
modelsDirect,
|
||||
moveTaskDirect
|
||||
moveTaskDirect,
|
||||
researchDirect,
|
||||
addTagDirect,
|
||||
deleteTagDirect,
|
||||
listTagsDirect,
|
||||
useTagDirect,
|
||||
renameTagDirect,
|
||||
copyTagDirect
|
||||
};
|
||||
|
||||
@@ -75,7 +75,13 @@ export function registerAddDependencyTool(server) {
|
||||
}
|
||||
|
||||
// Use handleApiResult to format the response
|
||||
return handleApiResult(result, log, 'Error adding dependency');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error adding dependency',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in addDependency tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -88,9 +88,11 @@ export function registerAddSubtaskTool(server) {
|
||||
details: args.details,
|
||||
status: args.status,
|
||||
dependencies: args.dependencies,
|
||||
skipGenerate: args.skipGenerate
|
||||
skipGenerate: args.skipGenerate,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -99,7 +101,13 @@ export function registerAddSubtaskTool(server) {
|
||||
log.error(`Failed to add subtask: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error adding subtask');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error adding subtask',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in addSubtask tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
99
mcp-server/src/tools/add-tag.js
Normal file
99
mcp-server/src/tools/add-tag.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* tools/add-tag.js
|
||||
* Tool to create a new tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { addTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the addTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerAddTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'add_tag',
|
||||
description: 'Create a new tag for organizing tasks in different contexts',
|
||||
parameters: z.object({
|
||||
name: z.string().describe('Name of the new tag to create'),
|
||||
copyFromCurrent: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Whether to copy tasks from the current tag (default: false)'
|
||||
),
|
||||
copyFromTag: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Specific tag to copy tasks from'),
|
||||
fromBranch: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Create tag name from current git branch (ignores name parameter)'
|
||||
),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional description for the tag'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting add-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await addTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
name: args.name,
|
||||
copyFromCurrent: args.copyFromCurrent,
|
||||
copyFromTag: args.copyFromTag,
|
||||
fromBranch: args.fromBranch,
|
||||
description: args.description,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error creating tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in add-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -99,7 +99,13 @@ export function registerAddTaskTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log);
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error adding task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in add-task tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -135,7 +135,13 @@ export function registerAnalyzeProjectComplexityTool(server) {
|
||||
log.info(
|
||||
`${toolName}: Direct function result: success=${result.success}`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error analyzing task complexity');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error analyzing task complexity',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
@@ -63,9 +63,11 @@ export function registerClearSubtasksTool(server) {
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id,
|
||||
all: args.all
|
||||
all: args.all,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -74,7 +76,13 @@ export function registerClearSubtasksTool(server) {
|
||||
log.error(`Failed to clear subtasks: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error clearing subtasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error clearing subtasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in clearSubtasks tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -69,7 +69,9 @@ export function registerComplexityReportTool(server) {
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error retrieving complexity report'
|
||||
'Error retrieving complexity report',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in complexity-report tool: ${error.message}`);
|
||||
|
||||
83
mcp-server/src/tools/copy-tag.js
Normal file
83
mcp-server/src/tools/copy-tag.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* tools/copy-tag.js
|
||||
* Tool to copy an existing tag to a new tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { copyTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the copyTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerCopyTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'copy_tag',
|
||||
description:
|
||||
'Copy an existing tag to create a new tag with all tasks and metadata',
|
||||
parameters: z.object({
|
||||
sourceName: z.string().describe('Name of the source tag to copy from'),
|
||||
targetName: z.string().describe('Name of the new tag to create'),
|
||||
description: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Optional description for the new tag'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting copy-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await copyTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
sourceName: args.sourceName,
|
||||
targetName: args.targetName,
|
||||
description: args.description,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error copying tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in copy-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
80
mcp-server/src/tools/delete-tag.js
Normal file
80
mcp-server/src/tools/delete-tag.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* tools/delete-tag.js
|
||||
* Tool to delete an existing tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { deleteTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the deleteTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerDeleteTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'delete_tag',
|
||||
description: 'Delete an existing tag and all its tasks',
|
||||
parameters: z.object({
|
||||
name: z.string().describe('Name of the tag to delete'),
|
||||
yes: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Skip confirmation prompts (default: true for MCP)'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting delete-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function (always skip confirmation for MCP)
|
||||
const result = await deleteTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
name: args.name,
|
||||
yes: args.yes !== undefined ? args.yes : true, // Default to true for MCP
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error deleting tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in delete-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -92,7 +92,13 @@ export function registerExpandAllTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log, 'Error expanding all tasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error expanding all tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Unexpected error in expand_all tool execute: ${error.message}`
|
||||
|
||||
@@ -79,7 +79,13 @@ export function registerExpandTaskTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log, 'Error expanding task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error expanding task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in expand-task tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -57,7 +57,13 @@ export function registerFixDependenciesTool(server) {
|
||||
log.error(`Failed to fix dependencies: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error fixing dependencies');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error fixing dependencies',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in fixDependencies tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -57,9 +57,11 @@ export function registerGenerateTool(server) {
|
||||
const result = await generateTaskFilesDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
outputDir: outputDir
|
||||
outputDir: outputDir,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -70,7 +72,13 @@ export function registerGenerateTool(server) {
|
||||
);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error generating task files');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error generating task files',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in generate tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -44,7 +44,11 @@ export function registerShowTaskTool(server) {
|
||||
name: 'get_task',
|
||||
description: 'Get detailed information about a specific task',
|
||||
parameters: z.object({
|
||||
id: z.string().describe('Task ID to get'),
|
||||
id: z
|
||||
.string()
|
||||
.describe(
|
||||
'Task ID(s) to get (can be comma-separated for multiple tasks)'
|
||||
),
|
||||
status: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -61,12 +65,11 @@ export function registerShowTaskTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Absolute path to the project root directory (Optional, usually from session)'
|
||||
)
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
const { id, file, status, projectRoot } = args;
|
||||
|
||||
try {
|
||||
@@ -112,7 +115,8 @@ export function registerShowTaskTool(server) {
|
||||
status: status,
|
||||
projectRoot: projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -126,7 +130,8 @@ export function registerShowTaskTool(server) {
|
||||
result,
|
||||
log,
|
||||
'Error retrieving task details',
|
||||
processTaskResponse
|
||||
processTaskResponse,
|
||||
projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in get-task tool: ${error.message}\n${error.stack}`);
|
||||
|
||||
@@ -28,7 +28,9 @@ export function registerListTasksTool(server) {
|
||||
status: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filter tasks by status (e.g., 'pending', 'done')"),
|
||||
.describe(
|
||||
"Filter tasks by status (e.g., 'pending', 'done') or multiple statuses separated by commas (e.g., 'blocked,deferred')"
|
||||
),
|
||||
withSubtasks: z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -81,15 +83,23 @@ export function registerListTasksTool(server) {
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
status: args.status,
|
||||
withSubtasks: args.withSubtasks,
|
||||
reportPath: complexityReportPath
|
||||
reportPath: complexityReportPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
log.info(
|
||||
`Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error getting tasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error getting tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error getting tasks: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -29,6 +29,13 @@ import { registerRemoveTaskTool } from './remove-task.js';
|
||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||
import { registerModelsTool } from './models.js';
|
||||
import { registerMoveTaskTool } from './move-task.js';
|
||||
import { registerAddTagTool } from './add-tag.js';
|
||||
import { registerDeleteTagTool } from './delete-tag.js';
|
||||
import { registerListTagsTool } from './list-tags.js';
|
||||
import { registerUseTagTool } from './use-tag.js';
|
||||
import { registerRenameTagTool } from './rename-tag.js';
|
||||
import { registerCopyTagTool } from './copy-tag.js';
|
||||
import { registerResearchTool } from './research.js';
|
||||
|
||||
/**
|
||||
* Register all Task Master tools with the MCP server
|
||||
@@ -43,17 +50,22 @@ export function registerTaskMasterTools(server) {
|
||||
registerModelsTool(server);
|
||||
registerParsePRDTool(server);
|
||||
|
||||
// Group 2: Task Listing & Viewing
|
||||
// Group 2: Task Analysis & Expansion
|
||||
registerAnalyzeProjectComplexityTool(server);
|
||||
registerExpandTaskTool(server);
|
||||
registerExpandAllTool(server);
|
||||
|
||||
// Group 3: Task Listing & Viewing
|
||||
registerListTasksTool(server);
|
||||
registerShowTaskTool(server);
|
||||
registerNextTaskTool(server);
|
||||
registerComplexityReportTool(server);
|
||||
|
||||
// Group 3: Task Status & Management
|
||||
// Group 4: Task Status & Management
|
||||
registerSetTaskStatusTool(server);
|
||||
registerGenerateTool(server);
|
||||
|
||||
// Group 4: Task Creation & Modification
|
||||
// Group 5: Task Creation & Modification
|
||||
registerAddTaskTool(server);
|
||||
registerAddSubtaskTool(server);
|
||||
registerUpdateTool(server);
|
||||
@@ -64,16 +76,22 @@ export function registerTaskMasterTools(server) {
|
||||
registerClearSubtasksTool(server);
|
||||
registerMoveTaskTool(server);
|
||||
|
||||
// Group 5: Task Analysis & Expansion
|
||||
registerAnalyzeProjectComplexityTool(server);
|
||||
registerExpandTaskTool(server);
|
||||
registerExpandAllTool(server);
|
||||
|
||||
// Group 6: Dependency Management
|
||||
registerAddDependencyTool(server);
|
||||
registerRemoveDependencyTool(server);
|
||||
registerValidateDependenciesTool(server);
|
||||
registerFixDependenciesTool(server);
|
||||
|
||||
// Group 7: Tag Management
|
||||
registerListTagsTool(server);
|
||||
registerAddTagTool(server);
|
||||
registerDeleteTagTool(server);
|
||||
registerUseTagTool(server);
|
||||
registerRenameTagTool(server);
|
||||
registerCopyTagTool(server);
|
||||
|
||||
// Group 8: Research Features
|
||||
registerResearchTool(server);
|
||||
} catch (error) {
|
||||
logger.error(`Error registering Task Master tools: ${error.message}`);
|
||||
throw error;
|
||||
|
||||
@@ -48,7 +48,13 @@ export function registerInitializeProjectTool(server) {
|
||||
|
||||
const result = await initializeProjectDirect(args, log, { session });
|
||||
|
||||
return handleApiResult(result, log, 'Initialization failed');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Initialization failed',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = `Project initialization tool failed: ${error.message || 'Unknown error'}`;
|
||||
log.error(errorMessage, error);
|
||||
|
||||
78
mcp-server/src/tools/list-tags.js
Normal file
78
mcp-server/src/tools/list-tags.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* tools/list-tags.js
|
||||
* Tool to list all available tags
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { listTagsDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the listTags tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerListTagsTool(server) {
|
||||
server.addTool({
|
||||
name: 'list_tags',
|
||||
description: 'List all available tags with task counts and metadata',
|
||||
parameters: z.object({
|
||||
showMetadata: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to include metadata in the output (default: false)'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting list-tags with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await listTagsDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
showMetadata: args.showMetadata,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error listing tags',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in list-tags tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -68,7 +68,13 @@ export function registerModelsTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(result, log);
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error managing models',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in models tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -102,7 +102,10 @@ export function registerMoveTaskTool(server) {
|
||||
message: `Successfully moved ${results.length} tasks`
|
||||
}
|
||||
},
|
||||
log
|
||||
log,
|
||||
'Error moving multiple tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} else {
|
||||
// Moving a single task
|
||||
@@ -117,7 +120,10 @@ export function registerMoveTaskTool(server) {
|
||||
log,
|
||||
{ session }
|
||||
),
|
||||
log
|
||||
log,
|
||||
'Error moving task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -64,13 +64,21 @@ export function registerNextTaskTool(server) {
|
||||
const result = await nextTaskDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
reportPath: complexityReportPath
|
||||
reportPath: complexityReportPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
log.info(`Next task result: ${result.success ? 'found' : 'none'}`);
|
||||
return handleApiResult(result, log, 'Error finding next task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error finding next task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding next task: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -64,7 +64,13 @@ export function registerParsePRDTool(server) {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
const result = await parsePRDDirect(args, log, { session });
|
||||
return handleApiResult(result, log);
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error parsing PRD',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in parse_prd: ${error.message}`);
|
||||
return createErrorResponse(`Failed to parse PRD: ${error.message}`);
|
||||
|
||||
@@ -68,7 +68,13 @@ export function registerRemoveDependencyTool(server) {
|
||||
log.error(`Failed to remove dependency: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error removing dependency');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error removing dependency',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in removeDependency tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -46,7 +46,7 @@ export function registerRemoveSubtaskTool(server) {
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
|
||||
|
||||
@@ -69,9 +69,11 @@ export function registerRemoveSubtaskTool(server) {
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id,
|
||||
convert: args.convert,
|
||||
skipGenerate: args.skipGenerate
|
||||
skipGenerate: args.skipGenerate,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -80,7 +82,13 @@ export function registerRemoveSubtaskTool(server) {
|
||||
log.error(`Failed to remove subtask: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error removing subtask');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error removing subtask',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in removeSubtask tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
@@ -35,7 +35,7 @@ export function registerRemoveTaskTool(server) {
|
||||
.optional()
|
||||
.describe('Whether to skip confirmation prompt (default: false)')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Removing task(s) with ID(s): ${args.id}`);
|
||||
|
||||
@@ -58,9 +58,11 @@ export function registerRemoveTaskTool(server) {
|
||||
const result = await removeTaskDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id
|
||||
id: args.id,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -69,7 +71,13 @@ export function registerRemoveTaskTool(server) {
|
||||
log.error(`Failed to remove task: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error removing task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error removing task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in remove-task tool: ${error.message}`);
|
||||
return createErrorResponse(`Failed to remove task: ${error.message}`);
|
||||
|
||||
77
mcp-server/src/tools/rename-tag.js
Normal file
77
mcp-server/src/tools/rename-tag.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* tools/rename-tag.js
|
||||
* Tool to rename an existing tag
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { renameTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the renameTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerRenameTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'rename_tag',
|
||||
description: 'Rename an existing tag',
|
||||
parameters: z.object({
|
||||
oldName: z.string().describe('Current name of the tag to rename'),
|
||||
newName: z.string().describe('New name for the tag'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting rename-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await renameTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
oldName: args.oldName,
|
||||
newName: args.newName,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error renaming tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in rename-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
102
mcp-server/src/tools/research.js
Normal file
102
mcp-server/src/tools/research.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* tools/research.js
|
||||
* Tool to perform AI-powered research queries with project context
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { researchDirect } from '../core/task-master-core.js';
|
||||
|
||||
/**
|
||||
* Register the research tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerResearchTool(server) {
|
||||
server.addTool({
|
||||
name: 'research',
|
||||
description: 'Perform AI-powered research queries with project context',
|
||||
parameters: z.object({
|
||||
query: z.string().describe('Research query/prompt (required)'),
|
||||
taskIds: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Comma-separated list of task/subtask IDs for context (e.g., "15,16.2,17")'
|
||||
),
|
||||
filePaths: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md")'
|
||||
),
|
||||
customContext: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Additional custom context text to include in the research'),
|
||||
includeProjectTree: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Include project file tree structure in context (default: false)'
|
||||
),
|
||||
detailLevel: z
|
||||
.enum(['low', 'medium', 'high'])
|
||||
.optional()
|
||||
.describe('Detail level for the research response (default: medium)'),
|
||||
saveTo: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Automatically save research results to specified task/subtask ID (e.g., "15" or "15.2")'
|
||||
),
|
||||
saveToFile: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Save research results to .taskmaster/docs/research/ directory (default: false)'
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(
|
||||
`Starting research with query: "${args.query.substring(0, 100)}${args.query.length > 100 ? '...' : ''}"`
|
||||
);
|
||||
|
||||
// Call the direct function
|
||||
const result = await researchDirect(
|
||||
{
|
||||
query: args.query,
|
||||
taskIds: args.taskIds,
|
||||
filePaths: args.filePaths,
|
||||
customContext: args.customContext,
|
||||
includeProjectTree: args.includeProjectTree || false,
|
||||
detailLevel: args.detailLevel || 'medium',
|
||||
saveTo: args.saveTo,
|
||||
saveToFile: args.saveToFile || false,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error performing research',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in research tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export function registerSetTaskStatusTool(server) {
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log }) => {
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
|
||||
|
||||
@@ -85,9 +85,11 @@ export function registerSetTaskStatusTool(server) {
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id,
|
||||
status: args.status,
|
||||
complexityReportPath
|
||||
complexityReportPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
@@ -100,7 +102,13 @@ export function registerSetTaskStatusTool(server) {
|
||||
);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error setting task status');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error setting task status',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in setTaskStatus tool: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
|
||||
@@ -75,7 +75,13 @@ export function registerUpdateSubtaskTool(server) {
|
||||
);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error updating subtask');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error updating subtask',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
@@ -34,6 +34,12 @@ export function registerUpdateTaskTool(server) {
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Use Perplexity AI for research-backed updates'),
|
||||
append: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Append timestamped information to task details instead of full update'
|
||||
),
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
@@ -67,6 +73,7 @@ export function registerUpdateTaskTool(server) {
|
||||
id: args.id,
|
||||
prompt: args.prompt,
|
||||
research: args.research,
|
||||
append: args.append,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
@@ -77,7 +84,13 @@ export function registerUpdateTaskTool(server) {
|
||||
log.info(
|
||||
`${toolName}: Direct function result: success=${result.success}`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error updating task');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error updating task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
@@ -80,7 +80,13 @@ export function registerUpdateTool(server) {
|
||||
log.info(
|
||||
`${toolName}: Direct function result: success=${result.success}`
|
||||
);
|
||||
return handleApiResult(result, log, 'Error updating tasks');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error updating tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Critical error in ${toolName} tool execute: ${error.message}`
|
||||
|
||||
75
mcp-server/src/tools/use-tag.js
Normal file
75
mcp-server/src/tools/use-tag.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* tools/use-tag.js
|
||||
* Tool to switch to a different tag context
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createErrorResponse,
|
||||
handleApiResult,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { useTagDirect } from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the useTag tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerUseTagTool(server) {
|
||||
server.addTool({
|
||||
name: 'use_tag',
|
||||
description: 'Switch to a different tag context for task operations',
|
||||
parameters: z.object({
|
||||
name: z.string().describe('Name of the tag to switch to'),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting use-tag with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksPath(
|
||||
{ projectRoot: args.projectRoot, file: args.file },
|
||||
log
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
`Failed to find tasks.json: ${error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await useTagDirect(
|
||||
{
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
name: args.name,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error switching tag',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in use-tag tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { contextManager } from '../core/context-manager.js'; // Import the singleton
|
||||
import { fileURLToPath } from 'url';
|
||||
import { getCurrentTag } from '../../../scripts/modules/utils.js';
|
||||
|
||||
// Import path utilities to ensure consistent path resolution
|
||||
import {
|
||||
@@ -59,6 +60,64 @@ function getVersionInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current tag information for MCP responses
|
||||
* @param {string} projectRoot - The project root directory
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Object} Tag information object
|
||||
*/
|
||||
function getTagInfo(projectRoot, log) {
|
||||
try {
|
||||
if (!projectRoot) {
|
||||
log.warn('No project root provided for tag information');
|
||||
return { currentTag: 'master', availableTags: ['master'] };
|
||||
}
|
||||
|
||||
const currentTag = getCurrentTag(projectRoot);
|
||||
|
||||
// Read available tags from tasks.json
|
||||
let availableTags = ['master']; // Default fallback
|
||||
try {
|
||||
const tasksJsonPath = path.join(
|
||||
projectRoot,
|
||||
'.taskmaster',
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
if (fs.existsSync(tasksJsonPath)) {
|
||||
const tasksData = JSON.parse(fs.readFileSync(tasksJsonPath, 'utf-8'));
|
||||
|
||||
// If it's the new tagged format, extract tag keys
|
||||
if (
|
||||
tasksData &&
|
||||
typeof tasksData === 'object' &&
|
||||
!Array.isArray(tasksData.tasks)
|
||||
) {
|
||||
const tagKeys = Object.keys(tasksData).filter(
|
||||
(key) =>
|
||||
tasksData[key] &&
|
||||
typeof tasksData[key] === 'object' &&
|
||||
Array.isArray(tasksData[key].tasks)
|
||||
);
|
||||
if (tagKeys.length > 0) {
|
||||
availableTags = tagKeys;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (tagError) {
|
||||
log.debug(`Could not read available tags: ${tagError.message}`);
|
||||
}
|
||||
|
||||
return {
|
||||
currentTag: currentTag || 'master',
|
||||
availableTags: availableTags
|
||||
};
|
||||
} catch (error) {
|
||||
log.warn(`Error getting tag information: ${error.message}`);
|
||||
return { currentTag: 'master', availableTags: ['master'] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get normalized project root path
|
||||
* @param {string|undefined} projectRootRaw - Raw project root from arguments
|
||||
@@ -242,21 +301,26 @@ function getProjectRootFromSession(session, log) {
|
||||
* @param {Object} log - Logger object
|
||||
* @param {string} errorPrefix - Prefix for error messages
|
||||
* @param {Function} processFunction - Optional function to process successful result data
|
||||
* @param {string} [projectRoot] - Optional project root for tag information
|
||||
* @returns {Object} - Standardized MCP response object
|
||||
*/
|
||||
async function handleApiResult(
|
||||
result,
|
||||
log,
|
||||
errorPrefix = 'API error',
|
||||
processFunction = processMCPResponseData
|
||||
processFunction = processMCPResponseData,
|
||||
projectRoot = null
|
||||
) {
|
||||
// Get version info for every response
|
||||
const versionInfo = getVersionInfo();
|
||||
|
||||
// Get tag info if project root is provided
|
||||
const tagInfo = projectRoot ? getTagInfo(projectRoot, log) : null;
|
||||
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error?.message || `Unknown ${errorPrefix}`;
|
||||
log.error(`${errorPrefix}: ${errorMsg}`);
|
||||
return createErrorResponse(errorMsg, versionInfo);
|
||||
return createErrorResponse(errorMsg, versionInfo, tagInfo);
|
||||
}
|
||||
|
||||
// Process the result data if needed
|
||||
@@ -266,12 +330,17 @@ async function handleApiResult(
|
||||
|
||||
log.info('Successfully completed operation');
|
||||
|
||||
// Create the response payload including version info
|
||||
// Create the response payload including version info and tag info
|
||||
const responsePayload = {
|
||||
data: processedData,
|
||||
version: versionInfo
|
||||
};
|
||||
|
||||
// Add tag information if available
|
||||
if (tagInfo) {
|
||||
responsePayload.tag = tagInfo;
|
||||
}
|
||||
|
||||
return createContentResponse(responsePayload);
|
||||
}
|
||||
|
||||
@@ -496,21 +565,30 @@ function createContentResponse(content) {
|
||||
* Creates error response for tools
|
||||
* @param {string} errorMessage - Error message to include in response
|
||||
* @param {Object} [versionInfo] - Optional version information object
|
||||
* @param {Object} [tagInfo] - Optional tag information object
|
||||
* @returns {Object} - Error content response object in FastMCP format
|
||||
*/
|
||||
function createErrorResponse(errorMessage, versionInfo) {
|
||||
function createErrorResponse(errorMessage, versionInfo, tagInfo) {
|
||||
// Provide fallback version info if not provided
|
||||
if (!versionInfo) {
|
||||
versionInfo = getVersionInfo();
|
||||
}
|
||||
|
||||
let responseText = `Error: ${errorMessage}
|
||||
Version: ${versionInfo.version}
|
||||
Name: ${versionInfo.name}`;
|
||||
|
||||
// Add tag information if available
|
||||
if (tagInfo) {
|
||||
responseText += `
|
||||
Current Tag: ${tagInfo.currentTag}`;
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error: ${errorMessage}
|
||||
Version: ${versionInfo.version}
|
||||
Name: ${versionInfo.name}`
|
||||
text: responseText
|
||||
}
|
||||
],
|
||||
isError: true
|
||||
@@ -704,6 +782,7 @@ function withNormalizedProjectRoot(executeFn) {
|
||||
export {
|
||||
getProjectRoot,
|
||||
getProjectRootFromSession,
|
||||
getTagInfo,
|
||||
handleApiResult,
|
||||
executeTaskMasterCommand,
|
||||
getCachedOrExecute,
|
||||
|
||||
@@ -60,7 +60,13 @@ export function registerValidateDependenciesTool(server) {
|
||||
log.error(`Failed to validate dependencies: ${result.error.message}`);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error validating dependencies');
|
||||
return handleApiResult(
|
||||
result,
|
||||
log,
|
||||
'Error validating dependencies',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`Error in validateDependencies tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
|
||||
Reference in New Issue
Block a user