Compare commits
21 Commits
task-maste
...
docs/auto-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
501a2bf5f1 | ||
|
|
0079b7defd | ||
|
|
0b2c6967c4 | ||
|
|
c0682ac795 | ||
|
|
01a7faea8f | ||
|
|
814265cd33 | ||
|
|
9b7b2ca7b2 | ||
|
|
949f091179 | ||
|
|
32c2b03c23 | ||
|
|
3bfd999d81 | ||
|
|
9fa79eb026 | ||
|
|
875134247a | ||
|
|
c2fc61ddb3 | ||
|
|
aaacc3dae3 | ||
|
|
46cd5dc186 | ||
|
|
49a31be416 | ||
|
|
2b69936ee7 | ||
|
|
b5fe723f8e | ||
|
|
d67b81d25d | ||
|
|
66c05053c0 | ||
|
|
d7ab4609aa |
5
.changeset/chore-fix-docs.md
Normal file
5
.changeset/chore-fix-docs.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Improve `analyze-complexity` cli docs and `--research` flag documentation
|
||||
7
.changeset/cursor-slash-commands.md
Normal file
7
.changeset/cursor-slash-commands.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add Cursor IDE custom slash command support
|
||||
|
||||
Expose Task Master commands as Cursor slash commands by copying assets/claude/commands to .cursor/commands on profile add and cleaning up on remove.
|
||||
5
.changeset/curvy-weeks-flow.md
Normal file
5
.changeset/curvy-weeks-flow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Change parent task back to "pending" when all subtasks are in "pending" state
|
||||
13
.changeset/mcp-timeout-configuration.md
Normal file
13
.changeset/mcp-timeout-configuration.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Enhanced Roo Code profile with MCP timeout configuration for improved reliability during long-running AI operations. The Roo profile now automatically configures a 300-second timeout for MCP server operations, preventing timeouts during complex tasks like `parse-prd`, `expand-all`, `analyze-complexity`, and `research` operations. This change also replaces static MCP configuration files with programmatic generation for better maintainability.
|
||||
|
||||
**What's New:**
|
||||
- 300-second timeout for MCP operations (up from default 60 seconds)
|
||||
- Programmatic MCP configuration generation (replaces static asset files)
|
||||
- Enhanced reliability for AI-powered operations
|
||||
- Consistent with other AI coding assistant profiles
|
||||
|
||||
**Migration:** No user action required - existing Roo Code installations will automatically receive the enhanced MCP configuration on next initialization.
|
||||
5
.changeset/petite-ideas-grab.md
Normal file
5
.changeset/petite-ideas-grab.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix Claude Code settings validation for pathToClaudeCodeExecutable
|
||||
5
.changeset/silly-pandas-find.md
Normal file
5
.changeset/silly-pandas-find.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix sonar deep research model failing, should be called `sonar-deep-research`
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -6,9 +6,6 @@ on:
|
||||
- main
|
||||
- next
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.27.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1254](https://github.com/eyaltoledano/claude-task-master/pull/1254) [`af53525`](https://github.com/eyaltoledano/claude-task-master/commit/af53525cbc660a595b67d4bb90d906911c71f45d) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fixed issue where `tm show` command could not find subtasks using dotted notation IDs (e.g., '8.1').
|
||||
- The command now properly searches within parent task subtasks and returns the correct subtask information.
|
||||
|
||||
## 0.27.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
16
README.md
16
README.md
@@ -60,6 +60,19 @@ The following documentation is also available in the `docs` directory:
|
||||
|
||||
> **Note:** After clicking the link, you'll still need to add your API keys to the configuration. The link installs the MCP server with placeholder keys that you'll need to replace with your actual API keys.
|
||||
|
||||
#### Claude Code Quick Install
|
||||
|
||||
For Claude Code users:
|
||||
|
||||
```bash
|
||||
claude mcp add taskmaster-ai -- npx -y task-master-ai
|
||||
```
|
||||
|
||||
Don't forget to add your API keys to the configuration:
|
||||
- in the root .env of your Project
|
||||
- in the "env" section of your mcp config for taskmaster-ai
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
Taskmaster utilizes AI across several commands, and those require a separate API key. You can use a variety of models from different AI providers provided you add your API keys. For example, if you want to use Claude 3.7, you'll need an Anthropic API key.
|
||||
@@ -92,10 +105,11 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
||||
| | Project | `<project_folder>/.cursor/mcp.json` | `<project_folder>\.cursor\mcp.json` | `mcpServers` |
|
||||
| **Windsurf** | Global | `~/.codeium/windsurf/mcp_config.json` | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` | `mcpServers` |
|
||||
| **VS Code** | Project | `<project_folder>/.vscode/mcp.json` | `<project_folder>\.vscode\mcp.json` | `servers` |
|
||||
| **Q CLI** | Global | `~/.aws/amazonq/mcp.json` | | `mcpServers` |
|
||||
|
||||
##### Manual Configuration
|
||||
|
||||
###### Cursor & Windsurf (`mcpServers`)
|
||||
###### Cursor & Windsurf & Q Developer CLI (`mcpServers`)
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
# Task Master Documentation
|
||||
|
||||
Welcome to the Task Master documentation. Use the links below to navigate to the information you need:
|
||||
Welcome to the Task Master documentation. This documentation site provides comprehensive guides for getting started with Task Master.
|
||||
|
||||
## Getting Started
|
||||
|
||||
- [Configuration Guide](archive/configuration.md) - Set up environment variables and customize Task Master
|
||||
- [Tutorial](archive/ctutorial.md) - Step-by-step guide to getting started with Task Master
|
||||
- [Quick Start Guide](/getting-started/quick-start) - Complete setup and first-time usage guide
|
||||
- [Requirements](/getting-started/quick-start/requirements) - What you need to get started
|
||||
- [Installation](/getting-started/quick-start/installation) - How to install Task Master
|
||||
|
||||
## Reference
|
||||
## Core Capabilities
|
||||
|
||||
- [Command Reference](archive/ccommand-reference.md) - Complete list of all available commands
|
||||
- [Task Structure](archive/ctask-structure.md) - Understanding the task format and features
|
||||
- [MCP Tools](/capabilities/mcp) - Model Control Protocol integration
|
||||
- [CLI Commands](/capabilities/cli-root-commands) - Command line interface reference
|
||||
- [Task Structure](/capabilities/task-structure) - Understanding tasks and subtasks
|
||||
|
||||
## Examples & Licensing
|
||||
## Best Practices
|
||||
|
||||
- [Example Interactions](archive/cexamples.md) - Common Cursor AI interaction examples
|
||||
- [Licensing Information](archive/clicensing.md) - Detailed information about the license
|
||||
- [Advanced Configuration](/best-practices/configuration-advanced) - Detailed configuration options
|
||||
- [Advanced Tasks](/best-practices/advanced-tasks) - Working with complex task structures
|
||||
|
||||
## Need More Help?
|
||||
|
||||
If you can't find what you're looking for in these docs, please check the [main README](../README.md) or visit our [GitHub repository](https://github.com/eyaltoledano/claude-task-master).
|
||||
If you can't find what you're looking for in these docs, please check the root README.md or visit our [GitHub repository](https://github.com/eyaltoledano/claude-task-master).
|
||||
|
||||
@@ -93,3 +93,57 @@ task-master generate
|
||||
```
|
||||
|
||||
This creates individual task files in the `tasks/` directory (e.g., `task_001.txt`, `task_002.txt`), making it easier to reference specific tasks.
|
||||
|
||||
## Custom Slash Commands
|
||||
|
||||
<Check>
|
||||
When you initialize Task Master with Cursor (`task-master init --cursor`), it automatically sets up custom slash commands for enhanced productivity.
|
||||
</Check>
|
||||
|
||||
Task Master automatically copies its complete set of slash commands to `.cursor/commands/` during project initialization. This provides you with convenient shortcuts for all Task Master operations directly within Cursor's interface.
|
||||
|
||||
### Available Slash Commands
|
||||
|
||||
The following slash commands become available in Cursor after initialization:
|
||||
|
||||
<Accordion title="Core Workflow Commands">
|
||||
- `/tm-main` - Main Task Master help and overview
|
||||
- `/tm-next-task` - Get the next available task to work on
|
||||
- `/tm-show-task` - View detailed information about a specific task
|
||||
- `/tm-list-tasks` - Display all tasks with various filtering options
|
||||
- `/tm-project-status` - View overall project status
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Task Management Commands">
|
||||
- `/tm-add-task` - Add a new task to your project
|
||||
- `/tm-update-task` - Update an existing task
|
||||
- `/tm-set-status-*` - Change task status (to-pending, to-in-progress, to-done, etc.)
|
||||
- `/tm-remove-task` - Remove a task from your project
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Analysis and Planning Commands">
|
||||
- `/tm-parse-prd` - Generate tasks from a PRD document
|
||||
- `/tm-analyze-complexity` - Analyze task complexity
|
||||
- `/tm-complexity-report` - View complexity analysis report
|
||||
- `/tm-expand-task` - Break down tasks into subtasks
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Advanced Workflow Commands">
|
||||
- `/tm-smart-workflow` - Intelligent task workflow management
|
||||
- `/tm-auto-implement-tasks` - Automated task implementation pipeline
|
||||
- `/tm-command-pipeline` - Execute multiple Task Master commands in sequence
|
||||
</Accordion>
|
||||
|
||||
### Using Slash Commands
|
||||
|
||||
Simply type the command name in Cursor's chat interface, and the AI will execute the corresponding Task Master operation:
|
||||
|
||||
```
|
||||
/tm-next-task
|
||||
```
|
||||
|
||||
The AI will understand the context and execute the appropriate `task-master` CLI command, providing you with the results directly in the chat.
|
||||
|
||||
### Command Cleanup
|
||||
|
||||
If you remove the Cursor profile from your project using `task-master remove --cursor`, the slash commands directory is automatically cleaned up to keep your project tidy.
|
||||
|
||||
@@ -156,7 +156,7 @@ sidebarTitle: "CLI Commands"
|
||||
# Use an alternative tasks file
|
||||
task-master analyze-complexity --file=custom-tasks.json
|
||||
|
||||
# Use Perplexity AI for research-backed complexity analysis
|
||||
# Use your configured research model for research-backed complexity analysis
|
||||
task-master analyze-complexity --research
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
@@ -108,5 +108,5 @@ You don’t need to configure everything up front. Most settings can be left as
|
||||
</Accordion>
|
||||
|
||||
<Note>
|
||||
For advanced configuration options and detailed customization, see our [Advanced Configuration Guide](/docs/best-practices/configuration-advanced) page.
|
||||
For advanced configuration options and detailed customization, see our [Advanced Configuration Guide](/best-practices/configuration-advanced) page.
|
||||
</Note>
|
||||
@@ -56,4 +56,4 @@ If you ran into problems and had to debug errors you can create new rules as you
|
||||
|
||||
By now you have all you need to get started executing code faster and smarter with Task Master.
|
||||
|
||||
If you have any questions please check out [Frequently Asked Questions](/docs/getting-started/faq)
|
||||
If you have any questions please check out [Frequently Asked Questions](/getting-started/faq)
|
||||
|
||||
@@ -30,6 +30,19 @@ cursor://anysphere.cursor-deeplink/mcp/install?name=taskmaster-ai&config=eyJjb21
|
||||
```
|
||||
|
||||
> **Note:** After clicking the link, you'll still need to add your API keys to the configuration. The link installs the MCP server with placeholder keys that you'll need to replace with your actual API keys.
|
||||
|
||||
### Claude Code Quick Install
|
||||
|
||||
For Claude Code users:
|
||||
|
||||
```bash
|
||||
claude mcp add taskmaster-ai -- npx -y task-master-ai
|
||||
```
|
||||
|
||||
Don't forget to add your API keys to the configuration:
|
||||
- in the root .env of your Project
|
||||
- in the "env" section of your mcp config for taskmaster-ai
|
||||
|
||||
</Accordion>
|
||||
## Installation Options
|
||||
|
||||
@@ -153,7 +166,14 @@ npx task-master init
|
||||
|
||||
# Initialize project with specific rules
|
||||
task-master init --rules cursor,windsurf,vscode
|
||||
|
||||
# Initialize project for Cursor (sets up rules and custom slash commands)
|
||||
task-master init --cursor
|
||||
```
|
||||
|
||||
This will prompt you for project details and set up a new project with the necessary files and structure.
|
||||
|
||||
<Note>
|
||||
When using the `--cursor` flag, Task Master automatically copies custom slash commands to `.cursor/commands/` for enhanced Cursor IDE integration. See the [Cursor Setup Guide](/archive/cursor-setup) for more details about these commands.
|
||||
</Note>
|
||||
</Accordion>
|
||||
@@ -6,13 +6,13 @@ sidebarTitle: "Quick Start"
|
||||
This guide is for new users who want to start using Task Master with minimal setup time.
|
||||
|
||||
It covers:
|
||||
- [Requirements](/docs/getting-started/quick-start/requirements): You will need Node.js and an AI model API Key.
|
||||
- [Installation](/docs/getting-started/quick-start/installation): How to Install Task Master.
|
||||
- [Configuration](/docs/getting-started/quick-start/configuration-quick): Setting up your API Key, MCP, and more.
|
||||
- [PRD](/docs/getting-started/quick-start/prd-quick): Writing and parsing your first PRD.
|
||||
- [Task Setup](/docs/getting-started/quick-start/tasks-quick): Preparing your tasks for execution.
|
||||
- [Executing Tasks](/docs/getting-started/quick-start/execute-quick): Using Task Master to execute tasks.
|
||||
- [Rules & Context](/docs/getting-started/quick-start/rules-quick): Learn how and why to build context in your project over time.
|
||||
- [Requirements](/getting-started/quick-start/requirements): You will need Node.js and an AI model API Key.
|
||||
- [Installation](/getting-started/quick-start/installation): How to Install Task Master.
|
||||
- [Configuration](/getting-started/quick-start/configuration-quick): Setting up your API Key, MCP, and more.
|
||||
- [PRD](/getting-started/quick-start/prd-quick): Writing and parsing your first PRD.
|
||||
- [Task Setup](/getting-started/quick-start/tasks-quick): Preparing your tasks for execution.
|
||||
- [Executing Tasks](/getting-started/quick-start/execute-quick): Using Task Master to execute tasks.
|
||||
- [Rules & Context](/getting-started/quick-start/rules-quick): Learn how and why to build context in your project over time.
|
||||
|
||||
<Tip>
|
||||
By the end of this guide, you'll have everything you need to begin working productively with Task Master.
|
||||
|
||||
@@ -61,9 +61,25 @@ Task Master can provide a complexity report which can be helpful to read before
|
||||
Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further?
|
||||
```
|
||||
|
||||
The agent will use the `analyze_project_complexity` MCP tool, or you can run it directly with the CLI command:
|
||||
```bash
|
||||
task-master analyze-complexity
|
||||
```
|
||||
|
||||
For more comprehensive analysis using your configured research model, you can use:
|
||||
```bash
|
||||
task-master analyze-complexity --research
|
||||
```
|
||||
|
||||
<Tip>
|
||||
The `--research` flag uses whatever research model you have configured in `.taskmaster/config.json` (configurable via `task-master models --setup`) for research-backed complexity analysis, providing more informed recommendations.
|
||||
</Tip>
|
||||
|
||||
You can view the report in a friendly table using:
|
||||
```
|
||||
Can you show me the complexity report in a more readable format?
|
||||
```
|
||||
|
||||
<Check>Now you are ready to begin [executing tasks](/docs/getting-started/quick-start/execute-quick)</Check>
|
||||
For more detailed CLI options, see the [Analyze Task Complexity](/capabilities/cli-root-commands#analyze-task-complexity) section.
|
||||
|
||||
<Check>Now you are ready to begin [executing tasks](/getting-started/quick-start/execute-quick)</Check>
|
||||
@@ -4,7 +4,7 @@ Welcome to v1 of the Task Master Docs. Expect weekly updates as we expand and re
|
||||
|
||||
We've organized the docs into three sections depending on your experience level and goals:
|
||||
|
||||
### Getting Started - Jump in to [Quick Start](/docs/getting-started/quick-start)
|
||||
### Getting Started - Jump in to [Quick Start](/getting-started/quick-start)
|
||||
Designed for first-time users. Get set up, create your first PRD, and run your first task.
|
||||
|
||||
### Best Practices
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 0.25.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`af53525`](https://github.com/eyaltoledano/claude-task-master/commit/af53525cbc660a595b67d4bb90d906911c71f45d)]:
|
||||
- task-master-ai@0.27.3
|
||||
|
||||
## 0.25.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"displayName": "TaskMaster",
|
||||
"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
|
||||
"version": "0.25.4",
|
||||
"version": "0.25.3",
|
||||
"publisher": "Hamster",
|
||||
"icon": "assets/icon.png",
|
||||
"engines": {
|
||||
@@ -240,7 +240,7 @@
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"task-master-ai": "0.27.3"
|
||||
"task-master-ai": "0.27.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
||||
@@ -235,6 +235,60 @@ node scripts/init.js
|
||||
- "MCP provider requires session context" → Ensure running in MCP environment
|
||||
- See the [MCP Provider Guide](./mcp-provider-guide.md) for detailed troubleshooting
|
||||
|
||||
### MCP Timeout Configuration
|
||||
|
||||
Long-running AI operations in taskmaster-ai can exceed the default 60-second MCP timeout. Operations like `parse_prd`, `expand_task`, `research`, and `analyze_project_complexity` may take 2-5 minutes to complete.
|
||||
|
||||
#### Adding Timeout Configuration
|
||||
|
||||
Add a `timeout` parameter to your MCP configuration to extend the timeout limit. The timeout configuration works identically across MCP clients including Cursor, Windsurf, and RooCode:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
||||
"timeout": 300,
|
||||
"env": {
|
||||
"ANTHROPIC_API_KEY": "your-anthropic-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Configuration Details:**
|
||||
- **`timeout: 300`** - Sets timeout to 300 seconds (5 minutes)
|
||||
- **Value range**: 1-3600 seconds (1 second to 1 hour)
|
||||
- **Recommended**: 300 seconds provides sufficient time for most AI operations
|
||||
- **Format**: Integer value in seconds (not milliseconds)
|
||||
|
||||
#### Automatic Setup
|
||||
|
||||
When adding taskmaster rules for supported editors, the timeout configuration is automatically included:
|
||||
|
||||
```bash
|
||||
# Automatically includes timeout configuration
|
||||
task-master rules add cursor
|
||||
task-master rules add roo
|
||||
task-master rules add windsurf
|
||||
task-master rules add vscode
|
||||
```
|
||||
|
||||
#### Troubleshooting Timeouts
|
||||
|
||||
If you're still experiencing timeout errors:
|
||||
|
||||
1. **Verify configuration**: Check that `timeout: 300` is present in your MCP config
|
||||
2. **Restart editor**: Restart your editor after making configuration changes
|
||||
3. **Increase timeout**: For very complex operations, try `timeout: 600` (10 minutes)
|
||||
4. **Check API keys**: Ensure required API keys are properly configured
|
||||
|
||||
**Expected behavior:**
|
||||
- **Before fix**: Operations fail after 60 seconds with `MCP request timed out after 60000ms`
|
||||
- **After fix**: Operations complete successfully within the configured timeout limit
|
||||
|
||||
### Google Vertex AI Configuration
|
||||
|
||||
Google Vertex AI is Google Cloud's enterprise AI platform and requires specific configuration:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Available Models as of September 19, 2025
|
||||
# Available Models as of September 23, 2025
|
||||
|
||||
## Main Models
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
| groq | deepseek-r1-distill-llama-70b | 0.52 | 0.75 | 0.99 |
|
||||
| perplexity | sonar-pro | — | 3 | 15 |
|
||||
| perplexity | sonar | — | 1 | 1 |
|
||||
| perplexity | deep-research | 0.211 | 2 | 8 |
|
||||
| perplexity | sonar-deep-research | 0.211 | 2 | 8 |
|
||||
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
|
||||
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
|
||||
| bedrock | us.anthropic.claude-3-opus-20240229-v1:0 | 0.725 | 15 | 75 |
|
||||
|
||||
76
output.txt
Normal file
76
output.txt
Normal file
File diff suppressed because one or more lines are too long
8
package-lock.json
generated
8
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.27.3",
|
||||
"version": "0.27.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "task-master-ai",
|
||||
"version": "0.27.3",
|
||||
"version": "0.27.2",
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
@@ -357,9 +357,9 @@
|
||||
}
|
||||
},
|
||||
"apps/extension": {
|
||||
"version": "0.25.4",
|
||||
"version": "0.25.3",
|
||||
"dependencies": {
|
||||
"task-master-ai": "0.27.3"
|
||||
"task-master-ai": "0.27.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.27.3",
|
||||
"version": "0.27.2",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -135,28 +135,15 @@ export class TaskService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single task by ID - delegates to storage layer
|
||||
* Get a single task by ID
|
||||
*/
|
||||
async getTask(taskId: string, tag?: string): Promise<Task | null> {
|
||||
// Use provided tag or get active tag
|
||||
const activeTag = tag || this.getActiveTag();
|
||||
const result = await this.getTaskList({
|
||||
tag,
|
||||
includeSubtasks: true
|
||||
});
|
||||
|
||||
try {
|
||||
// Delegate to storage layer which handles the specific logic for tasks vs subtasks
|
||||
return await this.storage.loadTask(String(taskId), activeTag);
|
||||
} catch (error) {
|
||||
throw new TaskMasterError(
|
||||
`Failed to get task ${taskId}`,
|
||||
ERROR_CODES.STORAGE_ERROR,
|
||||
{
|
||||
operation: 'getTask',
|
||||
resource: 'task',
|
||||
taskId: String(taskId),
|
||||
tag: activeTag
|
||||
},
|
||||
error as Error
|
||||
);
|
||||
}
|
||||
return result.tasks.find((t) => t.id === taskId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -105,65 +105,9 @@ export class FileStorage implements IStorage {
|
||||
|
||||
/**
|
||||
* Load a single task by ID from the tasks.json file
|
||||
* Handles both regular tasks and subtasks (with dotted notation like "1.2")
|
||||
*/
|
||||
async loadTask(taskId: string, tag?: string): Promise<Task | null> {
|
||||
const tasks = await this.loadTasks(tag);
|
||||
|
||||
// Check if this is a subtask (contains a dot)
|
||||
if (taskId.includes('.')) {
|
||||
const [parentId, subtaskId] = taskId.split('.');
|
||||
const parentTask = tasks.find((t) => String(t.id) === parentId);
|
||||
|
||||
if (!parentTask || !parentTask.subtasks) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const subtask = parentTask.subtasks.find(
|
||||
(st) => String(st.id) === subtaskId
|
||||
);
|
||||
if (!subtask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const toFullSubId = (maybeDotId: string | number): string => {
|
||||
const depId = String(maybeDotId);
|
||||
return depId.includes('.') ? depId : `${parentTask.id}.${depId}`;
|
||||
};
|
||||
const resolvedDependencies =
|
||||
subtask.dependencies?.map((dep) => toFullSubId(dep)) ?? [];
|
||||
|
||||
// Return a Task-like object for the subtask with the full dotted ID
|
||||
// Following the same pattern as findTaskById in utils.js
|
||||
const subtaskResult = {
|
||||
...subtask,
|
||||
id: taskId, // Use the full dotted ID
|
||||
title: subtask.title || `Subtask ${subtaskId}`,
|
||||
description: subtask.description || '',
|
||||
status: subtask.status || 'pending',
|
||||
priority: subtask.priority || parentTask.priority || 'medium',
|
||||
dependencies: resolvedDependencies,
|
||||
details: subtask.details || '',
|
||||
testStrategy: subtask.testStrategy || '',
|
||||
subtasks: [],
|
||||
tags: parentTask.tags || [],
|
||||
assignee: subtask.assignee || parentTask.assignee,
|
||||
complexity: subtask.complexity || parentTask.complexity,
|
||||
createdAt: subtask.createdAt || parentTask.createdAt,
|
||||
updatedAt: subtask.updatedAt || parentTask.updatedAt,
|
||||
// Add reference to parent task for context (like utils.js does)
|
||||
parentTask: {
|
||||
id: parentTask.id,
|
||||
title: parentTask.title,
|
||||
status: parentTask.status
|
||||
},
|
||||
isSubtask: true
|
||||
};
|
||||
|
||||
return subtaskResult;
|
||||
}
|
||||
|
||||
// Handle regular task lookup
|
||||
return tasks.find((task) => String(task.id) === String(taskId)) || null;
|
||||
}
|
||||
|
||||
@@ -465,8 +409,11 @@ export class FileStorage implements IStorage {
|
||||
const allDone = subs.every(isDoneLike);
|
||||
const anyInProgress = subs.some((s) => norm(s) === 'in-progress');
|
||||
const anyDone = subs.some(isDoneLike);
|
||||
const allPending = subs.every((s) => norm(s) === 'pending');
|
||||
|
||||
if (allDone) parentNewStatus = 'done';
|
||||
else if (anyInProgress || anyDone) parentNewStatus = 'in-progress';
|
||||
else if (allPending) parentNewStatus = 'pending';
|
||||
}
|
||||
|
||||
// Always bump updatedAt; update status only if changed
|
||||
|
||||
@@ -1847,7 +1847,7 @@ function registerCommands(programInstance) {
|
||||
)
|
||||
.option(
|
||||
'-r, --research',
|
||||
'Use Perplexity AI for research-backed complexity analysis'
|
||||
'Use configured research model for research-backed complexity analysis'
|
||||
)
|
||||
.option(
|
||||
'-i, --id <ids>',
|
||||
|
||||
@@ -310,6 +310,7 @@ function validateProviderModelCombination(providerName, modelId) {
|
||||
function validateClaudeCodeSettings(settings) {
|
||||
// Define the base settings schema without commandSpecific first
|
||||
const BaseSettingsSchema = z.object({
|
||||
pathToClaudeCodeExecutable: z.string().optional(),
|
||||
maxTurns: z.number().int().positive().optional(),
|
||||
customSystemPrompt: z.string().optional(),
|
||||
appendSystemPrompt: z.string().optional(),
|
||||
|
||||
@@ -522,7 +522,7 @@
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "deep-research",
|
||||
"id": "sonar-deep-research",
|
||||
"swe_score": 0.211,
|
||||
"cost_per_1m_tokens": {
|
||||
"input": 2,
|
||||
|
||||
@@ -1,5 +1,134 @@
|
||||
// Cursor conversion profile for rule-transformer
|
||||
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { log } from '../../scripts/modules/utils.js';
|
||||
import { createProfile } from './base-profile.js';
|
||||
|
||||
// Helper copy; use cpSync when available, fallback to manual recursion
|
||||
function copyRecursiveSync(src, dest) {
|
||||
if (fs.cpSync) {
|
||||
try {
|
||||
fs.cpSync(src, dest, { recursive: true, force: true });
|
||||
return;
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to copy ${src} to ${dest}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
const exists = fs.existsSync(src);
|
||||
let stats = null;
|
||||
let isDirectory = false;
|
||||
|
||||
if (exists) {
|
||||
try {
|
||||
stats = fs.statSync(src);
|
||||
isDirectory = stats.isDirectory();
|
||||
} catch (err) {
|
||||
// Handle TOCTOU race condition - treat as non-existent/not-a-directory
|
||||
isDirectory = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDirectory) {
|
||||
try {
|
||||
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
||||
for (const child of fs.readdirSync(src)) {
|
||||
copyRecursiveSync(path.join(src, child), path.join(dest, child));
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Failed to copy directory ${src} to ${dest}: ${err.message}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// ensure parent exists for file copies
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fs.copyFileSync(src, dest);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to copy file ${src} to ${dest}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to recursively remove directory
|
||||
function removeDirectoryRecursive(dirPath) {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
try {
|
||||
fs.rmSync(dirPath, { recursive: true, force: true });
|
||||
return true;
|
||||
} catch (err) {
|
||||
log('error', `Failed to remove directory ${dirPath}: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Resolve the Cursor profile directory from either project root, profile root, or rules dir
|
||||
function resolveCursorProfileDir(baseDir) {
|
||||
const base = path.basename(baseDir);
|
||||
// If called with .../.cursor/rules -> return .../.cursor
|
||||
if (base === 'rules' && path.basename(path.dirname(baseDir)) === '.cursor') {
|
||||
return path.dirname(baseDir);
|
||||
}
|
||||
// If called with .../.cursor -> return as-is
|
||||
if (base === '.cursor') return baseDir;
|
||||
// Otherwise assume project root and append .cursor
|
||||
return path.join(baseDir, '.cursor');
|
||||
}
|
||||
|
||||
// Lifecycle functions for Cursor profile
|
||||
function onAddRulesProfile(targetDir, assetsDir) {
|
||||
// Copy commands directory recursively
|
||||
const commandsSourceDir = path.join(assetsDir, 'claude', 'commands');
|
||||
const profileDir = resolveCursorProfileDir(targetDir);
|
||||
const commandsDestDir = path.join(profileDir, 'commands');
|
||||
|
||||
if (!fs.existsSync(commandsSourceDir)) {
|
||||
log(
|
||||
'warn',
|
||||
`[Cursor] Source commands directory does not exist: ${commandsSourceDir}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure fresh state to avoid stale command files
|
||||
try {
|
||||
fs.rmSync(commandsDestDir, { recursive: true, force: true });
|
||||
log(
|
||||
'debug',
|
||||
`[Cursor] Removed existing commands directory: ${commandsDestDir}`
|
||||
);
|
||||
} catch (deleteErr) {
|
||||
// Directory might not exist, which is fine
|
||||
log(
|
||||
'debug',
|
||||
`[Cursor] Commands directory did not exist or could not be removed: ${deleteErr.message}`
|
||||
);
|
||||
}
|
||||
|
||||
copyRecursiveSync(commandsSourceDir, commandsDestDir);
|
||||
log('debug', `[Cursor] Copied commands directory to ${commandsDestDir}`);
|
||||
} catch (err) {
|
||||
log(
|
||||
'error',
|
||||
`[Cursor] An error occurred during commands copy: ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function onRemoveRulesProfile(targetDir) {
|
||||
// Remove .cursor/commands directory recursively
|
||||
const profileDir = resolveCursorProfileDir(targetDir);
|
||||
const commandsDir = path.join(profileDir, 'commands');
|
||||
if (removeDirectoryRecursive(commandsDir)) {
|
||||
log(
|
||||
'debug',
|
||||
`[Cursor] Ensured commands directory removed at ${commandsDir}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export cursor profile using the base factory
|
||||
export const cursorProfile = createProfile({
|
||||
@@ -8,5 +137,10 @@ export const cursorProfile = createProfile({
|
||||
url: 'cursor.so',
|
||||
docsUrl: 'docs.cursor.com',
|
||||
targetExtension: '.mdc', // Cursor keeps .mdc extension
|
||||
supportsRulesSubdirectories: true
|
||||
supportsRulesSubdirectories: true,
|
||||
onAdd: onAddRulesProfile,
|
||||
onRemove: onRemoveRulesProfile
|
||||
});
|
||||
|
||||
// Export lifecycle functions separately to avoid naming conflicts
|
||||
export { onAddRulesProfile, onRemoveRulesProfile };
|
||||
|
||||
@@ -5,6 +5,40 @@ import { isSilentMode, log } from '../../scripts/modules/utils.js';
|
||||
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
|
||||
import { ROO_MODES } from '../constants/profiles.js';
|
||||
|
||||
// Import the shared MCP configuration helper
|
||||
import { formatJSONWithTabs } from '../utils/create-mcp-config.js';
|
||||
|
||||
// Roo-specific MCP configuration enhancements
|
||||
function enhanceRooMCPConfiguration(mcpPath) {
|
||||
if (!fs.existsSync(mcpPath)) {
|
||||
log('warn', `[Roo] MCP configuration file not found at ${mcpPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Read the existing configuration
|
||||
const mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
||||
|
||||
if (mcpConfig.mcpServers && mcpConfig.mcpServers['task-master-ai']) {
|
||||
const server = mcpConfig.mcpServers['task-master-ai'];
|
||||
|
||||
// Add Roo-specific timeout enhancement for long-running AI operations
|
||||
server.timeout = 300;
|
||||
|
||||
// Write the enhanced configuration back
|
||||
fs.writeFileSync(mcpPath, formatJSONWithTabs(mcpConfig) + '\n');
|
||||
log(
|
||||
'debug',
|
||||
`[Roo] Enhanced MCP configuration with timeout at ${mcpPath}`
|
||||
);
|
||||
} else {
|
||||
log('warn', `[Roo] task-master-ai server not found in MCP configuration`);
|
||||
}
|
||||
} catch (error) {
|
||||
log('error', `[Roo] Failed to enhance MCP configuration: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle functions for Roo profile
|
||||
function onAddRulesProfile(targetDir, assetsDir) {
|
||||
// Use the provided assets directory to find the roocode directory
|
||||
@@ -32,6 +66,9 @@ function onAddRulesProfile(targetDir, assetsDir) {
|
||||
}
|
||||
}
|
||||
|
||||
// Note: MCP configuration is now handled by the base profile system
|
||||
// The base profile will call setupMCPConfiguration, and we enhance it in onPostConvert
|
||||
|
||||
for (const mode of ROO_MODES) {
|
||||
const src = path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`);
|
||||
const dest = path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`);
|
||||
@@ -78,6 +115,15 @@ function onRemoveRulesProfile(targetDir) {
|
||||
|
||||
const rooDir = path.join(targetDir, '.roo');
|
||||
if (fs.existsSync(rooDir)) {
|
||||
// Remove MCP configuration
|
||||
const mcpPath = path.join(rooDir, 'mcp.json');
|
||||
try {
|
||||
fs.rmSync(mcpPath, { force: true });
|
||||
log('debug', `[Roo] Removed MCP configuration from ${mcpPath}`);
|
||||
} catch (err) {
|
||||
log('error', `[Roo] Failed to remove MCP configuration: ${err.message}`);
|
||||
}
|
||||
|
||||
fs.readdirSync(rooDir).forEach((entry) => {
|
||||
if (entry.startsWith('rules-')) {
|
||||
const modeDir = path.join(rooDir, entry);
|
||||
@@ -101,7 +147,13 @@ function onRemoveRulesProfile(targetDir) {
|
||||
}
|
||||
|
||||
function onPostConvertRulesProfile(targetDir, assetsDir) {
|
||||
onAddRulesProfile(targetDir, assetsDir);
|
||||
// Enhance the MCP configuration with Roo-specific features after base setup
|
||||
const mcpPath = path.join(targetDir, '.roo', 'mcp.json');
|
||||
try {
|
||||
enhanceRooMCPConfiguration(mcpPath);
|
||||
} catch (err) {
|
||||
log('error', `[Roo] Failed to enhance MCP configuration: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export roo profile using the base factory
|
||||
@@ -111,6 +163,7 @@ export const rooProfile = createProfile({
|
||||
url: 'roocode.com',
|
||||
docsUrl: 'docs.roocode.com',
|
||||
toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE,
|
||||
mcpConfig: true, // Enable MCP config - we enhance it with Roo-specific features
|
||||
onAdd: onAddRulesProfile,
|
||||
onRemove: onRemoveRulesProfile,
|
||||
onPostConvert: onPostConvertRulesProfile
|
||||
|
||||
@@ -262,3 +262,6 @@ export function removeTaskMasterMCPConfiguration(projectRoot, mcpConfigPath) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Export the formatting function for use by other modules
|
||||
export { formatJSONWithTabs };
|
||||
|
||||
@@ -210,7 +210,7 @@ export function convertAllRulesToProfileRules(projectRoot, profile) {
|
||||
if (typeof profile.onAddRulesProfile === 'function') {
|
||||
try {
|
||||
const assetsDir = getAssetsDir();
|
||||
profile.onAddRulesProfile(projectRoot, assetsDir);
|
||||
profile.onAddRulesProfile(targetDir, assetsDir);
|
||||
log(
|
||||
'debug',
|
||||
`[Rule Transformer] Called onAddRulesProfile for ${profile.profileName}`
|
||||
@@ -305,7 +305,7 @@ export function convertAllRulesToProfileRules(projectRoot, profile) {
|
||||
if (typeof profile.onPostConvertRulesProfile === 'function') {
|
||||
try {
|
||||
const assetsDir = getAssetsDir();
|
||||
profile.onPostConvertRulesProfile(projectRoot, assetsDir);
|
||||
profile.onPostConvertRulesProfile(targetDir, assetsDir);
|
||||
log(
|
||||
'debug',
|
||||
`[Rule Transformer] Called onPostConvertRulesProfile for ${profile.profileName}`
|
||||
@@ -347,7 +347,7 @@ export function removeProfileRules(projectRoot, profile) {
|
||||
// 1. Call onRemoveRulesProfile first (for custom cleanup like removing assets)
|
||||
if (typeof profile.onRemoveRulesProfile === 'function') {
|
||||
try {
|
||||
profile.onRemoveRulesProfile(projectRoot);
|
||||
profile.onRemoveRulesProfile(targetDir);
|
||||
log(
|
||||
'debug',
|
||||
`[Rule Transformer] Called onRemoveRulesProfile for ${profile.profileName}`
|
||||
|
||||
@@ -54,4 +54,33 @@ describe('Cursor Profile Initialization Functionality', () => {
|
||||
);
|
||||
expect(cursorProfile.conversionConfig.toolNames.search).toBe('search');
|
||||
});
|
||||
|
||||
test('cursor.js has lifecycle functions for command copying', () => {
|
||||
// Check that the source file contains our new lifecycle functions
|
||||
expect(cursorProfileContent).toContain('function onAddRulesProfile');
|
||||
expect(cursorProfileContent).toContain('function onRemoveRulesProfile');
|
||||
expect(cursorProfileContent).toContain('copyRecursiveSync');
|
||||
expect(cursorProfileContent).toContain('removeDirectoryRecursive');
|
||||
});
|
||||
|
||||
test('cursor.js copies commands from claude/commands to .cursor/commands', () => {
|
||||
// Check that the onAddRulesProfile function copies from the correct source
|
||||
expect(cursorProfileContent).toContain(
|
||||
"path.join(assetsDir, 'claude', 'commands')"
|
||||
);
|
||||
// Destination path is built via a resolver to handle both project root and rules dir
|
||||
expect(cursorProfileContent).toContain('resolveCursorProfileDir(');
|
||||
expect(cursorProfileContent).toMatch(
|
||||
/path\.join\(\s*profileDir\s*,\s*['"]commands['"]\s*\)/
|
||||
);
|
||||
expect(cursorProfileContent).toContain(
|
||||
'copyRecursiveSync(commandsSourceDir, commandsDestDir)'
|
||||
);
|
||||
|
||||
// Check that lifecycle functions are properly registered with the profile
|
||||
expect(cursorProfile.onAddRulesProfile).toBeDefined();
|
||||
expect(cursorProfile.onRemoveRulesProfile).toBeDefined();
|
||||
expect(typeof cursorProfile.onAddRulesProfile).toBe('function');
|
||||
expect(typeof cursorProfile.onRemoveRulesProfile).toBe('function');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('Roo Profile Initialization Functionality', () => {
|
||||
expect(rooProfile.displayName).toBe('Roo Code');
|
||||
expect(rooProfile.profileDir).toBe('.roo'); // default
|
||||
expect(rooProfile.rulesDir).toBe('.roo/rules'); // default
|
||||
expect(rooProfile.mcpConfig).toBe(true); // default
|
||||
expect(rooProfile.mcpConfig).toBe(true); // now uses standard MCP configuration with Roo enhancements
|
||||
});
|
||||
|
||||
test('roo.js uses custom ROO_STYLE tool mappings', () => {
|
||||
|
||||
@@ -8,18 +8,37 @@ jest.mock('child_process', () => ({
|
||||
execSync: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock console methods
|
||||
jest.mock('console', () => ({
|
||||
// Mock console methods to avoid chalk issues
|
||||
const mockLog = jest.fn();
|
||||
const originalConsole = global.console;
|
||||
const mockConsole = {
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
clear: jest.fn()
|
||||
};
|
||||
global.console = mockConsole;
|
||||
|
||||
// Mock utils logger to avoid chalk dependency issues
|
||||
await jest.unstable_mockModule('../../../scripts/modules/utils.js', () => ({
|
||||
default: undefined,
|
||||
log: mockLog,
|
||||
isSilentMode: () => false
|
||||
}));
|
||||
|
||||
// Import the cursor profile after mocking
|
||||
const { cursorProfile, onAddRulesProfile, onRemoveRulesProfile } = await import(
|
||||
'../../../src/profiles/cursor.js'
|
||||
);
|
||||
|
||||
describe('Cursor Integration', () => {
|
||||
let tempDir;
|
||||
|
||||
afterAll(() => {
|
||||
global.console = originalConsole;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
@@ -75,4 +94,127 @@ describe('Cursor Integration', () => {
|
||||
{ recursive: true }
|
||||
);
|
||||
});
|
||||
|
||||
test('cursor profile has lifecycle functions for command copying', () => {
|
||||
// Assert that the profile exports the lifecycle functions
|
||||
expect(typeof onAddRulesProfile).toBe('function');
|
||||
expect(typeof onRemoveRulesProfile).toBe('function');
|
||||
expect(cursorProfile.onAddRulesProfile).toBe(onAddRulesProfile);
|
||||
expect(cursorProfile.onRemoveRulesProfile).toBe(onRemoveRulesProfile);
|
||||
});
|
||||
|
||||
describe('command copying lifecycle', () => {
|
||||
let mockAssetsDir;
|
||||
let mockTargetDir;
|
||||
|
||||
beforeEach(() => {
|
||||
mockAssetsDir = path.join(tempDir, 'assets');
|
||||
mockTargetDir = path.join(tempDir, 'target');
|
||||
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Mock fs methods for the lifecycle functions
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation((filePath) => {
|
||||
const pathStr = filePath.toString();
|
||||
if (pathStr.includes('claude/commands')) {
|
||||
return true; // Mock that source commands exist
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
|
||||
jest.spyOn(fs, 'readdirSync').mockImplementation(() => ['tm']);
|
||||
jest
|
||||
.spyOn(fs, 'statSync')
|
||||
.mockImplementation(() => ({ isDirectory: () => true }));
|
||||
jest.spyOn(fs, 'copyFileSync').mockImplementation(() => {});
|
||||
jest.spyOn(fs, 'rmSync').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('onAddRulesProfile copies commands from assets to .cursor/commands', () => {
|
||||
// Detect if cpSync exists and set up appropriate spy
|
||||
if (fs.cpSync) {
|
||||
const cpSpy = jest.spyOn(fs, 'cpSync').mockImplementation(() => {});
|
||||
|
||||
// Act
|
||||
onAddRulesProfile(mockTargetDir, mockAssetsDir);
|
||||
|
||||
// Assert
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(
|
||||
path.join(mockAssetsDir, 'claude', 'commands')
|
||||
);
|
||||
expect(cpSpy).toHaveBeenCalledWith(
|
||||
path.join(mockAssetsDir, 'claude', 'commands'),
|
||||
path.join(mockTargetDir, '.cursor', 'commands'),
|
||||
expect.objectContaining({ recursive: true, force: true })
|
||||
);
|
||||
} else {
|
||||
// Act
|
||||
onAddRulesProfile(mockTargetDir, mockAssetsDir);
|
||||
|
||||
// Assert
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(
|
||||
path.join(mockAssetsDir, 'claude', 'commands')
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(mockTargetDir, '.cursor', 'commands'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.copyFileSync).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
test('onAddRulesProfile handles missing source directory gracefully', () => {
|
||||
// Arrange - mock source directory not existing
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
|
||||
|
||||
// Act
|
||||
onAddRulesProfile(mockTargetDir, mockAssetsDir);
|
||||
|
||||
// Assert - should not attempt to copy anything
|
||||
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
||||
expect(fs.copyFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('onRemoveRulesProfile removes .cursor/commands directory', () => {
|
||||
// Arrange - mock directory exists
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => true);
|
||||
|
||||
// Act
|
||||
onRemoveRulesProfile(mockTargetDir);
|
||||
|
||||
// Assert
|
||||
expect(fs.rmSync).toHaveBeenCalledWith(
|
||||
path.join(mockTargetDir, '.cursor', 'commands'),
|
||||
{ recursive: true, force: true }
|
||||
);
|
||||
});
|
||||
|
||||
test('onRemoveRulesProfile handles missing directory gracefully', () => {
|
||||
// Arrange - mock directory doesn't exist
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
|
||||
|
||||
// Act
|
||||
onRemoveRulesProfile(mockTargetDir);
|
||||
|
||||
// Assert - should still return true but not attempt removal
|
||||
expect(fs.rmSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('onRemoveRulesProfile handles removal errors gracefully', () => {
|
||||
// Arrange - mock directory exists but removal fails
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => true);
|
||||
jest.spyOn(fs, 'rmSync').mockImplementation(() => {
|
||||
throw new Error('Permission denied');
|
||||
});
|
||||
|
||||
// Act & Assert - should not throw
|
||||
expect(() => onRemoveRulesProfile(mockTargetDir)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -266,10 +266,10 @@ describe('MCP Configuration Validation', () => {
|
||||
expect(mcpEnabledProfiles).toContain('cursor');
|
||||
expect(mcpEnabledProfiles).toContain('gemini');
|
||||
expect(mcpEnabledProfiles).toContain('opencode');
|
||||
expect(mcpEnabledProfiles).toContain('roo');
|
||||
expect(mcpEnabledProfiles).toContain('vscode');
|
||||
expect(mcpEnabledProfiles).toContain('windsurf');
|
||||
expect(mcpEnabledProfiles).toContain('zed');
|
||||
expect(mcpEnabledProfiles).toContain('roo');
|
||||
expect(mcpEnabledProfiles).not.toContain('cline');
|
||||
expect(mcpEnabledProfiles).not.toContain('codex');
|
||||
expect(mcpEnabledProfiles).not.toContain('trae');
|
||||
@@ -384,6 +384,7 @@ describe('MCP Configuration Validation', () => {
|
||||
'claude',
|
||||
'cursor',
|
||||
'gemini',
|
||||
'kiro',
|
||||
'opencode',
|
||||
'roo',
|
||||
'windsurf',
|
||||
|
||||
Reference in New Issue
Block a user