feat(mcp): Refactor MCP tools for direct function calls & add docs
This commit introduces a major refactoring of the MCP server implementation to prioritize direct function calls over CLI execution, enhancing performance and reliability. It also includes substantial updates to documentation for consistency and interlinking.
**MCP Server & Core Logic Refactoring:**
1. **Introduce Direct Function Wrappers ():**
* Created to house direct wrappers for core Task Master functions (imported from ).
* Implemented as the first wrapper, calling .
* Added utility within to centralize file location logic, removing duplication.
* Established the map for registering these wrappers.
2. **Enhance MCP Utilities ():**
* Added : A primary utility function to streamline MCP tool methods. It handles logging, argument processing (incl. project root normalization), calling the direct action function (e.g., ), processing results via , and formatting the final MCP response.
* Added : Standardizes processing of objects returned by direct function wrappers.
* Added : Filters sensitive/large fields (like , ) from responses sent to the MCP client.
* Added , , to support the new workflow.
* Refactored to use internally, simplifying its usage (though it's now primarily a fallback).
3. **Update MCP Tools (, ):**
* Refactored to use with , significantly simplifying the tool's method.
* Updated (initially) to use the improved (Note: further refactoring to use a direct wrapper for would follow the pattern).
**Documentation Enhancements:**
4. **Comprehensive Interlinking:** Added links across rule files (, , , , , ) to connect related guidelines, improving navigation.
5. **Standardize CLI Syntax ():** Removed legacy ℹ️ Initialized Perplexity client with OpenAI compatibility layer examples, reinforcing ℹ️ Initialized Perplexity client with OpenAI compatibility layer
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master as the primary CLI.
6. **Add MCP Architecture & Workflow Sections:** Added detailed sections in and explaining MCP server structure and the workflow for adding new MCP tool integrations using the direct function pattern.
7. **Clarify MCP Role:** Updated and to better explain the MCP server's role and its specific utilities.
Overall, this establishes a cleaner, more performant, and maintainable pattern for integrating Task Master functionality with the MCP server, supported by improved documentation.
2025-03-30 00:29:12 -04:00
|
|
|
/**
|
|
|
|
* Integration test for direct function imports in MCP server
|
|
|
|
*/
|
|
|
|
|
|
|
|
import { jest } from '@jest/globals';
|
2025-05-16 23:24:25 +02:00
|
|
|
import path, { dirname } from 'path';
|
feat(mcp): Refactor MCP tools for direct function calls & add docs
This commit introduces a major refactoring of the MCP server implementation to prioritize direct function calls over CLI execution, enhancing performance and reliability. It also includes substantial updates to documentation for consistency and interlinking.
**MCP Server & Core Logic Refactoring:**
1. **Introduce Direct Function Wrappers ():**
* Created to house direct wrappers for core Task Master functions (imported from ).
* Implemented as the first wrapper, calling .
* Added utility within to centralize file location logic, removing duplication.
* Established the map for registering these wrappers.
2. **Enhance MCP Utilities ():**
* Added : A primary utility function to streamline MCP tool methods. It handles logging, argument processing (incl. project root normalization), calling the direct action function (e.g., ), processing results via , and formatting the final MCP response.
* Added : Standardizes processing of objects returned by direct function wrappers.
* Added : Filters sensitive/large fields (like , ) from responses sent to the MCP client.
* Added , , to support the new workflow.
* Refactored to use internally, simplifying its usage (though it's now primarily a fallback).
3. **Update MCP Tools (, ):**
* Refactored to use with , significantly simplifying the tool's method.
* Updated (initially) to use the improved (Note: further refactoring to use a direct wrapper for would follow the pattern).
**Documentation Enhancements:**
4. **Comprehensive Interlinking:** Added links across rule files (, , , , , ) to connect related guidelines, improving navigation.
5. **Standardize CLI Syntax ():** Removed legacy ℹ️ Initialized Perplexity client with OpenAI compatibility layer examples, reinforcing ℹ️ Initialized Perplexity client with OpenAI compatibility layer
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master as the primary CLI.
6. **Add MCP Architecture & Workflow Sections:** Added detailed sections in and explaining MCP server structure and the workflow for adding new MCP tool integrations using the direct function pattern.
7. **Clarify MCP Role:** Updated and to better explain the MCP server's role and its specific utilities.
Overall, this establishes a cleaner, more performant, and maintainable pattern for integrating Task Master functionality with the MCP server, supported by improved documentation.
2025-03-30 00:29:12 -04:00
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
|
|
// Get the current module's directory
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
|
|
const __dirname = dirname(__filename);
|
|
|
|
|
2025-04-07 19:55:03 -04:00
|
|
|
// Test file paths
|
|
|
|
const testProjectRoot = path.join(__dirname, '../../fixtures');
|
|
|
|
const testTasksPath = path.join(testProjectRoot, 'test-tasks.json');
|
|
|
|
|
|
|
|
// Create explicit mock functions
|
|
|
|
const mockExistsSync = jest.fn().mockReturnValue(true);
|
|
|
|
const mockWriteFileSync = jest.fn();
|
|
|
|
const mockReadFileSync = jest.fn();
|
|
|
|
const mockUnlinkSync = jest.fn();
|
|
|
|
const mockMkdirSync = jest.fn();
|
|
|
|
|
|
|
|
const mockFindTasksJsonPath = jest.fn().mockReturnValue(testTasksPath);
|
|
|
|
const mockReadJSON = jest.fn();
|
|
|
|
const mockWriteJSON = jest.fn();
|
|
|
|
const mockEnableSilentMode = jest.fn();
|
|
|
|
const mockDisableSilentMode = jest.fn();
|
2025-05-16 23:24:25 +02:00
|
|
|
const mockReadComplexityReport = jest.fn().mockReturnValue(null);
|
2025-04-07 19:55:03 -04:00
|
|
|
|
|
|
|
const mockGetAnthropicClient = jest.fn().mockReturnValue({});
|
|
|
|
const mockGetConfiguredAnthropicClient = jest.fn().mockReturnValue({});
|
2025-04-09 00:25:27 +02:00
|
|
|
const mockHandleAnthropicStream = jest.fn().mockResolvedValue(
|
|
|
|
JSON.stringify([
|
|
|
|
{
|
|
|
|
id: 1,
|
|
|
|
title: 'Mock Subtask 1',
|
|
|
|
description: 'First mock subtask',
|
|
|
|
dependencies: [],
|
|
|
|
details: 'Implementation details for mock subtask 1'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 2,
|
|
|
|
title: 'Mock Subtask 2',
|
|
|
|
description: 'Second mock subtask',
|
|
|
|
dependencies: [1],
|
|
|
|
details: 'Implementation details for mock subtask 2'
|
|
|
|
}
|
|
|
|
])
|
|
|
|
);
|
2025-04-07 19:55:03 -04:00
|
|
|
const mockParseSubtasksFromText = jest.fn().mockReturnValue([
|
2025-04-09 00:25:27 +02:00
|
|
|
{
|
|
|
|
id: 1,
|
|
|
|
title: 'Mock Subtask 1',
|
|
|
|
description: 'First mock subtask',
|
|
|
|
status: 'pending',
|
|
|
|
dependencies: []
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 2,
|
|
|
|
title: 'Mock Subtask 2',
|
|
|
|
description: 'Second mock subtask',
|
|
|
|
status: 'pending',
|
|
|
|
dependencies: [1]
|
|
|
|
}
|
2025-04-07 19:55:03 -04:00
|
|
|
]);
|
|
|
|
|
|
|
|
// Create a mock for expandTask that returns predefined responses instead of making real calls
|
2025-04-09 00:25:27 +02:00
|
|
|
const mockExpandTask = jest
|
|
|
|
.fn()
|
|
|
|
.mockImplementation(
|
|
|
|
(taskId, numSubtasks, useResearch, additionalContext, options) => {
|
|
|
|
const task = {
|
|
|
|
...(sampleTasks.tasks.find((t) => t.id === taskId) || {}),
|
|
|
|
subtasks: useResearch
|
|
|
|
? [
|
|
|
|
{
|
|
|
|
id: 1,
|
|
|
|
title: 'Research-Backed Subtask 1',
|
|
|
|
description: 'First research-backed subtask',
|
|
|
|
status: 'pending',
|
|
|
|
dependencies: []
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 2,
|
|
|
|
title: 'Research-Backed Subtask 2',
|
|
|
|
description: 'Second research-backed subtask',
|
|
|
|
status: 'pending',
|
|
|
|
dependencies: [1]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
: [
|
|
|
|
{
|
|
|
|
id: 1,
|
|
|
|
title: 'Mock Subtask 1',
|
|
|
|
description: 'First mock subtask',
|
|
|
|
status: 'pending',
|
|
|
|
dependencies: []
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 2,
|
|
|
|
title: 'Mock Subtask 2',
|
|
|
|
description: 'Second mock subtask',
|
|
|
|
status: 'pending',
|
|
|
|
dependencies: [1]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
|
|
|
return Promise.resolve(task);
|
|
|
|
}
|
|
|
|
);
|
2025-04-07 19:55:03 -04:00
|
|
|
|
|
|
|
const mockGenerateTaskFiles = jest.fn().mockResolvedValue(true);
|
|
|
|
const mockFindTaskById = jest.fn();
|
|
|
|
const mockTaskExists = jest.fn().mockReturnValue(true);
|
|
|
|
|
|
|
|
// Mock fs module to avoid file system operations
|
|
|
|
jest.mock('fs', () => ({
|
2025-04-09 00:25:27 +02:00
|
|
|
existsSync: mockExistsSync,
|
|
|
|
writeFileSync: mockWriteFileSync,
|
|
|
|
readFileSync: mockReadFileSync,
|
|
|
|
unlinkSync: mockUnlinkSync,
|
|
|
|
mkdirSync: mockMkdirSync
|
2025-04-07 19:55:03 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
// Mock utils functions to avoid actual file operations
|
|
|
|
jest.mock('../../../scripts/modules/utils.js', () => ({
|
2025-04-09 00:25:27 +02:00
|
|
|
readJSON: mockReadJSON,
|
|
|
|
writeJSON: mockWriteJSON,
|
|
|
|
enableSilentMode: mockEnableSilentMode,
|
|
|
|
disableSilentMode: mockDisableSilentMode,
|
2025-05-16 23:24:25 +02:00
|
|
|
readComplexityReport: mockReadComplexityReport,
|
2025-04-09 00:25:27 +02:00
|
|
|
CONFIG: {
|
2025-04-11 13:38:12 -04:00
|
|
|
model: 'claude-3-7-sonnet-20250219',
|
2025-04-09 00:25:27 +02:00
|
|
|
maxTokens: 64000,
|
|
|
|
temperature: 0.2,
|
|
|
|
defaultSubtasks: 5
|
|
|
|
}
|
2025-04-07 19:55:03 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
// Mock path-utils with findTasksJsonPath
|
|
|
|
jest.mock('../../../mcp-server/src/core/utils/path-utils.js', () => ({
|
2025-04-09 00:25:27 +02:00
|
|
|
findTasksJsonPath: mockFindTasksJsonPath
|
2025-04-07 19:55:03 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
// Mock the AI module to prevent any real API calls
|
2025-04-30 22:02:02 -04:00
|
|
|
jest.mock('../../../scripts/modules/ai-services-unified.js', () => ({
|
|
|
|
// Mock the functions exported by ai-services-unified.js as needed
|
|
|
|
// For example, if you are testing a function that uses generateTextService:
|
|
|
|
generateTextService: jest.fn().mockResolvedValue('Mock AI Response')
|
|
|
|
// Add other mocks for generateObjectService, streamTextService if used
|
2025-04-07 19:55:03 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
// Mock task-manager.js to avoid real operations
|
|
|
|
jest.mock('../../../scripts/modules/task-manager.js', () => ({
|
2025-04-09 00:25:27 +02:00
|
|
|
expandTask: mockExpandTask,
|
|
|
|
generateTaskFiles: mockGenerateTaskFiles,
|
|
|
|
findTaskById: mockFindTaskById,
|
|
|
|
taskExists: mockTaskExists
|
2025-04-07 19:55:03 -04:00
|
|
|
}));
|
|
|
|
|
|
|
|
// Import dependencies after mocks are set up
|
|
|
|
import { sampleTasks } from '../../fixtures/sample-tasks.js';
|
feat(mcp): Refactor MCP tools for direct function calls & add docs
This commit introduces a major refactoring of the MCP server implementation to prioritize direct function calls over CLI execution, enhancing performance and reliability. It also includes substantial updates to documentation for consistency and interlinking.
**MCP Server & Core Logic Refactoring:**
1. **Introduce Direct Function Wrappers ():**
* Created to house direct wrappers for core Task Master functions (imported from ).
* Implemented as the first wrapper, calling .
* Added utility within to centralize file location logic, removing duplication.
* Established the map for registering these wrappers.
2. **Enhance MCP Utilities ():**
* Added : A primary utility function to streamline MCP tool methods. It handles logging, argument processing (incl. project root normalization), calling the direct action function (e.g., ), processing results via , and formatting the final MCP response.
* Added : Standardizes processing of objects returned by direct function wrappers.
* Added : Filters sensitive/large fields (like , ) from responses sent to the MCP client.
* Added , , to support the new workflow.
* Refactored to use internally, simplifying its usage (though it's now primarily a fallback).
3. **Update MCP Tools (, ):**
* Refactored to use with , significantly simplifying the tool's method.
* Updated (initially) to use the improved (Note: further refactoring to use a direct wrapper for would follow the pattern).
**Documentation Enhancements:**
4. **Comprehensive Interlinking:** Added links across rule files (, , , , , ) to connect related guidelines, improving navigation.
5. **Standardize CLI Syntax ():** Removed legacy ℹ️ Initialized Perplexity client with OpenAI compatibility layer examples, reinforcing ℹ️ Initialized Perplexity client with OpenAI compatibility layer
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master as the primary CLI.
6. **Add MCP Architecture & Workflow Sections:** Added detailed sections in and explaining MCP server structure and the workflow for adding new MCP tool integrations using the direct function pattern.
7. **Clarify MCP Role:** Updated and to better explain the MCP server's role and its specific utilities.
Overall, this establishes a cleaner, more performant, and maintainable pattern for integrating Task Master functionality with the MCP server, supported by improved documentation.
2025-03-30 00:29:12 -04:00
|
|
|
|
|
|
|
// Mock logger
|
|
|
|
const mockLogger = {
|
2025-04-09 00:25:27 +02:00
|
|
|
info: jest.fn(),
|
|
|
|
error: jest.fn(),
|
|
|
|
debug: jest.fn(),
|
|
|
|
warn: jest.fn()
|
feat(mcp): Refactor MCP tools for direct function calls & add docs
This commit introduces a major refactoring of the MCP server implementation to prioritize direct function calls over CLI execution, enhancing performance and reliability. It also includes substantial updates to documentation for consistency and interlinking.
**MCP Server & Core Logic Refactoring:**
1. **Introduce Direct Function Wrappers ():**
* Created to house direct wrappers for core Task Master functions (imported from ).
* Implemented as the first wrapper, calling .
* Added utility within to centralize file location logic, removing duplication.
* Established the map for registering these wrappers.
2. **Enhance MCP Utilities ():**
* Added : A primary utility function to streamline MCP tool methods. It handles logging, argument processing (incl. project root normalization), calling the direct action function (e.g., ), processing results via , and formatting the final MCP response.
* Added : Standardizes processing of objects returned by direct function wrappers.
* Added : Filters sensitive/large fields (like , ) from responses sent to the MCP client.
* Added , , to support the new workflow.
* Refactored to use internally, simplifying its usage (though it's now primarily a fallback).
3. **Update MCP Tools (, ):**
* Refactored to use with , significantly simplifying the tool's method.
* Updated (initially) to use the improved (Note: further refactoring to use a direct wrapper for would follow the pattern).
**Documentation Enhancements:**
4. **Comprehensive Interlinking:** Added links across rule files (, , , , , ) to connect related guidelines, improving navigation.
5. **Standardize CLI Syntax ():** Removed legacy ℹ️ Initialized Perplexity client with OpenAI compatibility layer examples, reinforcing ℹ️ Initialized Perplexity client with OpenAI compatibility layer
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master as the primary CLI.
6. **Add MCP Architecture & Workflow Sections:** Added detailed sections in and explaining MCP server structure and the workflow for adding new MCP tool integrations using the direct function pattern.
7. **Clarify MCP Role:** Updated and to better explain the MCP server's role and its specific utilities.
Overall, this establishes a cleaner, more performant, and maintainable pattern for integrating Task Master functionality with the MCP server, supported by improved documentation.
2025-03-30 00:29:12 -04:00
|
|
|
};
|
|
|
|
|
2025-04-07 19:55:03 -04:00
|
|
|
// Mock session
|
|
|
|
const mockSession = {
|
2025-04-09 00:25:27 +02:00
|
|
|
env: {
|
|
|
|
ANTHROPIC_API_KEY: 'mock-api-key',
|
|
|
|
MODEL: 'claude-3-sonnet-20240229',
|
|
|
|
MAX_TOKENS: 4000,
|
|
|
|
TEMPERATURE: '0.2'
|
|
|
|
}
|
2025-04-07 19:55:03 -04:00
|
|
|
};
|
feat(mcp): Refactor MCP tools for direct function calls & add docs
This commit introduces a major refactoring of the MCP server implementation to prioritize direct function calls over CLI execution, enhancing performance and reliability. It also includes substantial updates to documentation for consistency and interlinking.
**MCP Server & Core Logic Refactoring:**
1. **Introduce Direct Function Wrappers ():**
* Created to house direct wrappers for core Task Master functions (imported from ).
* Implemented as the first wrapper, calling .
* Added utility within to centralize file location logic, removing duplication.
* Established the map for registering these wrappers.
2. **Enhance MCP Utilities ():**
* Added : A primary utility function to streamline MCP tool methods. It handles logging, argument processing (incl. project root normalization), calling the direct action function (e.g., ), processing results via , and formatting the final MCP response.
* Added : Standardizes processing of objects returned by direct function wrappers.
* Added : Filters sensitive/large fields (like , ) from responses sent to the MCP client.
* Added , , to support the new workflow.
* Refactored to use internally, simplifying its usage (though it's now primarily a fallback).
3. **Update MCP Tools (, ):**
* Refactored to use with , significantly simplifying the tool's method.
* Updated (initially) to use the improved (Note: further refactoring to use a direct wrapper for would follow the pattern).
**Documentation Enhancements:**
4. **Comprehensive Interlinking:** Added links across rule files (, , , , , ) to connect related guidelines, improving navigation.
5. **Standardize CLI Syntax ():** Removed legacy ℹ️ Initialized Perplexity client with OpenAI compatibility layer examples, reinforcing ℹ️ Initialized Perplexity client with OpenAI compatibility layer
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master
_____ _ __ __ _
|_ _|_ _ ___| | __ | \/ | __ _ ___| |_ ___ _ __
| |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
| | (_| \__ \ < | | | | (_| \__ \ || __/ |
|_|\__,_|___/_|\_\ |_| |_|\__,_|___/\__\___|_|
by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│ │
│ Version: 0.9.30 Project: Task Master │
│ │
╰────────────────────────────────────────────╯
╭─────────────────────╮
│ │
│ Task Master CLI │
│ │
╰─────────────────────╯
╭───────────────────╮
│ Task Generation │
╰───────────────────╯
parse-prd --input=<file.txt> [--tasks=10] Generate tasks from a PRD document
generate Create individual task files from tasks…
╭───────────────────╮
│ Task Management │
╰───────────────────╯
list [--status=<status>] [--with-subtas… List all tasks with their status
set-status --id=<id> --status=<status> Update task status (done, pending, etc.)
update --from=<id> --prompt="<context>" Update tasks based on new requirements
add-task --prompt="<text>" [--dependencies=… Add a new task using AI
add-dependency --id=<id> --depends-on=<id> Add a dependency to a task
remove-dependency --id=<id> --depends-on=<id> Remove a dependency from a task
╭──────────────────────────╮
│ Task Analysis & Detail │
╰──────────────────────────╯
analyze-complexity [--research] [--threshold=5] Analyze tasks and generate expansion re…
complexity-report [--file=<path>] Display the complexity analysis report
expand --id=<id> [--num=5] [--research] [… Break down tasks into detailed subtasks
expand --all [--force] [--research] Expand all pending tasks with subtasks
clear-subtasks --id=<id> Remove subtasks from specified tasks
╭─────────────────────────────╮
│ Task Navigation & Viewing │
╰─────────────────────────────╯
next Show the next task to work on based on …
show <id> Display detailed information about a sp…
╭─────────────────────────╮
│ Dependency Management │
╰─────────────────────────╯
validate-dependenci… Identify invalid dependencies without f…
fix-dependencies Fix invalid dependencies automatically
╭─────────────────────────╮
│ Environment Variables │
╰─────────────────────────╯
ANTHROPIC_API_KEY Your Anthropic API key Required
MODEL Claude model to use Default: claude-3-7-sonn…
MAX_TOKENS Maximum tokens for responses Default: 4000
TEMPERATURE Temperature for model responses Default: 0.7
PERPLEXITY_API_KEY Perplexity API key for research Optional
PERPLEXITY_MODEL Perplexity model to use Default: sonar-pro
DEBUG Enable debug logging Default: false
LOG_LEVEL Console output level (debug,info,warn,error) Default: info
DEFAULT_SUBTASKS Default number of subtasks to generate Default: 3
DEFAULT_PRIORITY Default task priority Default: medium
PROJECT_NAME Project name displayed in UI Default: Task Master as the primary CLI.
6. **Add MCP Architecture & Workflow Sections:** Added detailed sections in and explaining MCP server structure and the workflow for adding new MCP tool integrations using the direct function pattern.
7. **Clarify MCP Role:** Updated and to better explain the MCP server's role and its specific utilities.
Overall, this establishes a cleaner, more performant, and maintainable pattern for integrating Task Master functionality with the MCP server, supported by improved documentation.
2025-03-30 00:29:12 -04:00
|
|
|
|
|
|
|
describe('MCP Server Direct Functions', () => {
|
2025-04-09 00:25:27 +02:00
|
|
|
// Set up before each test
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.clearAllMocks();
|
|
|
|
|
|
|
|
// Default mockReadJSON implementation
|
|
|
|
mockReadJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks)));
|
|
|
|
|
|
|
|
// Default mockFindTaskById implementation
|
|
|
|
mockFindTaskById.mockImplementation((tasks, taskId) => {
|
|
|
|
const id = parseInt(taskId, 10);
|
|
|
|
return tasks.find((t) => t.id === id);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Default mockTaskExists implementation
|
|
|
|
mockTaskExists.mockImplementation((tasks, taskId) => {
|
|
|
|
const id = parseInt(taskId, 10);
|
|
|
|
return tasks.some((t) => t.id === id);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Default findTasksJsonPath implementation
|
|
|
|
mockFindTasksJsonPath.mockImplementation((args) => {
|
|
|
|
// Mock returning null for non-existent files
|
|
|
|
if (args.file === 'non-existent-file.json') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return testTasksPath;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('listTasksDirect', () => {
|
2025-05-16 23:24:25 +02:00
|
|
|
// Sample complexity report for testing
|
|
|
|
const mockComplexityReport = {
|
|
|
|
meta: {
|
|
|
|
generatedAt: '2025-03-24T20:01:35.986Z',
|
|
|
|
tasksAnalyzed: 3,
|
|
|
|
thresholdScore: 5,
|
|
|
|
projectName: 'Test Project',
|
|
|
|
usedResearch: false
|
|
|
|
},
|
|
|
|
complexityAnalysis: [
|
|
|
|
{
|
|
|
|
taskId: 1,
|
|
|
|
taskTitle: 'Initialize Project',
|
|
|
|
complexityScore: 3,
|
|
|
|
recommendedSubtasks: 2
|
|
|
|
},
|
|
|
|
{
|
|
|
|
taskId: 2,
|
|
|
|
taskTitle: 'Create Core Functionality',
|
|
|
|
complexityScore: 8,
|
|
|
|
recommendedSubtasks: 5
|
|
|
|
},
|
|
|
|
{
|
|
|
|
taskId: 3,
|
|
|
|
taskTitle: 'Implement UI Components',
|
|
|
|
complexityScore: 6,
|
|
|
|
recommendedSubtasks: 4
|
|
|
|
}
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
2025-04-09 00:25:27 +02:00
|
|
|
// Test wrapper function that doesn't rely on the actual implementation
|
|
|
|
async function testListTasks(args, mockLogger) {
|
|
|
|
// File not found case
|
|
|
|
if (args.file === 'non-existent-file.json') {
|
|
|
|
mockLogger.error('Tasks file not found');
|
|
|
|
return {
|
|
|
|
success: false,
|
|
|
|
error: {
|
|
|
|
code: 'FILE_NOT_FOUND_ERROR',
|
|
|
|
message: 'Tasks file not found'
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-05-16 23:24:25 +02:00
|
|
|
// Check for complexity report
|
|
|
|
const complexityReport = mockReadComplexityReport();
|
|
|
|
let tasksData = [...sampleTasks.tasks];
|
|
|
|
|
|
|
|
// Add complexity scores if report exists
|
|
|
|
if (complexityReport && complexityReport.complexityAnalysis) {
|
|
|
|
tasksData = tasksData.map((task) => {
|
|
|
|
const analysis = complexityReport.complexityAnalysis.find(
|
|
|
|
(a) => a.taskId === task.id
|
|
|
|
);
|
|
|
|
if (analysis) {
|
|
|
|
return { ...task, complexityScore: analysis.complexityScore };
|
|
|
|
}
|
|
|
|
return task;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-04-09 00:25:27 +02:00
|
|
|
// Success case
|
|
|
|
if (!args.status && !args.withSubtasks) {
|
|
|
|
return {
|
|
|
|
success: true,
|
|
|
|
data: {
|
2025-05-16 23:24:25 +02:00
|
|
|
tasks: tasksData,
|
2025-04-09 00:25:27 +02:00
|
|
|
stats: {
|
2025-05-16 23:24:25 +02:00
|
|
|
total: tasksData.length,
|
|
|
|
completed: tasksData.filter((t) => t.status === 'done').length,
|
|
|
|
inProgress: tasksData.filter((t) => t.status === 'in-progress')
|
2025-04-09 00:25:27 +02:00
|
|
|
.length,
|
2025-05-16 23:24:25 +02:00
|
|
|
pending: tasksData.filter((t) => t.status === 'pending').length
|
2025-04-09 00:25:27 +02:00
|
|
|
}
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Status filter case
|
|
|
|
if (args.status) {
|
2025-05-16 23:24:25 +02:00
|
|
|
const filteredTasks = tasksData.filter((t) => t.status === args.status);
|
2025-04-09 00:25:27 +02:00
|
|
|
return {
|
|
|
|
success: true,
|
|
|
|
data: {
|
|
|
|
tasks: filteredTasks,
|
|
|
|
filter: args.status,
|
|
|
|
stats: {
|
2025-05-16 23:24:25 +02:00
|
|
|
total: tasksData.length,
|
2025-04-09 00:25:27 +02:00
|
|
|
filtered: filteredTasks.length
|
|
|
|
}
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Include subtasks case
|
|
|
|
if (args.withSubtasks) {
|
|
|
|
return {
|
|
|
|
success: true,
|
|
|
|
data: {
|
2025-05-16 23:24:25 +02:00
|
|
|
tasks: tasksData,
|
2025-04-09 00:25:27 +02:00
|
|
|
includeSubtasks: true,
|
|
|
|
stats: {
|
2025-05-16 23:24:25 +02:00
|
|
|
total: tasksData.length
|
2025-04-09 00:25:27 +02:00
|
|
|
}
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default case
|
|
|
|
return {
|
|
|
|
success: true,
|
|
|
|
data: { tasks: [] }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
test('should return all tasks when no filter is provided', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testListTasks(args, mockLogger);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.tasks.length).toBe(sampleTasks.tasks.length);
|
|
|
|
expect(result.data.stats.total).toBe(sampleTasks.tasks.length);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should filter tasks by status', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
status: 'pending'
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testListTasks(args, mockLogger);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.filter).toBe('pending');
|
|
|
|
// Should only include pending tasks
|
|
|
|
result.data.tasks.forEach((task) => {
|
|
|
|
expect(task.status).toBe('pending');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should include subtasks when requested', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
withSubtasks: true
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testListTasks(args, mockLogger);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.includeSubtasks).toBe(true);
|
|
|
|
|
|
|
|
// Verify subtasks are included for tasks that have them
|
|
|
|
const tasksWithSubtasks = result.data.tasks.filter(
|
|
|
|
(t) => t.subtasks && t.subtasks.length > 0
|
|
|
|
);
|
|
|
|
expect(tasksWithSubtasks.length).toBeGreaterThan(0);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should handle file not found errors', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: 'non-existent-file.json'
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testListTasks(args, mockLogger);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(false);
|
|
|
|
expect(result.error.code).toBe('FILE_NOT_FOUND_ERROR');
|
|
|
|
expect(mockLogger.error).toHaveBeenCalled();
|
|
|
|
});
|
2025-05-16 23:24:25 +02:00
|
|
|
|
|
|
|
test('should include complexity scores when complexity report exists', async () => {
|
|
|
|
// Arrange
|
|
|
|
mockReadComplexityReport.mockReturnValueOnce(mockComplexityReport);
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
withSubtasks: true
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testListTasks(args, mockLogger);
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
|
|
|
|
// Check that tasks have complexity scores from the report
|
|
|
|
mockComplexityReport.complexityAnalysis.forEach((analysis) => {
|
|
|
|
const task = result.data.tasks.find((t) => t.id === analysis.taskId);
|
|
|
|
if (task) {
|
|
|
|
expect(task.complexityScore).toBe(analysis.complexityScore);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2025-04-09 00:25:27 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('expandTaskDirect', () => {
|
|
|
|
// Test wrapper function that returns appropriate results based on the test case
|
|
|
|
async function testExpandTask(args, mockLogger, options = {}) {
|
|
|
|
// Missing task ID case
|
|
|
|
if (!args.id) {
|
|
|
|
mockLogger.error('Task ID is required');
|
|
|
|
return {
|
|
|
|
success: false,
|
|
|
|
error: {
|
|
|
|
code: 'INPUT_VALIDATION_ERROR',
|
|
|
|
message: 'Task ID is required'
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Non-existent task ID case
|
|
|
|
if (args.id === '999') {
|
|
|
|
mockLogger.error(`Task with ID ${args.id} not found`);
|
|
|
|
return {
|
|
|
|
success: false,
|
|
|
|
error: {
|
|
|
|
code: 'TASK_NOT_FOUND',
|
|
|
|
message: `Task with ID ${args.id} not found`
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Completed task case
|
|
|
|
if (args.id === '1') {
|
|
|
|
mockLogger.error(
|
|
|
|
`Task ${args.id} is already marked as done and cannot be expanded`
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
success: false,
|
|
|
|
error: {
|
|
|
|
code: 'TASK_COMPLETED',
|
|
|
|
message: `Task ${args.id} is already marked as done and cannot be expanded`
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// For successful cases, record that functions were called but don't make real calls
|
|
|
|
mockEnableSilentMode();
|
|
|
|
|
|
|
|
// This is just a mock call that won't make real API requests
|
|
|
|
// We're using mockExpandTask which is already a mock function
|
|
|
|
const expandedTask = await mockExpandTask(
|
|
|
|
parseInt(args.id, 10),
|
|
|
|
args.num,
|
|
|
|
args.research || false,
|
|
|
|
args.prompt || '',
|
|
|
|
{ mcpLog: mockLogger, session: options.session }
|
|
|
|
);
|
|
|
|
|
|
|
|
mockDisableSilentMode();
|
|
|
|
|
|
|
|
return {
|
|
|
|
success: true,
|
|
|
|
data: {
|
|
|
|
task: expandedTask,
|
|
|
|
subtasksAdded: expandedTask.subtasks.length,
|
|
|
|
hasExistingSubtasks: false
|
2025-06-03 01:09:29 +02:00
|
|
|
}
|
2025-04-09 00:25:27 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
test('should expand a task with subtasks', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
id: '3', // ID 3 exists in sampleTasks with status 'pending'
|
|
|
|
num: 2
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandTask(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.task).toBeDefined();
|
|
|
|
expect(result.data.task.subtasks).toBeDefined();
|
|
|
|
expect(result.data.task.subtasks.length).toBe(2);
|
|
|
|
expect(mockExpandTask).toHaveBeenCalledWith(
|
|
|
|
3, // Task ID as number
|
|
|
|
2, // num parameter
|
|
|
|
false, // useResearch
|
|
|
|
'', // prompt
|
|
|
|
expect.objectContaining({
|
|
|
|
mcpLog: mockLogger,
|
|
|
|
session: mockSession
|
|
|
|
})
|
|
|
|
);
|
|
|
|
expect(mockEnableSilentMode).toHaveBeenCalled();
|
|
|
|
expect(mockDisableSilentMode).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should handle missing task ID', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath
|
|
|
|
// id is intentionally missing
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandTask(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(false);
|
|
|
|
expect(result.error.code).toBe('INPUT_VALIDATION_ERROR');
|
|
|
|
expect(mockLogger.error).toHaveBeenCalled();
|
|
|
|
// Make sure no real expand calls were made
|
|
|
|
expect(mockExpandTask).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should handle non-existent task ID', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
id: '999' // Non-existent task ID
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandTask(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(false);
|
|
|
|
expect(result.error.code).toBe('TASK_NOT_FOUND');
|
|
|
|
expect(mockLogger.error).toHaveBeenCalled();
|
|
|
|
// Make sure no real expand calls were made
|
|
|
|
expect(mockExpandTask).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should handle completed tasks', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
id: '1' // Task with 'done' status in sampleTasks
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandTask(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(false);
|
|
|
|
expect(result.error.code).toBe('TASK_COMPLETED');
|
|
|
|
expect(mockLogger.error).toHaveBeenCalled();
|
|
|
|
// Make sure no real expand calls were made
|
|
|
|
expect(mockExpandTask).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should use AI client when research flag is set', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
id: '3',
|
|
|
|
research: true
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandTask(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(mockExpandTask).toHaveBeenCalledWith(
|
|
|
|
3, // Task ID as number
|
|
|
|
undefined, // args.num is undefined
|
|
|
|
true, // useResearch should be true
|
|
|
|
'', // prompt
|
|
|
|
expect.objectContaining({
|
|
|
|
mcpLog: mockLogger,
|
|
|
|
session: mockSession
|
|
|
|
})
|
|
|
|
);
|
|
|
|
// Verify the result includes research-backed subtasks
|
|
|
|
expect(result.data.task.subtasks[0].title).toContain('Research-Backed');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('expandAllTasksDirect', () => {
|
|
|
|
// Test wrapper function that returns appropriate results based on the test case
|
|
|
|
async function testExpandAllTasks(args, mockLogger, options = {}) {
|
|
|
|
// For successful cases, record that functions were called but don't make real calls
|
|
|
|
mockEnableSilentMode();
|
|
|
|
|
|
|
|
// Mock expandAllTasks
|
|
|
|
const mockExpandAll = jest.fn().mockImplementation(async () => {
|
|
|
|
// Just simulate success without any real operations
|
|
|
|
return undefined; // expandAllTasks doesn't return anything
|
|
|
|
});
|
|
|
|
|
|
|
|
// Call mock expandAllTasks
|
|
|
|
await mockExpandAll(
|
|
|
|
args.num,
|
|
|
|
args.research || false,
|
|
|
|
args.prompt || '',
|
|
|
|
args.force || false,
|
|
|
|
{ mcpLog: mockLogger, session: options.session }
|
|
|
|
);
|
|
|
|
|
|
|
|
mockDisableSilentMode();
|
|
|
|
|
|
|
|
return {
|
|
|
|
success: true,
|
|
|
|
data: {
|
|
|
|
message: 'Successfully expanded all pending tasks with subtasks',
|
|
|
|
details: {
|
|
|
|
numSubtasks: args.num,
|
|
|
|
research: args.research || false,
|
|
|
|
prompt: args.prompt || '',
|
|
|
|
force: args.force || false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
test('should expand all pending tasks with subtasks', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
num: 3
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandAllTasks(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.message).toBe(
|
|
|
|
'Successfully expanded all pending tasks with subtasks'
|
|
|
|
);
|
|
|
|
expect(result.data.details.numSubtasks).toBe(3);
|
|
|
|
expect(mockEnableSilentMode).toHaveBeenCalled();
|
|
|
|
expect(mockDisableSilentMode).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should handle research flag', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
research: true,
|
|
|
|
num: 2
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandAllTasks(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.details.research).toBe(true);
|
|
|
|
expect(mockEnableSilentMode).toHaveBeenCalled();
|
|
|
|
expect(mockDisableSilentMode).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should handle force flag', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
force: true
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandAllTasks(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.details.force).toBe(true);
|
|
|
|
expect(mockEnableSilentMode).toHaveBeenCalled();
|
|
|
|
expect(mockDisableSilentMode).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should handle additional context/prompt', async () => {
|
|
|
|
// Arrange
|
|
|
|
const args = {
|
|
|
|
projectRoot: testProjectRoot,
|
|
|
|
file: testTasksPath,
|
|
|
|
prompt: 'Additional context for subtasks'
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await testExpandAllTasks(args, mockLogger, {
|
|
|
|
session: mockSession
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
expect(result.data.details.prompt).toBe(
|
|
|
|
'Additional context for subtasks'
|
|
|
|
);
|
|
|
|
expect(mockEnableSilentMode).toHaveBeenCalled();
|
|
|
|
expect(mockDisableSilentMode).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|