Compare commits

...

39 Commits

Author SHA1 Message Date
Ralph Khreish
181012b5ba fix: package-lock.json 2025-06-20 17:04:52 +03:00
Ralph Khreish
409a97195a fix: package-lock.json 2025-06-20 17:02:18 +03:00
Ralph Khreish
ee37e4bbbd chore: improve changeset 2025-06-20 16:54:51 +03:00
Ralph Khreish
6816882c5b chore: cleanup 2025-06-20 16:50:37 +03:00
He-Xun
1c8b1b405a chore: Add changeset for compatible API support 2025-06-20 16:44:14 +03:00
He-Xun
cd197ba1b5 feat: Add support for compatible API endpoints via baseURL 2025-06-20 16:44:14 +03:00
He-Xun
5359a33dca Fully revised as requested: restored all required checks, improved compatibility, and converted all comments to English. 2025-06-20 16:44:00 +03:00
He-Xun
3e1b8b957e Adjust the code according to the suggestions 2025-06-20 16:43:49 +03:00
He-Xun
91b9f11c03 add compatible platform api support 2025-06-20 16:42:24 +03:00
V4G4X
030694bb96 readme: add troubleshooting note for MCP tools not working 2025-06-20 15:28:00 +02:00
github-actions[bot]
3e0f696c49 docs: Auto-update and format models.md 2025-06-20 13:25:33 +00:00
Ben Vargas
4b0c9d9af6 chore: add changeset for Claude Code provider feature 2025-06-20 16:25:22 +03:00
Ben Vargas
3fa91f56e5 fix(models): add missing --claude-code flag to models command
The models command was missing the --claude-code provider flag, preventing users from setting Claude Code models via CLI. While the backend already supported claude-code as a provider hint, there was no command-line flag to trigger it.

Changes:
- Added --claude-code option to models command alongside existing provider flags
- Updated provider flags validation to include claudeCode option
- Added claude-code to providerHint logic for all three model roles (main, research, fallback)
- Updated error message to include --claude-code in list of mutually exclusive flags
- Added example usage in help text

This allows users to properly set Claude Code models using commands like:
  task-master models --set-main sonnet --claude-code
  task-master models --set-main opus --claude-code

Without this flag, users would get "Model ID not found" errors when trying to set claude-code models, as the system couldn't determine the correct provider for generic model names like "sonnet" or "opus".
2025-06-20 16:25:22 +03:00
Ben Vargas
e69ac5d5cf style: apply biome formatting to test files 2025-06-20 16:25:22 +03:00
Ben Vargas
c60c9354a4 docs: add Claude Code support information to README
- Added Claude Code to the list of supported providers in Requirements section
- Noted that Claude Code requires no API key but needs Claude Code CLI
- Added example of configuring claude-code/sonnet model
- Created dedicated Claude Code Support section with key information
- Added link to detailed Claude Code setup documentation

This ensures users are aware of the Claude Code option as a no-API-key
alternative for using Claude models.
2025-06-20 16:25:22 +03:00
Ben Vargas
30b895be2c revert: remove maxTokens update functionality from init
This functionality was out of scope for the Claude Code provider PR.
The automatic updating of maxTokens values in config.json during
initialization is a general improvement that should be in a separate PR.

Additionally, Claude Code ignores maxTokens and temperature parameters
anyway, making this change irrelevant for the Claude Code integration.

Removed:
- scripts/modules/update-config-tokens.js
- Import and usage in scripts/init.js
2025-06-20 16:25:22 +03:00
Ben Vargas
9995075093 test: add comprehensive tests for ClaudeCodeProvider
Addresses code review feedback about missing automated tests for the ClaudeCodeProvider.

## Changes

- Added unit tests for ClaudeCodeProvider class covering constructor, validateAuth, and getClient methods
- Added unit tests for ClaudeCodeLanguageModel testing lazy loading behavior and error handling
- Added integration tests verifying optional dependency behavior when @anthropic-ai/claude-code is not installed

## Test Coverage

1. **Unit Tests**:
   - ClaudeCodeProvider: Basic functionality, no API key requirement, client creation
   - ClaudeCodeLanguageModel: Model initialization, lazy loading, error messages, warning generation

2. **Integration Tests**:
   - Optional dependency behavior when package is not installed
   - Clear error messages for users about missing package
   - Provider instantiation works but usage fails gracefully

All tests pass and provide comprehensive coverage for the claude-code provider implementation.
2025-06-20 16:25:22 +03:00
Ben Vargas
b62cb1bbe7 feat: make @anthropic-ai/claude-code an optional dependency
This change makes the Claude Code SDK package optional, preventing installation failures for users who don't need Claude Code functionality.

Changes:
- Added @anthropic-ai/claude-code to optionalDependencies in package.json
- Implemented lazy loading in language-model.js to only import the SDK when actually used
- Updated documentation to explain the optional installation requirement
- Applied formatting fixes to ensure code consistency

Benefits:
- Users without Claude Code subscriptions don't need to install the dependency
- Reduces package size for users who don't use Claude Code
- Prevents installation failures if the package is unavailable
- Provides clear error messages when the package is needed but not installed

The implementation uses dynamic imports to load the SDK only when doGenerate() or doStream() is called, ensuring the provider can be instantiated without the package present.
2025-06-20 16:25:22 +03:00
Ben Vargas
7defcba465 fix(docs): correct invalid commands in claude-code usage examples
- Remove non-existent 'do', 'estimate', and 'analyze' commands
- Replace with actual Task Master commands: next, show, set-status
- Use correct syntax for parse-prd and analyze-complexity
2025-06-20 16:25:22 +03:00
Ben Vargas
3e838ed34b feat: add Claude Code provider support
Implements Claude Code as a new AI provider that uses the Claude Code CLI
without requiring API keys. This enables users to leverage Claude models
through their local Claude Code installation.

Key changes:
- Add complete AI SDK v1 implementation for Claude Code provider
  - Custom SDK with streaming/non-streaming support
  - Session management for conversation continuity
  - JSON extraction for object generation mode
  - Support for advanced settings (maxTurns, allowedTools, etc.)

- Integrate Claude Code into Task Master's provider system
  - Update ai-services-unified.js to handle keyless authentication
  - Add provider to supported-models.json with opus/sonnet models
  - Ensure correct maxTokens values are applied (opus: 32000, sonnet: 64000)

- Fix maxTokens configuration issue
  - Add max_tokens property to getAvailableModels() output
  - Update setModel() to properly handle claude-code models
  - Create update-config-tokens.js utility for init process

- Add comprehensive documentation
  - User guide with configuration examples
  - Advanced settings explanation and future integration options

The implementation maintains full backward compatibility with existing
providers while adding seamless Claude Code support to all Task Master
commands.
2025-06-20 16:25:22 +03:00
ejones40
1b8c320c57 Add pyproject.toml as project root marker (#804)
* feat: Add pyproject.toml as project root marker - Added 'pyproject.toml' to the project markers array in findProjectRoot() - Enables Task Master to recognize Python projects using pyproject.toml - Improves project root detection for modern Python development workflows - Maintains compatibility with existing Node.js and Git-based detection

* chore: add changeset

---------

Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
2025-06-20 15:15:13 +02:00
Ralph Khreish
5da5b59bde Fix/expand command tag corruption (#827)
* fix(expand): Fix tag corruption in expand command - Fix tag parameter passing through MCP expand-task flow - Add tag parameter to direct function and tool registration - Fix contextGatherer method name from _buildDependencyContext to _buildDependencyGraphs - Add comprehensive test coverage for tag handling in expand-task - Ensures tagged task structure is preserved during expansion - Prevents corruption when tag is undefined. Fixes expand command causing tag corruption in tagged task lists. All existing tests pass and new test coverage added.

* test(e2e): Add comprehensive tag-aware expand testing to verify tag corruption fix - Add new test section for feature-expand tag creation and testing - Verify tag preservation during expand, force expand, and expand --all operations - Test that master tag remains intact and feature-expand tag receives subtasks correctly - Fix file path references to use correct .taskmaster/tasks/tasks.json location - Fix config file check to use .taskmaster/config.json instead of .taskmasterconfig - All tag corruption verification tests pass successfully in E2E test

* fix(changeset): Update E2E test improvements changeset to properly reflect tag corruption fix verification

* chore(changeset): combine duplicate changesets for expand tag corruption fix

Merge eighty-breads-wonder.md into bright-llamas-enter.md to consolidate
the expand command fix and its comprehensive E2E testing enhancements
into a single changeset entry.

* Delete .changeset/eighty-breads-wonder.md

* Version Packages

* chore: fix package.json

* fix(expand): Enhance context handling in expandAllTasks function
- Added `tag` to context destructuring for better context management.
- Updated `readJSON` call to include `contextTag` for improved data integrity.
- Ensured the correct tag is passed during task expansion to prevent tag corruption.

---------

Co-authored-by: Parththipan Thaniperumkarunai <parththipan.thaniperumkarunai@milkmonkey.de>
Co-authored-by: Parthy <52548018+mm-parthy@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-20 15:12:40 +02:00
Ralph Khreish
04f44a2d3d chore: fix package.json 2025-06-20 16:10:52 +03:00
github-actions[bot]
36fe838fd5 Version Packages 2025-06-20 16:10:52 +03:00
github-actions[bot]
415b1835d4 docs: Auto-update and format models.md 2025-06-20 13:05:31 +00:00
Ralph Khreish
78112277b3 fix(bedrock): improve AWS credential handling and add model definitions (#826)
* fix(bedrock): improve AWS credential handling and add model definitions

- Change error to warning when AWS credentials are missing in environment
- Allow fallback to system configuration (aws config files or instance profiles)
- Remove hardcoded region and profile parameters in Bedrock client
- Add Claude 3.7 Sonnet and DeepSeek R1 model definitions for Bedrock
- Update config manager to properly handle Bedrock provider

* chore: cleanup and format and small refactor

---------

Co-authored-by: Ray Krueger <raykrueger@gmail.com>
2025-06-20 15:05:20 +02:00
Ralph Khreish
2bb4260966 fix: Fix external provider support (#726) 2025-06-20 14:59:53 +02:00
Nathan Marley
3a2325a963 fix: switch to ESM export to avoid mixed format (#633)
* fix: switch to ESM export to avoid mixed format

The CLI entrypoint was using `module.exports` alongside ESM `import` statements,
resulting in an invalid mixed module format. Replaced the CommonJS export with
a proper ESM `export` to maintain consistency and prevent module resolution issues.

* chore: add changeset

---------

Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
2025-06-20 14:12:36 +02:00
Ralph Khreish
1bd6d4f246 fix: providers config for azure, bedrock, and vertex (#822)
* fix: providers config for azure, bedrock, and vertex

* chore: improve changelog

* chore: fix CI
2025-06-20 13:13:53 +02:00
Joe Danziger
a09a2d0967 feat: Flexible brand rules management (#460)
* chore(docs): update docs and rules related to model management.

* feat(ai): Add OpenRouter AI provider support

Integrates the OpenRouter AI provider using the Vercel AI SDK adapter (@openrouter/ai-sdk-provider). This allows users to configure and utilize models available through the OpenRouter platform.

- Added src/ai-providers/openrouter.js with standard Vercel AI SDK wrapper functions (generateText, streamText, generateObject).

- Updated ai-services-unified.js to include the OpenRouter provider in the PROVIDER_FUNCTIONS map and API key resolution logic.

- Verified config-manager.js handles OpenRouter API key checks correctly.

- Users can configure OpenRouter models via .taskmasterconfig using the task-master models command or MCP models tool. Requires OPENROUTER_API_KEY.

- Enhanced error handling in ai-services-unified.js to provide clearer messages when generateObjectService fails due to lack of underlying tool support in the selected model/provider endpoint.

* feat(cli): Add --status/-s filter flag to show command and get-task MCP tool

Implements the ability to filter subtasks displayed by the `task-master show <id>` command using the `--status` (or `-s`) flag. This is also available in the MCP context.

- Modified `commands.js` to add the `--status` option to the `show` command definition.

- Updated `utils.js` (`findTaskById`) to handle the filtering logic and return original subtask counts/arrays when filtering.

- Updated `ui.js` (`displayTaskById`) to use the filtered subtasks for the table, display a summary line when filtering, and use the original subtask list for the progress bar calculation.

- Updated MCP `get_task` tool and `showTaskDirect` function to accept and pass the `status` parameter.

- Added changeset entry.

* fix(tasks): Improve next task logic to be subtask-aware

* fix(tasks): Enable removing multiple tasks/subtasks via comma-separated IDs

- Refactors the core `removeTask` function (`task-manager/remove-task.js`) to accept and iterate over comma-separated task/subtask IDs.

- Updates dependency cleanup and file regeneration logic to run once after processing all specified IDs.

- Adjusts the `remove-task` CLI command (`commands.js`) description and confirmation prompt to handle multiple IDs correctly.

- Fixes a bug in the CLI confirmation prompt where task/subtask titles were not being displayed correctly.

- Updates the `remove_task` MCP tool description to reflect the new multi-ID capability.

This addresses the previously known issue where only the first ID in a comma-separated list was processed.

Closes #140

* Update README.md (#342)

* Update Discord badge (#337)

* refactor(init): Improve robustness and dependencies; Update template deps for AI SDKs; Silence npm install in MCP; Improve conditional model setup logic; Refactor init.js flags; Tweak Getting Started text; Fix MCP server launch command; Update default model in config template

* Refactor: Improve MCP logging, update E2E & tests

Refactors MCP server logging and updates testing infrastructure.

- MCP Server:

  - Replaced manual logger wrappers with centralized `createLogWrapper` utility.

  - Updated direct function calls to use `{ session, mcpLog }` context.

  - Removed deprecated `model` parameter from analyze, expand-all, expand-task tools.

  - Adjusted MCP tool import paths and parameter descriptions.

- Documentation:

  - Modified `docs/configuration.md`.

  - Modified `docs/tutorial.md`.

- Testing:

  - E2E Script (`run_e2e.sh`):

    - Removed `set -e`.

    - Added LLM analysis function (`analyze_log_with_llm`) & integration.

    - Adjusted test run directory creation timing.

    - Added debug echo statements.

  - Deleted Unit Tests: Removed `ai-client-factory.test.js`, `ai-client-utils.test.js`, `ai-services.test.js`.

  - Modified Fixtures: Updated `scripts/task-complexity-report.json`.

- Dev Scripts:

  - Modified `scripts/dev.js`.

* chore(tests): Passes tests for merge candidate
- Adjusted the interactive model default choice to be 'no change' instead of 'cancel setup'
- E2E script has been perfected and works as designed provided there are all provider API keys .env in the root
- Fixes the entire test suite to make sure it passes with the new architecture.
- Fixes dependency command to properly show there is a validation failure if there is one.
- Refactored config-manager.test.js mocking strategy and fixed assertions to read the real supported-models.json
- Fixed rule-transformer.test.js assertion syntax and transformation logic adjusting replacement for search which was too broad.
- Skip unstable tests in utils.test.js (log, readJSON, writeJSON error paths) due to SIGABRT crash. These tests trigger a native crash (SIGABRT), likely stemming from a conflict between internal chalk usage within the functions and Jest's test environment, possibly related to ESM module handling.

* chore(wtf): removes chai. not sure how that even made it in here. also removes duplicate test in scripts/.

* fix: ensure API key detection properly reads .env in MCP context

Problem:
- Task Master model configuration wasn't properly checking for API keys in the project's .env file when running through MCP
- The isApiKeySet function was only checking session.env and process.env but not inspecting the .env file directly
- This caused incorrect API key status reporting in MCP tools even when keys were properly set in .env

Solution:
- Modified resolveEnvVariable function in utils.js to properly read from .env file at projectRoot
- Updated isApiKeySet to correctly pass projectRoot to resolveEnvVariable
- Enhanced the key detection logic to have consistent behavior between CLI and MCP contexts
- Maintains the correct precedence: session.env → .env file → process.env

Testing:
- Verified working correctly with both MCP and CLI tools
- API keys properly detected in .env file in both contexts
- Deleted .cursor/mcp.json to confirm introspection of .env as fallback works

* fix(update): pass projectRoot through update command flow

Modified ai-services-unified.js, update.js tool, and update-tasks.js direct function to correctly pass projectRoot. This enables the .env file API key fallback mechanism for the update command when running via MCP, ensuring consistent key resolution with the CLI context.

* fix(analyze-complexity): pass projectRoot through analyze-complexity flow

Modified analyze-task-complexity.js core function, direct function, and analyze.js tool to correctly pass projectRoot. Fixed import error in tools/index.js. Added debug logging to _resolveApiKey in ai-services-unified.js. This enables the .env API key fallback for analyze_project_complexity.

* fix(add-task): pass projectRoot and fix logging/refs

Modified add-task core, direct function, and tool to pass projectRoot for .env API key fallback. Fixed logFn reference error and removed deprecated reportProgress call in core addTask function. Verified working.

* fix(parse-prd): pass projectRoot and fix schema/logging

Modified parse-prd core, direct function, and tool to pass projectRoot for .env API key fallback. Corrected Zod schema used in generateObjectService call. Fixed logFn reference error in core parsePRD. Updated unit test mock for utils.js.

* fix(update-task): pass projectRoot and adjust parsing

Modified update-task-by-id core, direct function, and tool to pass projectRoot. Reverted parsing logic in core function to prioritize `{...}` extraction, resolving parsing errors. Fixed ReferenceError by correctly destructuring projectRoot.

* fix(update-subtask): pass projectRoot and allow updating done subtasks

Modified update-subtask-by-id core, direct function, and tool to pass projectRoot for .env API key fallback. Removed check preventing appending details to completed subtasks.

* fix(mcp, expand): pass projectRoot through expand/expand-all flows

Problem: expand_task & expand_all MCP tools failed with .env keys due to missing projectRoot propagation for API key resolution. Also fixed a ReferenceError: wasSilent is not defined in expandTaskDirect.

Solution: Modified core logic, direct functions, and MCP tools for expand-task and expand-all to correctly destructure projectRoot from arguments and pass it down through the context object to the AI service call (generateTextService). Fixed wasSilent scope in expandTaskDirect.

Verification: Tested expand_task successfully in MCP using .env keys. Reviewed expand_all flow for correct projectRoot propagation.

* chore: prettier

* fix(expand-all): add projectRoot to expandAllTasksDirect invokation.

* fix(update-tasks): Improve AI response parsing for 'update' command

Refactors the JSON array parsing logic within
in .

The previous logic primarily relied on extracting content from markdown
code blocks (json or javascript), which proved brittle when the AI
response included comments or non-JSON text within the block, leading to
parsing errors for the  command.

This change modifies the parsing strategy to first attempt extracting
content directly between the outermost '[' and ']' brackets. This is
more robust as it targets the expected array structure directly. If
bracket extraction fails, it falls back to looking for a strict json
code block, then prefix stripping, before attempting a raw parse.

This approach aligns with the successful parsing strategy used for
single-object responses in  and resolves the
parsing errors previously observed with the  command.

* refactor(mcp): introduce withNormalizedProjectRoot HOF for path normalization

Added HOF to mcp tools utils to normalize projectRoot from args/session. Refactored get-task tool to use HOF. Updated relevant documentation.

* refactor(mcp): apply withNormalizedProjectRoot HOF to update tool

Problem: The  MCP tool previously handled project root acquisition and path resolution within its  method, leading to potential inconsistencies and repetition.

Solution: Refactored the  tool () to utilize the new  Higher-Order Function (HOF) from .

Specific Changes:
- Imported  HOF.
- Updated the Zod schema for the  parameter to be optional, as the HOF handles deriving it from the session if not provided.
- Wrapped the entire  function body with the  HOF.
- Removed the manual call to  from within the  function body.
- Destructured the  from the  object received by the wrapped  function, ensuring it's the normalized path provided by the HOF.
- Used the normalized  variable when calling  and when passing arguments to .

This change standardizes project root handling for the  tool, simplifies its  method, and ensures consistent path normalization. This serves as the pattern for refactoring other MCP tools.

* fix: apply to all tools withNormalizedProjectRoot to fix projectRoot issues for linux and windows

* fix: add rest of tools that need wrapper

* chore: cleanup tools to stop using rootFolder and remove unused imports

* chore: more cleanup

* refactor: Improve update-subtask, consolidate utils, update config

This commit introduces several improvements and refactorings across MCP tools, core logic, and configuration.

**Major Changes:**

1.  **Refactor updateSubtaskById:**
    - Switched from generateTextService to generateObjectService for structured AI responses, using a Zod schema (subtaskSchema) for validation.
    - Revised prompts to have the AI generate relevant content based on user request and context (parent/sibling tasks), while explicitly preventing AI from handling timestamp/tag formatting.
    - Implemented **local timestamp generation (new Date().toISOString()) and formatting** (using <info added on ...> tags) within the function *after* receiving the AI response. This ensures reliable and correctly formatted details are appended.
    - Corrected logic to append only the locally formatted, AI-generated content block to the existing subtask.details.

2.  **Consolidate MCP Utilities:**
    - Moved/consolidated the withNormalizedProjectRoot HOF into mcp-server/src/tools/utils.js.
    - Updated MCP tools (like update-subtask.js) to import withNormalizedProjectRoot from the new location.

3.  **Refactor Project Initialization:**
    - Deleted the redundant mcp-server/src/core/direct-functions/initialize-project-direct.js file.
    - Updated mcp-server/src/core/task-master-core.js to import initializeProjectDirect from its correct location (./direct-functions/initialize-project.js).

**Other Changes:**

-   Updated .taskmasterconfig fallback model to claude-3-7-sonnet-20250219.
-   Clarified model cost representation in the models tool description (taskmaster.mdc and mcp-server/src/tools/models.js).

* fix: displayBanner logging when silentMode is active (#385)

* fix: improve error handling, test options, and model configuration

- Enhance error validation in parse-prd.js and update-tasks.js
- Fix bug where mcpLog was incorrectly passed as logWrapper
- Improve error messages and response formatting
- Add --skip-verification flag to E2E tests
- Update MCP server config that ships with init to match new API key structure
- Fix task force/append handling in parse-prd command
- Increase column width in update-tasks display

* chore: fixes parse prd to show loading indicator in cli.

* fix(parse-prd): suggested fix for mcpLog was incorrect. reverting to my previously working code.

* chore(init): No longer ships readme with task-master init (commented out for now). No longer looking for task-master-mcp, instead checked for task-master-ai - this should prevent the init sequence from needlessly adding another mcp server with task-master-mcp to the mpc.json which a ton of people probably ran into.

* chore: restores 3.7 sonnet as the main role.

* fix(add/remove-dependency): dependency mcp tools were failing due to hard-coded tasks path in generate task files.

* chore: removes tasks json backup that was temporarily created.

* fix(next): adjusts mcp tool response to correctly return the next task/subtask. Also adds nextSteps to the next task response.

* chore: prettier

* chore: readme typos

* fix(config): restores sonnet 3.7 as default main role.

* Version Packages

* hotfix: move production package to "dependencies" (#399)

* Version Packages

* Fix: issues with 0.13.0 not working (#402)

* Exit prerelease mode and version packages

* hotfix: move production package to "dependencies"

* Enter prerelease mode and version packages

* Enter prerelease mode and version packages

* chore: cleanup

* chore: improve pre.json and add pre-release workflow

* chore: fix package.json

* chore: cleanup

* chore: improve pre-release workflow

* chore: allow github actions to commit

* extract fileMap and conversionConfig into brand profile

* extract into brand profile

* add windsurf profile

* add remove brand rules function

* fix regex

* add rules command to add/remove rules for a specific brand

* fix post processing for roo

* allow multiples

* add cursor profile

* update test for new structure

* move rules to assets

* use assets/rules for rules files

* use standardized setupMCP function

* fix formatting

* fix formatting

* add logging

* fix escapes

* default to cursor

* allow init with certain rulesets; no more .windsurfrules

* update docs

* update log msg

* fix formatting

* keep mdc extension for cursor

* don't rewrite .mdc to .md inside the files

* fix roo init (add modes)

* fix cursor init (don't use roo transformation by default)

* use more generic function names

* update docs

* fix formatting

* update function names

* add changeset

* add rules to mcp initialize project

* register tool with mcp server

* update docs

* add integration test

* fix cursor initialization

* rule selection

* fix formatting

* fix MCP - remove yes flag

* add import

* update roo tests

* add/update tests

* remove test

* add rules command test

* update MCP responses, centralize rules profiles & helpers

* fix logging and MCP response messages

* fix formatting

* incorrect test

* fix tests

* update fileMap

* fix file extension transformations

* fix formatting

* add rules command test

* test already covered

* fix formatting

* move renaming logic into profiles

* make sure dir is deleted (DS_Store)

* add confirmation for rules removal

* add force flag for rules remove

* use force flag for test

* remove yes parameter

* fix formatting

* import brand profiles from rule-transformer.js

* update comment

* add interactive rules setup

* optimize

* only copy rules specifically listed in fileMap

* update comment

* add cline profile

* add brandDir to remove ambiguity and support Cline

* specify whether to create mcp config and filename

* add mcpConfigName value for parh

* fix formatting

* remove rules just for this repository - only include rules to be distributed

* update error message

* update "brand rules" to "rules"

* update to minor

* remove comment

* remove comments

* move to /src/utils

* optimize imports

* move rules-setup.js to /src/utils

* move rule-transformer.js to /src/utils

* move confirmation to /src/ui/confirm.js

* default to all rules

* use profile js for mcp config settings

* only run rules interactive setup if not provided via command line

* update comments

* initialize with all brands if nothing specified

* update var name

* clean up

* enumerate brands for brand rules

* update instructions

* add test to check for brand profiles

* fix quotes

* update semantics and terminology from 'brand rules' to 'rules profiles'

* fix formatting

* fix formatting

* update function name and remove copying of cursor rules, now handled by rules transformer

* update comment

* rename to mcp-config-setup.js

* use enums for rules actions

* add aggregate reporting for rules add command

* add missing log message

* use simpler path

* use base profile with modifications for each brand

* use displayName and don't select any defaults in setup

* add confirmation if removing ALL rules profiles, and add --force flag on rules remove

* Use profile-detection instead of rules-detection

* add newline at end of mcp config

* add proper formatting for mcp.json

* update rules

* update rules

* update rules

* add checks for other rules and other profile folder items before removing

* update confirmation for rules remove

* update docs

* update changeset

* fix for filepath at bottom of rule

* Update cline profile and add test; adjust other rules tests

* update changeset

* update changeset

* clarify init for all profiles if not specified

* update rule text

* revert text

* use "rule profiles" instead of "rules profiles"

* use standard tool mappings for windsurf

* add Trae support

* update changeset

* update wording

* update to 'rule profile'

* remove unneeded exports to optimize loc

* combine to /src/utils/profiles.js; add codex and claude code profiles

* rename function and add boxen

* add claude and codex integration tests

* organize tests into profiles folder

* mock fs for transformer tests

* update UI

* add cline and trae integration tests

* update test

* update function name

* update formatting

* Update change set with new profiles

* move profile integration tests to subdirectory

* properly create temp directories in /tmp folder

* fix formatting

* use taskmaster subfolder for the 2 TM rules

* update wording

* ensure subdirectory exists

* update rules from next

* update from next

* update taskmaster rule

* add details on new rules command and init

* fix mcp init

* fix MCP path to assets

* remove duplication

* remove duplication

* MCP server path fixes for rules command

* fix for CLI roo rules add/remove

* update tests

* fix formatting

* fix pattern for interactive rule profiles setup

* restore comments

* restore comments

* restore comments

* remove unused import, fix quotes

* add missing integration tests

* add VS Code profile and tests

* update docs and rules to include vscode profile

* add rules subdirectory support per-profile

* move profiles to /src

* fix formatting

* rename to remove ambiguity

* use --setup for rules interactive setup

* Fix Cursor deeplink installation with copy-paste instructions (#723)

* change roo boomerang to orchestrator; update tests that don't use modes

* fix newline

* chore: cleanup

---------

Co-authored-by: Eyal Toledano <eyal@microangel.so>
Co-authored-by: Yuval <yuvalbl@users.noreply.github.com>
Co-authored-by: Marijn van der Werf <marijn.vanderwerf@gmail.com>
Co-authored-by: Eyal Toledano <eutait@gmail.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-20 10:09:36 +02:00
github-actions[bot]
02e0db09df docs: Auto-update and format models.md 2025-06-20 07:59:03 +00:00
Riccardo (Ricky) Esclapon
3bcce8d70e Update SWE scores (#657) 2025-06-20 09:58:53 +02:00
Bartu OZEL
636fb3f680 docs: Update configuration.md (#782) 2025-06-16 20:12:54 +02:00
Ralph Khreish
8cde6c2708 fix(contextGatherer): cannot read properties of undefined reading forEach (#789) 2025-06-16 10:32:34 +02:00
Ralph Khreish
246acd1035 Merge Release 0.17.0 pull request #780 from eyaltoledano/changeset-release/main
Version Packages
2025-06-15 03:16:16 +02:00
Ralph Khreish
de5acbc6c9 chore: fix formatting 2025-06-15 04:12:13 +03:00
Ralph Khreish
664eb5b896 cleanup release 2025-06-15 04:10:51 +03:00
github-actions[bot]
dbaf492bdb Version Packages 2025-06-15 00:59:24 +00:00
Ralph Khreish
0c8a0b81a0 Merge pull request #779 from eyaltoledano/next
Release 0.17.0
2025-06-15 02:59:01 +02:00
131 changed files with 12944 additions and 1802 deletions

View File

@@ -0,0 +1,12 @@
---
"task-master-ai": patch
---
Fix expand command preserving tagged task structure and preventing data corruption
- Enhance E2E tests with comprehensive tag-aware expand testing to verify tag corruption fix
- Add new test section for feature-expand tag creation and testing during expand operations
- Verify tag preservation during expand, force expand, and expand --all operations
- Test that master tag remains intact while feature-expand tag receives subtasks correctly
- Fix file path references to use correct .taskmaster/config.json and .taskmaster/tasks/tasks.json locations
- All tag corruption verification tests pass successfully, confirming the expand command tag corruption bug fix works as expected

View File

@@ -1,78 +0,0 @@
---
"task-master-ai": minor
---
Add comprehensive AI-powered research command with intelligent context gathering and interactive follow-ups.
The new `research` command provides AI-powered research capabilities that automatically gather relevant project context to answer your questions. The command intelligently selects context from multiple sources and supports interactive follow-up questions in CLI mode.
**Key Features:**
- **Intelligent Task Discovery**: Automatically finds relevant tasks and subtasks using fuzzy search based on your query keywords, supplementing any explicitly provided task IDs
- **Multi-Source Context**: Gathers context from tasks, files, project structure, and custom text to provide comprehensive answers
- **Interactive Follow-ups**: CLI users can ask follow-up questions that build on the conversation history while allowing fresh context discovery for each question
- **Flexible Detail Levels**: Choose from low (concise), medium (balanced), or high (comprehensive) response detail levels
- **Token Transparency**: Displays detailed token breakdown showing context size, sources, and estimated costs
- **Enhanced Display**: Syntax-highlighted code blocks and structured output with clear visual separation
**Usage Examples:**
```bash
# Basic research with auto-discovered context
task-master research "How should I implement user authentication?"
# Research with specific task context
task-master research "What's the best approach for this?" --id=15,23.2
# Research with file context and project tree
task-master research "How does the current auth system work?" --files=src/auth.js,config/auth.json --tree
# Research with custom context and low detail
task-master research "Quick implementation steps?" --context="Using JWT tokens" --detail=low
```
**Context Sources:**
- **Tasks**: Automatically discovers relevant tasks/subtasks via fuzzy search, plus any explicitly specified via `--id`
- **Files**: Include specific files via `--files` for code-aware responses
- **Project Tree**: Add `--tree` to include project structure overview
- **Custom Context**: Provide additional context via `--context` for domain-specific information
**Interactive Features (CLI only):**
- Follow-up questions that maintain conversation history
- Fresh fuzzy search for each follow-up to discover newly relevant tasks
- Cumulative context building across the conversation
- Clean visual separation between exchanges
- **Save to Tasks**: Save entire research conversations (including follow-ups) directly to task or subtask details with timestamps
- **Clean Menu Interface**: Streamlined inquirer-based menu for follow-up actions without redundant UI elements
**Save Functionality:**
The research command now supports saving complete conversation threads to tasks or subtasks:
- Save research results and follow-up conversations to any task (e.g., "15") or subtask (e.g., "15.2")
- Automatic timestamping and formatting of conversation history
- Validation of task/subtask existence before saving
- Appends to existing task details without overwriting content
- Supports both CLI interactive mode and MCP programmatic access via `--save-to` flag
**Enhanced CLI Options:**
```bash
# Auto-save research results to a task
task-master research "Implementation approach?" --save-to=15
# Combine auto-save with context gathering
task-master research "How to optimize this?" --id=23 --save-to=23.1
```
**MCP Integration:**
- `saveTo` parameter for automatic saving to specified task/subtask ID
- Structured response format with telemetry data
- Silent operation mode for programmatic usage
- Full feature parity with CLI except interactive follow-ups
The research command integrates with the existing AI service layer and supports all configured AI providers. Both CLI and MCP interfaces provide comprehensive research capabilities with intelligent context gathering and flexible output options.

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Fix Cursor deeplink installation by providing copy-paste instructions for GitHub compatibility

View File

@@ -1,14 +0,0 @@
---
'task-master-ai': patch
---
Fix critical bugs in task move functionality:
- **Fixed moving tasks to become subtasks of empty parents**: When moving a task to become a subtask of a parent that had no existing subtasks (e.g., task 89 → task 98.1), the operation would fail with validation errors.
- **Fixed moving subtasks between parents**: Subtasks can now be properly moved between different parent tasks, including to parents that previously had no subtasks.
- **Improved comma-separated batch moves**: Multiple tasks can now be moved simultaneously using comma-separated IDs (e.g., "88,90" → "92,93") with proper error handling and atomic operations.
These fixes enables proper task hierarchy reorganization for corner cases that were previously broken.

View File

@@ -1,19 +0,0 @@
---
"task-master-ai": minor
---
Enhance update-task with --append flag for timestamped task updates
Adds the `--append` flag to `update-task` command, enabling it to behave like `update-subtask` with timestamped information appending. This provides more flexible task updating options:
**CLI Enhancement:**
- `task-master update-task --id=5 --prompt="New info"` - Full task update (existing behavior)
- `task-master update-task --id=5 --append --prompt="Progress update"` - Append timestamped info to task details
**Full MCP Integration:**
- MCP tool `update_task` now supports `append` parameter
- Seamless integration with Cursor and other MCP clients
- Consistent behavior between CLI and MCP interfaces
Instead of requiring separate subtask creation for progress tracking, you can now append timestamped information directly to parent tasks while preserving the option for comprehensive task updates.

View File

@@ -0,0 +1,8 @@
---
"task-master-ai": minor
---
Can now configure baseURL of provider with `<PROVIDER>_BASE_URL`
- For example:
- `OPENAI_BASE_URL`

View File

@@ -1,12 +0,0 @@
---
"task-master-ai": minor
---
Add --tag flag support to core commands for multi-context task management. Commands like parse-prd, analyze-complexity, and others now support targeting specific task lists, enabling rapid prototyping and parallel development workflows.
Key features:
- parse-prd --tag=feature-name: Parse PRDs into separate task contexts on the fly
- analyze-complexity --tag=branch: Generate tag-specific complexity reports
- All task operations can target specific contexts while preserving other lists
- Non-existent tags are created automatically for seamless workflow

View File

@@ -0,0 +1,17 @@
---
'task-master-ai': minor
---
Added comprehensive rule profile management:
**New Profile Support**: Added comprehensive IDE profile support with eight specialized profiles: Claude Code, Cline, Codex, Cursor, Roo, Trae, VS Code, and Windsurf. Each profile is optimized for its respective IDE with appropriate mappings and configuration.
**Initialization**: You can now specify which rule profiles to include at project initialization using `--rules <profiles>` or `-r <profiles>` (e.g., `task-master init -r cursor,roo`). Only the selected profiles and configuration are included.
**Add/Remove Commands**: `task-master rules add <profiles>` and `task-master rules remove <profiles>` let you manage specific rule profiles and MCP config after initialization, supporting multiple profiles at once.
**Interactive Setup**: `task-master rules setup` launches an interactive prompt to select which rule profiles to add to your project. This does **not** re-initialize your project or affect shell aliases; it only manages rules.
**Selective Removal**: Rules removal intelligently preserves existing non-Task Master rules and files and only removes Task Master-specific rules. Profile directories are only removed when completely empty and all conditions are met (no existing rules, no other files/folders, MCP config completely removed).
**Safety Features**: Confirmation messages clearly explain that only Task Master-specific rules and MCP configurations will be removed, while preserving existing custom rules and other files.
**Robust Validation**: Includes comprehensive checks for array types in MCP config processing and error handling throughout the rules management system.
This enables more flexible, rule-specific project setups with intelligent cleanup that preserves user customizations while safely managing Task Master components.
- Resolves #338

View File

@@ -2,4 +2,4 @@
"task-master-ai": patch
---
Update o3 model price
Improves Amazon Bedrock support

View File

@@ -1,8 +0,0 @@
---
"task-master-ai": patch
---
Fixes issue with expand CLI command "Complexity report not found"
- Closes #735
- Closes #728

View File

@@ -1,24 +0,0 @@
{
"mode": "exit",
"tag": "rc",
"initialVersions": {
"task-master-ai": "0.17.0-rc.1"
},
"changesets": [
"bright-windows-sing",
"chatty-rats-talk",
"cold-pears-poke",
"free-pants-rescue",
"large-wolves-strive",
"late-dryers-relax",
"nasty-chefs-add",
"quick-flies-sniff",
"six-cups-see",
"slick-webs-lead",
"slow-lies-make",
"stale-bats-sin",
"tiny-ads-decide",
"two-lies-start",
"yellow-olives-admire"
]
}

View File

@@ -1,7 +0,0 @@
---
"task-master-ai": patch
---
Fix issue with generate command which was creating tasks in the legacy tasks location.
- No longer creates individual task files automatically. You can still use `generate` if you need to create our update your task files.

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": minor
---
Add better support for python projects by adding `pyproject.toml` as a projectRoot marker

View File

@@ -1,140 +0,0 @@
---
"task-master-ai": minor
---
Introduces Tagged Lists: AI Multi-Context Task Management System
This major feature release introduces Tagged Lists, a comprehensive system that transforms Taskmaster into a multi-context task management powerhouse. You can now organize tasks into completely isolated contexts, enabling parallel (agentic) development workflows, team collaboration, and project experimentation without conflicts.
**🏷️ Tagged Task Lists Architecture:**
The new tagged system fundamentally improves how tasks are organized:
- **Legacy Format**: `{ "tasks": [...] }`
- **New Tagged Format**: `{ "master": { "tasks": [...], "metadata": {...} }, "feature-xyz": { "tasks": [...], "metadata": {...} } }`
- **Automatic Migration**: Existing projects will seamlessly migrate to tagged format with zero user intervention
- **State Management**: New `.taskmaster/state.json` tracks current tag, last switched time, migration status and more.
- **Configuration Integration**: Enhanced `.taskmaster/config.json` with tag-specific settings and defaults.
By default, your existing task list will be migrated to the `master` tag.
**🚀 Complete Tag Management Suite:**
**Core Tag Commands:**
- `task-master tags [--show-metadata]` - List all tags with task counts, completion stats, and metadata
- `task-master add-tag <name> [options]` - Create new tag contexts with optional task copying
- `task-master delete-tag <name> [--yes]` - Delete tags (and attached tasks) with double confirmation protection
- `task-master use-tag <name>` - Switch contexts and immediately see next available task
- `task-master rename-tag <old> <new>` - Rename tags with automatic current tag reference updates
- `task-master copy-tag <source> <target> [options]` - Duplicate tag contexts for experimentation
**🤖 Full MCP Integration for Tag Management:**
Task Master's multi-context capabilities are now fully exposed through the MCP server, enabling powerful agentic workflows:
- **`list_tags`**: List all available tag contexts.
- **`add_tag`**: Programmatically create new tags.
- **`delete_tag`**: Remove tag contexts.
- **`use_tag`**: Switch the agent's active task context.
- **`rename_tag`**: Rename existing tags.
- **`copy_tag`**: Duplicate entire task contexts for experimentation.
**Tag Creation Options:**
- `--copy-from-current` - Copy tasks from currently active tag
- `--copy-from=<tag>` - Copy tasks from specific tag
- `--from-branch` - Creates a new tag using the active git branch name (for `add-tag` only)
- `--description="<text>"` - Add custom tag descriptions
- Empty tag creation for fresh contexts
**🎯 Universal --tag Flag Support:**
Every task operation now supports tag-specific execution:
- `task-master list --tag=feature-branch` - View tasks in specific context
- `task-master add-task --tag=experiment --prompt="..."` - Create tasks in specific tag
- `task-master parse-prd document.txt --tag=v2-redesign` - Parse PRDs into dedicated contexts
- `task-master analyze-complexity --tag=performance-work` - Generate tag-specific reports
- `task-master set-status --tag=hotfix --id=5 --status=done` - Update tasks in specific contexts
- `task-master expand --tag=research --id=3` - Break down tasks within tag contexts
This way you or your agent can store out of context tasks into the appropriate tags for later, allowing you to maintain a groomed and scoped master list. Focus on value, not chores.
**📊 Enhanced Workflow Features:**
**Smart Context Switching:**
- `use-tag` command shows immediate next task after switching
- Automatic tag creation when targeting non-existent tags
- Current tag persistence across terminal sessions
- Branch-tag mapping for future Git integration
**Intelligent File Management:**
- Tag-specific complexity reports: `task-complexity-report_tagname.json`
- Master tag uses default filenames: `task-complexity-report.json`
- Automatic file isolation prevents cross-tag contamination
**Advanced Confirmation Logic:**
- Commands only prompt when target tag has existing tasks
- Empty tags allow immediate operations without confirmation
- Smart append vs overwrite detection
**🔄 Seamless Migration & Compatibility:**
**Zero-Disruption Migration:**
- Existing `tasks.json` files automatically migrate on first command
- Master tag receives proper metadata (creation date, description)
- Migration notice shown once with helpful explanation
- All existing commands work identically to before
**State Management:**
- `.taskmaster/state.json` tracks current tag and migration status
- Automatic state creation and maintenance
- Branch-tag mapping foundation for Git integration
- Migration notice tracking to avoid repeated notifications
- Grounds for future context additions
**Backward Compatibility:**
- All existing workflows continue unchanged
- Legacy commands work exactly as before
- Gradual adoption - users can ignore tags entirely if desired
- No breaking changes to existing tasks or file formats
**💡 Real-World Use Cases:**
**Team Collaboration:**
- `task-master add-tag alice --copy-from-current` - Create teammate-specific contexts
- `task-master add-tag bob --copy-from=master` - Onboard new team members
- `task-master use-tag alice` - Switch to teammate's work context
**Feature Development:**
- `task-master parse-prd feature-spec.txt --tag=user-auth` - Dedicated feature planning
- `task-master add-tag experiment --copy-from=user-auth` - Safe experimentation
- `task-master analyze-complexity --tag=user-auth` - Feature-specific analysis
**Release Management:**
- `task-master add-tag v2.0 --description="Next major release"` - Version-specific planning
- `task-master copy-tag master v2.1` - Release branch preparation
- `task-master use-tag hotfix` - Emergency fix context
**Project Phases:**
- `task-master add-tag research --description="Discovery phase"` - Research tasks
- `task-master add-tag implementation --copy-from=research` - Development phase
- `task-master add-tag testing --copy-from=implementation` - QA phase
**🛠️ Technical Implementation:**
**Data Structure:**
- Tagged format with complete isolation between contexts
- Rich metadata per tag (creation date, description, update tracking)
- Automatic metadata enhancement for existing tags
- Clean separation of tag data and internal state
**Performance Optimizations:**
- Dynamic task counting without stored counters
- Efficient tag resolution and caching
- Minimal file I/O with smart data loading
- Responsive table layouts adapting to terminal width
**Error Handling:**
- Comprehensive validation for tag names (alphanumeric, hyphens, underscores)
- Reserved name protection (master, main, default)
- Graceful handling of missing tags and corrupted data
- Detailed error messages with suggested corrections
This release establishes the foundation for advanced multi-context workflows while maintaining the simplicity and power that makes Task Master effective for individual developers.

View File

@@ -1,25 +0,0 @@
---
"task-master-ai": minor
---
Research Save-to-File Feature & Critical MCP Tag Corruption Fix
**🔬 New Research Save-to-File Functionality:**
Added comprehensive save-to-file capability to the research command, enabling users to preserve research sessions for future reference and documentation.
**CLI Integration:**
- New `--save-file` flag for `task-master research` command
- Consistent with existing `--save` and `--save-to` flags for intuitive usage
- Interactive "Save to file" option in follow-up questions menu
**MCP Integration:**
- New `saveToFile` boolean parameter for the `research` MCP tool
- Enables programmatic research saving for AI agents and integrated tools
**File Management:**
- Automatically creates `.taskmaster/docs/research/` directory structure
- Generates timestamped, slugified filenames (e.g., `2025-01-13_what-is-typescript.md`)
- Comprehensive Markdown format with metadata headers including query, timestamp, and context sources
- Clean conversation history formatting without duplicate information

View File

@@ -1,6 +0,0 @@
---
"task-master-ai": minor
---
No longer automatically creates individual task files as they are not used by the applicatoin. You can still generate them anytime using the `generate` command.

View File

@@ -0,0 +1,11 @@
---
"task-master-ai": patch
---
Improve provider validation system with clean constants structure
- **Fixed "Invalid provider hint" errors**: Resolved validation failures for Azure, Vertex, and Bedrock providers
- **Improved search UX**: Integrated search for better model discovery with real-time filtering
- **Better organization**: Moved custom provider options to bottom of model selection with clear section separators
This change ensures all custom providers (Azure, Vertex, Bedrock, OpenRouter, Ollama) work correctly in `task-master models --setup`

View File

@@ -1,20 +0,0 @@
---
'task-master-ai': minor
---
Enhanced get-task/show command to support comma-separated task IDs for efficient batch operations
**New Features:**
- **Multiple Task Retrieval**: Pass comma-separated IDs to get/show multiple tasks at once (e.g., `task-master show 1,3,5` or MCP `get_task` with `id: "1,3,5"`)
- **Smart Display Logic**: Single ID shows detailed view, multiple IDs show compact summary table with interactive options
- **Batch Action Menu**: Interactive menu for multiple tasks with copy-paste ready commands for common operations (mark as done/in-progress, expand all, view dependencies, etc.)
- **MCP Array Response**: MCP tool returns structured array of task objects for efficient AI agent context gathering
**Benefits:**
- **Faster Context Gathering**: AI agents can collect multiple tasks/subtasks in one call instead of iterating
- **Improved Workflow**: Interactive batch operations reduce repetitive command execution
- **Better UX**: Responsive layout adapts to terminal width, maintains consistency with existing UI patterns
- **API Efficiency**: RESTful array responses in MCP format enable more sophisticated integrations
This enhancement maintains full backward compatibility while significantly improving efficiency for both human users and AI agents working with multiple tasks.

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix weird `task-master init` bug when using in certain environments

View File

@@ -1,8 +0,0 @@
---
"task-master-ai": minor
---
Adds support for filtering tasks by multiple statuses at once using comma-separated statuses.
Example: `cancelled,deferred`

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Improve mcp keys check in cursor

View File

@@ -1,6 +0,0 @@
---
"task-master-ai": patch
---
Improves dependency management when moving tasks by updating subtask dependencies that reference sibling subtasks by their old parent-based ID

View File

@@ -0,0 +1,22 @@
---
"task-master-ai": minor
---
Add Claude Code provider support
Introduces a new provider that enables using Claude models (Opus and Sonnet) through the Claude Code CLI without requiring an API key.
Key features:
- New claude-code provider with support for opus and sonnet models
- No API key required - uses local Claude Code CLI installation
- Optional dependency - won't affect users who don't need Claude Code
- Lazy loading ensures the provider only loads when requested
- Full integration with existing Task Master commands and workflows
- Comprehensive test coverage for reliability
- New --claude-code flag for the models command
Users can now configure Claude Code models with:
task-master models --set-main sonnet --claude-code
task-master models --set-research opus --claude-code
The @anthropic-ai/claude-code package is optional and won't be installed unless explicitly needed.

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
Adds tag to CLI and MCP outputs/responses so you know which tag you are performing operations on.

View File

@@ -33,6 +33,7 @@ All your standard command executions should operate on the user's current task c
For new projects or when users are getting started, operate within the `master` tag context:
- Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input='<prd-file.txt>'` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to generate initial tasks.json with tagged structure
- Configure rule sets during initialization with `--rules` flag (e.g., `task-master init --rules cursor,windsurf`) or manage them later with `task-master rules add/remove` commands
- Begin coding sessions with `get_tasks` / `task-master list` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to see current tasks, status, and IDs
- Determine the next task to work on using `next_task` / `task-master next` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc))
- Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) before breaking down tasks
@@ -294,6 +295,17 @@ Taskmaster configuration is managed through two main mechanisms:
**If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.cursor/mcp.json`.
**If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project.
## Rules Management
Taskmaster supports multiple AI coding assistant rule sets that can be configured during project initialization or managed afterward:
- **Available Profiles**: Claude Code, Cline, Codex, Cursor, Roo Code, Trae, Windsurf (claude, cline, codex, cursor, roo, trae, windsurf)
- **During Initialization**: Use `task-master init --rules cursor,windsurf` to specify which rule sets to include
- **After Initialization**: Use `task-master rules add <profiles>` or `task-master rules remove <profiles>` to manage rule sets
- **Interactive Setup**: Use `task-master rules setup` to launch an interactive prompt for selecting rule profiles
- **Default Behavior**: If no `--rules` flag is specified during initialization, all available rule profiles are included
- **Rule Structure**: Each profile creates its own directory (e.g., `.cursor/rules`, `.roo/rules`) with appropriate configuration files
## Determining the Next Task
- Run `next_task` / `task-master next` to show the next task to work on.

View File

@@ -554,4 +554,4 @@ Environment variables are used **only** for sensitive API keys related to AI pro
---
For details on how these commands fit into the development process, see the [Development Workflow Guide](mdc:.cursor/rules/dev_workflow.mdc).
For details on how these commands fit into the development process, see the [Development Workflow Guide](mdc:.cursor/rules/dev_workflow.mdc).

10
.gitignore vendored
View File

@@ -77,3 +77,13 @@ dev-debug.log
# NPMRC
.npmrc
# Added by Task Master AI
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,14 +1,14 @@
{
"models": {
"main": {
"provider": "anthropic",
"modelId": "claude-sonnet-4-20250514",
"provider": "vertex",
"modelId": "gemini-1.5-pro-002",
"maxTokens": 50000,
"temperature": 0.2
},
"research": {
"provider": "perplexity",
"modelId": "sonar-pro",
"modelId": "sonar",
"maxTokens": 8700,
"temperature": 0.1
},
@@ -20,7 +20,6 @@
}
},
"global": {
"userId": "1234567890",
"logLevel": "info",
"debug": false,
"defaultSubtasks": 5,
@@ -28,6 +27,7 @@
"projectName": "Taskmaster",
"ollamaBaseURL": "http://localhost:11434/api",
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
"userId": "1234567890",
"azureBaseURL": "https://your-endpoint.azure.com/",
"defaultTag": "master"
}

View File

@@ -1,10 +1,16 @@
# task-master-ai
## 0.17.0-rc.1
## 0.17.1
### Patch Changes
- [#789](https://github.com/eyaltoledano/claude-task-master/pull/789) [`8cde6c2`](https://github.com/eyaltoledano/claude-task-master/commit/8cde6c27087f401d085fe267091ae75334309d96) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix contextGatherer bug when adding a task `Cannot read properties of undefined (reading 'forEach')`
## 0.17.0
### Minor Changes
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add comprehensive AI-powered research command with intelligent context gathering and interactive follow-ups.
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Add comprehensive AI-powered research command with intelligent context gathering and interactive follow-ups.
The new `research` command provides AI-powered research capabilities that automatically gather relevant project context to answer your questions. The command intelligently selects context from multiple sources and supports interactive follow-up questions in CLI mode.
@@ -78,7 +84,7 @@
The research command integrates with the existing AI service layer and supports all configured AI providers. Both CLI and MCP interfaces provide comprehensive research capabilities with intelligent context gathering and flexible output options.
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhance update-task with --append flag for timestamped task updates
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Enhance update-task with --append flag for timestamped task updates
Adds the `--append` flag to `update-task` command, enabling it to behave like `update-subtask` with timestamped information appending. This provides more flexible task updating options:
@@ -95,7 +101,7 @@
Instead of requiring separate subtask creation for progress tracking, you can now append timestamped information directly to parent tasks while preserving the option for comprehensive task updates.
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add --tag flag support to core commands for multi-context task management. Commands like parse-prd, analyze-complexity, and others now support targeting specific task lists, enabling rapid prototyping and parallel development workflows.
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Add --tag flag support to core commands for multi-context task management. Commands like parse-prd, analyze-complexity, and others now support targeting specific task lists, enabling rapid prototyping and parallel development workflows.
Key features:
@@ -104,19 +110,21 @@
- All task operations can target specific contexts while preserving other lists
- Non-existent tags are created automatically for seamless workflow
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Introduces Tagged Lists: AI Multi-Context Task Management System
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Introduces Tagged Lists: AI Multi-Context Task Management System
This major release introduces Tagged Lists, a comprehensive system that transforms Task Master into a multi-context task management powerhouse. You can now organize tasks into completely isolated contexts, enabling parallel (agentic) development workflows, team collaboration, and project experimentation without conflicts.
This major feature release introduces Tagged Lists, a comprehensive system that transforms Taskmaster into a multi-context task management powerhouse. You can now organize tasks into completely isolated contexts, enabling parallel (agentic) development workflows, team collaboration, and project experimentation without conflicts.
**🏷️ Tagged Task Lists Architecture:**
The new tagged system fundamentally changes how tasks are organized:
The new tagged system fundamentally improves how tasks are organized:
- **Legacy Format**: `{ "tasks": [...] }`
- **New Tagged Format**: `{ "master": { "tasks": [...], "metadata": {...} }, "feature-xyz": { "tasks": [...], "metadata": {...} } }`
- **Automatic Migration**: Existing projects seamlessly migrate to tagged format with zero user intervention
- **State Management**: New `.taskmaster/state.json` tracks current tag, last switched time, and migration status
- **Configuration Integration**: Enhanced `.taskmaster/config.json` with tag-specific settings and defaults
- **Automatic Migration**: Existing projects will seamlessly migrate to tagged format with zero user intervention
- **State Management**: New `.taskmaster/state.json` tracks current tag, last switched time, migration status and more.
- **Configuration Integration**: Enhanced `.taskmaster/config.json` with tag-specific settings and defaults.
By default, your existing task list will be migrated to the `master` tag.
**🚀 Complete Tag Management Suite:**
@@ -124,7 +132,7 @@
- `task-master tags [--show-metadata]` - List all tags with task counts, completion stats, and metadata
- `task-master add-tag <name> [options]` - Create new tag contexts with optional task copying
- `task-master delete-tag <name> [--yes]` - Delete tags with double confirmation protection
- `task-master delete-tag <name> [--yes]` - Delete tags (and attached tasks) with double confirmation protection
- `task-master use-tag <name>` - Switch contexts and immediately see next available task
- `task-master rename-tag <old> <new>` - Rename tags with automatic current tag reference updates
- `task-master copy-tag <source> <target> [options]` - Duplicate tag contexts for experimentation
@@ -144,7 +152,7 @@
- `--copy-from-current` - Copy tasks from currently active tag
- `--copy-from=<tag>` - Copy tasks from specific tag
- `--from-branch` - Creates a new tag usin active git branch name (for `add-tag` only)
- `--from-branch` - Creates a new tag using the active git branch name (for `add-tag` only)
- `--description="<text>"` - Add custom tag descriptions
- Empty tag creation for fresh contexts
@@ -159,6 +167,8 @@
- `task-master set-status --tag=hotfix --id=5 --status=done` - Update tasks in specific contexts
- `task-master expand --tag=research --id=3` - Break down tasks within tag contexts
This way you or your agent can store out of context tasks into the appropriate tags for later, allowing you to maintain a groomed and scoped master list. Focus on value, not chores.
**📊 Enhanced Workflow Features:**
**Smart Context Switching:**
@@ -255,7 +265,7 @@
This release establishes the foundation for advanced multi-context workflows while maintaining the simplicity and power that makes Task Master effective for individual developers.
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Research Save-to-File Feature & Critical MCP Tag Corruption Fix
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Research Save-to-File Feature & Critical MCP Tag Corruption Fix
**🔬 New Research Save-to-File Functionality:**
@@ -279,9 +289,9 @@
- Comprehensive Markdown format with metadata headers including query, timestamp, and context sources
- Clean conversation history formatting without duplicate information
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - No longer automatically creates individual task files as they are not used by the applicatoin. You can still generate them anytime using the `generate` command.
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - No longer automatically creates individual task files as they are not used by the applicatoin. You can still generate them anytime using the `generate` command.
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhanced get-task/show command to support comma-separated task IDs for efficient batch operations
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Enhanced get-task/show command to support comma-separated task IDs for efficient batch operations
**New Features:**
@@ -299,17 +309,17 @@
This enhancement maintains full backward compatibility while significantly improving efficiency for both human users and AI agents working with multiple tasks.
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Adds support for filtering tasks by multiple statuses at once using comma-separated statuses.
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Adds support for filtering tasks by multiple statuses at once using comma-separated statuses.
Example: `cancelled,deferred`
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Adds tag to CLI output so you know which tag you are performing operations on. Already supported in the MCP response.
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Adds tag to CLI and MCP outputs/responses so you know which tag you are performing operations on.
### Patch Changes
- [#723](https://github.com/eyaltoledano/claude-task-master/pull/723) [`40a5238`](https://github.com/eyaltoledano/claude-task-master/commit/40a52385baf43a5ed97ce29bc5c4fb3fea766b70) Thanks [@joedanz](https://github.com/joedanz)! - Fix Cursor deeplink installation by providing copy-paste instructions for GitHub compatibility
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`5ec1f61`](https://github.com/eyaltoledano/claude-task-master/commit/5ec1f61c13f468648b7fdc8fa112e95aec25f76d) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Fix Cursor deeplink installation by providing copy-paste instructions for GitHub compatibility
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix critical bugs in task move functionality:
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Fix critical bugs in task move functionality:
- **Fixed moving tasks to become subtasks of empty parents**: When moving a task to become a subtask of a parent that had no existing subtasks (e.g., task 89 → task 98.1), the operation would fail with validation errors.
- **Fixed moving subtasks between parents**: Subtasks can now be properly moved between different parent tasks, including to parents that previously had no subtasks.
@@ -317,80 +327,21 @@
These fixes enables proper task hierarchy reorganization for corner cases that were previously broken.
- [#695](https://github.com/eyaltoledano/claude-task-master/pull/695) [`1ece6f1`](https://github.com/eyaltoledano/claude-task-master/commit/1ece6f19048df6ae2a0b25cbfb84d2c0f430642c) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - improve findTasks algorithm for resolving tasks path
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`d76bea4`](https://github.com/eyaltoledano/claude-task-master/commit/d76bea49b381c523183f39e33c2a4269371576ed) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Update o3 model price
- [#695](https://github.com/eyaltoledano/claude-task-master/pull/695) [`ee0be04`](https://github.com/eyaltoledano/claude-task-master/commit/ee0be04302cc602246de5cd296291db69bc8b300) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix update tool on MCP giving `No valid tasks found`
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Adds ability to automatically create/switch tags to match the current git branch. The configuration to enable the git workflow and then use the auto switching is in config.json."
- [#699](https://github.com/eyaltoledano/claude-task-master/pull/699) [`27edbd8`](https://github.com/eyaltoledano/claude-task-master/commit/27edbd8f3fe5e2ac200b80e7f27f4c0e74a074d6) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Enhanced add-task fuzzy search intelligence and improved user experience
**Smarter Task Discovery:**
- Remove hardcoded category system that always matched "Task management"
- Eliminate arbitrary limits on fuzzy search results (5→25 high relevance, 3→10 medium relevance, 8→20 detailed tasks)
- Improve semantic weighting in Fuse.js search (details=3, description=2, title=1.5) for better relevance
- Generate context-driven task recommendations based on true semantic similarity
**Enhanced Terminal Experience:**
- Fix duplicate banner display issue that was "eating" terminal history (closes #553)
- Remove console.clear() and redundant displayBanner() calls from UI functions
- Preserve command history for better development workflow
- Streamline banner display across all commands (list, next, show, set-status, clear-subtasks, dependency commands)
**Visual Improvements:**
- Replace emoji complexity indicators with clean filled circle characters (●) for professional appearance
- Improve consistency and readability of task complexity display
**AI Provider Compatibility:**
- Change generateObject mode from 'tool' to 'auto' for better cross-provider compatibility
- Add qwen3-235n-a22b:free model support (closes #687)
- Add smart warnings for free OpenRouter models with limitations (rate limits, restricted context, no tool_use)
**Technical Improvements:**
- Enhanced context generation in add-task to rely on semantic similarity rather than rigid pattern matching
- Improved dependency analysis and common pattern detection
- Better handling of task relationships and relevance scoring
- More intelligent task suggestion algorithms
The add-task system now provides truly relevant task context based on semantic understanding rather than arbitrary categories and limits, while maintaining a cleaner and more professional terminal experience.
- [#751](https://github.com/eyaltoledano/claude-task-master/pull/751) [`4901908`](https://github.com/eyaltoledano/claude-task-master/commit/4901908f5d1f5c39e82a23d650047074691deda4) Thanks [@zahorniak](https://github.com/zahorniak)! - Update o3 model price
- [#755](https://github.com/eyaltoledano/claude-task-master/pull/755) [`dc7a541`](https://github.com/eyaltoledano/claude-task-master/commit/dc7a5414c0364a87412192ac01ccc49cf7b68768) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fixes issue with expand CLI command "Complexity report not found"
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`0849c0c`](https://github.com/eyaltoledano/claude-task-master/commit/0849c0c2cedb16ac44ba5cc2d109625a9b4efd67) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Fixes issue with expand CLI command "Complexity report not found"
- Closes #735
- Closes #728
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix issue with generate command which was creating tasks in the legacy tasks location.
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Fix issue with generate command which was creating tasks in the legacy tasks location.
- [#771](https://github.com/eyaltoledano/claude-task-master/pull/771) [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improves dependency management when moving tasks by updating subtask dependencies that reference sibling subtasks by their old parent-based ID
- No longer creates individual task files automatically. You can still use `generate` if you need to create our update your task files.
- [#699](https://github.com/eyaltoledano/claude-task-master/pull/699) [`2e55757`](https://github.com/eyaltoledano/claude-task-master/commit/2e55757b2698ba20b78f09ec0286951297510b8e) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Add sync-readme command for a task export to GitHub README
- [#779](https://github.com/eyaltoledano/claude-task-master/pull/779) [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Improves dependency management when moving tasks by updating subtask dependencies that reference sibling subtasks by their old parent-based ID
Introduces a new `sync-readme` command that exports your task list to your project's README.md file.
**Features:**
- **Flexible filtering**: Supports `--status` filtering (e.g., pending, done) and `--with-subtasks` flag
- **Smart content management**: Automatically replaces existing exports or appends to new READMEs
- **Metadata display**: Shows export timestamp, subtask inclusion status, and filter settings
**Usage:**
- `task-master sync-readme` - Export tasks without subtasks
- `task-master sync-readme --with-subtasks` - Include subtasks in export
- `task-master sync-readme --status=pending` - Only export pending tasks
- `task-master sync-readme --status=done --with-subtasks` - Export completed tasks with subtasks
Perfect for showcasing project progress on GitHub. Experimental. Open to feedback.
- Updated dependencies [[`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`40a5238`](https://github.com/eyaltoledano/claude-task-master/commit/40a52385baf43a5ed97ce29bc5c4fb3fea766b70), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`1ece6f1`](https://github.com/eyaltoledano/claude-task-master/commit/1ece6f19048df6ae2a0b25cbfb84d2c0f430642c), [`ee0be04`](https://github.com/eyaltoledano/claude-task-master/commit/ee0be04302cc602246de5cd296291db69bc8b300), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`27edbd8`](https://github.com/eyaltoledano/claude-task-master/commit/27edbd8f3fe5e2ac200b80e7f27f4c0e74a074d6), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`4901908`](https://github.com/eyaltoledano/claude-task-master/commit/4901908f5d1f5c39e82a23d650047074691deda4), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`dc7a541`](https://github.com/eyaltoledano/claude-task-master/commit/dc7a5414c0364a87412192ac01ccc49cf7b68768), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4), [`2e55757`](https://github.com/eyaltoledano/claude-task-master/commit/2e55757b2698ba20b78f09ec0286951297510b8e), [`7250241`](https://github.com/eyaltoledano/claude-task-master/commit/72502416c6969055e0d139e408e726e03371c4f4)]:
- task-master-ai@0.17.0-rc.1
- Updated dependencies [[`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`5ec1f61`](https://github.com/eyaltoledano/claude-task-master/commit/5ec1f61c13f468648b7fdc8fa112e95aec25f76d), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`d76bea4`](https://github.com/eyaltoledano/claude-task-master/commit/d76bea49b381c523183f39e33c2a4269371576ed), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`0849c0c`](https://github.com/eyaltoledano/claude-task-master/commit/0849c0c2cedb16ac44ba5cc2d109625a9b4efd67), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f), [`c0b3f43`](https://github.com/eyaltoledano/claude-task-master/commit/c0b3f432a60891550b00acb113dc877bd432995f)]:
- task-master-ai@0.17.0
## 0.16.2

View File

@@ -47,8 +47,9 @@ At least one (1) of the following is required:
- Perplexity API key (for research model)
- xAI API Key (for research or main model)
- OpenRouter API Key (for research or main model)
- Claude Code (no API key required - requires Claude Code CLI)
Using the research model is optional but highly recommended. You will need at least ONE API key. Adding all API keys enables you to seamlessly switch between model providers at will.
Using the research model is optional but highly recommended. You will need at least ONE API key (unless using Claude Code). Adding all API keys enables you to seamlessly switch between model providers at will.
## Quick Start
@@ -93,6 +94,8 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
> 🔑 Replace `YOUR_…_KEY_HERE` with your real API keys. You can remove keys you don't use.
> **Note**: If you see `0 tools enabled` in the MCP settings, try removing the `--package=task-master-ai` flag from `args`.
###### VSCode (`servers` + `type`)
```json
@@ -131,7 +134,12 @@ In your editor's AI chat pane, say:
Change the main, research and fallback models to <model_name>, <model_name> and <model_name> respectively.
```
[Table of available models](docs/models.md)
For example, to use Claude Code (no API key required):
```txt
Change the main model to claude-code/sonnet
```
[Table of available models](docs/models.md) | [Claude Code setup](docs/examples/claude-code-usage.md)
#### 4. Initialize Task Master
@@ -189,6 +197,9 @@ task-master init
# If installed locally
npx task-master init
# Initialize project with specific rules
task-master init --rules cursor,windsurf,vscode
```
This will prompt you for project details and set up a new project with the necessary files and structure.
@@ -216,8 +227,21 @@ task-master research "What are the latest best practices for JWT authentication?
# Generate task files
task-master generate
# Add rules after initialization
task-master rules add windsurf,roo,vscode
```
## Claude Code Support
Task Master now supports Claude models through the Claude Code CLI, which requires no API key:
- **Models**: `claude-code/opus` and `claude-code/sonnet`
- **Requirements**: Claude Code CLI installed
- **Benefits**: No API key needed, uses your local Claude instance
[Learn more about Claude Code setup](docs/examples/claude-code-usage.md)
## Troubleshooting
### If `task-master init` doesn't respond

View File

@@ -1,8 +1,8 @@
{
"customModes": [
{
"slug": "boomerang",
"name": "Boomerang",
"slug": "orchestrator",
"name": "Orchestrator",
"roleDefinition": "You are Roo, a strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized modes. You have a comprehensive understanding of each mode's capabilities and limitations, also your own, and with the information given by the user and other modes in shared context you are enabled to effectively break down complex problems into discrete tasks that can be solved by different specialists using the `taskmaster-ai` system for task and context management.",
"customInstructions": "Your role is to coordinate complex workflows by delegating tasks to specialized modes, using `taskmaster-ai` as the central hub for task definition, progress tracking, and context management. \nAs an orchestrator, you should:\nn1. When given a complex task, use contextual information (which gets updated frequently) to break it down into logical subtasks that can be delegated to appropriate specialized modes.\nn2. For each subtask, use the `new_task` tool to delegate. Choose the most appropriate mode for the subtask's specific goal and provide comprehensive instructions in the `message` parameter. \nThese instructions must include:\n* All necessary context from the parent task or previous subtasks required to complete the work.\n* A clearly defined scope, specifying exactly what the subtask should accomplish.\n* An explicit statement that the subtask should *only* perform the work outlined in these instructions and not deviate.\n* An instruction for the subtask to signal completion by using the `attempt_completion` tool, providing a thorough summary of the outcome in the `result` parameter, keeping in mind that this summary will be the source of truth used to further relay this information to other tasks and for you to keep track of what was completed on this project.\nn3. Track and manage the progress of all subtasks. When a subtask is completed, acknowledge its results and determine the next steps.\nn4. Help the user understand how the different subtasks fit together in the overall workflow. Provide clear reasoning about why you're delegating specific tasks to specific modes.\nn5. Ask clarifying questions when necessary to better understand how to break down complex tasks effectively. If it seems complex delegate to architect to accomplish that \nn6. Use subtasks to maintain clarity. If a request significantly shifts focus or requires a different expertise (mode), consider creating a subtask rather than overloading the current one.",
"groups": [

View File

@@ -0,0 +1,53 @@
---
description: Guidelines for creating and maintaining Cursor rules to ensure consistency and effectiveness.
globs: .cursor/rules/*.mdc
alwaysApply: true
---
- **Required Rule Structure:**
```markdown
---
description: Clear, one-line description of what the rule enforces
globs: path/to/files/*.ext, other/path/**/*
alwaysApply: boolean
---
- **Main Points in Bold**
- Sub-points with details
- Examples and explanations
```
- **File References:**
- Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files
- Example: [prisma.mdc](mdc:.cursor/rules/prisma.mdc) for rule references
- Example: [schema.prisma](mdc:prisma/schema.prisma) for code references
- **Code Examples:**
- Use language-specific code blocks
```typescript
// ✅ DO: Show good examples
const goodExample = true;
// ❌ DON'T: Show anti-patterns
const badExample = false;
```
- **Rule Content Guidelines:**
- Start with high-level overview
- Include specific, actionable requirements
- Show examples of correct implementation
- Reference existing code when possible
- Keep rules DRY by referencing other rules
- **Rule Maintenance:**
- Update rules when new patterns emerge
- Add examples from actual codebase
- Remove outdated patterns
- Cross-reference related rules
- **Best Practices:**
- Use bullet points for clarity
- Keep descriptions concise
- Include both DO and DON'T examples
- Reference actual code over theoretical examples
- Use consistent formatting across rules

View File

@@ -0,0 +1,424 @@
---
description: Guide for using Taskmaster to manage task-driven development workflows
globs: **/*
alwaysApply: true
---
# Taskmaster Development Workflow
This guide outlines the standard process for using Taskmaster to manage software development projects. It is written as a set of instructions for you, the AI agent.
- **Your Default Stance**: For most projects, the user can work directly within the `master` task context. Your initial actions should operate on this default context unless a clear pattern for multi-context work emerges.
- **Your Goal**: Your role is to elevate the user's workflow by intelligently introducing advanced features like **Tagged Task Lists** when you detect the appropriate context. Do not force tags on the user; suggest them as a helpful solution to a specific need.
## The Basic Loop
The fundamental development cycle you will facilitate is:
1. **`list`**: Show the user what needs to be done.
2. **`next`**: Help the user decide what to work on.
3. **`show <id>`**: Provide details for a specific task.
4. **`expand <id>`**: Break down a complex task into smaller, manageable subtasks.
5. **Implement**: The user writes the code and tests.
6. **`update-subtask`**: Log progress and findings on behalf of the user.
7. **`set-status`**: Mark tasks and subtasks as `done` as work is completed.
8. **Repeat**.
All your standard command executions should operate on the user's current task context, which defaults to `master`.
---
## Standard Development Workflow Process
### Simple Workflow (Default Starting Point)
For new projects or when users are getting started, operate within the `master` tag context:
- Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input='<prd-file.txt>'` (see @`taskmaster.mdc`) to generate initial tasks.json with tagged structure
- Configure rule sets during initialization with `--rules` flag (e.g., `task-master init --rules cursor,windsurf`) or manage them later with `task-master rules add/remove` commands
- Begin coding sessions with `get_tasks` / `task-master list` (see @`taskmaster.mdc`) to see current tasks, status, and IDs
- Determine the next task to work on using `next_task` / `task-master next` (see @`taskmaster.mdc`)
- Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see @`taskmaster.mdc`) before breaking down tasks
- Review complexity report using `complexity_report` / `task-master complexity-report` (see @`taskmaster.mdc`)
- Select tasks based on dependencies (all marked 'done'), priority level, and ID order
- View specific task details using `get_task` / `task-master show <id>` (see @`taskmaster.mdc`) to understand implementation requirements
- Break down complex tasks using `expand_task` / `task-master expand --id=<id> --force --research` (see @`taskmaster.mdc`) with appropriate flags like `--force` (to replace existing subtasks) and `--research`
- Implement code following task details, dependencies, and project standards
- Mark completed tasks with `set_task_status` / `task-master set-status --id=<id> --status=done` (see @`taskmaster.mdc`)
- Update dependent tasks when implementation differs from original plan using `update` / `task-master update --from=<id> --prompt="..."` or `update_task` / `task-master update-task --id=<id> --prompt="..."` (see @`taskmaster.mdc`)
---
## Leveling Up: Agent-Led Multi-Context Workflows
While the basic workflow is powerful, your primary opportunity to add value is by identifying when to introduce **Tagged Task Lists**. These patterns are your tools for creating a more organized and efficient development environment for the user, especially if you detect agentic or parallel development happening across the same session.
**Critical Principle**: Most users should never see a difference in their experience. Only introduce advanced workflows when you detect clear indicators that the project has evolved beyond simple task management.
### When to Introduce Tags: Your Decision Patterns
Here are the patterns to look for. When you detect one, you should propose the corresponding workflow to the user.
#### Pattern 1: Simple Git Feature Branching
This is the most common and direct use case for tags.
- **Trigger**: The user creates a new git branch (e.g., `git checkout -b feature/user-auth`).
- **Your Action**: Propose creating a new tag that mirrors the branch name to isolate the feature's tasks from `master`.
- **Your Suggested Prompt**: *"I see you've created a new branch named 'feature/user-auth'. To keep all related tasks neatly organized and separate from your main list, I can create a corresponding task tag for you. This helps prevent merge conflicts in your `tasks.json` file later. Shall I create the 'feature-user-auth' tag?"*
- **Tool to Use**: `task-master add-tag --from-branch`
#### Pattern 2: Team Collaboration
- **Trigger**: The user mentions working with teammates (e.g., "My teammate Alice is handling the database schema," or "I need to review Bob's work on the API.").
- **Your Action**: Suggest creating a separate tag for the user's work to prevent conflicts with shared master context.
- **Your Suggested Prompt**: *"Since you're working with Alice, I can create a separate task context for your work to avoid conflicts. This way, Alice can continue working with the master list while you have your own isolated context. When you're ready to merge your work, we can coordinate the tasks back to master. Shall I create a tag for your current work?"*
- **Tool to Use**: `task-master add-tag my-work --copy-from-current --description="My tasks while collaborating with Alice"`
#### Pattern 3: Experiments or Risky Refactors
- **Trigger**: The user wants to try something that might not be kept (e.g., "I want to experiment with switching our state management library," or "Let's refactor the old API module, but I want to keep the current tasks as a reference.").
- **Your Action**: Propose creating a sandboxed tag for the experimental work.
- **Your Suggested Prompt**: *"This sounds like a great experiment. To keep these new tasks separate from our main plan, I can create a temporary 'experiment-zustand' tag for this work. If we decide not to proceed, we can simply delete the tag without affecting the main task list. Sound good?"*
- **Tool to Use**: `task-master add-tag experiment-zustand --description="Exploring Zustand migration"`
#### Pattern 4: Large Feature Initiatives (PRD-Driven)
This is a more structured approach for significant new features or epics.
- **Trigger**: The user describes a large, multi-step feature that would benefit from a formal plan.
- **Your Action**: Propose a comprehensive, PRD-driven workflow.
- **Your Suggested Prompt**: *"This sounds like a significant new feature. To manage this effectively, I suggest we create a dedicated task context for it. Here's the plan: I'll create a new tag called 'feature-xyz', then we can draft a Product Requirements Document (PRD) together to scope the work. Once the PRD is ready, I'll automatically generate all the necessary tasks within that new tag. How does that sound?"*
- **Your Implementation Flow**:
1. **Create an empty tag**: `task-master add-tag feature-xyz --description "Tasks for the new XYZ feature"`. You can also start by creating a git branch if applicable, and then create the tag from that branch.
2. **Collaborate & Create PRD**: Work with the user to create a detailed PRD file (e.g., `.taskmaster/docs/feature-xyz-prd.txt`).
3. **Parse PRD into the new tag**: `task-master parse-prd .taskmaster/docs/feature-xyz-prd.txt --tag feature-xyz`
4. **Prepare the new task list**: Follow up by suggesting `analyze-complexity` and `expand-all` for the newly created tasks within the `feature-xyz` tag.
#### Pattern 5: Version-Based Development
Tailor your approach based on the project maturity indicated by tag names.
- **Prototype/MVP Tags** (`prototype`, `mvp`, `poc`, `v0.x`):
- **Your Approach**: Focus on speed and functionality over perfection
- **Task Generation**: Create tasks that emphasize "get it working" over "get it perfect"
- **Complexity Level**: Lower complexity, fewer subtasks, more direct implementation paths
- **Research Prompts**: Include context like "This is a prototype - prioritize speed and basic functionality over optimization"
- **Example Prompt Addition**: *"Since this is for the MVP, I'll focus on tasks that get core functionality working quickly rather than over-engineering."*
- **Production/Mature Tags** (`v1.0+`, `production`, `stable`):
- **Your Approach**: Emphasize robustness, testing, and maintainability
- **Task Generation**: Include comprehensive error handling, testing, documentation, and optimization
- **Complexity Level**: Higher complexity, more detailed subtasks, thorough implementation paths
- **Research Prompts**: Include context like "This is for production - prioritize reliability, performance, and maintainability"
- **Example Prompt Addition**: *"Since this is for production, I'll ensure tasks include proper error handling, testing, and documentation."*
### Advanced Workflow (Tag-Based & PRD-Driven)
**When to Transition**: Recognize when the project has evolved (or has initiated a project which existing code) beyond simple task management. Look for these indicators:
- User mentions teammates or collaboration needs
- Project has grown to 15+ tasks with mixed priorities
- User creates feature branches or mentions major initiatives
- User initializes Taskmaster on an existing, complex codebase
- User describes large features that would benefit from dedicated planning
**Your Role in Transition**: Guide the user to a more sophisticated workflow that leverages tags for organization and PRDs for comprehensive planning.
#### Master List Strategy (High-Value Focus)
Once you transition to tag-based workflows, the `master` tag should ideally contain only:
- **High-level deliverables** that provide significant business value
- **Major milestones** and epic-level features
- **Critical infrastructure** work that affects the entire project
- **Release-blocking** items
**What NOT to put in master**:
- Detailed implementation subtasks (these go in feature-specific tags' parent tasks)
- Refactoring work (create dedicated tags like `refactor-auth`)
- Experimental features (use `experiment-*` tags)
- Team member-specific tasks (use person-specific tags)
#### PRD-Driven Feature Development
**For New Major Features**:
1. **Identify the Initiative**: When user describes a significant feature
2. **Create Dedicated Tag**: `add_tag feature-[name] --description="[Feature description]"`
3. **Collaborative PRD Creation**: Work with user to create comprehensive PRD in `.taskmaster/docs/feature-[name]-prd.txt`
4. **Parse & Prepare**:
- `parse_prd .taskmaster/docs/feature-[name]-prd.txt --tag=feature-[name]`
- `analyze_project_complexity --tag=feature-[name] --research`
- `expand_all --tag=feature-[name] --research`
5. **Add Master Reference**: Create a high-level task in `master` that references the feature tag
**For Existing Codebase Analysis**:
When users initialize Taskmaster on existing projects:
1. **Codebase Discovery**: Use your native tools for producing deep context about the code base. You may use `research` tool with `--tree` and `--files` to collect up to date information using the existing architecture as context.
2. **Collaborative Assessment**: Work with user to identify improvement areas, technical debt, or new features
3. **Strategic PRD Creation**: Co-author PRDs that include:
- Current state analysis (based on your codebase research)
- Proposed improvements or new features
- Implementation strategy considering existing code
4. **Tag-Based Organization**: Parse PRDs into appropriate tags (`refactor-api`, `feature-dashboard`, `tech-debt`, etc.)
5. **Master List Curation**: Keep only the most valuable initiatives in master
The parse-prd's `--append` flag enables the user to parse multple PRDs within tags or across tags. PRDs should be focused and the number of tasks they are parsed into should be strategically chosen relative to the PRD's complexity and level of detail.
### Workflow Transition Examples
**Example 1: Simple → Team-Based**
```
User: "Alice is going to help with the API work"
Your Response: "Great! To avoid conflicts, I'll create a separate task context for your work. Alice can continue with the master list while you work in your own context. When you're ready to merge, we can coordinate the tasks back together."
Action: add_tag my-api-work --copy-from-current --description="My API tasks while collaborating with Alice"
```
**Example 2: Simple → PRD-Driven**
```
User: "I want to add a complete user dashboard with analytics, user management, and reporting"
Your Response: "This sounds like a major feature that would benefit from detailed planning. Let me create a dedicated context for this work and we can draft a PRD together to ensure we capture all requirements."
Actions:
1. add_tag feature-dashboard --description="User dashboard with analytics and management"
2. Collaborate on PRD creation
3. parse_prd dashboard-prd.txt --tag=feature-dashboard
4. Add high-level "User Dashboard" task to master
```
**Example 3: Existing Project → Strategic Planning**
```
User: "I just initialized Taskmaster on my existing React app. It's getting messy and I want to improve it."
Your Response: "Let me research your codebase to understand the current architecture, then we can create a strategic plan for improvements."
Actions:
1. research "Current React app architecture and improvement opportunities" --tree --files=src/
2. Collaborate on improvement PRD based on findings
3. Create tags for different improvement areas (refactor-components, improve-state-management, etc.)
4. Keep only major improvement initiatives in master
```
---
## Primary Interaction: MCP Server vs. CLI
Taskmaster offers two primary ways to interact:
1. **MCP Server (Recommended for Integrated Tools)**:
- For AI agents and integrated development environments (like Cursor), interacting via the **MCP server is the preferred method**.
- The MCP server exposes Taskmaster functionality through a set of tools (e.g., `get_tasks`, `add_subtask`).
- This method offers better performance, structured data exchange, and richer error handling compared to CLI parsing.
- Refer to @`mcp.mdc` for details on the MCP architecture and available tools.
- A comprehensive list and description of MCP tools and their corresponding CLI commands can be found in @`taskmaster.mdc`.
- **Restart the MCP server** if core logic in `scripts/modules` or MCP tool/direct function definitions change.
- **Note**: MCP tools fully support tagged task lists with complete tag management capabilities.
2. **`task-master` CLI (For Users & Fallback)**:
- The global `task-master` command provides a user-friendly interface for direct terminal interaction.
- It can also serve as a fallback if the MCP server is inaccessible or a specific function isn't exposed via MCP.
- Install globally with `npm install -g task-master-ai` or use locally via `npx task-master-ai ...`.
- The CLI commands often mirror the MCP tools (e.g., `task-master list` corresponds to `get_tasks`).
- Refer to @`taskmaster.mdc` for a detailed command reference.
- **Tagged Task Lists**: CLI fully supports the new tagged system with seamless migration.
## How the Tag System Works (For Your Reference)
- **Data Structure**: Tasks are organized into separate contexts (tags) like "master", "feature-branch", or "v2.0".
- **Silent Migration**: Existing projects automatically migrate to use a "master" tag with zero disruption.
- **Context Isolation**: Tasks in different tags are completely separate. Changes in one tag do not affect any other tag.
- **Manual Control**: The user is always in control. There is no automatic switching. You facilitate switching by using `use-tag <name>`.
- **Full CLI & MCP Support**: All tag management commands are available through both the CLI and MCP tools for you to use. Refer to @`taskmaster.mdc` for a full command list.
---
## Task Complexity Analysis
- Run `analyze_project_complexity` / `task-master analyze-complexity --research` (see @`taskmaster.mdc`) for comprehensive analysis
- Review complexity report via `complexity_report` / `task-master complexity-report` (see @`taskmaster.mdc`) for a formatted, readable version.
- Focus on tasks with highest complexity scores (8-10) for detailed breakdown
- Use analysis results to determine appropriate subtask allocation
- Note that reports are automatically used by the `expand_task` tool/command
## Task Breakdown Process
- Use `expand_task` / `task-master expand --id=<id>`. It automatically uses the complexity report if found, otherwise generates default number of subtasks.
- Use `--num=<number>` to specify an explicit number of subtasks, overriding defaults or complexity report recommendations.
- Add `--research` flag to leverage Perplexity AI for research-backed expansion.
- Add `--force` flag to clear existing subtasks before generating new ones (default is to append).
- Use `--prompt="<context>"` to provide additional context when needed.
- Review and adjust generated subtasks as necessary.
- Use `expand_all` tool or `task-master expand --all` to expand multiple pending tasks at once, respecting flags like `--force` and `--research`.
- If subtasks need complete replacement (regardless of the `--force` flag on `expand`), clear them first with `clear_subtasks` / `task-master clear-subtasks --id=<id>`.
## Implementation Drift Handling
- When implementation differs significantly from planned approach
- When future tasks need modification due to current implementation choices
- When new dependencies or requirements emerge
- Use `update` / `task-master update --from=<futureTaskId> --prompt='<explanation>\nUpdate context...' --research` to update multiple future tasks.
- Use `update_task` / `task-master update-task --id=<taskId> --prompt='<explanation>\nUpdate context...' --research` to update a single specific task.
## Task Status Management
- Use 'pending' for tasks ready to be worked on
- Use 'done' for completed and verified tasks
- Use 'deferred' for postponed tasks
- Add custom status values as needed for project-specific workflows
## Task Structure Fields
- **id**: Unique identifier for the task (Example: `1`, `1.1`)
- **title**: Brief, descriptive title (Example: `"Initialize Repo"`)
- **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`)
- **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`)
- **dependencies**: IDs of prerequisite tasks (Example: `[1, 2.1]`)
- Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending)
- This helps quickly identify which prerequisite tasks are blocking work
- **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`)
- **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`)
- **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`)
- **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`)
- Refer to task structure details (previously linked to `tasks.mdc`).
## Configuration Management (Updated)
Taskmaster configuration is managed through two main mechanisms:
1. **`.taskmaster/config.json` File (Primary):**
* Located in the project root directory.
* Stores most configuration settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default subtasks/priority, project name, etc.
* **Tagged System Settings**: Includes `global.defaultTag` (defaults to "master") and `tags` section for tag management configuration.
* **Managed via `task-master models --setup` command.** Do not edit manually unless you know what you are doing.
* **View/Set specific models via `task-master models` command or `models` MCP tool.**
* Created automatically when you run `task-master models --setup` for the first time or during tagged system migration.
2. **Environment Variables (`.env` / `mcp.json`):**
* Used **only** for sensitive API keys and specific endpoint URLs.
* Place API keys (one per provider) in a `.env` file in the project root for CLI usage.
* For MCP/Cursor integration, configure these keys in the `env` section of `.cursor/mcp.json`.
* Available keys/variables: See `assets/env.example` or the Configuration section in the command reference (previously linked to `taskmaster.mdc`).
3. **`.taskmaster/state.json` File (Tagged System State):**
* Tracks current tag context and migration status.
* Automatically created during tagged system migration.
* Contains: `currentTag`, `lastSwitched`, `migrationNoticeShown`.
**Important:** Non-API key settings (like model selections, `MAX_TOKENS`, `TASKMASTER_LOG_LEVEL`) are **no longer configured via environment variables**. Use the `task-master models` command (or `--setup` for interactive configuration) or the `models` MCP tool.
**If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.cursor/mcp.json`.
**If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project.
## Rules Management
Taskmaster supports multiple AI coding assistant rule sets that can be configured during project initialization or managed afterward:
- **Available Profiles**: Claude Code, Cline, Codex, Cursor, Roo Code, Trae, Windsurf (claude, cline, codex, cursor, roo, trae, windsurf)
- **During Initialization**: Use `task-master init --rules cursor,windsurf` to specify which rule sets to include
- **After Initialization**: Use `task-master rules add <profiles>` or `task-master rules remove <profiles>` to manage rule sets
- **Interactive Setup**: Use `task-master rules setup` to launch an interactive prompt for selecting rule profiles
- **Default Behavior**: If no `--rules` flag is specified during initialization, all available rule profiles are included
- **Rule Structure**: Each profile creates its own directory (e.g., `.cursor/rules`, `.roo/rules`) with appropriate configuration files
## Determining the Next Task
- Run `next_task` / `task-master next` to show the next task to work on.
- The command identifies tasks with all dependencies satisfied
- Tasks are prioritized by priority level, dependency count, and ID
- The command shows comprehensive task information including:
- Basic task details and description
- Implementation details
- Subtasks (if they exist)
- Contextual suggested actions
- Recommended before starting any new development work
- Respects your project's dependency structure
- Ensures tasks are completed in the appropriate sequence
- Provides ready-to-use commands for common task actions
## Viewing Specific Task Details
- Run `get_task` / `task-master show <id>` to view a specific task.
- Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1)
- Displays comprehensive information similar to the next command, but for a specific task
- For parent tasks, shows all subtasks and their current status
- For subtasks, shows parent task information and relationship
- Provides contextual suggested actions appropriate for the specific task
- Useful for examining task details before implementation or checking status
## Managing Task Dependencies
- Use `add_dependency` / `task-master add-dependency --id=<id> --depends-on=<id>` to add a dependency.
- Use `remove_dependency` / `task-master remove-dependency --id=<id> --depends-on=<id>` to remove a dependency.
- The system prevents circular dependencies and duplicate dependency entries
- Dependencies are checked for existence before being added or removed
- Task files are automatically regenerated after dependency changes
- Dependencies are visualized with status indicators in task listings and files
## Task Reorganization
- Use `move_task` / `task-master move --from=<id> --to=<id>` to move tasks or subtasks within the hierarchy
- This command supports several use cases:
- Moving a standalone task to become a subtask (e.g., `--from=5 --to=7`)
- Moving a subtask to become a standalone task (e.g., `--from=5.2 --to=7`)
- Moving a subtask to a different parent (e.g., `--from=5.2 --to=7.3`)
- Reordering subtasks within the same parent (e.g., `--from=5.2 --to=5.4`)
- Moving a task to a new, non-existent ID position (e.g., `--from=5 --to=25`)
- Moving multiple tasks at once using comma-separated IDs (e.g., `--from=10,11,12 --to=16,17,18`)
- The system includes validation to prevent data loss:
- Allows moving to non-existent IDs by creating placeholder tasks
- Prevents moving to existing task IDs that have content (to avoid overwriting)
- Validates source tasks exist before attempting to move them
- The system maintains proper parent-child relationships and dependency integrity
- Task files are automatically regenerated after the move operation
- This provides greater flexibility in organizing and refining your task structure as project understanding evolves
- This is especially useful when dealing with potential merge conflicts arising from teams creating tasks on separate branches. Solve these conflicts very easily by moving your tasks and keeping theirs.
## Iterative Subtask Implementation
Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation:
1. **Understand the Goal (Preparation):**
* Use `get_task` / `task-master show <subtaskId>` (see @`taskmaster.mdc`) to thoroughly understand the specific goals and requirements of the subtask.
2. **Initial Exploration & Planning (Iteration 1):**
* This is the first attempt at creating a concrete implementation plan.
* Explore the codebase to identify the precise files, functions, and even specific lines of code that will need modification.
* Determine the intended code changes (diffs) and their locations.
* Gather *all* relevant details from this exploration phase.
3. **Log the Plan:**
* Run `update_subtask` / `task-master update-subtask --id=<subtaskId> --prompt='<detailed plan>'`.
* Provide the *complete and detailed* findings from the exploration phase in the prompt. Include file paths, line numbers, proposed diffs, reasoning, and any potential challenges identified. Do not omit details. The goal is to create a rich, timestamped log within the subtask's `details`.
4. **Verify the Plan:**
* Run `get_task` / `task-master show <subtaskId>` again to confirm that the detailed implementation plan has been successfully appended to the subtask's details.
5. **Begin Implementation:**
* Set the subtask status using `set_task_status` / `task-master set-status --id=<subtaskId> --status=in-progress`.
* Start coding based on the logged plan.
6. **Refine and Log Progress (Iteration 2+):**
* As implementation progresses, you will encounter challenges, discover nuances, or confirm successful approaches.
* **Before appending new information**: Briefly review the *existing* details logged in the subtask (using `get_task` or recalling from context) to ensure the update adds fresh insights and avoids redundancy.
* **Regularly** use `update_subtask` / `task-master update-subtask --id=<subtaskId> --prompt='<update details>\n- What worked...\n- What didn't work...'` to append new findings.
* **Crucially, log:**
* What worked ("fundamental truths" discovered).
* What didn't work and why (to avoid repeating mistakes).
* Specific code snippets or configurations that were successful.
* Decisions made, especially if confirmed with user input.
* Any deviations from the initial plan and the reasoning.
* The objective is to continuously enrich the subtask's details, creating a log of the implementation journey that helps the AI (and human developers) learn, adapt, and avoid repeating errors.
7. **Review & Update Rules (Post-Implementation):**
* Once the implementation for the subtask is functionally complete, review all code changes and the relevant chat history.
* Identify any new or modified code patterns, conventions, or best practices established during the implementation.
* Create new or update existing rules following internal guidelines (previously linked to `cursor_rules.mdc` and `self_improve.mdc`).
8. **Mark Task Complete:**
* After verifying the implementation and updating any necessary rules, mark the subtask as completed: `set_task_status` / `task-master set-status --id=<subtaskId> --status=done`.
9. **Commit Changes (If using Git):**
* Stage the relevant code changes and any updated/new rule files (`git add .`).
* Craft a comprehensive Git commit message summarizing the work done for the subtask, including both code implementation and any rule adjustments.
* Execute the commit command directly in the terminal (e.g., `git commit -m 'feat(module): Implement feature X for subtask <subtaskId>\n\n- Details about changes...\n- Updated rule Y for pattern Z'`).
* Consider if a Changeset is needed according to internal versioning guidelines (previously linked to `changeset.mdc`). If so, run `npm run changeset`, stage the generated file, and amend the commit or create a new one.
10. **Proceed to Next Subtask:**
* Identify the next subtask (e.g., using `next_task` / `task-master next`).
## Code Analysis & Refactoring Techniques
- **Top-Level Function Search**:
- Useful for understanding module structure or planning refactors.
- Use grep/ripgrep to find exported functions/constants:
`rg "export (async function|function|const) \w+"` or similar patterns.
- Can help compare functions between files during migrations or identify potential naming conflicts.
---
*This workflow provides a general guideline. Adapt it based on your specific project needs and team practices.*

View File

@@ -0,0 +1,72 @@
---
description: Guidelines for continuously improving Cursor rules based on emerging code patterns and best practices.
globs: **/*
alwaysApply: true
---
- **Rule Improvement Triggers:**
- New code patterns not covered by existing rules
- Repeated similar implementations across files
- Common error patterns that could be prevented
- New libraries or tools being used consistently
- Emerging best practices in the codebase
- **Analysis Process:**
- Compare new code with existing rules
- Identify patterns that should be standardized
- Look for references to external documentation
- Check for consistent error handling patterns
- Monitor test patterns and coverage
- **Rule Updates:**
- **Add New Rules When:**
- A new technology/pattern is used in 3+ files
- Common bugs could be prevented by a rule
- Code reviews repeatedly mention the same feedback
- New security or performance patterns emerge
- **Modify Existing Rules When:**
- Better examples exist in the codebase
- Additional edge cases are discovered
- Related rules have been updated
- Implementation details have changed
- **Example Pattern Recognition:**
```typescript
// If you see repeated patterns like:
const data = await prisma.user.findMany({
select: { id: true, email: true },
where: { status: 'ACTIVE' }
});
// Consider adding to [prisma.mdc](mdc:.cursor/rules/prisma.mdc):
// - Standard select fields
// - Common where conditions
// - Performance optimization patterns
```
- **Rule Quality Checks:**
- Rules should be actionable and specific
- Examples should come from actual code
- References should be up to date
- Patterns should be consistently enforced
- **Continuous Improvement:**
- Monitor code review comments
- Track common development questions
- Update rules after major refactors
- Add links to relevant documentation
- Cross-reference related rules
- **Rule Deprecation:**
- Mark outdated patterns as deprecated
- Remove rules that no longer apply
- Update references to deprecated rules
- Document migration paths for old patterns
- **Documentation Updates:**
- Keep examples synchronized with code
- Update references to external docs
- Maintain links between related rules
- Document breaking changes
Follow [cursor_rules.mdc](mdc:.cursor/rules/cursor_rules.mdc) for proper rule formatting and structure.

558
assets/rules/taskmaster.mdc Normal file
View File

@@ -0,0 +1,558 @@
---
description: Comprehensive reference for Taskmaster MCP tools and CLI commands.
globs: **/*
alwaysApply: true
---
# Taskmaster Tool & Command Reference
This document provides a detailed reference for interacting with Taskmaster, covering both the recommended MCP tools, suitable for integrations like Cursor, and the corresponding `task-master` CLI commands, designed for direct user interaction or fallback.
**Note:** For interacting with Taskmaster programmatically or via integrated tools, using the **MCP tools is strongly recommended** due to better performance, structured data, and error handling. The CLI commands serve as a user-friendly alternative and fallback.
**Important:** Several MCP tools involve AI processing... The AI-powered tools include `parse_prd`, `analyze_project_complexity`, `update_subtask`, `update_task`, `update`, `expand_all`, `expand_task`, and `add_task`.
**🏷️ Tagged Task Lists System:** Task Master now supports **tagged task lists** for multi-context task management. This allows you to maintain separate, isolated lists of tasks for different features, branches, or experiments. Existing projects are seamlessly migrated to use a default "master" tag. Most commands now support a `--tag <name>` flag to specify which context to operate on. If omitted, commands use the currently active tag.
---
## Initialization & Setup
### 1. Initialize Project (`init`)
* **MCP Tool:** `initialize_project`
* **CLI Command:** `task-master init [options]`
* **Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project.`
* **Key CLI Options:**
* `--name <name>`: `Set the name for your project in Taskmaster's configuration.`
* `--description <text>`: `Provide a brief description for your project.`
* `--version <version>`: `Set the initial version for your project, e.g., '0.1.0'.`
* `-y, --yes`: `Initialize Taskmaster quickly using default settings without interactive prompts.`
* **Usage:** Run this once at the beginning of a new project.
* **MCP Variant Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project by running the 'task-master init' command.`
* **Key MCP Parameters/Options:**
* `projectName`: `Set the name for your project.` (CLI: `--name <name>`)
* `projectDescription`: `Provide a brief description for your project.` (CLI: `--description <text>`)
* `projectVersion`: `Set the initial version for your project, e.g., '0.1.0'.` (CLI: `--version <version>`)
* `authorName`: `Author name.` (CLI: `--author <author>`)
* `skipInstall`: `Skip installing dependencies. Default is false.` (CLI: `--skip-install`)
* `addAliases`: `Add shell aliases tm and taskmaster. Default is false.` (CLI: `--aliases`)
* `yes`: `Skip prompts and use defaults/provided arguments. Default is false.` (CLI: `-y, --yes`)
* **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Cursor. Operates on the current working directory of the MCP server.
* **Important:** Once complete, you *MUST* parse a prd in order to generate tasks. There will be no tasks files until then. The next step after initializing should be to create a PRD using the example PRD in .taskmaster/templates/example_prd.txt.
* **Tagging:** Use the `--tag` option to parse the PRD into a specific, non-default tag context. If the tag doesn't exist, it will be created automatically. Example: `task-master parse-prd spec.txt --tag=new-feature`.
### 2. Parse PRD (`parse_prd`)
* **MCP Tool:** `parse_prd`
* **CLI Command:** `task-master parse-prd [file] [options]`
* **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.`
* **Key Parameters/Options:**
* `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input <file>`)
* `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to '.taskmaster/tasks/tasks.json'.` (CLI: `-o, --output <file>`)
* `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks <number>`)
* `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`)
* **Usage:** Useful for bootstrapping a project from an existing requirements document.
* **Notes:** Task Master will strictly adhere to any specific requirements mentioned in the PRD, such as libraries, database schemas, frameworks, tech stacks, etc., while filling in any gaps where the PRD isn't fully specified. Tasks are designed to provide the most direct implementation path while avoiding over-engineering.
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. If the user does not have a PRD, suggest discussing their idea and then use the example PRD in `.taskmaster/templates/example_prd.txt` as a template for creating the PRD based on their idea, for use with `parse-prd`.
---
## AI Model Configuration
### 2. Manage Models (`models`)
* **MCP Tool:** `models`
* **CLI Command:** `task-master models [options]`
* **Description:** `View the current AI model configuration or set specific models for different roles (main, research, fallback). Allows setting custom model IDs for Ollama and OpenRouter.`
* **Key MCP Parameters/Options:**
* `setMain <model_id>`: `Set the primary model ID for task generation/updates.` (CLI: `--set-main <model_id>`)
* `setResearch <model_id>`: `Set the model ID for research-backed operations.` (CLI: `--set-research <model_id>`)
* `setFallback <model_id>`: `Set the model ID to use if the primary fails.` (CLI: `--set-fallback <model_id>`)
* `ollama <boolean>`: `Indicates the set model ID is a custom Ollama model.` (CLI: `--ollama`)
* `openrouter <boolean>`: `Indicates the set model ID is a custom OpenRouter model.` (CLI: `--openrouter`)
* `listAvailableModels <boolean>`: `If true, lists available models not currently assigned to a role.` (CLI: No direct equivalent; CLI lists available automatically)
* `projectRoot <string>`: `Optional. Absolute path to the project root directory.` (CLI: Determined automatically)
* **Key CLI Options:**
* `--set-main <model_id>`: `Set the primary model.`
* `--set-research <model_id>`: `Set the research model.`
* `--set-fallback <model_id>`: `Set the fallback model.`
* `--ollama`: `Specify that the provided model ID is for Ollama (use with --set-*).`
* `--openrouter`: `Specify that the provided model ID is for OpenRouter (use with --set-*). Validates against OpenRouter API.`
* `--bedrock`: `Specify that the provided model ID is for AWS Bedrock (use with --set-*).`
* `--setup`: `Run interactive setup to configure models, including custom Ollama/OpenRouter IDs.`
* **Usage (MCP):** Call without set flags to get current config. Use `setMain`, `setResearch`, or `setFallback` with a valid model ID to update the configuration. Use `listAvailableModels: true` to get a list of unassigned models. To set a custom model, provide the model ID and set `ollama: true` or `openrouter: true`.
* **Usage (CLI):** Run without flags to view current configuration and available models. Use set flags to update specific roles. Use `--setup` for guided configuration, including custom models. To set a custom model via flags, use `--set-<role>=<model_id>` along with either `--ollama` or `--openrouter`.
* **Notes:** Configuration is stored in `.taskmaster/config.json` in the project root. This command/tool modifies that file. Use `listAvailableModels` or `task-master models` to see internally supported models. OpenRouter custom models are validated against their live API. Ollama custom models are not validated live.
* **API note:** API keys for selected AI providers (based on their model) need to exist in the mcp.json file to be accessible in MCP context. The API keys must be present in the local .env file for the CLI to be able to read them.
* **Model costs:** The costs in supported models are expressed in dollars. An input/output value of 3 is $3.00. A value of 0.8 is $0.80.
* **Warning:** DO NOT MANUALLY EDIT THE .taskmaster/config.json FILE. Use the included commands either in the MCP or CLI format as needed. Always prioritize MCP tools when available and use the CLI as a fallback.
---
## Task Listing & Viewing
### 3. Get Tasks (`get_tasks`)
* **MCP Tool:** `get_tasks`
* **CLI Command:** `task-master list [options]`
* **Description:** `List your Taskmaster tasks, optionally filtering by status and showing subtasks.`
* **Key Parameters/Options:**
* `status`: `Show only Taskmaster tasks matching this status (or multiple statuses, comma-separated), e.g., 'pending' or 'done,in-progress'.` (CLI: `-s, --status <status>`)
* `withSubtasks`: `Include subtasks indented under their parent tasks in the list.` (CLI: `--with-subtasks`)
* `tag`: `Specify which tag context to list tasks from. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Get an overview of the project status, often used at the start of a work session.
### 4. Get Next Task (`next_task`)
* **MCP Tool:** `next_task`
* **CLI Command:** `task-master next [options]`
* **Description:** `Ask Taskmaster to show the next available task you can work on, based on status and completed dependencies.`
* **Key Parameters/Options:**
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* `tag`: `Specify which tag context to use. Defaults to the current active tag.` (CLI: `--tag <name>`)
* **Usage:** Identify what to work on next according to the plan.
### 5. Get Task Details (`get_task`)
* **MCP Tool:** `get_task`
* **CLI Command:** `task-master show [id] [options]`
* **Description:** `Display detailed information for one or more specific Taskmaster tasks or subtasks by ID.`
* **Key Parameters/Options:**
* `id`: `Required. The ID of the Taskmaster task (e.g., '15'), subtask (e.g., '15.2'), or a comma-separated list of IDs ('1,5,10.2') you want to view.` (CLI: `[id]` positional or `-i, --id <id>`)
* `tag`: `Specify which tag context to get the task(s) from. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Understand the full details for a specific task. When multiple IDs are provided, a summary table is shown.
* **CRITICAL INFORMATION** If you need to collect information from multiple tasks, use comma-separated IDs (i.e. 1,2,3) to receive an array of tasks. Do not needlessly get tasks one at a time if you need to get many as that is wasteful.
---
## Task Creation & Modification
### 6. Add Task (`add_task`)
* **MCP Tool:** `add_task`
* **CLI Command:** `task-master add-task [options]`
* **Description:** `Add a new task to Taskmaster by describing it; AI will structure it.`
* **Key Parameters/Options:**
* `prompt`: `Required. Describe the new task you want Taskmaster to create, e.g., "Implement user authentication using JWT".` (CLI: `-p, --prompt <text>`)
* `dependencies`: `Specify the IDs of any Taskmaster tasks that must be completed before this new one can start, e.g., '12,14'.` (CLI: `-d, --dependencies <ids>`)
* `priority`: `Set the priority for the new task: 'high', 'medium', or 'low'. Default is 'medium'.` (CLI: `--priority <priority>`)
* `research`: `Enable Taskmaster to use the research role for potentially more informed task creation.` (CLI: `-r, --research`)
* `tag`: `Specify which tag context to add the task to. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Quickly add newly identified tasks during development.
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
### 7. Add Subtask (`add_subtask`)
* **MCP Tool:** `add_subtask`
* **CLI Command:** `task-master add-subtask [options]`
* **Description:** `Add a new subtask to a Taskmaster parent task, or convert an existing task into a subtask.`
* **Key Parameters/Options:**
* `id` / `parent`: `Required. The ID of the Taskmaster task that will be the parent.` (MCP: `id`, CLI: `-p, --parent <id>`)
* `taskId`: `Use this if you want to convert an existing top-level Taskmaster task into a subtask of the specified parent.` (CLI: `-i, --task-id <id>`)
* `title`: `Required if not using taskId. The title for the new subtask Taskmaster should create.` (CLI: `-t, --title <title>`)
* `description`: `A brief description for the new subtask.` (CLI: `-d, --description <text>`)
* `details`: `Provide implementation notes or details for the new subtask.` (CLI: `--details <text>`)
* `dependencies`: `Specify IDs of other tasks or subtasks, e.g., '15' or '16.1', that must be done before this new subtask.` (CLI: `--dependencies <ids>`)
* `status`: `Set the initial status for the new subtask. Default is 'pending'.` (CLI: `-s, --status <status>`)
* `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after adding the subtask.` (CLI: `--skip-generate`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Break down tasks manually or reorganize existing tasks.
### 8. Update Tasks (`update`)
* **MCP Tool:** `update`
* **CLI Command:** `task-master update [options]`
* **Description:** `Update multiple upcoming tasks in Taskmaster based on new context or changes, starting from a specific task ID.`
* **Key Parameters/Options:**
* `from`: `Required. The ID of the first task Taskmaster should update. All tasks with this ID or higher that are not 'done' will be considered.` (CLI: `--from <id>`)
* `prompt`: `Required. Explain the change or new context for Taskmaster to apply to the tasks, e.g., "We are now using React Query instead of Redux Toolkit for data fetching".` (CLI: `-p, --prompt <text>`)
* `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Handle significant implementation changes or pivots that affect multiple future tasks. Example CLI: `task-master update --from='18' --prompt='Switching to React Query.\nNeed to refactor data fetching...'`
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
### 9. Update Task (`update_task`)
* **MCP Tool:** `update_task`
* **CLI Command:** `task-master update-task [options]`
* **Description:** `Modify a specific Taskmaster task by ID, incorporating new information or changes. By default, this replaces the existing task details.`
* **Key Parameters/Options:**
* `id`: `Required. The specific ID of the Taskmaster task, e.g., '15', you want to update.` (CLI: `-i, --id <id>`)
* `prompt`: `Required. Explain the specific changes or provide the new information Taskmaster should incorporate into this task.` (CLI: `-p, --prompt <text>`)
* `append`: `If true, appends the prompt content to the task's details with a timestamp, rather than replacing them. Behaves like update-subtask.` (CLI: `--append`)
* `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
* `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Refine a specific task based on new understanding. Use `--append` to log progress without creating subtasks.
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
### 10. Update Subtask (`update_subtask`)
* **MCP Tool:** `update_subtask`
* **CLI Command:** `task-master update-subtask [options]`
* **Description:** `Append timestamped notes or details to a specific Taskmaster subtask without overwriting existing content. Intended for iterative implementation logging.`
* **Key Parameters/Options:**
* `id`: `Required. The ID of the Taskmaster subtask, e.g., '5.2', to update with new information.` (CLI: `-i, --id <id>`)
* `prompt`: `Required. The information, findings, or progress notes to append to the subtask's details with a timestamp.` (CLI: `-p, --prompt <text>`)
* `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
* `tag`: `Specify which tag context the subtask belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Log implementation progress, findings, and discoveries during subtask development. Each update is timestamped and appended to preserve the implementation journey.
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
### 11. Set Task Status (`set_task_status`)
* **MCP Tool:** `set_task_status`
* **CLI Command:** `task-master set-status [options]`
* **Description:** `Update the status of one or more Taskmaster tasks or subtasks, e.g., 'pending', 'in-progress', 'done'.`
* **Key Parameters/Options:**
* `id`: `Required. The ID(s) of the Taskmaster task(s) or subtask(s), e.g., '15', '15.2', or '16,17.1', to update.` (CLI: `-i, --id <id>`)
* `status`: `Required. The new status to set, e.g., 'done', 'pending', 'in-progress', 'review', 'cancelled'.` (CLI: `-s, --status <status>`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Mark progress as tasks move through the development cycle.
### 12. Remove Task (`remove_task`)
* **MCP Tool:** `remove_task`
* **CLI Command:** `task-master remove-task [options]`
* **Description:** `Permanently remove a task or subtask from the Taskmaster tasks list.`
* **Key Parameters/Options:**
* `id`: `Required. The ID of the Taskmaster task, e.g., '5', or subtask, e.g., '5.2', to permanently remove.` (CLI: `-i, --id <id>`)
* `yes`: `Skip the confirmation prompt and immediately delete the task.` (CLI: `-y, --yes`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Permanently delete tasks or subtasks that are no longer needed in the project.
* **Notes:** Use with caution as this operation cannot be undone. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you just want to exclude a task from active planning but keep it for reference. The command automatically cleans up dependency references in other tasks.
---
## Task Structure & Breakdown
### 13. Expand Task (`expand_task`)
* **MCP Tool:** `expand_task`
* **CLI Command:** `task-master expand [options]`
* **Description:** `Use Taskmaster's AI to break down a complex task into smaller, manageable subtasks. Appends subtasks by default.`
* **Key Parameters/Options:**
* `id`: `The ID of the specific Taskmaster task you want to break down into subtasks.` (CLI: `-i, --id <id>`)
* `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create. Uses complexity analysis/defaults otherwise.` (CLI: `-n, --num <number>`)
* `research`: `Enable Taskmaster to use the research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`)
* `prompt`: `Optional: Provide extra context or specific instructions to Taskmaster for generating the subtasks.` (CLI: `-p, --prompt <text>`)
* `force`: `Optional: If true, clear existing subtasks before generating new ones. Default is false (append).` (CLI: `--force`)
* `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Generate a detailed implementation plan for a complex task before starting coding. Automatically uses complexity report recommendations if available and `num` is not specified.
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
### 14. Expand All Tasks (`expand_all`)
* **MCP Tool:** `expand_all`
* **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag)
* **Description:** `Tell Taskmaster to automatically expand all eligible pending/in-progress tasks based on complexity analysis or defaults. Appends subtasks by default.`
* **Key Parameters/Options:**
* `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create per task.` (CLI: `-n, --num <number>`)
* `research`: `Enable research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`)
* `prompt`: `Optional: Provide extra context for Taskmaster to apply generally during expansion.` (CLI: `-p, --prompt <text>`)
* `force`: `Optional: If true, clear existing subtasks before generating new ones for each eligible task. Default is false (append).` (CLI: `--force`)
* `tag`: `Specify which tag context to expand. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once.
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
### 15. Clear Subtasks (`clear_subtasks`)
* **MCP Tool:** `clear_subtasks`
* **CLI Command:** `task-master clear-subtasks [options]`
* **Description:** `Remove all subtasks from one or more specified Taskmaster parent tasks.`
* **Key Parameters/Options:**
* `id`: `The ID(s) of the Taskmaster parent task(s) whose subtasks you want to remove, e.g., '15' or '16,18'. Required unless using `all`.) (CLI: `-i, --id <ids>`)
* `all`: `Tell Taskmaster to remove subtasks from all parent tasks.` (CLI: `--all`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement.
### 16. Remove Subtask (`remove_subtask`)
* **MCP Tool:** `remove_subtask`
* **CLI Command:** `task-master remove-subtask [options]`
* **Description:** `Remove a subtask from its Taskmaster parent, optionally converting it into a standalone task.`
* **Key Parameters/Options:**
* `id`: `Required. The ID(s) of the Taskmaster subtask(s) to remove, e.g., '15.2' or '16.1,16.3'.` (CLI: `-i, --id <id>`)
* `convert`: `If used, Taskmaster will turn the subtask into a regular top-level task instead of deleting it.` (CLI: `-c, --convert`)
* `skipGenerate`: `Prevent Taskmaster from automatically regenerating markdown task files after removing the subtask.` (CLI: `--skip-generate`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task.
### 17. Move Task (`move_task`)
* **MCP Tool:** `move_task`
* **CLI Command:** `task-master move [options]`
* **Description:** `Move a task or subtask to a new position within the task hierarchy.`
* **Key Parameters/Options:**
* `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from <id>`)
* `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to <id>`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like:
* Moving a task to become a subtask
* Moving a subtask to become a standalone task
* Moving a subtask to a different parent
* Reordering subtasks within the same parent
* Moving a task to a new, non-existent ID (automatically creates placeholders)
* Moving multiple tasks at once with comma-separated IDs
* **Validation Features:**
* Allows moving tasks to non-existent destination IDs (creates placeholder tasks)
* Prevents moving to existing task IDs that already have content (to avoid overwriting)
* Validates that source tasks exist before attempting to move them
* Maintains proper parent-child relationships
* **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3.
* **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions.
* **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches.
---
## Dependency Management
### 18. Add Dependency (`add_dependency`)
* **MCP Tool:** `add_dependency`
* **CLI Command:** `task-master add-dependency [options]`
* **Description:** `Define a dependency in Taskmaster, making one task a prerequisite for another.`
* **Key Parameters/Options:**
* `id`: `Required. The ID of the Taskmaster task that will depend on another.` (CLI: `-i, --id <id>`)
* `dependsOn`: `Required. The ID of the Taskmaster task that must be completed first, the prerequisite.` (CLI: `-d, --depends-on <id>`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <path>`)
* **Usage:** Establish the correct order of execution between tasks.
### 19. Remove Dependency (`remove_dependency`)
* **MCP Tool:** `remove_dependency`
* **CLI Command:** `task-master remove-dependency [options]`
* **Description:** `Remove a dependency relationship between two Taskmaster tasks.`
* **Key Parameters/Options:**
* `id`: `Required. The ID of the Taskmaster task you want to remove a prerequisite from.` (CLI: `-i, --id <id>`)
* `dependsOn`: `Required. The ID of the Taskmaster task that should no longer be a prerequisite.` (CLI: `-d, --depends-on <id>`)
* `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Update task relationships when the order of execution changes.
### 20. Validate Dependencies (`validate_dependencies`)
* **MCP Tool:** `validate_dependencies`
* **CLI Command:** `task-master validate-dependencies [options]`
* **Description:** `Check your Taskmaster tasks for dependency issues (like circular references or links to non-existent tasks) without making changes.`
* **Key Parameters/Options:**
* `tag`: `Specify which tag context to validate. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Audit the integrity of your task dependencies.
### 21. Fix Dependencies (`fix_dependencies`)
* **MCP Tool:** `fix_dependencies`
* **CLI Command:** `task-master fix-dependencies [options]`
* **Description:** `Automatically fix dependency issues (like circular references or links to non-existent tasks) in your Taskmaster tasks.`
* **Key Parameters/Options:**
* `tag`: `Specify which tag context to fix dependencies in. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Clean up dependency errors automatically.
---
## Analysis & Reporting
### 22. Analyze Project Complexity (`analyze_project_complexity`)
* **MCP Tool:** `analyze_project_complexity`
* **CLI Command:** `task-master analyze-complexity [options]`
* **Description:** `Have Taskmaster analyze your tasks to determine their complexity and suggest which ones need to be broken down further.`
* **Key Parameters/Options:**
* `output`: `Where to save the complexity analysis report. Default is '.taskmaster/reports/task-complexity-report.json' (or '..._tagname.json' if a tag is used).` (CLI: `-o, --output <file>`)
* `threshold`: `The minimum complexity score (1-10) that should trigger a recommendation to expand a task.` (CLI: `-t, --threshold <number>`)
* `research`: `Enable research role for more accurate complexity analysis. Requires appropriate API key.` (CLI: `-r, --research`)
* `tag`: `Specify which tag context to analyze. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Used before breaking down tasks to identify which ones need the most attention.
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
### 23. View Complexity Report (`complexity_report`)
* **MCP Tool:** `complexity_report`
* **CLI Command:** `task-master complexity-report [options]`
* **Description:** `Display the task complexity analysis report in a readable format.`
* **Key Parameters/Options:**
* `tag`: `Specify which tag context to show the report for. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to the complexity report (default: '.taskmaster/reports/task-complexity-report.json').` (CLI: `-f, --file <file>`)
* **Usage:** Review and understand the complexity analysis results after running analyze-complexity.
---
## File Management
### 24. Generate Task Files (`generate`)
* **MCP Tool:** `generate`
* **CLI Command:** `task-master generate [options]`
* **Description:** `Create or update individual Markdown files for each task based on your tasks.json.`
* **Key Parameters/Options:**
* `output`: `The directory where Taskmaster should save the task files (default: in a 'tasks' directory).` (CLI: `-o, --output <directory>`)
* `tag`: `Specify which tag context to generate files for. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* **Usage:** Run this after making changes to tasks.json to keep individual task files up to date. This command is now manual and no longer runs automatically.
---
## AI-Powered Research
### 25. Research (`research`)
* **MCP Tool:** `research`
* **CLI Command:** `task-master research [options]`
* **Description:** `Perform AI-powered research queries with project context to get fresh, up-to-date information beyond the AI's knowledge cutoff.`
* **Key Parameters/Options:**
* `query`: `Required. Research query/prompt (e.g., "What are the latest best practices for React Query v5?").` (CLI: `[query]` positional or `-q, --query <text>`)
* `taskIds`: `Comma-separated list of task/subtask IDs from the current tag context (e.g., "15,16.2,17").` (CLI: `-i, --id <ids>`)
* `filePaths`: `Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md").` (CLI: `-f, --files <paths>`)
* `customContext`: `Additional custom context text to include in the research.` (CLI: `-c, --context <text>`)
* `includeProjectTree`: `Include project file tree structure in context (default: false).` (CLI: `--tree`)
* `detailLevel`: `Detail level for the research response: 'low', 'medium', 'high' (default: medium).` (CLI: `--detail <level>`)
* `saveTo`: `Task or subtask ID (e.g., "15", "15.2") to automatically save the research conversation to.` (CLI: `--save-to <id>`)
* `saveFile`: `If true, saves the research conversation to a markdown file in '.taskmaster/docs/research/'.` (CLI: `--save-file`)
* `noFollowup`: `Disables the interactive follow-up question menu in the CLI.` (CLI: `--no-followup`)
* `tag`: `Specify which tag context to use for task-based context gathering. Defaults to the current active tag.` (CLI: `--tag <name>`)
* `projectRoot`: `The directory of the project. Must be an absolute path.` (CLI: Determined automatically)
* **Usage:** **This is a POWERFUL tool that agents should use FREQUENTLY** to:
* Get fresh information beyond knowledge cutoff dates
* Research latest best practices, library updates, security patches
* Find implementation examples for specific technologies
* Validate approaches against current industry standards
* Get contextual advice based on project files and tasks
* **When to Consider Using Research:**
* **Before implementing any task** - Research current best practices
* **When encountering new technologies** - Get up-to-date implementation guidance (libraries, apis, etc)
* **For security-related tasks** - Find latest security recommendations
* **When updating dependencies** - Research breaking changes and migration guides
* **For performance optimization** - Get current performance best practices
* **When debugging complex issues** - Research known solutions and workarounds
* **Research + Action Pattern:**
* Use `research` to gather fresh information
* Use `update_subtask` to commit findings with timestamps
* Use `update_task` to incorporate research into task details
* Use `add_task` with research flag for informed task creation
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. The research provides FRESH data beyond the AI's training cutoff, making it invaluable for current best practices and recent developments.
---
## Tag Management
This new suite of commands allows you to manage different task contexts (tags).
### 26. List Tags (`tags`)
* **MCP Tool:** `list_tags`
* **CLI Command:** `task-master tags [options]`
* **Description:** `List all available tags with task counts, completion status, and other metadata.`
* **Key Parameters/Options:**
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
* `--show-metadata`: `Include detailed metadata in the output (e.g., creation date, description).` (CLI: `--show-metadata`)
### 27. Add Tag (`add_tag`)
* **MCP Tool:** `add_tag`
* **CLI Command:** `task-master add-tag <tagName> [options]`
* **Description:** `Create a new, empty tag context, or copy tasks from another tag.`
* **Key Parameters/Options:**
* `tagName`: `Name of the new tag to create (alphanumeric, hyphens, underscores).` (CLI: `<tagName>` positional)
* `--from-branch`: `Creates a tag with a name derived from the current git branch, ignoring the <tagName> argument.` (CLI: `--from-branch`)
* `--copy-from-current`: `Copy tasks from the currently active tag to the new tag.` (CLI: `--copy-from-current`)
* `--copy-from <tag>`: `Copy tasks from a specific source tag to the new tag.` (CLI: `--copy-from <tag>`)
* `--description <text>`: `Provide an optional description for the new tag.` (CLI: `-d, --description <text>`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
### 28. Delete Tag (`delete_tag`)
* **MCP Tool:** `delete_tag`
* **CLI Command:** `task-master delete-tag <tagName> [options]`
* **Description:** `Permanently delete a tag and all of its associated tasks.`
* **Key Parameters/Options:**
* `tagName`: `Name of the tag to delete.` (CLI: `<tagName>` positional)
* `--yes`: `Skip the confirmation prompt.` (CLI: `-y, --yes`)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
### 29. Use Tag (`use_tag`)
* **MCP Tool:** `use_tag`
* **CLI Command:** `task-master use-tag <tagName>`
* **Description:** `Switch your active task context to a different tag.`
* **Key Parameters/Options:**
* `tagName`: `Name of the tag to switch to.` (CLI: `<tagName>` positional)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
### 30. Rename Tag (`rename_tag`)
* **MCP Tool:** `rename_tag`
* **CLI Command:** `task-master rename-tag <oldName> <newName>`
* **Description:** `Rename an existing tag.`
* **Key Parameters/Options:**
* `oldName`: `The current name of the tag.` (CLI: `<oldName>` positional)
* `newName`: `The new name for the tag.` (CLI: `<newName>` positional)
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
### 31. Copy Tag (`copy_tag`)
* **MCP Tool:** `copy_tag`
* **CLI Command:** `task-master copy-tag <sourceName> <targetName> [options]`
* **Description:** `Copy an entire tag context, including all its tasks and metadata, to a new tag.`
* **Key Parameters/Options:**
* `sourceName`: `Name of the tag to copy from.` (CLI: `<sourceName>` positional)
* `targetName`: `Name of the new tag to create.` (CLI: `<targetName>` positional)
* `--description <text>`: `Optional description for the new tag.` (CLI: `-d, --description <text>`)
---
## Miscellaneous
### 32. Sync Readme (`sync-readme`) -- experimental
* **MCP Tool:** N/A
* **CLI Command:** `task-master sync-readme [options]`
* **Description:** `Exports your task list to your project's README.md file, useful for showcasing progress.`
* **Key Parameters/Options:**
* `status`: `Filter tasks by status (e.g., 'pending', 'done').` (CLI: `-s, --status <status>`)
* `withSubtasks`: `Include subtasks in the export.` (CLI: `--with-subtasks`)
* `tag`: `Specify which tag context to export from. Defaults to the current active tag.` (CLI: `--tag <name>`)
---
## Environment Variables Configuration (Updated)
Taskmaster primarily uses the **`.taskmaster/config.json`** file (in project root) for configuration (models, parameters, logging level, etc.), managed via `task-master models --setup`.
Environment variables are used **only** for sensitive API keys related to AI providers and specific overrides like the Ollama base URL:
* **API Keys (Required for corresponding provider):**
* `ANTHROPIC_API_KEY`
* `PERPLEXITY_API_KEY`
* `OPENAI_API_KEY`
* `GOOGLE_API_KEY`
* `MISTRAL_API_KEY`
* `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too)
* `OPENROUTER_API_KEY`
* `XAI_API_KEY`
* `OLLAMA_API_KEY` (Requires `OLLAMA_BASE_URL` too)
* **Endpoints (Optional/Provider Specific inside .taskmaster/config.json):**
* `AZURE_OPENAI_ENDPOINT`
* `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`)
**Set API keys** in your **`.env`** file in the project root (for CLI use) or within the `env` section of your **`.cursor/mcp.json`** file (for MCP/Cursor integration). All other settings (model choice, max tokens, temperature, log level, custom endpoints) are managed in `.taskmaster/config.json` via `task-master models` command or `models` MCP tool.
---
For details on how these commands fit into the development process, see the [Development Workflow Guide](mdc:.cursor/rules/dev_workflow.mdc).

View File

@@ -373,8 +373,4 @@ if (process.argv.length <= 2) {
}
// Add exports at the end of the file
if (typeof module !== 'undefined') {
module.exports = {
detectCamelCaseFlags
};
}
export { detectCamelCaseFlags };

View File

@@ -302,8 +302,60 @@ task-master delete-tag <tag-name> --yes
```bash
# Initialize a new project with Task Master structure
task-master init
# Initialize a new project applying specific rules
task-master init --rules cursor,windsurf,vscode
```
- The `--rules` flag allows you to specify one or more rule profiles (e.g., `cursor`, `roo`, `windsurf`, `cline`) to apply during initialization.
- If omitted, all available rule profiles are installed by default (claude, cline, codex, cursor, roo, trae, vscode, windsurf).
- You can use multiple comma-separated profiles in a single command.
## Manage Rules
```bash
# Add rule profiles to your project
# (e.g., .roo/rules, .windsurf/rules)
task-master rules add <profile1,profile2,...>
# Remove rule sets from your project
task-master rules remove <profile1,profile2,...>
# Remove rule sets bypassing safety check (dangerous)
task-master rules remove <profile1,profile2,...> --force
# Launch interactive rules setup to select rules
# (does not re-initialize project or ask about shell aliases)
task-master rules setup
```
- Adding rules creates the profile and rules directory (e.g., `.roo/rules`) and copies/initializes the rules.
- Removing rules deletes the profile and rules directory and associated MCP config.
- **Safety Check**: Attempting to remove rule profiles will trigger a critical warning requiring confirmation. Use `--force` to bypass.
- You can use multiple comma-separated rules in a single command.
- The `setup` action launches an interactive prompt to select which rules to apply. The list of rules is always current with the available profiles, and no manual updates are needed. This command does **not** re-initialize your project or affect shell aliases; it only manages rules interactively.
**Examples:**
```bash
task-master rules add windsurf,roo,vscode
task-master rules remove windsurf
task-master rules setup
```
### Interactive Rules Setup
You can launch the interactive rules setup at any time with:
```bash
task-master rules setup
```
This command opens a prompt where you can select which rule profiles (e.g., Cursor, Roo, Windsurf) you want to add to your project. This does **not** re-initialize your project or ask about shell aliases; it only manages rules.
- Use this command to add rule profiles interactively after project creation.
- The same interactive prompt is also used during `init` if you don't specify rules with `--rules`.
## Configure AI Models
```bash

View File

@@ -2,7 +2,6 @@
Taskmaster uses two primary methods for configuration:
1. **`.taskmaster/config.json` File (Recommended - New Structure)**
1. **`.taskmaster/config.json` File (Recommended - New Structure)**
- This JSON file stores most configuration settings, including AI model selections, parameters, logging levels, and project defaults.
@@ -73,6 +72,7 @@ Taskmaster uses two primary methods for configuration:
- `XAI_API_KEY`: Your X-AI API key.
- **Optional Endpoint Overrides:**
- **Per-role `baseURL` in `.taskmasterconfig`:** You can add a `baseURL` property to any model role (`main`, `research`, `fallback`) to override the default API endpoint for that provider. If omitted, the provider's standard endpoint is used.
- **Environment Variable Overrides (`<PROVIDER>_BASE_URL`):** For greater flexibility, especially with third-party services, you can set an environment variable like `OPENAI_BASE_URL` or `MISTRAL_BASE_URL`. This will override any `baseURL` set in the configuration file for that provider. This is the recommended way to connect to OpenAI-compatible APIs.
- `AZURE_OPENAI_ENDPOINT`: Required if using Azure OpenAI key (can also be set as `baseURL` for the Azure model role).
- `OLLAMA_BASE_URL`: Override the default Ollama API URL (Default: `http://localhost:11434/api`).
- `VERTEX_PROJECT_ID`: Your Google Cloud project ID for Vertex AI. Required when using the 'vertex' provider.
@@ -132,13 +132,14 @@ PERPLEXITY_API_KEY=pplx-your-key-here
# etc.
# Optional Endpoint Overrides
# Use a specific provider's base URL, e.g., for an OpenAI-compatible API
# OPENAI_BASE_URL=https://api.third-party.com/v1
#
# AZURE_OPENAI_ENDPOINT=https://your-azure-endpoint.openai.azure.com/
# OLLAMA_BASE_URL=http://custom-ollama-host:11434/api
# Google Vertex AI Configuration (Required if using 'vertex' provider)
# VERTEX_PROJECT_ID=your-gcp-project-id
# VERTEX_LOCATION=us-central1
# GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-credentials.json
```
## Troubleshooting

View File

@@ -64,7 +64,7 @@ To manually verify that the Roo files are properly included in the package:
ls -la .roo/rules
ls -la .roo/rules-architect
ls -la .roo/rules-ask
ls -la .roo/rules-boomerang
ls -la .roo/rules-orchestrator
ls -la .roo/rules-code
ls -la .roo/rules-debug
ls -la .roo/rules-test

View File

@@ -0,0 +1,169 @@
# Claude Code Provider Usage Example
The Claude Code provider allows you to use Claude models through the Claude Code CLI without requiring an API key.
## Configuration
To use the Claude Code provider, update your `.taskmaster/config.json`:
```json
{
"models": {
"main": {
"provider": "claude-code",
"modelId": "sonnet",
"maxTokens": 64000,
"temperature": 0.2
},
"research": {
"provider": "claude-code",
"modelId": "opus",
"maxTokens": 32000,
"temperature": 0.1
},
"fallback": {
"provider": "claude-code",
"modelId": "sonnet",
"maxTokens": 64000,
"temperature": 0.2
}
}
}
```
## Available Models
- `opus` - Claude Opus model (SWE score: 0.725)
- `sonnet` - Claude Sonnet model (SWE score: 0.727)
## Usage
Once configured, you can use Claude Code with all Task Master commands:
```bash
# Generate tasks from a PRD
task-master parse-prd --input=prd.txt
# Analyze project complexity
task-master analyze-complexity
# Show the next task to work on
task-master next
# View a specific task
task-master show task-001
# Update task status
task-master set-status --id=task-001 --status=in-progress
```
## Requirements
1. Claude Code CLI must be installed and authenticated on your system
2. Install the optional `@anthropic-ai/claude-code` package if you enable this provider:
```bash
npm install @anthropic-ai/claude-code
```
3. No API key is required in your environment variables or MCP configuration
## Advanced Settings
The Claude Code SDK supports additional settings that provide fine-grained control over Claude's behavior. While these settings are implemented in the underlying SDK (`src/ai-providers/custom-sdk/claude-code/`), they are not currently exposed through Task Master's standard API due to architectural constraints.
### Supported Settings
```javascript
const settings = {
// Maximum conversation turns Claude can make in a single request
maxTurns: 5,
// Custom system prompt to override Claude Code's default behavior
customSystemPrompt: "You are a helpful assistant focused on code quality",
// Permission mode for file system operations
permissionMode: 'default', // Options: 'default', 'restricted', 'permissive'
// Explicitly allow only certain tools
allowedTools: ['Read', 'LS'], // Claude can only read files and list directories
// Explicitly disallow certain tools
disallowedTools: ['Write', 'Edit'], // Prevent Claude from modifying files
// MCP servers for additional tool integrations
mcpServers: []
};
```
### Current Limitations
Task Master uses a standardized `BaseAIProvider` interface that only passes through common parameters (modelId, messages, maxTokens, temperature) to maintain consistency across all providers. The Claude Code advanced settings are implemented in the SDK but not accessible through Task Master's high-level commands.
### Future Integration Options
For developers who need to use these advanced settings, there are three potential approaches:
#### Option 1: Extend BaseAIProvider
Modify the core Task Master architecture to support provider-specific settings:
```javascript
// In BaseAIProvider
const result = await generateText({
model: client(params.modelId),
messages: params.messages,
maxTokens: params.maxTokens,
temperature: params.temperature,
...params.providerSettings // New: pass through provider-specific settings
});
```
#### Option 2: Override Methods in ClaudeCodeProvider
Create custom implementations that extract and use Claude-specific settings:
```javascript
// In ClaudeCodeProvider
async generateText(params) {
const { maxTurns, allowedTools, disallowedTools, ...baseParams } = params;
const client = this.getClient({
...baseParams,
settings: { maxTurns, allowedTools, disallowedTools }
});
// Continue with generation...
}
```
#### Option 3: Direct SDK Usage
For immediate access to advanced features, developers can use the Claude Code SDK directly:
```javascript
import { createClaudeCode } from 'task-master-ai/ai-providers/custom-sdk/claude-code';
const claude = createClaudeCode({
defaultSettings: {
maxTurns: 5,
allowedTools: ['Read', 'LS'],
disallowedTools: ['Write', 'Edit']
}
});
const model = claude('sonnet');
const result = await generateText({
model,
messages: [{ role: 'user', content: 'Analyze this code...' }]
});
```
### Why These Settings Matter
- **maxTurns**: Useful for complex refactoring tasks that require multiple iterations
- **customSystemPrompt**: Allows specializing Claude for specific domains or coding standards
- **permissionMode**: Critical for security in production environments
- **allowedTools/disallowedTools**: Enable read-only analysis modes or restrict access to sensitive operations
- **mcpServers**: Future extensibility for custom tool integrations
## Notes
- The Claude Code provider doesn't track usage costs (shown as 0 in telemetry)
- Session management is handled automatically for conversation continuity
- Some AI SDK parameters (temperature, maxTokens) are not supported by Claude Code CLI and will be ignored

View File

@@ -1,128 +1,137 @@
# Available Models as of June 15, 2025
# Available Models as of June 20, 2025
## Main Models
| Provider | Model Name | SWE Score | Input Cost | Output Cost |
| ---------- | ---------------------------------------------- | --------- | ---------- | ----------- |
| anthropic | claude-sonnet-4-20250514 | 0.727 | 3 | 15 |
| anthropic | claude-opus-4-20250514 | 0.725 | 15 | 75 |
| anthropic | claude-3-7-sonnet-20250219 | 0.623 | 3 | 15 |
| anthropic | claude-3-5-sonnet-20241022 | 0.49 | 3 | 15 |
| openai | gpt-4o | 0.332 | 2.5 | 10 |
| openai | o1 | 0.489 | 15 | 60 |
| openai | o3 | 0.5 | 2 | 8 |
| openai | o3-mini | 0.493 | 1.1 | 4.4 |
| openai | o4-mini | 0.45 | 1.1 | 4.4 |
| openai | o1-mini | 0.4 | 1.1 | 4.4 |
| openai | o1-pro | | 150 | 600 |
| openai | gpt-4-5-preview | 0.38 | 75 | 150 |
| openai | gpt-4-1-mini | | 0.4 | 1.6 |
| openai | gpt-4-1-nano | — | 0.1 | 0.4 |
| openai | gpt-4o-mini | 0.3 | 0.15 | 0.6 |
| google | gemini-2.5-pro-preview-05-06 | 0.638 | | |
| google | gemini-2.5-pro-preview-03-25 | 0.638 | — | — |
| google | gemini-2.5-flash-preview-04-17 | | — | — |
| google | gemini-2.0-flash | 0.754 | 0.15 | 0.6 |
| google | gemini-2.0-flash-lite | — | | |
| perplexity | sonar-pro | — | 3 | 15 |
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
| xai | grok-3 | | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| ollama | devstral:latest | — | 0 | 0 |
| ollama | qwen3:latest | — | 0 | 0 |
| ollama | qwen3:14b | — | 0 | 0 |
| ollama | qwen3:32b | — | 0 | 0 |
| ollama | mistral-small3.1:latest | — | 0 | 0 |
| ollama | llama3.3:latest | — | 0 | 0 |
| ollama | phi4:latest | — | 0 | 0 |
| openrouter | google/gemini-2.5-flash-preview-05-20 | — | 0.15 | 0.6 |
| openrouter | google/gemini-2.5-flash-preview-05-20:thinking | — | 0.15 | 3.5 |
| openrouter | google/gemini-2.5-pro-exp-03-25 | — | 0 | 0 |
| openrouter | deepseek/deepseek-chat-v3-0324:free | — | 0 | 0 |
| openrouter | deepseek/deepseek-chat-v3-0324 | — | 0.27 | 1.1 |
| openrouter | openai/gpt-4.1 | — | 2 | 8 |
| openrouter | openai/gpt-4.1-mini | — | 0.4 | 1.6 |
| openrouter | openai/gpt-4.1-nano | — | 0.1 | 0.4 |
| openrouter | openai/o3 | — | 10 | 40 |
| openrouter | openai/codex-mini | — | 1.5 | 6 |
| openrouter | openai/gpt-4o-mini | — | 0.15 | 0.6 |
| openrouter | openai/o4-mini | 0.45 | 1.1 | 4.4 |
| openrouter | openai/o4-mini-high | | 1.1 | 4.4 |
| openrouter | openai/o1-pro | — | 150 | 600 |
| openrouter | meta-llama/llama-3.3-70b-instruct | — | 120 | 600 |
| openrouter | meta-llama/llama-4-maverick | — | 0.18 | 0.6 |
| openrouter | meta-llama/llama-4-scout | — | 0.08 | 0.3 |
| openrouter | qwen/qwen-max | — | 1.6 | 6.4 |
| openrouter | qwen/qwen-turbo | — | 0.05 | 0.2 |
| openrouter | qwen/qwen3-235b-a22b | — | 0.14 | 2 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct:free | — | 0 | 0 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct | — | 0.1 | 0.3 |
| openrouter | mistralai/devstral-small | — | 0.1 | 0.3 |
| openrouter | mistralai/mistral-nemo | — | 0.03 | 0.07 |
| openrouter | thudm/glm-4-32b:free | — | 0 | 0 |
| Provider | Model Name | SWE Score | Input Cost | Output Cost |
| ----------- | ---------------------------------------------- | --------- | ---------- | ----------- |
| bedrock | us.anthropic.claude-3-7-sonnet-20250219-v1:0 | 0.623 | 3 | 15 |
| anthropic | claude-sonnet-4-20250514 | 0.727 | 3 | 15 |
| anthropic | claude-opus-4-20250514 | 0.725 | 15 | 75 |
| anthropic | claude-3-7-sonnet-20250219 | 0.623 | 3 | 15 |
| anthropic | claude-3-5-sonnet-20241022 | 0.49 | 3 | 15 |
| openai | gpt-4o | 0.332 | 2.5 | 10 |
| openai | o1 | 0.489 | 15 | 60 |
| openai | o3 | 0.5 | 2 | 8 |
| openai | o3-mini | 0.493 | 1.1 | 4.4 |
| openai | o4-mini | 0.45 | 1.1 | 4.4 |
| openai | o1-mini | 0.4 | 1.1 | 4.4 |
| openai | o1-pro | | 150 | 600 |
| openai | gpt-4-5-preview | 0.38 | 75 | 150 |
| openai | gpt-4-1-mini | — | 0.4 | 1.6 |
| openai | gpt-4-1-nano | — | 0.1 | 0.4 |
| openai | gpt-4o-mini | 0.3 | 0.15 | 0.6 |
| google | gemini-2.5-pro-preview-05-06 | 0.638 | — | — |
| google | gemini-2.5-pro-preview-03-25 | 0.638 | — | — |
| google | gemini-2.5-flash-preview-04-17 | 0.604 | — | — |
| google | gemini-2.0-flash | 0.518 | 0.15 | 0.6 |
| google | gemini-2.0-flash-lite | — | | |
| perplexity | sonar-pro | — | 3 | 15 |
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
| xai | grok-3 | — | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| ollama | devstral:latest | — | 0 | 0 |
| ollama | qwen3:latest | — | 0 | 0 |
| ollama | qwen3:14b | — | 0 | 0 |
| ollama | qwen3:32b | — | 0 | 0 |
| ollama | mistral-small3.1:latest | — | 0 | 0 |
| ollama | llama3.3:latest | — | 0 | 0 |
| ollama | phi4:latest | — | 0 | 0 |
| openrouter | google/gemini-2.5-flash-preview-05-20 | — | 0.15 | 0.6 |
| openrouter | google/gemini-2.5-flash-preview-05-20:thinking | — | 0.15 | 3.5 |
| openrouter | google/gemini-2.5-pro-exp-03-25 | — | 0 | 0 |
| openrouter | deepseek/deepseek-chat-v3-0324:free | — | 0 | 0 |
| openrouter | deepseek/deepseek-chat-v3-0324 | — | 0.27 | 1.1 |
| openrouter | openai/gpt-4.1 | — | 2 | 8 |
| openrouter | openai/gpt-4.1-mini | — | 0.4 | 1.6 |
| openrouter | openai/gpt-4.1-nano | — | 0.1 | 0.4 |
| openrouter | openai/o3 | — | 10 | 40 |
| openrouter | openai/codex-mini | — | 1.5 | 6 |
| openrouter | openai/gpt-4o-mini | — | 0.15 | 0.6 |
| openrouter | openai/o4-mini | 0.45 | 1.1 | 4.4 |
| openrouter | openai/o4-mini-high | — | 1.1 | 4.4 |
| openrouter | openai/o1-pro | — | 150 | 600 |
| openrouter | meta-llama/llama-3.3-70b-instruct | — | 120 | 600 |
| openrouter | meta-llama/llama-4-maverick | — | 0.18 | 0.6 |
| openrouter | meta-llama/llama-4-scout | — | 0.08 | 0.3 |
| openrouter | qwen/qwen-max | — | 1.6 | 6.4 |
| openrouter | qwen/qwen-turbo | — | 0.05 | 0.2 |
| openrouter | qwen/qwen3-235b-a22b | — | 0.14 | 2 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct:free | — | 0 | 0 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct | — | 0.1 | 0.3 |
| openrouter | mistralai/devstral-small | — | 0.1 | 0.3 |
| openrouter | mistralai/mistral-nemo | — | 0.03 | 0.07 |
| openrouter | thudm/glm-4-32b:free | — | 0 | 0 |
| claude-code | opus | 0.725 | 0 | 0 |
| claude-code | sonnet | 0.727 | 0 | 0 |
## Research Models
| Provider | Model Name | SWE Score | Input Cost | Output Cost |
| ---------- | -------------------------- | --------- | ---------- | ----------- |
| openai | gpt-4o-search-preview | 0.33 | 2.5 | 10 |
| openai | gpt-4o-mini-search-preview | 0.3 | 0.15 | 0.6 |
| perplexity | sonar-pro | — | 3 | 15 |
| perplexity | sonar | — | 1 | 1 |
| perplexity | deep-research | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
| xai | grok-3 | | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| Provider | Model Name | SWE Score | Input Cost | Output Cost |
| ----------- | -------------------------- | --------- | ---------- | ----------- |
| bedrock | us.deepseek.r1-v1:0 | | 1.35 | 5.4 |
| openai | gpt-4o-search-preview | 0.33 | 2.5 | 10 |
| openai | gpt-4o-mini-search-preview | 0.3 | 0.15 | 0.6 |
| perplexity | sonar-pro | — | 3 | 15 |
| perplexity | sonar | | 1 | 1 |
| perplexity | deep-research | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
| xai | grok-3 | — | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| claude-code | opus | 0.725 | 0 | 0 |
| claude-code | sonnet | 0.727 | 0 | 0 |
## Fallback Models
| Provider | Model Name | SWE Score | Input Cost | Output Cost |
| ---------- | ---------------------------------------------- | --------- | ---------- | ----------- |
| anthropic | claude-sonnet-4-20250514 | 0.727 | 3 | 15 |
| anthropic | claude-opus-4-20250514 | 0.725 | 15 | 75 |
| anthropic | claude-3-7-sonnet-20250219 | 0.623 | 3 | 15 |
| anthropic | claude-3-5-sonnet-20241022 | 0.49 | 3 | 15 |
| openai | gpt-4o | 0.332 | 2.5 | 10 |
| openai | o3 | 0.5 | 2 | 8 |
| openai | o4-mini | 0.45 | 1.1 | 4.4 |
| google | gemini-2.5-pro-preview-05-06 | 0.638 | | |
| google | gemini-2.5-pro-preview-03-25 | 0.638 | — | — |
| google | gemini-2.5-flash-preview-04-17 | | — | — |
| google | gemini-2.0-flash | 0.754 | 0.15 | 0.6 |
| google | gemini-2.0-flash-lite | — | | |
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
| xai | grok-3 | | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| ollama | devstral:latest | — | 0 | 0 |
| ollama | qwen3:latest | — | 0 | 0 |
| ollama | qwen3:14b | — | 0 | 0 |
| ollama | qwen3:32b | — | 0 | 0 |
| ollama | mistral-small3.1:latest | — | 0 | 0 |
| ollama | llama3.3:latest | — | 0 | 0 |
| ollama | phi4:latest | — | 0 | 0 |
| openrouter | google/gemini-2.5-flash-preview-05-20 | — | 0.15 | 0.6 |
| openrouter | google/gemini-2.5-flash-preview-05-20:thinking | — | 0.15 | 3.5 |
| openrouter | google/gemini-2.5-pro-exp-03-25 | — | 0 | 0 |
| openrouter | deepseek/deepseek-chat-v3-0324:free | — | 0 | 0 |
| openrouter | openai/gpt-4.1 | — | 2 | 8 |
| openrouter | openai/gpt-4.1-mini | — | 0.4 | 1.6 |
| openrouter | openai/gpt-4.1-nano | — | 0.1 | 0.4 |
| openrouter | openai/o3 | — | 10 | 40 |
| openrouter | openai/codex-mini | — | 1.5 | 6 |
| openrouter | openai/gpt-4o-mini | — | 0.15 | 0.6 |
| openrouter | openai/o4-mini | 0.45 | 1.1 | 4.4 |
| openrouter | openai/o4-mini-high | | 1.1 | 4.4 |
| openrouter | openai/o1-pro | — | 150 | 600 |
| openrouter | meta-llama/llama-3.3-70b-instruct | — | 120 | 600 |
| openrouter | meta-llama/llama-4-maverick | — | 0.18 | 0.6 |
| openrouter | meta-llama/llama-4-scout | — | 0.08 | 0.3 |
| openrouter | qwen/qwen-max | — | 1.6 | 6.4 |
| openrouter | qwen/qwen-turbo | — | 0.05 | 0.2 |
| openrouter | qwen/qwen3-235b-a22b | — | 0.14 | 2 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct:free | — | 0 | 0 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct | — | 0.1 | 0.3 |
| openrouter | mistralai/mistral-nemo | — | 0.03 | 0.07 |
| openrouter | thudm/glm-4-32b:free | — | 0 | 0 |
| Provider | Model Name | SWE Score | Input Cost | Output Cost |
| ----------- | ---------------------------------------------- | --------- | ---------- | ----------- |
| bedrock | us.anthropic.claude-3-7-sonnet-20250219-v1:0 | 0.623 | 3 | 15 |
| anthropic | claude-sonnet-4-20250514 | 0.727 | 3 | 15 |
| anthropic | claude-opus-4-20250514 | 0.725 | 15 | 75 |
| anthropic | claude-3-7-sonnet-20250219 | 0.623 | 3 | 15 |
| anthropic | claude-3-5-sonnet-20241022 | 0.49 | 3 | 15 |
| openai | gpt-4o | 0.332 | 2.5 | 10 |
| openai | o3 | 0.5 | 2 | 8 |
| openai | o4-mini | 0.45 | 1.1 | 4.4 |
| google | gemini-2.5-pro-preview-05-06 | 0.638 | — | — |
| google | gemini-2.5-pro-preview-03-25 | 0.638 | — | — |
| google | gemini-2.5-flash-preview-04-17 | 0.604 | — | — |
| google | gemini-2.0-flash | 0.518 | 0.15 | 0.6 |
| google | gemini-2.0-flash-lite | | | |
| perplexity | sonar-reasoning-pro | 0.211 | 2 | 8 |
| perplexity | sonar-reasoning | 0.211 | 1 | 5 |
| xai | grok-3 | — | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| ollama | devstral:latest | — | 0 | 0 |
| ollama | qwen3:latest | — | 0 | 0 |
| ollama | qwen3:14b | — | 0 | 0 |
| ollama | qwen3:32b | — | 0 | 0 |
| ollama | mistral-small3.1:latest | — | 0 | 0 |
| ollama | llama3.3:latest | — | 0 | 0 |
| ollama | phi4:latest | — | 0 | 0 |
| openrouter | google/gemini-2.5-flash-preview-05-20 | — | 0.15 | 0.6 |
| openrouter | google/gemini-2.5-flash-preview-05-20:thinking | — | 0.15 | 3.5 |
| openrouter | google/gemini-2.5-pro-exp-03-25 | — | 0 | 0 |
| openrouter | deepseek/deepseek-chat-v3-0324:free | — | 0 | 0 |
| openrouter | openai/gpt-4.1 | — | 2 | 8 |
| openrouter | openai/gpt-4.1-mini | — | 0.4 | 1.6 |
| openrouter | openai/gpt-4.1-nano | — | 0.1 | 0.4 |
| openrouter | openai/o3 | — | 10 | 40 |
| openrouter | openai/codex-mini | — | 1.5 | 6 |
| openrouter | openai/gpt-4o-mini | — | 0.15 | 0.6 |
| openrouter | openai/o4-mini | 0.45 | 1.1 | 4.4 |
| openrouter | openai/o4-mini-high | — | 1.1 | 4.4 |
| openrouter | openai/o1-pro | — | 150 | 600 |
| openrouter | meta-llama/llama-3.3-70b-instruct | — | 120 | 600 |
| openrouter | meta-llama/llama-4-maverick | — | 0.18 | 0.6 |
| openrouter | meta-llama/llama-4-scout | — | 0.08 | 0.3 |
| openrouter | qwen/qwen-max | — | 1.6 | 6.4 |
| openrouter | qwen/qwen-turbo | — | 0.05 | 0.2 |
| openrouter | qwen/qwen3-235b-a22b | — | 0.14 | 2 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct:free | — | 0 | 0 |
| openrouter | mistralai/mistral-small-3.1-24b-instruct | — | 0.1 | 0.3 |
| openrouter | mistralai/mistral-nemo | — | 0.03 | 0.07 |
| openrouter | thudm/glm-4-32b:free | — | 0 | 0 |
| claude-code | opus | 0.725 | 0 | 0 |
| claude-code | sonnet | 0.727 | 0 | 0 |

View File

@@ -26,6 +26,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {string} [args.prompt] - Additional context to guide subtask generation.
* @param {boolean} [args.force] - Force expansion even if subtasks exist.
* @param {string} [args.projectRoot] - Project root directory.
* @param {string} [args.tag] - Tag for the task
* @param {Object} log - Logger object
* @param {Object} context - Context object containing session
* @param {Object} [context.session] - MCP Session object
@@ -34,7 +35,8 @@ import { createLogWrapper } from '../../tools/utils.js';
export async function expandTaskDirect(args, log, context = {}) {
const { session } = context; // Extract session
// Destructure expected args, including projectRoot
const { tasksJsonPath, id, num, research, prompt, force, projectRoot } = args;
const { tasksJsonPath, id, num, research, prompt, force, projectRoot, tag } =
args;
// Log session root data for debugging
log.info(
@@ -194,7 +196,8 @@ export async function expandTaskDirect(args, log, context = {}) {
session,
projectRoot,
commandName: 'expand-task',
outputType: 'mcp'
outputType: 'mcp',
tag
},
forceFlag
);

View File

@@ -5,11 +5,13 @@ import {
// isSilentMode // Not used directly here
} from '../../../../scripts/modules/utils.js';
import os from 'os'; // Import os module for home directory check
import { RULE_PROFILES } from '../../../../src/constants/profiles.js';
import { convertAllRulesToProfileRules } from '../../../../src/utils/rule-transformer.js';
/**
* Direct function wrapper for initializing a project.
* Derives target directory from session, sets CWD, and calls core init logic.
* @param {object} args - Arguments containing initialization options (addAliases, skipInstall, yes, projectRoot)
* @param {object} args - Arguments containing initialization options (addAliases, skipInstall, yes, projectRoot, rules)
* @param {object} log - The FastMCP logger instance.
* @param {object} context - The context object, must contain { session }.
* @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
@@ -68,6 +70,17 @@ export async function initializeProjectDirect(args, log, context = {}) {
yes: true // Force yes mode
};
// Handle rules option just like CLI
if (Array.isArray(args.rules) && args.rules.length > 0) {
options.rules = args.rules;
log.info(`Including rules: ${args.rules.join(', ')}`);
} else {
options.rules = RULE_PROFILES;
log.info(
`No rule profiles specified, defaulting to: ${RULE_PROFILES.join(', ')}`
);
}
log.info(`Initializing project with options: ${JSON.stringify(options)}`);
const result = await initializeProject(options); // Call core logic

View File

@@ -13,6 +13,41 @@ import {
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
import { CUSTOM_PROVIDERS_ARRAY } from '../../../../src/constants/providers.js';
// Define supported roles for model setting
const MODEL_ROLES = ['main', 'research', 'fallback'];
/**
* Determine provider hint from custom provider flags
* @param {Object} args - Arguments containing provider flags
* @returns {string|undefined} Provider hint or undefined if no custom provider flag is set
*/
function getProviderHint(args) {
return CUSTOM_PROVIDERS_ARRAY.find((provider) => args[provider]);
}
/**
* Handle setting models for different roles
* @param {Object} args - Arguments containing role-specific model IDs
* @param {Object} context - Context object with session, mcpLog, projectRoot
* @returns {Object|null} Result if a model was set, null if no model setting was requested
*/
async function handleModelSetting(args, context) {
for (const role of MODEL_ROLES) {
const roleKey = `set${role.charAt(0).toUpperCase() + role.slice(1)}`; // setMain, setResearch, setFallback
if (args[roleKey]) {
const providerHint = getProviderHint(args);
return await setModel(role, args[roleKey], {
...context,
providerHint
});
}
}
return null; // No model setting was requested
}
/**
* Get or update model configuration
@@ -31,16 +66,21 @@ export async function modelsDirect(args, log, context = {}) {
log.info(`Executing models_direct with args: ${JSON.stringify(args)}`);
log.info(`Using project root: ${projectRoot}`);
// Validate flags: cannot use both openrouter and ollama simultaneously
if (args.openrouter && args.ollama) {
// Validate flags: only one custom provider flag can be used simultaneously
const customProviderFlags = CUSTOM_PROVIDERS_ARRAY.filter(
(provider) => args[provider]
);
if (customProviderFlags.length > 1) {
log.error(
'Error: Cannot use both openrouter and ollama flags simultaneously.'
'Error: Cannot use multiple custom provider flags simultaneously.'
);
return {
success: false,
error: {
code: 'INVALID_ARGS',
message: 'Cannot use both openrouter and ollama flags simultaneously.'
message:
'Cannot use multiple custom provider flags simultaneously. Choose only one: openrouter, ollama, bedrock, azure, or vertex.'
}
};
}
@@ -54,55 +94,22 @@ export async function modelsDirect(args, log, context = {}) {
return await getAvailableModelsList({
session,
mcpLog,
projectRoot // Pass projectRoot to function
projectRoot
});
}
// Handle setting a specific model
if (args.setMain) {
return await setModel('main', args.setMain, {
session,
mcpLog,
projectRoot, // Pass projectRoot to function
providerHint: args.openrouter
? 'openrouter'
: args.ollama
? 'ollama'
: undefined // Pass hint
});
}
if (args.setResearch) {
return await setModel('research', args.setResearch, {
session,
mcpLog,
projectRoot, // Pass projectRoot to function
providerHint: args.openrouter
? 'openrouter'
: args.ollama
? 'ollama'
: undefined // Pass hint
});
}
if (args.setFallback) {
return await setModel('fallback', args.setFallback, {
session,
mcpLog,
projectRoot, // Pass projectRoot to function
providerHint: args.openrouter
? 'openrouter'
: args.ollama
? 'ollama'
: undefined // Pass hint
});
// Handle setting any model role using unified function
const modelContext = { session, mcpLog, projectRoot };
const modelSetResult = await handleModelSetting(args, modelContext);
if (modelSetResult) {
return modelSetResult;
}
// Default action: get current configuration
return await getModelConfiguration({
session,
mcpLog,
projectRoot // Pass projectRoot to function
projectRoot
});
} finally {
disableSilentMode();

View File

@@ -0,0 +1,210 @@
/**
* rules.js
* Direct function implementation for adding or removing rules
*/
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import {
convertAllRulesToProfileRules,
removeProfileRules,
getRulesProfile,
isValidProfile
} from '../../../../src/utils/rule-transformer.js';
import { RULE_PROFILES } from '../../../../src/constants/profiles.js';
import { RULES_ACTIONS } from '../../../../src/constants/rules-actions.js';
import {
wouldRemovalLeaveNoProfiles,
getInstalledProfiles
} from '../../../../src/utils/profiles.js';
import path from 'path';
import fs from 'fs';
/**
* Direct function wrapper for adding or removing rules.
* @param {Object} args - Command arguments
* @param {"add"|"remove"} args.action - Action to perform: add or remove rules
* @param {string[]} args.profiles - List of profiles to add or remove
* @param {string} args.projectRoot - Absolute path to the project root
* @param {boolean} [args.yes=true] - Run non-interactively
* @param {Object} log - Logger object
* @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function rulesDirect(args, log, context = {}) {
enableSilentMode();
try {
const { action, profiles, projectRoot, yes, force } = args;
if (
!action ||
!Array.isArray(profiles) ||
profiles.length === 0 ||
!projectRoot
) {
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'action, profiles, and projectRoot are required.'
}
};
}
const removalResults = [];
const addResults = [];
if (action === RULES_ACTIONS.REMOVE) {
// Safety check: Ensure this won't remove all rule profiles (unless forced)
if (!force && wouldRemovalLeaveNoProfiles(projectRoot, profiles)) {
const installedProfiles = getInstalledProfiles(projectRoot);
const remainingProfiles = installedProfiles.filter(
(profile) => !profiles.includes(profile)
);
return {
success: false,
error: {
code: 'CRITICAL_REMOVAL_BLOCKED',
message: `CRITICAL: This operation would remove ALL remaining rule profiles (${profiles.join(', ')}), leaving your project with no rules configurations. This could significantly impact functionality. Currently installed profiles: ${installedProfiles.join(', ')}. If you're certain you want to proceed, set force: true or use the CLI with --force flag.`
}
};
}
for (const profile of profiles) {
if (!isValidProfile(profile)) {
removalResults.push({
profileName: profile,
success: false,
error: `The requested rule profile for '${profile}' is unavailable. Supported profiles are: ${RULE_PROFILES.join(', ')}.`
});
continue;
}
const profileConfig = getRulesProfile(profile);
const result = removeProfileRules(projectRoot, profileConfig);
removalResults.push(result);
}
const successes = removalResults
.filter((r) => r.success)
.map((r) => r.profileName);
const skipped = removalResults
.filter((r) => r.skipped)
.map((r) => r.profileName);
const errors = removalResults.filter(
(r) => r.error && !r.success && !r.skipped
);
const withNotices = removalResults.filter((r) => r.notice);
let summary = '';
if (successes.length > 0) {
summary += `Successfully removed Task Master rules: ${successes.join(', ')}.`;
}
if (skipped.length > 0) {
summary += `Skipped (default or protected): ${skipped.join(', ')}.`;
}
if (errors.length > 0) {
summary += errors
.map((r) => `Error removing ${r.profileName}: ${r.error}`)
.join(' ');
}
if (withNotices.length > 0) {
summary += ` Notices: ${withNotices.map((r) => `${r.profileName} - ${r.notice}`).join('; ')}.`;
}
disableSilentMode();
return {
success: errors.length === 0,
data: { summary, results: removalResults }
};
} else if (action === RULES_ACTIONS.ADD) {
for (const profile of profiles) {
if (!isValidProfile(profile)) {
addResults.push({
profileName: profile,
success: false,
error: `Profile not found: static import missing for '${profile}'. Valid profiles: ${RULE_PROFILES.join(', ')}`
});
continue;
}
const profileConfig = getRulesProfile(profile);
const { success, failed } = convertAllRulesToProfileRules(
projectRoot,
profileConfig
);
// Determine paths
const rulesDir = profileConfig.rulesDir;
const profileRulesDir = path.join(projectRoot, rulesDir);
const profileDir = profileConfig.profileDir;
const mcpConfig = profileConfig.mcpConfig !== false;
const mcpPath =
mcpConfig && profileConfig.mcpConfigPath
? path.join(projectRoot, profileConfig.mcpConfigPath)
: null;
// Check what was created
const mcpConfigCreated =
mcpConfig && mcpPath ? fs.existsSync(mcpPath) : undefined;
const rulesDirCreated = fs.existsSync(profileRulesDir);
const profileFolderCreated = fs.existsSync(
path.join(projectRoot, profileDir)
);
const error =
failed > 0 ? `${failed} rule files failed to convert.` : null;
const resultObj = {
profileName: profile,
mcpConfigCreated,
rulesDirCreated,
profileFolderCreated,
skipped: false,
error,
success:
(mcpConfig ? mcpConfigCreated : true) &&
rulesDirCreated &&
success > 0 &&
!error
};
addResults.push(resultObj);
}
const successes = addResults
.filter((r) => r.success)
.map((r) => r.profileName);
const errors = addResults.filter((r) => r.error && !r.success);
let summary = '';
if (successes.length > 0) {
summary += `Successfully added rules: ${successes.join(', ')}.`;
}
if (errors.length > 0) {
summary += errors
.map((r) => ` Error adding ${r.profileName}: ${r.error}`)
.join(' ');
}
disableSilentMode();
return {
success: errors.length === 0,
data: { summary, results: addResults }
};
} else {
disableSilentMode();
return {
success: false,
error: {
code: 'INVALID_ACTION',
message: `Unknown action. Use "${RULES_ACTIONS.ADD}" or "${RULES_ACTIONS.REMOVE}".`
}
};
}
} catch (error) {
disableSilentMode();
log.error(`[rulesDirect] Error: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'RULES_ERROR',
message: error.message
}
};
}
}

View File

@@ -45,7 +45,8 @@ export function registerExpandTaskTool(server) {
.boolean()
.optional()
.default(false)
.describe('Force expansion even if subtasks exist')
.describe('Force expansion even if subtasks exist'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
@@ -73,7 +74,8 @@ export function registerExpandTaskTool(server) {
research: args.research,
prompt: args.prompt,
force: args.force,
projectRoot: args.projectRoot
projectRoot: args.projectRoot,
tag: args.tag || 'master'
},
log,
{ session }

View File

@@ -36,6 +36,7 @@ import { registerUseTagTool } from './use-tag.js';
import { registerRenameTagTool } from './rename-tag.js';
import { registerCopyTagTool } from './copy-tag.js';
import { registerResearchTool } from './research.js';
import { registerRulesTool } from './rules.js';
/**
* Register all Task Master tools with the MCP server
@@ -48,6 +49,7 @@ export function registerTaskMasterTools(server) {
// Group 1: Initialization & Setup
registerInitializeProjectTool(server);
registerModelsTool(server);
registerRulesTool(server);
registerParsePRDTool(server);
// Group 2: Task Analysis & Expansion

View File

@@ -5,6 +5,7 @@ import {
withNormalizedProjectRoot
} from './utils.js';
import { initializeProjectDirect } from '../core/task-master-core.js';
import { RULE_PROFILES } from '../../../src/constants/profiles.js';
export function registerInitializeProjectTool(server) {
server.addTool({
@@ -35,6 +36,12 @@ export function registerInitializeProjectTool(server) {
.string()
.describe(
'The root directory for the project. ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY. IF NOT SET, THE TOOL WILL NOT WORK.'
),
rules: z
.array(z.enum(RULE_PROFILES))
.optional()
.describe(
`List of rule profiles to include at initialization. If omitted, defaults to all available profiles. Available options: ${RULE_PROFILES.join(', ')}`
)
}),
execute: withNormalizedProjectRoot(async (args, context) => {

View File

@@ -55,7 +55,21 @@ export function registerModelsTool(server) {
ollama: z
.boolean()
.optional()
.describe('Indicates the set model ID is a custom Ollama model.')
.describe('Indicates the set model ID is a custom Ollama model.'),
bedrock: z
.boolean()
.optional()
.describe('Indicates the set model ID is a custom AWS Bedrock model.'),
azure: z
.boolean()
.optional()
.describe('Indicates the set model ID is a custom Azure OpenAI model.'),
vertex: z
.boolean()
.optional()
.describe(
'Indicates the set model ID is a custom Google Vertex AI model.'
)
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {

View File

@@ -0,0 +1,59 @@
/**
* tools/rules.js
* Tool to add or remove rules from a project (MCP server)
*/
import { z } from 'zod';
import {
createErrorResponse,
handleApiResult,
withNormalizedProjectRoot
} from './utils.js';
import { rulesDirect } from '../core/direct-functions/rules.js';
import { RULE_PROFILES } from '../../../src/constants/profiles.js';
/**
* Register the rules tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerRulesTool(server) {
server.addTool({
name: 'rules',
description: 'Add or remove rule profiles from the project.',
parameters: z.object({
action: z
.enum(['add', 'remove'])
.describe('Whether to add or remove rule profiles.'),
profiles: z
.array(z.enum(RULE_PROFILES))
.min(1)
.describe(
`List of rule profiles to add or remove (e.g., [\"cursor\", \"roo\"]). Available options: ${RULE_PROFILES.join(', ')}`
),
projectRoot: z
.string()
.describe(
'The root directory of the project. Must be an absolute path.'
),
force: z
.boolean()
.optional()
.default(false)
.describe(
'DANGEROUS: Force removal even if it would leave no rule profiles. Only use if you are absolutely certain.'
)
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(
`[rules tool] Executing action: ${args.action} for profiles: ${args.profiles.join(', ')} in ${args.projectRoot}`
);
const result = await rulesDirect(args, log, { session });
return handleApiResult(result, log);
} catch (error) {
log.error(`[rules tool] Error: ${error.message}`);
return createErrorResponse(error.message, { details: error.stack });
}
})
});
}

376
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "task-master-ai",
"version": "0.17.0-rc.1",
"version": "0.17.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "task-master-ai",
"version": "0.17.0-rc.1",
"version": "0.17.1",
"license": "MIT WITH Commons-Clause",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^2.2.9",
@@ -20,6 +20,7 @@
"@ai-sdk/xai": "^1.2.15",
"@anthropic-ai/sdk": "^0.39.0",
"@aws-sdk/credential-providers": "^3.817.0",
"@inquirer/search": "^3.0.15",
"@openrouter/ai-sdk-provider": "^0.4.5",
"ai": "^4.3.10",
"boxen": "^8.0.1",
@@ -42,7 +43,6 @@
"ollama-ai-provider": "^1.2.0",
"openai": "^4.89.0",
"ora": "^8.2.0",
"task-master-ai": "0.17.0-rc.1",
"uuid": "^11.1.0",
"zod": "^3.23.8"
},
@@ -68,6 +68,9 @@
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@anthropic-ai/claude-code": "^1.0.25"
}
},
"node_modules/@ai-sdk/amazon-bedrock": {
@@ -446,6 +449,28 @@
"node": ">=6.0.0"
}
},
"node_modules/@anthropic-ai/claude-code": {
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.25.tgz",
"integrity": "sha512-5p4FLlFO4TuRf0zV0axiOxiAkUC8eer0lqJi/A/pA46LESv31Alw6xaNYgwQVkP6oSbP5PydK36u7YrB9QSaXQ==",
"hasInstallScript": true,
"license": "SEE LICENSE IN README.md",
"optional": true,
"bin": {
"claude": "cli.js"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "^0.33.5",
"@img/sharp-darwin-x64": "^0.33.5",
"@img/sharp-linux-arm": "^0.33.5",
"@img/sharp-linux-arm64": "^0.33.5",
"@img/sharp-linux-x64": "^0.33.5",
"@img/sharp-win32-x64": "^0.33.5"
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.39.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz",
@@ -2651,6 +2676,215 @@
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@inquirer/checkbox": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.4.tgz",
@@ -2697,13 +2931,13 @@
}
},
"node_modules/@inquirer/core": {
"version": "10.1.9",
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.9.tgz",
"integrity": "sha512-sXhVB8n20NYkUBfDYgizGHlpRVaCRjtuzNZA6xpALIUbkgfd2Hjz+DfEN6+h1BRnuxw0/P4jCIMjMsEOAMwAJw==",
"version": "10.1.13",
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz",
"integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==",
"license": "MIT",
"dependencies": {
"@inquirer/figures": "^1.0.11",
"@inquirer/type": "^3.0.5",
"@inquirer/figures": "^1.0.12",
"@inquirer/type": "^3.0.7",
"ansi-escapes": "^4.3.2",
"cli-width": "^4.1.0",
"mute-stream": "^2.0.0",
@@ -2823,9 +3057,9 @@
}
},
"node_modules/@inquirer/figures": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz",
"integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==",
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz",
"integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -2947,14 +3181,14 @@
}
},
"node_modules/@inquirer/search": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.11.tgz",
"integrity": "sha512-9CWQT0ikYcg6Ls3TOa7jljsD7PgjcsYEM0bYE+Gkz+uoW9u8eaJCRHJKkucpRE5+xKtaaDbrND+nPDoxzjYyew==",
"version": "3.0.15",
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz",
"integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/figures": "^1.0.11",
"@inquirer/type": "^3.0.5",
"@inquirer/core": "^10.1.13",
"@inquirer/figures": "^1.0.12",
"@inquirer/type": "^3.0.7",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
@@ -2994,9 +3228,9 @@
}
},
"node_modules/@inquirer/type": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.5.tgz",
"integrity": "sha512-ZJpeIYYueOz/i/ONzrfof8g89kNdO2hjGuvULROo3O8rlB2CRtSseE5KeirnyE4t/thAn/EwvS/vuQeJCn+NZg==",
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz",
"integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -3868,6 +4102,19 @@
"node": ">= 0.6"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3966,6 +4213,16 @@
"node": ">=8.0.0"
}
},
"node_modules/@paralleldrive/cuid2": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
"integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.1.5"
}
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
@@ -5328,9 +5585,9 @@
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7159,16 +7416,19 @@
}
},
"node_modules/formidable": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz",
"integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==",
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
"integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@paralleldrive/cuid2": "^2.2.2",
"dezalgo": "^1.0.4",
"hexoid": "^2.0.0",
"once": "^1.4.0"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
@@ -7672,16 +7932,6 @@
"node": ">=18.0.0"
}
},
"node_modules/hexoid": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz",
"integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -11370,58 +11620,6 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/task-master-ai": {
"version": "0.17.0-rc.1",
"resolved": "https://registry.npmjs.org/task-master-ai/-/task-master-ai-0.17.0-rc.1.tgz",
"integrity": "sha512-4V70dlqedWj/i5RHXyKeqm2IFwNnFYB0Ndq4S8Mc9wghpENkAhXE1qVg27NscP1n+TrEu1HtgXVOfCMDdgvzag==",
"license": "MIT WITH Commons-Clause",
"dependencies": {
"@ai-sdk/amazon-bedrock": "^2.2.9",
"@ai-sdk/anthropic": "^1.2.10",
"@ai-sdk/azure": "^1.3.17",
"@ai-sdk/google": "^1.2.13",
"@ai-sdk/google-vertex": "^2.2.23",
"@ai-sdk/mistral": "^1.2.7",
"@ai-sdk/openai": "^1.3.20",
"@ai-sdk/perplexity": "^1.1.7",
"@ai-sdk/xai": "^1.2.15",
"@anthropic-ai/sdk": "^0.39.0",
"@aws-sdk/credential-providers": "^3.817.0",
"@openrouter/ai-sdk-provider": "^0.4.5",
"ai": "^4.3.10",
"boxen": "^8.0.1",
"chalk": "^5.4.1",
"cli-highlight": "^2.1.11",
"cli-table3": "^0.6.5",
"commander": "^11.1.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.21.2",
"fastmcp": "^2.2.2",
"figlet": "^1.8.0",
"fuse.js": "^7.1.0",
"gpt-tokens": "^1.3.14",
"gradient-string": "^3.0.0",
"helmet": "^8.1.0",
"inquirer": "^12.5.0",
"jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0",
"ollama-ai-provider": "^1.2.0",
"openai": "^4.89.0",
"ora": "^8.2.0",
"task-master-ai": "0.17.0-rc.1",
"uuid": "^11.1.0",
"zod": "^3.23.8"
},
"bin": {
"task-master": "bin/task-master.js",
"task-master-ai": "mcp-server/server.js",
"task-master-mcp": "mcp-server/server.js"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/term-size": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "task-master-ai",
"version": "0.17.0-rc.1",
"version": "0.17.1",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js",
"type": "module",
@@ -50,6 +50,7 @@
"@ai-sdk/xai": "^1.2.15",
"@anthropic-ai/sdk": "^0.39.0",
"@aws-sdk/credential-providers": "^3.817.0",
"@inquirer/search": "^3.0.15",
"@openrouter/ai-sdk-provider": "^0.4.5",
"ai": "^4.3.10",
"boxen": "^8.0.1",
@@ -72,10 +73,12 @@
"ollama-ai-provider": "^1.2.0",
"openai": "^4.89.0",
"ora": "^8.2.0",
"task-master-ai": "0.17.0-rc.1",
"uuid": "^11.1.0",
"zod": "^3.23.8"
},
"optionalDependencies": {
"@anthropic-ai/claude-code": "^1.0.25"
},
"engines": {
"node": ">=18.0.0"
},
@@ -116,4 +119,4 @@
"supertest": "^7.1.0",
"tsx": "^4.16.2"
}
}
}

View File

@@ -23,7 +23,12 @@ import figlet from 'figlet';
import boxen from 'boxen';
import gradient from 'gradient-string';
import { isSilentMode } from './modules/utils.js';
import { convertAllCursorRulesToRooRules } from './modules/rule-transformer.js';
import { RULE_PROFILES } from '../src/constants/profiles.js';
import {
convertAllRulesToProfileRules,
getRulesProfile
} from '../src/utils/rule-transformer.js';
import { execSync } from 'child_process';
import {
EXAMPLE_PRD_FILE,
@@ -221,70 +226,9 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
// case 'scripts_README.md':
// sourcePath = path.join(__dirname, '..', 'assets', 'scripts_README.md');
// break;
case 'dev_workflow.mdc':
sourcePath = path.join(
__dirname,
'..',
'.cursor',
'rules',
'dev_workflow.mdc'
);
break;
case 'taskmaster.mdc':
sourcePath = path.join(
__dirname,
'..',
'.cursor',
'rules',
'taskmaster.mdc'
);
break;
case 'cursor_rules.mdc':
sourcePath = path.join(
__dirname,
'..',
'.cursor',
'rules',
'cursor_rules.mdc'
);
break;
case 'self_improve.mdc':
sourcePath = path.join(
__dirname,
'..',
'.cursor',
'rules',
'self_improve.mdc'
);
break;
// case 'README-task-master.md':
// sourcePath = path.join(__dirname, '..', 'README-task-master.md');
break;
case 'windsurfrules':
sourcePath = path.join(__dirname, '..', 'assets', '.windsurfrules');
break;
case '.roomodes':
sourcePath = path.join(__dirname, '..', 'assets', 'roocode', '.roomodes');
break;
case 'architect-rules':
case 'ask-rules':
case 'boomerang-rules':
case 'code-rules':
case 'debug-rules':
case 'test-rules': {
// Extract the mode name from the template name (e.g., 'architect' from 'architect-rules')
const mode = templateName.split('-')[0];
sourcePath = path.join(
__dirname,
'..',
'assets',
'roocode',
'.roo',
`rules-${mode}`,
templateName
);
break;
}
// case 'README-task-master.md':
// sourcePath = path.join(__dirname, '..', 'README-task-master.md');
// break;
default:
// For other files like env.example, gitignore, etc. that don't have direct equivalents
sourcePath = path.join(__dirname, '..', 'assets', templateName);
@@ -334,21 +278,6 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
return;
}
// Handle .windsurfrules - append the entire content
if (filename === '.windsurfrules') {
log(
'info',
`${targetPath} already exists, appending content instead of overwriting...`
);
const existingContent = fs.readFileSync(targetPath, 'utf8');
// Add a separator comment before appending our content
const updatedContent = `${existingContent.trim()}\n\n# Added by Task Master - Development Workflow Rules\n\n${content}`;
fs.writeFileSync(targetPath, updatedContent);
log('success', `Updated ${targetPath} with additional rules`);
return;
}
// Handle README.md - offer to preserve or create a different file
if (filename === 'README-task-master.md') {
log('info', `${targetPath} already exists`);
@@ -375,7 +304,7 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
log('info', `Created file: ${targetPath}`);
}
// Main function to initialize a new project (No longer needs isInteractive logic)
// Main function to initialize a new project
async function initializeProject(options = {}) {
// Receives options as argument
// Only display banner if not in silent mode
@@ -397,6 +326,11 @@ async function initializeProject(options = {}) {
// console.log('Skip prompts determined:', skipPrompts);
// }
const selectedRuleProfiles =
options.rules && Array.isArray(options.rules) && options.rules.length > 0
? options.rules
: RULE_PROFILES; // Default to all profiles
if (skipPrompts) {
if (!isSilentMode()) {
console.log('SKIPPING PROMPTS - Using defaults or provided values');
@@ -423,16 +357,16 @@ async function initializeProject(options = {}) {
};
}
createProjectStructure(addAliases, dryRun, options);
createProjectStructure(addAliases, dryRun, options, selectedRuleProfiles);
} else {
// Interactive logic
log('info', 'Required options not provided, proceeding with prompts.');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
try {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// Only prompt for shell aliases
const addAliasesInput = await promptQuestion(
rl,
@@ -456,14 +390,32 @@ async function initializeProject(options = {}) {
chalk.yellow('\nDo you want to continue with these settings? (Y/n): ')
);
const shouldContinue = confirmInput.trim().toLowerCase() !== 'n';
rl.close();
if (!shouldContinue) {
rl.close();
log('info', 'Project initialization cancelled by user');
process.exit(0);
return;
}
// Only run interactive rules if rules flag not provided via command line
if (options.rulesExplicitlyProvided) {
log(
'info',
`Using rule profiles provided via command line: ${selectedRuleProfiles.join(', ')}`
);
} else {
try {
const targetDir = process.cwd();
execSync('npx task-master rules setup', {
stdio: 'inherit',
cwd: targetDir
});
} catch (error) {
log('error', 'Failed to run interactive rules setup:', error.message);
}
}
const dryRun = options.dryRun || false;
if (dryRun) {
@@ -479,7 +431,13 @@ async function initializeProject(options = {}) {
}
// Create structure using only necessary values
createProjectStructure(addAliasesPrompted, dryRun, options);
createProjectStructure(
addAliasesPrompted,
dryRun,
options,
selectedRuleProfiles
);
rl.close();
} catch (error) {
rl.close();
log('error', `Error during initialization process: ${error.message}`);
@@ -498,23 +456,15 @@ function promptQuestion(rl, question) {
}
// Function to create the project structure
function createProjectStructure(addAliases, dryRun, options) {
function createProjectStructure(
addAliases,
dryRun,
options,
selectedRuleProfiles = RULE_PROFILES // Default to all rule profiles
) {
const targetDir = process.cwd();
log('info', `Initializing project in ${targetDir}`);
// Define Roo modes locally (external integration, not part of core Task Master)
const ROO_MODES = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test'];
// Create directories
ensureDirectoryExists(path.join(targetDir, '.cursor/rules'));
// Create Roo directories
ensureDirectoryExists(path.join(targetDir, '.roo'));
ensureDirectoryExists(path.join(targetDir, '.roo/rules'));
for (const mode of ROO_MODES) {
ensureDirectoryExists(path.join(targetDir, '.roo', `rules-${mode}`));
}
// Create NEW .taskmaster directory structure (using constants)
ensureDirectoryExists(path.join(targetDir, TASKMASTER_DIR));
ensureDirectoryExists(path.join(targetDir, TASKMASTER_TASKS_DIR));
@@ -525,14 +475,22 @@ function createProjectStructure(addAliases, dryRun, options) {
// Create initial state.json file for tag management
createInitialStateFile(targetDir);
// Setup MCP configuration for integration with Cursor
setupMCPConfiguration(targetDir);
// Copy template files with replacements
const replacements = {
year: new Date().getFullYear()
};
// Helper function to create rule profiles
function _processSingleProfile(profileName) {
const profile = getRulesProfile(profileName);
if (profile) {
convertAllRulesToProfileRules(targetDir, profile);
// Also triggers MCP config setup (if applicable)
} else {
log('warn', `Unknown rule profile: ${profileName}`);
}
}
// Copy .env.example
copyTemplateFile(
'env.example',
@@ -552,48 +510,6 @@ function createProjectStructure(addAliases, dryRun, options) {
// Copy .gitignore
copyTemplateFile('gitignore', path.join(targetDir, GITIGNORE_FILE));
// Copy dev_workflow.mdc
copyTemplateFile(
'dev_workflow.mdc',
path.join(targetDir, '.cursor/rules/dev_workflow.mdc')
);
// Copy taskmaster.mdc
copyTemplateFile(
'taskmaster.mdc',
path.join(targetDir, '.cursor/rules/taskmaster.mdc')
);
// Copy cursor_rules.mdc
copyTemplateFile(
'cursor_rules.mdc',
path.join(targetDir, '.cursor/rules/cursor_rules.mdc')
);
// Copy self_improve.mdc
copyTemplateFile(
'self_improve.mdc',
path.join(targetDir, '.cursor/rules/self_improve.mdc')
);
// Generate Roo rules from Cursor rules
log('info', 'Generating Roo rules from Cursor rules...');
convertAllCursorRulesToRooRules(targetDir);
// Copy .windsurfrules
copyTemplateFile('windsurfrules', path.join(targetDir, '.windsurfrules'));
// Copy .roomodes for Roo Code integration
copyTemplateFile('.roomodes', path.join(targetDir, '.roomodes'));
// Copy Roo rule files for each mode
for (const mode of ROO_MODES) {
copyTemplateFile(
`${mode}-rules`,
path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)
);
}
// Copy example_prd.txt to NEW location
copyTemplateFile('example_prd.txt', path.join(targetDir, EXAMPLE_PRD_FILE));
@@ -608,6 +524,17 @@ function createProjectStructure(addAliases, dryRun, options) {
log('warn', 'Git not available, skipping repository initialization');
}
// Generate profile rules from assets/rules
log('info', 'Generating profile rules from assets/rules...');
for (const profileName of selectedRuleProfiles) {
_processSingleProfile(profileName);
}
// Add shell aliases if requested
if (addAliases) {
addShellAliases();
}
// Run npm install automatically
const npmInstallOptions = {
cwd: targetDir,
@@ -729,114 +656,5 @@ function createProjectStructure(addAliases, dryRun, options) {
}
}
// Function to setup MCP configuration for Cursor integration
function setupMCPConfiguration(targetDir) {
const mcpDirPath = path.join(targetDir, '.cursor');
const mcpJsonPath = path.join(mcpDirPath, 'mcp.json');
log('info', 'Setting up MCP configuration for Cursor integration...');
// Create .cursor directory if it doesn't exist
ensureDirectoryExists(mcpDirPath);
// New MCP config to be added - references the installed package
const newMCPServer = {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai'],
env: {
ANTHROPIC_API_KEY: 'ANTHROPIC_API_KEY_HERE',
PERPLEXITY_API_KEY: 'PERPLEXITY_API_KEY_HERE',
OPENAI_API_KEY: 'OPENAI_API_KEY_HERE',
GOOGLE_API_KEY: 'GOOGLE_API_KEY_HERE',
XAI_API_KEY: 'XAI_API_KEY_HERE',
OPENROUTER_API_KEY: 'OPENROUTER_API_KEY_HERE',
MISTRAL_API_KEY: 'MISTRAL_API_KEY_HERE',
AZURE_OPENAI_API_KEY: 'AZURE_OPENAI_API_KEY_HERE',
OLLAMA_API_KEY: 'OLLAMA_API_KEY_HERE'
}
}
};
// Check if mcp.json already existsimage.png
if (fs.existsSync(mcpJsonPath)) {
log(
'info',
'MCP configuration file already exists, checking for existing task-master-mcp...'
);
try {
// Read existing config
const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
// Initialize mcpServers if it doesn't exist
if (!mcpConfig.mcpServers) {
mcpConfig.mcpServers = {};
}
// Check if any existing server configuration already has task-master-mcp in its args
const hasMCPString = Object.values(mcpConfig.mcpServers).some(
(server) =>
server.args &&
server.args.some(
(arg) => typeof arg === 'string' && arg.includes('task-master-ai')
)
);
if (hasMCPString) {
log(
'info',
'Found existing task-master-ai MCP configuration in mcp.json, leaving untouched'
);
return; // Exit early, don't modify the existing configuration
}
// Add the task-master-ai server if it doesn't exist
if (!mcpConfig.mcpServers['task-master-ai']) {
mcpConfig.mcpServers['task-master-ai'] = newMCPServer['task-master-ai'];
log(
'info',
'Added task-master-ai server to existing MCP configuration'
);
} else {
log('info', 'task-master-ai server already configured in mcp.json');
}
// Write the updated configuration
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 4));
log('success', 'Updated MCP configuration file');
} catch (error) {
log('error', `Failed to update MCP configuration: ${error.message}`);
// Create a backup before potentially modifying
const backupPath = `${mcpJsonPath}.backup-${Date.now()}`;
if (fs.existsSync(mcpJsonPath)) {
fs.copyFileSync(mcpJsonPath, backupPath);
log('info', `Created backup of existing mcp.json at ${backupPath}`);
}
// Create new configuration
const newMCPConfig = {
mcpServers: newMCPServer
};
fs.writeFileSync(mcpJsonPath, JSON.stringify(newMCPConfig, null, 4));
log(
'warn',
'Created new MCP configuration file (backup of original file was created if it existed)'
);
}
} else {
// If mcp.json doesn't exist, create it
const newMCPConfig = {
mcpServers: newMCPServer
};
fs.writeFileSync(mcpJsonPath, JSON.stringify(newMCPConfig, null, 4));
log('success', 'Created MCP configuration file for Cursor integration');
}
// Add note to console about MCP integration
log('info', 'MCP server will use the installed task-master-ai package');
}
// Ensure necessary functions are exported
export { initializeProject, log }; // Only export what's needed by commands.js
export { initializeProject, log };

View File

@@ -44,7 +44,8 @@ import {
OllamaAIProvider,
BedrockAIProvider,
AzureProvider,
VertexAIProvider
VertexAIProvider,
ClaudeCodeProvider
} from '../../src/ai-providers/index.js';
// Create provider instances
@@ -58,7 +59,8 @@ const PROVIDERS = {
ollama: new OllamaAIProvider(),
bedrock: new BedrockAIProvider(),
azure: new AzureProvider(),
vertex: new VertexAIProvider()
vertex: new VertexAIProvider(),
'claude-code': new ClaudeCodeProvider()
};
// Helper function to get cost for a specific model
@@ -225,6 +227,11 @@ function _extractErrorMessage(error) {
* @throws {Error} If a required API key is missing.
*/
function _resolveApiKey(providerName, session, projectRoot = null) {
// Claude Code doesn't require an API key
if (providerName === 'claude-code') {
return 'claude-code-no-key-required';
}
const keyMap = {
openai: 'OPENAI_API_KEY',
anthropic: 'ANTHROPIC_API_KEY',
@@ -236,7 +243,8 @@ function _resolveApiKey(providerName, session, projectRoot = null) {
xai: 'XAI_API_KEY',
ollama: 'OLLAMA_API_KEY',
bedrock: 'AWS_ACCESS_KEY_ID',
vertex: 'GOOGLE_API_KEY'
vertex: 'GOOGLE_API_KEY',
'claude-code': 'CLAUDE_CODE_API_KEY' // Not actually used, but included for consistency
};
const envVarName = keyMap[providerName];

View File

@@ -11,6 +11,7 @@ import fs from 'fs';
import https from 'https';
import http from 'http';
import inquirer from 'inquirer';
import search from '@inquirer/search';
import ora from 'ora'; // Import ora
import {
@@ -71,6 +72,8 @@ import {
getBaseUrlForRole
} from './config-manager.js';
import { CUSTOM_PROVIDERS } from '../../src/constants/providers.js';
import {
COMPLEXITY_REPORT_FILE,
PRD_FILE,
@@ -96,6 +99,14 @@ import {
displayTaggedTasksFYI,
displayCurrentTagIndicator
} from './ui.js';
import {
confirmProfilesRemove,
confirmRemoveAllRemainingProfiles
} from '../../src/ui/confirm.js';
import {
wouldRemovalLeaveNoProfiles,
getInstalledProfiles
} from '../../src/utils/profiles.js';
import { initializeProject } from '../init.js';
import {
@@ -108,8 +119,27 @@ import {
isValidTaskStatus,
TASK_STATUS_OPTIONS
} from '../../src/constants/task-status.js';
import {
isValidRulesAction,
RULES_ACTIONS,
RULES_SETUP_ACTION
} from '../../src/constants/rules-actions.js';
import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
import { syncTasksToReadme } from './sync-readme.js';
import { RULE_PROFILES } from '../../src/constants/profiles.js';
import {
convertAllRulesToProfileRules,
removeProfileRules,
isValidProfile,
getRulesProfile
} from '../../src/utils/rule-transformer.js';
import {
runInteractiveProfilesSetup,
generateProfileSummary,
categorizeProfileResults,
generateProfileRemovalSummary,
categorizeRemovalResults
} from '../../src/utils/profiles.js';
/**
* Runs the interactive setup process for model configuration.
@@ -264,20 +294,14 @@ async function runInteractiveSetup(projectRoot) {
}
: null;
const customOpenRouterOption = {
name: '* Custom OpenRouter model', // Symbol updated
value: '__CUSTOM_OPENROUTER__'
};
const customOllamaOption = {
name: '* Custom Ollama model', // Symbol updated
value: '__CUSTOM_OLLAMA__'
};
const customBedrockOption = {
name: '* Custom Bedrock model', // Add Bedrock custom option
value: '__CUSTOM_BEDROCK__'
};
// Define custom provider options
const customProviderOptions = [
{ name: '* Custom OpenRouter model', value: '__CUSTOM_OPENROUTER__' },
{ name: '* Custom Ollama model', value: '__CUSTOM_OLLAMA__' },
{ name: '* Custom Bedrock model', value: '__CUSTOM_BEDROCK__' },
{ name: '* Custom Azure model', value: '__CUSTOM_AZURE__' },
{ name: '* Custom Vertex model', value: '__CUSTOM_VERTEX__' }
];
let choices = [];
let defaultIndex = 0; // Default to 'Cancel'
@@ -317,43 +341,42 @@ async function runInteractiveSetup(projectRoot) {
);
}
// Construct final choices list based on whether 'None' is allowed
const commonPrefix = [];
// Construct final choices list with custom options moved to bottom
const systemOptions = [];
if (noChangeOption) {
commonPrefix.push(noChangeOption);
systemOptions.push(noChangeOption);
}
commonPrefix.push(cancelOption);
commonPrefix.push(customOpenRouterOption);
commonPrefix.push(customOllamaOption);
commonPrefix.push(customBedrockOption);
systemOptions.push(cancelOption);
const prefixLength = commonPrefix.length; // Initial prefix length
const systemLength = systemOptions.length;
if (allowNone) {
choices = [
...commonPrefix,
new inquirer.Separator(),
{ name: '⚪ None (disable)', value: null }, // Symbol updated
new inquirer.Separator(),
...roleChoices
...systemOptions,
new inquirer.Separator('\n── Standard Models ──'),
{ name: '⚪ None (disable)', value: null },
...roleChoices,
new inquirer.Separator('\n── Custom Providers ──'),
...customProviderOptions
];
// Adjust default index: Prefix + Sep1 + None + Sep2 (+3)
const noneOptionIndex = prefixLength + 1;
// Adjust default index: System + Sep1 + None (+2)
const noneOptionIndex = systemLength + 1;
defaultIndex =
currentChoiceIndex !== -1
? currentChoiceIndex + prefixLength + 3 // Offset by prefix and separators
? currentChoiceIndex + systemLength + 2 // Offset by system options and separators
: noneOptionIndex; // Default to 'None' if no current model matched
} else {
choices = [
...commonPrefix,
new inquirer.Separator(),
...systemOptions,
new inquirer.Separator('\n── Standard Models ──'),
...roleChoices,
new inquirer.Separator()
new inquirer.Separator('\n── Custom Providers ──'),
...customProviderOptions
];
// Adjust default index: Prefix + Sep (+1)
// Adjust default index: System + Sep (+1)
defaultIndex =
currentChoiceIndex !== -1
? currentChoiceIndex + prefixLength + 1 // Offset by prefix and separator
? currentChoiceIndex + systemLength + 1 // Offset by system options and separator
: noChangeOption
? 1
: 0; // Default to 'No Change' if present, else 'Cancel'
@@ -376,32 +399,63 @@ async function runInteractiveSetup(projectRoot) {
const researchPromptData = getPromptData('research');
const fallbackPromptData = getPromptData('fallback', true); // Allow 'None' for fallback
const answers = await inquirer.prompt([
{
type: 'list',
name: 'mainModel',
message: 'Select the main model for generation/updates:',
choices: mainPromptData.choices,
default: mainPromptData.default
},
{
type: 'list',
name: 'researchModel',
// Display helpful intro message
console.log(chalk.cyan('\n🎯 Interactive Model Setup'));
console.log(chalk.gray('━'.repeat(50)));
console.log(chalk.yellow('💡 Navigation tips:'));
console.log(chalk.gray(' • Type to search and filter options'));
console.log(chalk.gray(' • Use ↑↓ arrow keys to navigate results'));
console.log(
chalk.gray(
' • Standard models are listed first, custom providers at bottom'
)
);
console.log(chalk.gray(' • Press Enter to select\n'));
// Helper function to create search source for models
const createSearchSource = (choices, defaultValue) => {
return (searchTerm = '') => {
const filteredChoices = choices.filter((choice) => {
if (choice.type === 'separator') return true; // Always show separators
const searchText = choice.name || '';
return searchText.toLowerCase().includes(searchTerm.toLowerCase());
});
return Promise.resolve(filteredChoices);
};
};
const answers = {};
// Main model selection
answers.mainModel = await search({
message: 'Select the main model for generation/updates:',
source: createSearchSource(mainPromptData.choices, mainPromptData.default),
pageSize: 15
});
if (answers.mainModel !== '__CANCEL__') {
// Research model selection
answers.researchModel = await search({
message: 'Select the research model:',
choices: researchPromptData.choices,
default: researchPromptData.default,
when: (ans) => ans.mainModel !== '__CANCEL__'
},
{
type: 'list',
name: 'fallbackModel',
message: 'Select the fallback model (optional):',
choices: fallbackPromptData.choices,
default: fallbackPromptData.default,
when: (ans) =>
ans.mainModel !== '__CANCEL__' && ans.researchModel !== '__CANCEL__'
source: createSearchSource(
researchPromptData.choices,
researchPromptData.default
),
pageSize: 15
});
if (answers.researchModel !== '__CANCEL__') {
// Fallback model selection
answers.fallbackModel = await search({
message: 'Select the fallback model (optional):',
source: createSearchSource(
fallbackPromptData.choices,
fallbackPromptData.default
),
pageSize: 15
});
}
]);
}
let setupSuccess = true;
let setupConfigModified = false;
@@ -441,7 +495,7 @@ async function runInteractiveSetup(projectRoot) {
return true; // Continue setup, but don't set this role
}
modelIdToSet = customId;
providerHint = 'openrouter';
providerHint = CUSTOM_PROVIDERS.OPENROUTER;
// Validate against live OpenRouter list
const openRouterModels = await fetchOpenRouterModelsCLI();
if (
@@ -470,7 +524,7 @@ async function runInteractiveSetup(projectRoot) {
return true; // Continue setup, but don't set this role
}
modelIdToSet = customId;
providerHint = 'ollama';
providerHint = CUSTOM_PROVIDERS.OLLAMA;
// Get the Ollama base URL from config for this role
const ollamaBaseURL = getBaseUrlForRole(role, projectRoot);
// Validate against live Ollama list
@@ -511,16 +565,16 @@ async function runInteractiveSetup(projectRoot) {
return true; // Continue setup, but don't set this role
}
modelIdToSet = customId;
providerHint = 'bedrock';
providerHint = CUSTOM_PROVIDERS.BEDROCK;
// Check if AWS environment variables exist
if (
!process.env.AWS_ACCESS_KEY_ID ||
!process.env.AWS_SECRET_ACCESS_KEY
) {
console.error(
chalk.red(
'Error: AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY environment variables are missing. Please set them before using custom Bedrock models.'
console.warn(
chalk.yellow(
'Warning: AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY environment variables are missing. Will fallback to system configuration. (ex: aws config files or ec2 instance profiles)'
)
);
setupSuccess = false;
@@ -532,6 +586,76 @@ async function runInteractiveSetup(projectRoot) {
`Custom Bedrock model "${modelIdToSet}" will be used. No validation performed.`
)
);
} else if (selectedValue === '__CUSTOM_AZURE__') {
isCustomSelection = true;
const { customId } = await inquirer.prompt([
{
type: 'input',
name: 'customId',
message: `Enter the custom Azure OpenAI Model ID for the ${role} role (e.g., gpt-4o):`
}
]);
if (!customId) {
console.log(chalk.yellow('No custom ID entered. Skipping role.'));
return true; // Continue setup, but don't set this role
}
modelIdToSet = customId;
providerHint = CUSTOM_PROVIDERS.AZURE;
// Check if Azure environment variables exist
if (
!process.env.AZURE_OPENAI_API_KEY ||
!process.env.AZURE_OPENAI_ENDPOINT
) {
console.error(
chalk.red(
'Error: AZURE_OPENAI_API_KEY and/or AZURE_OPENAI_ENDPOINT environment variables are missing. Please set them before using custom Azure models.'
)
);
setupSuccess = false;
return true; // Continue setup, but mark as failed
}
console.log(
chalk.blue(
`Custom Azure OpenAI model "${modelIdToSet}" will be used. No validation performed.`
)
);
} else if (selectedValue === '__CUSTOM_VERTEX__') {
isCustomSelection = true;
const { customId } = await inquirer.prompt([
{
type: 'input',
name: 'customId',
message: `Enter the custom Vertex AI Model ID for the ${role} role (e.g., gemini-1.5-pro-002):`
}
]);
if (!customId) {
console.log(chalk.yellow('No custom ID entered. Skipping role.'));
return true; // Continue setup, but don't set this role
}
modelIdToSet = customId;
providerHint = CUSTOM_PROVIDERS.VERTEX;
// Check if Google/Vertex environment variables exist
if (
!process.env.GOOGLE_API_KEY &&
!process.env.GOOGLE_APPLICATION_CREDENTIALS
) {
console.error(
chalk.red(
'Error: Either GOOGLE_API_KEY or GOOGLE_APPLICATION_CREDENTIALS environment variable is required. Please set one before using custom Vertex models.'
)
);
setupSuccess = false;
return true; // Continue setup, but mark as failed
}
console.log(
chalk.blue(
`Custom Vertex AI model "${modelIdToSet}" will be used. No validation performed.`
)
);
} else if (
selectedValue &&
typeof selectedValue === 'object' &&
@@ -3211,17 +3335,35 @@ ${result.result}
.option('-d, --description <description>', 'Project description')
.option('-v, --version <version>', 'Project version', '0.1.0') // Set default here
.option('-a, --author <author>', 'Author name')
.option(
'-r, --rules <rules...>',
'List of rules to add (roo, windsurf, cursor, ...). Accepts comma or space separated values.'
)
.option('--skip-install', 'Skip installing dependencies')
.option('--dry-run', 'Show what would be done without making changes')
.option('--aliases', 'Add shell aliases (tm, taskmaster)')
.action(async (cmdOptions) => {
// cmdOptions contains parsed arguments
// Parse rules: accept space or comma separated, default to all available rules
let selectedProfiles = RULE_PROFILES;
let rulesExplicitlyProvided = false;
if (cmdOptions.rules && Array.isArray(cmdOptions.rules)) {
const userSpecifiedProfiles = cmdOptions.rules
.flatMap((r) => r.split(','))
.map((r) => r.trim())
.filter(Boolean);
// Only override defaults if user specified valid rules
if (userSpecifiedProfiles.length > 0) {
selectedProfiles = userSpecifiedProfiles;
rulesExplicitlyProvided = true;
}
}
cmdOptions.rules = selectedProfiles;
cmdOptions.rulesExplicitlyProvided = rulesExplicitlyProvided;
try {
console.log('DEBUG: Running init command action in commands.js');
console.log(
'DEBUG: Options received by action:',
JSON.stringify(cmdOptions)
);
// Directly call the initializeProject function, passing the parsed options
await initializeProject(cmdOptions);
// initializeProject handles its own flow, including potential process.exit()
@@ -3262,6 +3404,18 @@ ${result.result}
'--bedrock',
'Allow setting a custom Bedrock model ID (use with --set-*) '
)
.option(
'--claude-code',
'Allow setting a Claude Code model ID (use with --set-*)'
)
.option(
'--azure',
'Allow setting a custom Azure OpenAI model ID (use with --set-*) '
)
.option(
'--vertex',
'Allow setting a custom Vertex AI model ID (use with --set-*) '
)
.addHelpText(
'after',
`
@@ -3273,6 +3427,9 @@ Examples:
$ task-master models --set-main my-custom-model --ollama # Set custom Ollama model for main role
$ task-master models --set-main anthropic.claude-3-sonnet-20240229-v1:0 --bedrock # Set custom Bedrock model for main role
$ task-master models --set-main some/other-model --openrouter # Set custom OpenRouter model for main role
$ task-master models --set-main sonnet --claude-code # Set Claude Code model for main role
$ task-master models --set-main gpt-4o --azure # Set custom Azure OpenAI model for main role
$ task-master models --set-main claude-3-5-sonnet@20241022 --vertex # Set custom Vertex AI model for main role
$ task-master models --setup # Run interactive setup`
)
.action(async (options) => {
@@ -3285,12 +3442,13 @@ Examples:
const providerFlags = [
options.openrouter,
options.ollama,
options.bedrock
options.bedrock,
options.claudeCode
].filter(Boolean).length;
if (providerFlags > 1) {
console.error(
chalk.red(
'Error: Cannot use multiple provider flags (--openrouter, --ollama, --bedrock) simultaneously.'
'Error: Cannot use multiple provider flags (--openrouter, --ollama, --bedrock, --claude-code) simultaneously.'
)
);
process.exit(1);
@@ -3332,7 +3490,9 @@ Examples:
? 'ollama'
: options.bedrock
? 'bedrock'
: undefined
: options.claudeCode
? 'claude-code'
: undefined
});
if (result.success) {
console.log(chalk.green(`${result.data.message}`));
@@ -3354,7 +3514,9 @@ Examples:
? 'ollama'
: options.bedrock
? 'bedrock'
: undefined
: options.claudeCode
? 'claude-code'
: undefined
});
if (result.success) {
console.log(chalk.green(`${result.data.message}`));
@@ -3378,7 +3540,9 @@ Examples:
? 'ollama'
: options.bedrock
? 'bedrock'
: undefined
: options.claudeCode
? 'claude-code'
: undefined
});
if (result.success) {
console.log(chalk.green(`${result.data.message}`));
@@ -3618,6 +3782,254 @@ Examples:
}
});
// Add/remove profile rules command
programInstance
.command('rules [action] [profiles...]')
.description(
`Add or remove rules for one or more profiles. Valid actions: ${Object.values(RULES_ACTIONS).join(', ')} (e.g., task-master rules ${RULES_ACTIONS.ADD} windsurf roo)`
)
.option(
'-f, --force',
'Skip confirmation prompt when removing rules (dangerous)'
)
.option(
`--${RULES_SETUP_ACTION}`,
'Run interactive setup to select rule profiles to add'
)
.addHelpText(
'after',
`
Examples:
$ task-master rules ${RULES_ACTIONS.ADD} windsurf roo # Add Windsurf and Roo rule sets
$ task-master rules ${RULES_ACTIONS.REMOVE} windsurf # Remove Windsurf rule set
$ task-master rules --${RULES_SETUP_ACTION} # Interactive setup to select rule profiles`
)
.action(async (action, profiles, options) => {
const projectDir = process.cwd();
/**
* 'task-master rules --setup' action:
*
* Launches an interactive prompt to select which rule profiles to add to the current project.
* This does NOT perform project initialization or ask about shell aliases—only rules selection.
*
* Example usage:
* $ task-master rules --setup
*
* Useful for adding rules after project creation.
*
* The list of profiles is always up-to-date with the available profiles.
*/
if (options[RULES_SETUP_ACTION]) {
// Run interactive rules setup ONLY (no project init)
const selectedRuleProfiles = await runInteractiveProfilesSetup();
for (const profile of selectedRuleProfiles) {
if (!isValidProfile(profile)) {
console.warn(
`Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
);
continue;
}
const profileConfig = getRulesProfile(profile);
const addResult = convertAllRulesToProfileRules(
projectDir,
profileConfig
);
if (typeof profileConfig.onAddRulesProfile === 'function') {
profileConfig.onAddRulesProfile(projectDir);
}
console.log(chalk.green(generateProfileSummary(profile, addResult)));
}
return;
}
// Validate action for non-setup mode
if (!action || !isValidRulesAction(action)) {
console.error(
chalk.red(
`Error: Invalid or missing action '${action || 'none'}'. Valid actions are: ${Object.values(RULES_ACTIONS).join(', ')}`
)
);
console.error(
chalk.yellow(
`For interactive setup, use: task-master rules --${RULES_SETUP_ACTION}`
)
);
process.exit(1);
}
if (!profiles || profiles.length === 0) {
console.error(
'Please specify at least one rule profile (e.g., windsurf, roo).'
);
process.exit(1);
}
// Support both space- and comma-separated profile lists
const expandedProfiles = profiles
.flatMap((b) => b.split(',').map((s) => s.trim()))
.filter(Boolean);
if (action === RULES_ACTIONS.REMOVE) {
let confirmed = true;
if (!options.force) {
// Check if this removal would leave no profiles remaining
if (wouldRemovalLeaveNoProfiles(projectDir, expandedProfiles)) {
const installedProfiles = getInstalledProfiles(projectDir);
confirmed = await confirmRemoveAllRemainingProfiles(
expandedProfiles,
installedProfiles
);
} else {
confirmed = await confirmProfilesRemove(expandedProfiles);
}
}
if (!confirmed) {
console.log(chalk.yellow('Aborted: No rules were removed.'));
return;
}
}
const removalResults = [];
const addResults = [];
for (const profile of expandedProfiles) {
if (!isValidProfile(profile)) {
console.warn(
`Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
);
continue;
}
const profileConfig = getRulesProfile(profile);
if (action === RULES_ACTIONS.ADD) {
console.log(chalk.blue(`Adding rules for profile: ${profile}...`));
const addResult = convertAllRulesToProfileRules(
projectDir,
profileConfig
);
if (typeof profileConfig.onAddRulesProfile === 'function') {
const assetsDir = path.join(process.cwd(), 'assets');
profileConfig.onAddRulesProfile(projectDir, assetsDir);
}
console.log(
chalk.blue(`Completed adding rules for profile: ${profile}`)
);
// Store result with profile name for summary
addResults.push({
profileName: profile,
success: addResult.success,
failed: addResult.failed
});
console.log(chalk.green(generateProfileSummary(profile, addResult)));
} else if (action === RULES_ACTIONS.REMOVE) {
console.log(chalk.blue(`Removing rules for profile: ${profile}...`));
const result = removeProfileRules(projectDir, profileConfig);
removalResults.push(result);
console.log(
chalk.green(generateProfileRemovalSummary(profile, result))
);
} else {
console.error(
`Unknown action. Use "${RULES_ACTIONS.ADD}" or "${RULES_ACTIONS.REMOVE}".`
);
process.exit(1);
}
}
// Print summary for additions
if (action === RULES_ACTIONS.ADD && addResults.length > 0) {
const {
allSuccessfulProfiles,
totalSuccess,
totalFailed,
simpleProfiles
} = categorizeProfileResults(addResults);
if (allSuccessfulProfiles.length > 0) {
console.log(
chalk.green(
`\nSuccessfully added rules for: ${allSuccessfulProfiles.join(', ')}`
)
);
// Create a more descriptive summary
if (totalSuccess > 0 && simpleProfiles.length > 0) {
console.log(
chalk.green(
`Total: ${totalSuccess} rules added, ${totalFailed} failed, ${simpleProfiles.length} integration guide(s) copied.`
)
);
} else if (totalSuccess > 0) {
console.log(
chalk.green(
`Total: ${totalSuccess} rules added, ${totalFailed} failed.`
)
);
} else if (simpleProfiles.length > 0) {
console.log(
chalk.green(
`Total: ${simpleProfiles.length} integration guide(s) copied.`
)
);
}
}
}
// Print summary for removals
if (action === RULES_ACTIONS.REMOVE && removalResults.length > 0) {
const {
successfulRemovals,
skippedRemovals,
failedRemovals,
removalsWithNotices
} = categorizeRemovalResults(removalResults);
if (successfulRemovals.length > 0) {
console.log(
chalk.green(
`\nSuccessfully removed profiles for: ${successfulRemovals.join(', ')}`
)
);
}
if (skippedRemovals.length > 0) {
console.log(
chalk.yellow(
`Skipped (default or protected): ${skippedRemovals.join(', ')}`
)
);
}
if (failedRemovals.length > 0) {
console.log(chalk.red('\nErrors occurred:'));
failedRemovals.forEach((r) => {
console.log(chalk.red(` ${r.profileName}: ${r.error}`));
});
}
// Display notices about preserved files/configurations
if (removalsWithNotices.length > 0) {
console.log(chalk.cyan('\nNotices:'));
removalsWithNotices.forEach((r) => {
console.log(chalk.cyan(` ${r.profileName}: ${r.notice}`));
});
}
// Overall summary
const totalProcessed = removalResults.length;
const totalSuccessful = successfulRemovals.length;
const totalSkipped = skippedRemovals.length;
const totalFailed = failedRemovals.length;
console.log(
chalk.blue(
`\nTotal: ${totalProcessed} profile(s) processed - ${totalSuccessful} removed, ${totalSkipped} skipped, ${totalFailed} failed.`
)
);
}
});
programInstance
.command('migrate')
.description(

View File

@@ -5,6 +5,12 @@ import { fileURLToPath } from 'url';
import { log, findProjectRoot, resolveEnvVariable } from './utils.js';
import { LEGACY_CONFIG_FILE } from '../../src/constants/paths.js';
import { findConfigPath } from '../../src/utils/path-utils.js';
import {
VALIDATED_PROVIDERS,
CUSTOM_PROVIDERS,
CUSTOM_PROVIDERS_ARRAY,
ALL_PROVIDERS
} from '../../src/constants/providers.js';
// Calculate __dirname in ESM
const __filename = fileURLToPath(import.meta.url);
@@ -29,9 +35,6 @@ try {
process.exit(1); // Exit if models can't be loaded
}
// Define valid providers dynamically from the loaded MODEL_MAP
const VALID_PROVIDERS = Object.keys(MODEL_MAP || {});
// Default configuration values (used if config file is missing or incomplete)
const DEFAULTS = {
models: {
@@ -233,12 +236,25 @@ function getConfig(explicitRoot = null, forceReload = false) {
}
/**
* Validates if a provider name is in the list of supported providers.
* Validates if a provider name is supported.
* Custom providers (azure, vertex, bedrock, openrouter, ollama) are always allowed.
* Validated providers must exist in the MODEL_MAP from supported-models.json.
* @param {string} providerName The name of the provider.
* @returns {boolean} True if the provider is valid, false otherwise.
*/
function validateProvider(providerName) {
return VALID_PROVIDERS.includes(providerName);
// Custom providers are always allowed
if (CUSTOM_PROVIDERS_ARRAY.includes(providerName)) {
return true;
}
// Validated providers must exist in MODEL_MAP
if (VALIDATED_PROVIDERS.includes(providerName)) {
return !!(MODEL_MAP && MODEL_MAP[providerName]);
}
// Unknown providers are not allowed
return false;
}
/**
@@ -480,10 +496,22 @@ function getParametersForRole(role, explicitRoot = null) {
*/
function isApiKeySet(providerName, session = null, projectRoot = null) {
// Define the expected environment variable name for each provider
if (providerName?.toLowerCase() === 'ollama') {
// Providers that don't require API keys for authentication
const providersWithoutApiKeys = [
CUSTOM_PROVIDERS.OLLAMA,
CUSTOM_PROVIDERS.BEDROCK
];
if (providersWithoutApiKeys.includes(providerName?.toLowerCase())) {
return true; // Indicate key status is effectively "OK"
}
// Claude Code doesn't require an API key
if (providerName?.toLowerCase() === 'claude-code') {
return true; // No API key needed
}
const keyMap = {
openai: 'OPENAI_API_KEY',
anthropic: 'ANTHROPIC_API_KEY',
@@ -493,7 +521,9 @@ function isApiKeySet(providerName, session = null, projectRoot = null) {
azure: 'AZURE_OPENAI_API_KEY',
openrouter: 'OPENROUTER_API_KEY',
xai: 'XAI_API_KEY',
vertex: 'GOOGLE_API_KEY' // Vertex uses the same key as Google
vertex: 'GOOGLE_API_KEY', // Vertex uses the same key as Google
'claude-code': 'CLAUDE_CODE_API_KEY', // Not actually used, but included for consistency
bedrock: 'AWS_ACCESS_KEY_ID' // Bedrock uses AWS credentials
// Add other providers as needed
};
@@ -541,10 +571,11 @@ function getMcpApiKeyStatus(providerName, projectRoot = null) {
const mcpConfigRaw = fs.readFileSync(mcpConfigPath, 'utf-8');
const mcpConfig = JSON.parse(mcpConfigRaw);
const mcpEnv = mcpConfig?.mcpServers?.['taskmaster-ai']?.env;
const mcpEnv =
mcpConfig?.mcpServers?.['task-master-ai']?.env ||
mcpConfig?.mcpServers?.['taskmaster-ai']?.env;
if (!mcpEnv) {
// console.warn(chalk.yellow('Warning: Could not find taskmaster-ai env in mcp.json.'));
return false; // Structure missing
return false;
}
let apiKeyToCheck = null;
@@ -577,6 +608,8 @@ function getMcpApiKeyStatus(providerName, projectRoot = null) {
break;
case 'ollama':
return true; // No key needed
case 'claude-code':
return true; // No key needed
case 'mistral':
apiKeyToCheck = mcpEnv.MISTRAL_API_KEY;
placeholderValue = 'YOUR_MISTRAL_API_KEY_HERE';
@@ -589,6 +622,10 @@ function getMcpApiKeyStatus(providerName, projectRoot = null) {
apiKeyToCheck = mcpEnv.GOOGLE_API_KEY; // Vertex uses Google API key
placeholderValue = 'YOUR_GOOGLE_API_KEY_HERE';
break;
case 'bedrock':
apiKeyToCheck = mcpEnv.AWS_ACCESS_KEY_ID; // Bedrock uses AWS credentials
placeholderValue = 'YOUR_AWS_ACCESS_KEY_ID_HERE';
break;
default:
return false; // Unknown provider
}
@@ -636,7 +673,8 @@ function getAvailableModels() {
provider: provider,
swe_score: sweScore,
cost_per_1m_tokens: cost,
allowed_roles: allowedRoles
allowed_roles: allowedRoles,
max_tokens: modelObj.max_tokens
});
});
} else {
@@ -736,18 +774,24 @@ function getUserId(explicitRoot = null) {
}
/**
* Gets a list of all provider names defined in the MODEL_MAP.
* @returns {string[]} An array of provider names.
* Gets a list of all known provider names (both validated and custom).
* @returns {string[]} An array of all provider names.
*/
function getAllProviders() {
return Object.keys(MODEL_MAP || {});
return ALL_PROVIDERS;
}
function getBaseUrlForRole(role, explicitRoot = null) {
const roleConfig = getModelConfigForRole(role, explicitRoot);
return roleConfig && typeof roleConfig.baseURL === 'string'
? roleConfig.baseURL
: undefined;
if (roleConfig && typeof roleConfig.baseURL === 'string') {
return roleConfig.baseURL;
}
const provider = roleConfig?.provider;
if (provider) {
const envVarName = `${provider.toUpperCase()}_BASE_URL`;
return resolveEnvVariable(envVarName, null, explicitRoot);
}
return undefined;
}
export {
@@ -759,7 +803,9 @@ export {
// Validation
validateProvider,
validateProviderModelCombination,
VALID_PROVIDERS,
VALIDATED_PROVIDERS,
CUSTOM_PROVIDERS,
ALL_PROVIDERS,
MODEL_MAP,
getAvailableModels,
// Role-specific getters (No env var overrides)

View File

@@ -1,314 +0,0 @@
/**
* Rule Transformer Module
* Handles conversion of Cursor rules to Roo rules
*
* This module procedurally generates .roo/rules files from .cursor/rules files,
* eliminating the need to maintain both sets of files manually.
*/
import fs from 'fs';
import path from 'path';
import { log } from './utils.js';
// Configuration for term conversions - centralized for easier future updates
const conversionConfig = {
// Product and brand name replacements
brandTerms: [
{ from: /cursor\.so/g, to: 'roocode.com' },
{ from: /\[cursor\.so\]/g, to: '[roocode.com]' },
{ from: /href="https:\/\/cursor\.so/g, to: 'href="https://roocode.com' },
{ from: /\(https:\/\/cursor\.so/g, to: '(https://roocode.com' },
{
from: /\bcursor\b/gi,
to: (match) => (match === 'Cursor' ? 'Roo Code' : 'roo')
},
{ from: /Cursor/g, to: 'Roo Code' }
],
// File extension replacements
fileExtensions: [{ from: /\.mdc\b/g, to: '.md' }],
// Documentation URL replacements
docUrls: [
{
from: /https:\/\/docs\.cursor\.com\/[^\s)'"]+/g,
to: (match) => match.replace('docs.cursor.com', 'docs.roocode.com')
},
{ from: /https:\/\/docs\.roo\.com\//g, to: 'https://docs.roocode.com/' }
],
// Tool references - direct replacements
toolNames: {
search: 'search_files',
read_file: 'read_file',
edit_file: 'apply_diff',
create_file: 'write_to_file',
run_command: 'execute_command',
terminal_command: 'execute_command',
use_mcp: 'use_mcp_tool',
switch_mode: 'switch_mode'
},
// Tool references in context - more specific replacements
toolContexts: [
{ from: /\bsearch tool\b/g, to: 'search_files tool' },
{ from: /\bedit_file tool\b/g, to: 'apply_diff tool' },
{ from: /\buse the search\b/g, to: 'use the search_files' },
{ from: /\bThe edit_file\b/g, to: 'The apply_diff' },
{ from: /\brun_command executes\b/g, to: 'execute_command executes' },
{ from: /\buse_mcp connects\b/g, to: 'use_mcp_tool connects' },
// Additional contextual patterns for flexibility
{ from: /\bCursor search\b/g, to: 'Roo Code search_files' },
{ from: /\bCursor edit\b/g, to: 'Roo Code apply_diff' },
{ from: /\bCursor create\b/g, to: 'Roo Code write_to_file' },
{ from: /\bCursor run\b/g, to: 'Roo Code execute_command' }
],
// Tool group and category names
toolGroups: [
{ from: /\bSearch tools\b/g, to: 'Read Group tools' },
{ from: /\bEdit tools\b/g, to: 'Edit Group tools' },
{ from: /\bRun tools\b/g, to: 'Command Group tools' },
{ from: /\bMCP servers\b/g, to: 'MCP Group tools' },
{ from: /\bSearch Group\b/g, to: 'Read Group' },
{ from: /\bEdit Group\b/g, to: 'Edit Group' },
{ from: /\bRun Group\b/g, to: 'Command Group' }
],
// File references in markdown links
fileReferences: {
pathPattern: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
replacement: (match, text, filePath) => {
// Get the base filename
const baseName = path.basename(filePath, '.mdc');
// Get the new filename (either from mapping or by replacing extension)
const newFileName = fileMap[`${baseName}.mdc`] || `${baseName}.md`;
// Return the updated link
return `[${text}](mdc:.roo/rules/${newFileName})`;
}
}
};
// File name mapping (specific files with naming changes)
const fileMap = {
'cursor_rules.mdc': 'roo_rules.md',
'dev_workflow.mdc': 'dev_workflow.md',
'self_improve.mdc': 'self_improve.md',
'taskmaster.mdc': 'taskmaster.md'
// Add other mappings as needed
};
/**
* Replace basic Cursor terms with Roo equivalents
*/
function replaceBasicTerms(content) {
let result = content;
// Apply brand term replacements
conversionConfig.brandTerms.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
// Apply file extension replacements
conversionConfig.fileExtensions.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
return result;
}
/**
* Replace Cursor tool references with Roo tool equivalents
*/
function replaceToolReferences(content) {
let result = content;
// Basic pattern for direct tool name replacements
const toolNames = conversionConfig.toolNames;
const toolReferencePattern = new RegExp(
`\\b(${Object.keys(toolNames).join('|')})\\b`,
'g'
);
// Apply direct tool name replacements
result = result.replace(toolReferencePattern, (match, toolName) => {
return toolNames[toolName] || toolName;
});
// Apply contextual tool replacements
conversionConfig.toolContexts.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
// Apply tool group replacements
conversionConfig.toolGroups.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
return result;
}
/**
* Update documentation URLs to point to Roo documentation
*/
function updateDocReferences(content) {
let result = content;
// Apply documentation URL replacements
conversionConfig.docUrls.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
return result;
}
/**
* Update file references in markdown links
*/
function updateFileReferences(content) {
const { pathPattern, replacement } = conversionConfig.fileReferences;
return content.replace(pathPattern, replacement);
}
/**
* Main transformation function that applies all conversions
*/
function transformCursorToRooRules(content) {
// Apply all transformations in appropriate order
let result = content;
result = replaceBasicTerms(result);
result = replaceToolReferences(result);
result = updateDocReferences(result);
result = updateFileReferences(result);
// Super aggressive failsafe pass to catch any variations we might have missed
// This ensures critical transformations are applied even in contexts we didn't anticipate
// 1. Handle cursor.so in any possible context
result = result.replace(/cursor\.so/gi, 'roocode.com');
// Edge case: URL with different formatting
result = result.replace(/cursor\s*\.\s*so/gi, 'roocode.com');
result = result.replace(/https?:\/\/cursor\.so/gi, 'https://roocode.com');
result = result.replace(
/https?:\/\/www\.cursor\.so/gi,
'https://www.roocode.com'
);
// 2. Handle tool references - even partial ones
result = result.replace(/\bedit_file\b/gi, 'apply_diff');
result = result.replace(/\bsearch tool\b/gi, 'search_files tool');
result = result.replace(/\bSearch Tool\b/g, 'Search_Files Tool');
// 3. Handle basic terms (with case handling)
result = result.replace(/\bcursor\b/gi, (match) =>
match.charAt(0) === 'C' ? 'Roo Code' : 'roo'
);
result = result.replace(/Cursor/g, 'Roo Code');
result = result.replace(/CURSOR/g, 'ROO CODE');
// 4. Handle file extensions
result = result.replace(/\.mdc\b/g, '.md');
// 5. Handle any missed URL patterns
result = result.replace(/docs\.cursor\.com/gi, 'docs.roocode.com');
result = result.replace(/docs\.roo\.com/gi, 'docs.roocode.com');
return result;
}
/**
* Convert a single Cursor rule file to Roo rule format
*/
function convertCursorRuleToRooRule(sourcePath, targetPath) {
try {
log(
'info',
`Converting Cursor rule ${path.basename(sourcePath)} to Roo rule ${path.basename(targetPath)}`
);
// Read source content
const content = fs.readFileSync(sourcePath, 'utf8');
// Transform content
const transformedContent = transformCursorToRooRules(content);
// Ensure target directory exists
const targetDir = path.dirname(targetPath);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Write transformed content
fs.writeFileSync(targetPath, transformedContent);
log(
'success',
`Successfully converted ${path.basename(sourcePath)} to ${path.basename(targetPath)}`
);
return true;
} catch (error) {
log(
'error',
`Failed to convert rule file ${path.basename(sourcePath)}: ${error.message}`
);
return false;
}
}
/**
* Process all Cursor rules and convert to Roo rules
*/
function convertAllCursorRulesToRooRules(projectDir) {
const cursorRulesDir = path.join(projectDir, '.cursor', 'rules');
const rooRulesDir = path.join(projectDir, '.roo', 'rules');
if (!fs.existsSync(cursorRulesDir)) {
log('warn', `Cursor rules directory not found: ${cursorRulesDir}`);
return { success: 0, failed: 0 };
}
// Ensure Roo rules directory exists
if (!fs.existsSync(rooRulesDir)) {
fs.mkdirSync(rooRulesDir, { recursive: true });
log('info', `Created Roo rules directory: ${rooRulesDir}`);
}
// Count successful and failed conversions
let success = 0;
let failed = 0;
// Process each file in the Cursor rules directory
fs.readdirSync(cursorRulesDir).forEach((file) => {
if (file.endsWith('.mdc')) {
const sourcePath = path.join(cursorRulesDir, file);
// Determine target file name (either from mapping or by replacing extension)
const targetFilename = fileMap[file] || file.replace('.mdc', '.md');
const targetPath = path.join(rooRulesDir, targetFilename);
// Convert the file
if (convertCursorRuleToRooRule(sourcePath, targetPath)) {
success++;
} else {
failed++;
}
}
});
log(
'info',
`Rule conversion complete: ${success} successful, ${failed} failed`
);
return { success, failed };
}
export { convertAllCursorRulesToRooRules, convertCursorRuleToRooRule };

View File

@@ -1,30 +1,58 @@
{
"bedrock": [
{
"id": "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
"swe_score": 0.623,
"cost_per_1m_tokens": { "input": 3, "output": 15 },
"allowed_roles": ["main", "fallback"],
"max_tokens": 65536
},
{
"id": "us.deepseek.r1-v1:0",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 1.35, "output": 5.4 },
"allowed_roles": ["research"],
"max_tokens": 65536
}
],
"anthropic": [
{
"id": "claude-sonnet-4-20250514",
"swe_score": 0.727,
"cost_per_1m_tokens": { "input": 3.0, "output": 15.0 },
"cost_per_1m_tokens": {
"input": 3.0,
"output": 15.0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 64000
},
{
"id": "claude-opus-4-20250514",
"swe_score": 0.725,
"cost_per_1m_tokens": { "input": 15.0, "output": 75.0 },
"cost_per_1m_tokens": {
"input": 15.0,
"output": 75.0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 32000
},
{
"id": "claude-3-7-sonnet-20250219",
"swe_score": 0.623,
"cost_per_1m_tokens": { "input": 3.0, "output": 15.0 },
"cost_per_1m_tokens": {
"input": 3.0,
"output": 15.0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 120000
},
{
"id": "claude-3-5-sonnet-20241022",
"swe_score": 0.49,
"cost_per_1m_tokens": { "input": 3.0, "output": 15.0 },
"cost_per_1m_tokens": {
"input": 3.0,
"output": 15.0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 64000
}
@@ -33,81 +61,120 @@
{
"id": "gpt-4o",
"swe_score": 0.332,
"cost_per_1m_tokens": { "input": 2.5, "output": 10.0 },
"cost_per_1m_tokens": {
"input": 2.5,
"output": 10.0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 16384
},
{
"id": "o1",
"swe_score": 0.489,
"cost_per_1m_tokens": { "input": 15.0, "output": 60.0 },
"cost_per_1m_tokens": {
"input": 15.0,
"output": 60.0
},
"allowed_roles": ["main"]
},
{
"id": "o3",
"swe_score": 0.5,
"cost_per_1m_tokens": { "input": 2.0, "output": 8.0 },
"cost_per_1m_tokens": {
"input": 2.0,
"output": 8.0
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "o3-mini",
"swe_score": 0.493,
"cost_per_1m_tokens": { "input": 1.1, "output": 4.4 },
"cost_per_1m_tokens": {
"input": 1.1,
"output": 4.4
},
"allowed_roles": ["main"],
"max_tokens": 100000
},
{
"id": "o4-mini",
"swe_score": 0.45,
"cost_per_1m_tokens": { "input": 1.1, "output": 4.4 },
"cost_per_1m_tokens": {
"input": 1.1,
"output": 4.4
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "o1-mini",
"swe_score": 0.4,
"cost_per_1m_tokens": { "input": 1.1, "output": 4.4 },
"cost_per_1m_tokens": {
"input": 1.1,
"output": 4.4
},
"allowed_roles": ["main"]
},
{
"id": "o1-pro",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 150.0, "output": 600.0 },
"cost_per_1m_tokens": {
"input": 150.0,
"output": 600.0
},
"allowed_roles": ["main"]
},
{
"id": "gpt-4-5-preview",
"swe_score": 0.38,
"cost_per_1m_tokens": { "input": 75.0, "output": 150.0 },
"cost_per_1m_tokens": {
"input": 75.0,
"output": 150.0
},
"allowed_roles": ["main"]
},
{
"id": "gpt-4-1-mini",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.4, "output": 1.6 },
"cost_per_1m_tokens": {
"input": 0.4,
"output": 1.6
},
"allowed_roles": ["main"]
},
{
"id": "gpt-4-1-nano",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.1, "output": 0.4 },
"cost_per_1m_tokens": {
"input": 0.1,
"output": 0.4
},
"allowed_roles": ["main"]
},
{
"id": "gpt-4o-mini",
"swe_score": 0.3,
"cost_per_1m_tokens": { "input": 0.15, "output": 0.6 },
"cost_per_1m_tokens": {
"input": 0.15,
"output": 0.6
},
"allowed_roles": ["main"]
},
{
"id": "gpt-4o-search-preview",
"swe_score": 0.33,
"cost_per_1m_tokens": { "input": 2.5, "output": 10.0 },
"cost_per_1m_tokens": {
"input": 2.5,
"output": 10.0
},
"allowed_roles": ["research"]
},
{
"id": "gpt-4o-mini-search-preview",
"swe_score": 0.3,
"cost_per_1m_tokens": { "input": 0.15, "output": 0.6 },
"cost_per_1m_tokens": {
"input": 0.15,
"output": 0.6
},
"allowed_roles": ["research"]
}
],
@@ -128,15 +195,18 @@
},
{
"id": "gemini-2.5-flash-preview-04-17",
"swe_score": 0,
"swe_score": 0.604,
"cost_per_1m_tokens": null,
"allowed_roles": ["main", "fallback"],
"max_tokens": 1048000
},
{
"id": "gemini-2.0-flash",
"swe_score": 0.754,
"cost_per_1m_tokens": { "input": 0.15, "output": 0.6 },
"swe_score": 0.518,
"cost_per_1m_tokens": {
"input": 0.15,
"output": 0.6
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1048000
},
@@ -152,35 +222,50 @@
{
"id": "sonar-pro",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 3, "output": 15 },
"cost_per_1m_tokens": {
"input": 3,
"output": 15
},
"allowed_roles": ["main", "research"],
"max_tokens": 8700
},
{
"id": "sonar",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 1, "output": 1 },
"cost_per_1m_tokens": {
"input": 1,
"output": 1
},
"allowed_roles": ["research"],
"max_tokens": 8700
},
{
"id": "deep-research",
"swe_score": 0.211,
"cost_per_1m_tokens": { "input": 2, "output": 8 },
"cost_per_1m_tokens": {
"input": 2,
"output": 8
},
"allowed_roles": ["research"],
"max_tokens": 8700
},
{
"id": "sonar-reasoning-pro",
"swe_score": 0.211,
"cost_per_1m_tokens": { "input": 2, "output": 8 },
"cost_per_1m_tokens": {
"input": 2,
"output": 8
},
"allowed_roles": ["main", "research", "fallback"],
"max_tokens": 8700
},
{
"id": "sonar-reasoning",
"swe_score": 0.211,
"cost_per_1m_tokens": { "input": 1, "output": 5 },
"cost_per_1m_tokens": {
"input": 1,
"output": 5
},
"allowed_roles": ["main", "research", "fallback"],
"max_tokens": 8700
}
@@ -190,7 +275,10 @@
"id": "grok-3",
"name": "Grok 3",
"swe_score": null,
"cost_per_1m_tokens": { "input": 3, "output": 15 },
"cost_per_1m_tokens": {
"input": 3,
"output": 15
},
"allowed_roles": ["main", "fallback", "research"],
"max_tokens": 131072
},
@@ -198,7 +286,10 @@
"id": "grok-3-fast",
"name": "Grok 3 Fast",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 5, "output": 25 },
"cost_per_1m_tokens": {
"input": 5,
"output": 25
},
"allowed_roles": ["main", "fallback", "research"],
"max_tokens": 131072
}
@@ -207,43 +298,64 @@
{
"id": "devstral:latest",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "qwen3:latest",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "qwen3:14b",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "qwen3:32b",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "mistral-small3.1:latest",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "llama3.3:latest",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"]
},
{
"id": "phi4:latest",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"]
}
],
@@ -251,177 +363,268 @@
{
"id": "google/gemini-2.5-flash-preview-05-20",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.15, "output": 0.6 },
"cost_per_1m_tokens": {
"input": 0.15,
"output": 0.6
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1048576
},
{
"id": "google/gemini-2.5-flash-preview-05-20:thinking",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.15, "output": 3.5 },
"cost_per_1m_tokens": {
"input": 0.15,
"output": 3.5
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1048576
},
{
"id": "google/gemini-2.5-pro-exp-03-25",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1000000
},
{
"id": "deepseek/deepseek-chat-v3-0324:free",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 163840
},
{
"id": "deepseek/deepseek-chat-v3-0324",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.27, "output": 1.1 },
"cost_per_1m_tokens": {
"input": 0.27,
"output": 1.1
},
"allowed_roles": ["main"],
"max_tokens": 64000
},
{
"id": "openai/gpt-4.1",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 2, "output": 8 },
"cost_per_1m_tokens": {
"input": 2,
"output": 8
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1000000
},
{
"id": "openai/gpt-4.1-mini",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.4, "output": 1.6 },
"cost_per_1m_tokens": {
"input": 0.4,
"output": 1.6
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1000000
},
{
"id": "openai/gpt-4.1-nano",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.1, "output": 0.4 },
"cost_per_1m_tokens": {
"input": 0.1,
"output": 0.4
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1000000
},
{
"id": "openai/o3",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 10, "output": 40 },
"cost_per_1m_tokens": {
"input": 10,
"output": 40
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 200000
},
{
"id": "openai/codex-mini",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 1.5, "output": 6 },
"cost_per_1m_tokens": {
"input": 1.5,
"output": 6
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 100000
},
{
"id": "openai/gpt-4o-mini",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.15, "output": 0.6 },
"cost_per_1m_tokens": {
"input": 0.15,
"output": 0.6
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 100000
},
{
"id": "openai/o4-mini",
"swe_score": 0.45,
"cost_per_1m_tokens": { "input": 1.1, "output": 4.4 },
"cost_per_1m_tokens": {
"input": 1.1,
"output": 4.4
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 100000
},
{
"id": "openai/o4-mini-high",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 1.1, "output": 4.4 },
"cost_per_1m_tokens": {
"input": 1.1,
"output": 4.4
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 100000
},
{
"id": "openai/o1-pro",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 150, "output": 600 },
"cost_per_1m_tokens": {
"input": 150,
"output": 600
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 100000
},
{
"id": "meta-llama/llama-3.3-70b-instruct",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 120, "output": 600 },
"cost_per_1m_tokens": {
"input": 120,
"output": 600
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1048576
},
{
"id": "meta-llama/llama-4-maverick",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.18, "output": 0.6 },
"cost_per_1m_tokens": {
"input": 0.18,
"output": 0.6
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1000000
},
{
"id": "meta-llama/llama-4-scout",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.08, "output": 0.3 },
"cost_per_1m_tokens": {
"input": 0.08,
"output": 0.3
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1000000
},
{
"id": "qwen/qwen-max",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 1.6, "output": 6.4 },
"cost_per_1m_tokens": {
"input": 1.6,
"output": 6.4
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 32768
},
{
"id": "qwen/qwen-turbo",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.05, "output": 0.2 },
"cost_per_1m_tokens": {
"input": 0.05,
"output": 0.2
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 1000000
},
{
"id": "qwen/qwen3-235b-a22b",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.14, "output": 2 },
"cost_per_1m_tokens": {
"input": 0.14,
"output": 2
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 24000
},
{
"id": "mistralai/mistral-small-3.1-24b-instruct:free",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 96000
},
{
"id": "mistralai/mistral-small-3.1-24b-instruct",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.1, "output": 0.3 },
"cost_per_1m_tokens": {
"input": 0.1,
"output": 0.3
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 128000
},
{
"id": "mistralai/devstral-small",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.1, "output": 0.3 },
"cost_per_1m_tokens": {
"input": 0.1,
"output": 0.3
},
"allowed_roles": ["main"],
"max_tokens": 110000
},
{
"id": "mistralai/mistral-nemo",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0.03, "output": 0.07 },
"cost_per_1m_tokens": {
"input": 0.03,
"output": 0.07
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 100000
},
{
"id": "thudm/glm-4-32b:free",
"swe_score": 0,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"cost_per_1m_tokens": {
"input": 0,
"output": 0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 32768
}
],
"claude-code": [
{
"id": "opus",
"swe_score": 0.725,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"allowed_roles": ["main", "fallback", "research"],
"max_tokens": 32000
},
{
"id": "sonnet",
"swe_score": 0.727,
"cost_per_1m_tokens": { "input": 0, "output": 0 },
"allowed_roles": ["main", "fallback", "research"],
"max_tokens": 64000
}
]
}

View File

@@ -32,7 +32,12 @@ async function expandAllTasks(
context = {},
outputFormat = 'text' // Assume text default for CLI
) {
const { session, mcpLog, projectRoot: providedProjectRoot } = context;
const {
session,
mcpLog,
projectRoot: providedProjectRoot,
tag: contextTag
} = context;
const isMCPCall = !!mcpLog; // Determine if called from MCP
const projectRoot = providedProjectRoot || findProjectRoot();
@@ -74,7 +79,7 @@ async function expandAllTasks(
try {
logger.info(`Reading tasks from ${tasksPath}`);
const data = readJSON(tasksPath, projectRoot);
const data = readJSON(tasksPath, projectRoot, contextTag);
if (!data || !data.tasks) {
throw new Error(`Invalid tasks data in ${tasksPath}`);
}
@@ -124,7 +129,7 @@ async function expandAllTasks(
numSubtasks,
useResearch,
additionalContext,
{ ...context, projectRoot }, // Pass the whole context object with projectRoot
{ ...context, projectRoot, tag: data.tag || contextTag }, // Pass the whole context object with projectRoot and resolved tag
force
);
expandedCount++;

View File

@@ -417,7 +417,7 @@ async function expandTask(
context = {},
force = false
) {
const { session, mcpLog, projectRoot: contextProjectRoot } = context;
const { session, mcpLog, projectRoot: contextProjectRoot, tag } = context;
const outputFormat = mcpLog ? 'json' : 'text';
// Determine projectRoot: Use from context if available, otherwise derive from tasksPath
@@ -439,7 +439,7 @@ async function expandTask(
try {
// --- Task Loading/Filtering (Unchanged) ---
logger.info(`Reading tasks from ${tasksPath}`);
const data = readJSON(tasksPath, projectRoot);
const data = readJSON(tasksPath, projectRoot, tag);
if (!data || !data.tasks)
throw new Error(`Invalid tasks data in ${tasksPath}`);
const taskIndex = data.tasks.findIndex(
@@ -668,7 +668,7 @@ async function expandTask(
// --- End Change: Append instead of replace ---
data.tasks[taskIndex] = task; // Assign the modified task back
writeJSON(tasksPath, data);
writeJSON(tasksPath, data, projectRoot, tag);
// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
// Display AI Usage Summary for CLI

View File

@@ -23,6 +23,7 @@ import {
} from '../config-manager.js';
import { findConfigPath } from '../../../src/utils/path-utils.js';
import { log } from '../utils.js';
import { CUSTOM_PROVIDERS } from '../../../src/constants/providers.js';
/**
* Fetches the list of models from OpenRouter API.
@@ -424,7 +425,7 @@ async function setModel(role, modelId, options = {}) {
let warningMessage = null;
// Find the model data in internal list initially to see if it exists at all
const modelData = availableModels.find((m) => m.id === modelId);
let modelData = availableModels.find((m) => m.id === modelId);
// --- Revised Logic: Prioritize providerHint --- //
@@ -440,7 +441,7 @@ async function setModel(role, modelId, options = {}) {
} else {
// Either not found internally, OR found but under a DIFFERENT provider than hinted.
// Proceed with custom logic based ONLY on the hint.
if (providerHint === 'openrouter') {
if (providerHint === CUSTOM_PROVIDERS.OPENROUTER) {
// Check OpenRouter ONLY because hint was openrouter
report('info', `Checking OpenRouter for ${modelId} (as hinted)...`);
const openRouterModels = await fetchOpenRouterModels();
@@ -449,7 +450,7 @@ async function setModel(role, modelId, options = {}) {
openRouterModels &&
openRouterModels.some((m) => m.id === modelId)
) {
determinedProvider = 'openrouter';
determinedProvider = CUSTOM_PROVIDERS.OPENROUTER;
// Check if this is a free model (ends with :free)
if (modelId.endsWith(':free')) {
@@ -465,7 +466,7 @@ async function setModel(role, modelId, options = {}) {
`Model ID "${modelId}" not found in the live OpenRouter model list. Please verify the ID and ensure it's available on OpenRouter.`
);
}
} else if (providerHint === 'ollama') {
} else if (providerHint === CUSTOM_PROVIDERS.OLLAMA) {
// Check Ollama ONLY because hint was ollama
report('info', `Checking Ollama for ${modelId} (as hinted)...`);
@@ -479,7 +480,7 @@ async function setModel(role, modelId, options = {}) {
`Unable to connect to Ollama server at ${ollamaBaseURL}. Please ensure Ollama is running and try again.`
);
} else if (ollamaModels.some((m) => m.model === modelId)) {
determinedProvider = 'ollama';
determinedProvider = CUSTOM_PROVIDERS.OLLAMA;
warningMessage = `Warning: Custom Ollama model '${modelId}' set. Ensure your Ollama server is running and has pulled this model. Taskmaster cannot guarantee compatibility.`;
report('warn', warningMessage);
} else {
@@ -489,13 +490,41 @@ async function setModel(role, modelId, options = {}) {
`Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}`
);
}
} else if (providerHint === 'bedrock') {
} else if (providerHint === CUSTOM_PROVIDERS.BEDROCK) {
// Set provider without model validation since Bedrock models are managed by AWS
determinedProvider = 'bedrock';
determinedProvider = CUSTOM_PROVIDERS.BEDROCK;
warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`;
report('warn', warningMessage);
} else if (providerHint === CUSTOM_PROVIDERS.CLAUDE_CODE) {
// Claude Code provider - check if model exists in our list
determinedProvider = CUSTOM_PROVIDERS.CLAUDE_CODE;
// Re-find modelData specifically for claude-code provider
const claudeCodeModels = availableModels.filter(
(m) => m.provider === 'claude-code'
);
const claudeCodeModelData = claudeCodeModels.find(
(m) => m.id === modelId
);
if (claudeCodeModelData) {
// Update modelData to the found claude-code model
modelData = claudeCodeModelData;
report('info', `Setting Claude Code model '${modelId}'.`);
} else {
warningMessage = `Warning: Claude Code model '${modelId}' not found in supported models. Setting without validation.`;
report('warn', warningMessage);
}
} else if (providerHint === CUSTOM_PROVIDERS.AZURE) {
// Set provider without model validation since Azure models are managed by Azure
determinedProvider = CUSTOM_PROVIDERS.AZURE;
warningMessage = `Warning: Custom Azure model '${modelId}' set. Please ensure the model deployment is valid and accessible in your Azure account.`;
report('warn', warningMessage);
} else if (providerHint === CUSTOM_PROVIDERS.VERTEX) {
// Set provider without model validation since Vertex models are managed by Google Cloud
determinedProvider = CUSTOM_PROVIDERS.VERTEX;
warningMessage = `Warning: Custom Vertex AI model '${modelId}' set. Please ensure the model is valid and accessible in your Google Cloud project.`;
report('warn', warningMessage);
} else {
// Invalid provider hint - should not happen
// Invalid provider hint - should not happen with our constants
throw new Error(`Invalid provider hint received: ${providerHint}`);
}
}
@@ -514,7 +543,7 @@ async function setModel(role, modelId, options = {}) {
success: false,
error: {
code: 'MODEL_NOT_FOUND_NO_HINT',
message: `Model ID "${modelId}" not found in Taskmaster's supported models. If this is a custom model, please specify the provider using --openrouter or --ollama.`
message: `Model ID "${modelId}" not found in Taskmaster's supported models. If this is a custom model, please specify the provider using --openrouter, --ollama, --bedrock, --azure, or --vertex.`
}
};
}
@@ -536,11 +565,16 @@ async function setModel(role, modelId, options = {}) {
// Update configuration
currentConfig.models[role] = {
...currentConfig.models[role], // Keep existing params like maxTokens
...currentConfig.models[role], // Keep existing params like temperature
provider: determinedProvider,
modelId: modelId
};
// If model data is available, update maxTokens from supported-models.json
if (modelData && modelData.max_tokens) {
currentConfig.models[role].maxTokens = modelData.max_tokens;
}
// Write updated configuration
const writeResult = writeConfig(currentConfig, projectRoot);
if (!writeResult) {

View File

@@ -73,7 +73,7 @@ function resolveEnvVariable(key, session = null, projectRoot = null) {
*/
function findProjectRoot(
startDir = process.cwd(),
markers = ['package.json', '.git', LEGACY_CONFIG_FILE]
markers = ['package.json', 'pyproject.toml', '.git', LEGACY_CONFIG_FILE]
) {
let currentPath = path.resolve(startDir);
const rootPath = path.parse(currentPath).root;

View File

@@ -123,7 +123,7 @@ export class ContextGatherer {
// Dependency Graph Analysis
if (dependencyTasks.length > 0) {
const dependencyResults = this._buildDependencyContext(dependencyTasks);
const dependencyResults = this._buildDependencyGraphs(dependencyTasks);
dependencyResults.allRelatedTaskIds.forEach((id) =>
finalTaskIds.add(String(id))
);

View File

@@ -21,18 +21,10 @@ export class BedrockAIProvider extends BaseAIProvider {
*/
getClient(params) {
try {
const {
profile = process.env.AWS_PROFILE || 'default',
region = process.env.AWS_DEFAULT_REGION || 'us-east-1',
baseURL
} = params;
const credentialProvider = fromNodeProviderChain({ profile });
const credentialProvider = fromNodeProviderChain();
return createAmazonBedrock({
region,
credentialProvider,
...(baseURL && { baseURL })
credentialProvider
});
} catch (error) {
this.handleError('client initialization', error);

View File

@@ -0,0 +1,47 @@
/**
* src/ai-providers/claude-code.js
*
* Implementation for interacting with Claude models via Claude Code CLI
* using a custom AI SDK implementation.
*/
import { createClaudeCode } from './custom-sdk/claude-code/index.js';
import { BaseAIProvider } from './base-provider.js';
export class ClaudeCodeProvider extends BaseAIProvider {
constructor() {
super();
this.name = 'Claude Code';
}
/**
* Override validateAuth to skip API key validation for Claude Code
* @param {object} params - Parameters to validate
*/
validateAuth(params) {
// Claude Code doesn't require an API key
// No validation needed
}
/**
* Creates and returns a Claude Code client instance.
* @param {object} params - Parameters for client initialization
* @param {string} [params.baseURL] - Optional custom API endpoint (not used by Claude Code)
* @returns {Function} Claude Code client function
* @throws {Error} If initialization fails
*/
getClient(params) {
try {
// Claude Code doesn't use API keys or base URLs
// Just return the provider factory
return createClaudeCode({
defaultSettings: {
// Add any default settings if needed
// These can be overridden per request
}
});
} catch (error) {
this.handleError('client initialization', error);
}
}
}

View File

@@ -0,0 +1,126 @@
/**
* @fileoverview Error handling utilities for Claude Code provider
*/
import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider';
/**
* @typedef {import('./types.js').ClaudeCodeErrorMetadata} ClaudeCodeErrorMetadata
*/
/**
* Create an API call error with Claude Code specific metadata
* @param {Object} params - Error parameters
* @param {string} params.message - Error message
* @param {string} [params.code] - Error code
* @param {number} [params.exitCode] - Process exit code
* @param {string} [params.stderr] - Standard error output
* @param {string} [params.promptExcerpt] - Excerpt of the prompt
* @param {boolean} [params.isRetryable=false] - Whether the error is retryable
* @returns {APICallError}
*/
export function createAPICallError({
message,
code,
exitCode,
stderr,
promptExcerpt,
isRetryable = false
}) {
/** @type {ClaudeCodeErrorMetadata} */
const metadata = {
code,
exitCode,
stderr,
promptExcerpt
};
return new APICallError({
message,
isRetryable,
url: 'claude-code-cli://command',
requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
data: metadata
});
}
/**
* Create an authentication error
* @param {Object} params - Error parameters
* @param {string} params.message - Error message
* @returns {LoadAPIKeyError}
*/
export function createAuthenticationError({ message }) {
return new LoadAPIKeyError({
message:
message ||
'Authentication failed. Please ensure Claude Code CLI is properly authenticated.'
});
}
/**
* Create a timeout error
* @param {Object} params - Error parameters
* @param {string} params.message - Error message
* @param {string} [params.promptExcerpt] - Excerpt of the prompt
* @param {number} params.timeoutMs - Timeout in milliseconds
* @returns {APICallError}
*/
export function createTimeoutError({ message, promptExcerpt, timeoutMs }) {
// Store timeoutMs in metadata for potential use by error handlers
/** @type {ClaudeCodeErrorMetadata & { timeoutMs: number }} */
const metadata = {
code: 'TIMEOUT',
promptExcerpt,
timeoutMs
};
return new APICallError({
message,
isRetryable: true,
url: 'claude-code-cli://command',
requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined,
data: metadata
});
}
/**
* Check if an error is an authentication error
* @param {unknown} error - Error to check
* @returns {boolean}
*/
export function isAuthenticationError(error) {
if (error instanceof LoadAPIKeyError) return true;
if (
error instanceof APICallError &&
/** @type {ClaudeCodeErrorMetadata} */ (error.data)?.exitCode === 401
)
return true;
return false;
}
/**
* Check if an error is a timeout error
* @param {unknown} error - Error to check
* @returns {boolean}
*/
export function isTimeoutError(error) {
if (
error instanceof APICallError &&
/** @type {ClaudeCodeErrorMetadata} */ (error.data)?.code === 'TIMEOUT'
)
return true;
return false;
}
/**
* Get error metadata from an error
* @param {unknown} error - Error to extract metadata from
* @returns {ClaudeCodeErrorMetadata|undefined}
*/
export function getErrorMetadata(error) {
if (error instanceof APICallError && error.data) {
return /** @type {ClaudeCodeErrorMetadata} */ (error.data);
}
return undefined;
}

View File

@@ -0,0 +1,83 @@
/**
* @fileoverview Claude Code provider factory and exports
*/
import { NoSuchModelError } from '@ai-sdk/provider';
import { ClaudeCodeLanguageModel } from './language-model.js';
/**
* @typedef {import('./types.js').ClaudeCodeSettings} ClaudeCodeSettings
* @typedef {import('./types.js').ClaudeCodeModelId} ClaudeCodeModelId
* @typedef {import('./types.js').ClaudeCodeProvider} ClaudeCodeProvider
* @typedef {import('./types.js').ClaudeCodeProviderSettings} ClaudeCodeProviderSettings
*/
/**
* Create a Claude Code provider using the official SDK
* @param {ClaudeCodeProviderSettings} [options={}] - Provider configuration options
* @returns {ClaudeCodeProvider} Claude Code provider instance
*/
export function createClaudeCode(options = {}) {
/**
* Create a language model instance
* @param {ClaudeCodeModelId} modelId - Model ID
* @param {ClaudeCodeSettings} [settings={}] - Model settings
* @returns {ClaudeCodeLanguageModel}
*/
const createModel = (modelId, settings = {}) => {
return new ClaudeCodeLanguageModel({
id: modelId,
settings: {
...options.defaultSettings,
...settings
}
});
};
/**
* Provider function
* @param {ClaudeCodeModelId} modelId - Model ID
* @param {ClaudeCodeSettings} [settings] - Model settings
* @returns {ClaudeCodeLanguageModel}
*/
const provider = function (modelId, settings) {
if (new.target) {
throw new Error(
'The Claude Code model function cannot be called with the new keyword.'
);
}
return createModel(modelId, settings);
};
provider.languageModel = createModel;
provider.chat = createModel; // Alias for languageModel
// Add textEmbeddingModel method that throws NoSuchModelError
provider.textEmbeddingModel = (modelId) => {
throw new NoSuchModelError({
modelId,
modelType: 'textEmbeddingModel'
});
};
return /** @type {ClaudeCodeProvider} */ (provider);
}
/**
* Default Claude Code provider instance
*/
export const claudeCode = createClaudeCode();
// Provider exports
export { ClaudeCodeLanguageModel } from './language-model.js';
// Error handling exports
export {
isAuthenticationError,
isTimeoutError,
getErrorMetadata,
createAPICallError,
createAuthenticationError,
createTimeoutError
} from './errors.js';

View File

@@ -0,0 +1,59 @@
/**
* @fileoverview Extract JSON from Claude's response, handling markdown blocks and other formatting
*/
/**
* Extract JSON from Claude's response
* @param {string} text - The text to extract JSON from
* @returns {string} - The extracted JSON string
*/
export function extractJson(text) {
// Remove markdown code blocks if present
let jsonText = text.trim();
// Remove ```json blocks
jsonText = jsonText.replace(/^```json\s*/gm, '');
jsonText = jsonText.replace(/^```\s*/gm, '');
jsonText = jsonText.replace(/```\s*$/gm, '');
// Remove common TypeScript/JavaScript patterns
jsonText = jsonText.replace(/^const\s+\w+\s*=\s*/, ''); // Remove "const varName = "
jsonText = jsonText.replace(/^let\s+\w+\s*=\s*/, ''); // Remove "let varName = "
jsonText = jsonText.replace(/^var\s+\w+\s*=\s*/, ''); // Remove "var varName = "
jsonText = jsonText.replace(/;?\s*$/, ''); // Remove trailing semicolons
// Try to extract JSON object or array
const objectMatch = jsonText.match(/{[\s\S]*}/);
const arrayMatch = jsonText.match(/\[[\s\S]*\]/);
if (objectMatch) {
jsonText = objectMatch[0];
} else if (arrayMatch) {
jsonText = arrayMatch[0];
}
// First try to parse as valid JSON
try {
JSON.parse(jsonText);
return jsonText;
} catch {
// If it's not valid JSON, it might be a JavaScript object literal
// Try to convert it to valid JSON
try {
// This is a simple conversion that handles basic cases
// Replace unquoted keys with quoted keys
const converted = jsonText
.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":')
// Replace single quotes with double quotes
.replace(/'/g, '"');
// Validate the converted JSON
JSON.parse(converted);
return converted;
} catch {
// If all else fails, return the original text
// The AI SDK will handle the error appropriately
return text;
}
}
}

View File

@@ -0,0 +1,458 @@
/**
* @fileoverview Claude Code Language Model implementation
*/
import { NoSuchModelError } from '@ai-sdk/provider';
import { generateId } from '@ai-sdk/provider-utils';
import { convertToClaudeCodeMessages } from './message-converter.js';
import { extractJson } from './json-extractor.js';
import { createAPICallError, createAuthenticationError } from './errors.js';
let query;
let AbortError;
async function loadClaudeCodeModule() {
if (!query || !AbortError) {
try {
const mod = await import('@anthropic-ai/claude-code');
query = mod.query;
AbortError = mod.AbortError;
} catch (err) {
throw new Error(
"Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider."
);
}
}
}
/**
* @typedef {import('./types.js').ClaudeCodeSettings} ClaudeCodeSettings
* @typedef {import('./types.js').ClaudeCodeModelId} ClaudeCodeModelId
* @typedef {import('./types.js').ClaudeCodeLanguageModelOptions} ClaudeCodeLanguageModelOptions
*/
const modelMap = {
opus: 'opus',
sonnet: 'sonnet'
};
export class ClaudeCodeLanguageModel {
specificationVersion = 'v1';
defaultObjectGenerationMode = 'json';
supportsImageUrls = false;
supportsStructuredOutputs = false;
/** @type {ClaudeCodeModelId} */
modelId;
/** @type {ClaudeCodeSettings} */
settings;
/** @type {string|undefined} */
sessionId;
/**
* @param {ClaudeCodeLanguageModelOptions} options
*/
constructor(options) {
this.modelId = options.id;
this.settings = options.settings ?? {};
// Validate model ID format
if (
!this.modelId ||
typeof this.modelId !== 'string' ||
this.modelId.trim() === ''
) {
throw new NoSuchModelError({
modelId: this.modelId,
modelType: 'languageModel'
});
}
}
get provider() {
return 'claude-code';
}
/**
* Get the model name for Claude Code CLI
* @returns {string}
*/
getModel() {
const mapped = modelMap[this.modelId];
return mapped ?? this.modelId;
}
/**
* Generate unsupported parameter warnings
* @param {Object} options - Generation options
* @returns {Array} Warnings array
*/
generateUnsupportedWarnings(options) {
const warnings = [];
const unsupportedParams = [];
// Check for unsupported parameters
if (options.temperature !== undefined)
unsupportedParams.push('temperature');
if (options.maxTokens !== undefined) unsupportedParams.push('maxTokens');
if (options.topP !== undefined) unsupportedParams.push('topP');
if (options.topK !== undefined) unsupportedParams.push('topK');
if (options.presencePenalty !== undefined)
unsupportedParams.push('presencePenalty');
if (options.frequencyPenalty !== undefined)
unsupportedParams.push('frequencyPenalty');
if (options.stopSequences !== undefined && options.stopSequences.length > 0)
unsupportedParams.push('stopSequences');
if (options.seed !== undefined) unsupportedParams.push('seed');
if (unsupportedParams.length > 0) {
// Add a warning for each unsupported parameter
for (const param of unsupportedParams) {
warnings.push({
type: 'unsupported-setting',
setting: param,
details: `Claude Code CLI does not support the ${param} parameter. It will be ignored.`
});
}
}
return warnings;
}
/**
* Generate text using Claude Code
* @param {Object} options - Generation options
* @returns {Promise<Object>}
*/
async doGenerate(options) {
await loadClaudeCodeModule();
const { messagesPrompt } = convertToClaudeCodeMessages(
options.prompt,
options.mode
);
const abortController = new AbortController();
if (options.abortSignal) {
options.abortSignal.addEventListener('abort', () =>
abortController.abort()
);
}
const queryOptions = {
model: this.getModel(),
abortController,
resume: this.sessionId,
pathToClaudeCodeExecutable: this.settings.pathToClaudeCodeExecutable,
customSystemPrompt: this.settings.customSystemPrompt,
appendSystemPrompt: this.settings.appendSystemPrompt,
maxTurns: this.settings.maxTurns,
maxThinkingTokens: this.settings.maxThinkingTokens,
cwd: this.settings.cwd,
executable: this.settings.executable,
executableArgs: this.settings.executableArgs,
permissionMode: this.settings.permissionMode,
permissionPromptToolName: this.settings.permissionPromptToolName,
continue: this.settings.continue,
allowedTools: this.settings.allowedTools,
disallowedTools: this.settings.disallowedTools,
mcpServers: this.settings.mcpServers
};
let text = '';
let usage = { promptTokens: 0, completionTokens: 0 };
let finishReason = 'stop';
let costUsd;
let durationMs;
let rawUsage;
const warnings = this.generateUnsupportedWarnings(options);
try {
const response = query({
prompt: messagesPrompt,
options: queryOptions
});
for await (const message of response) {
if (message.type === 'assistant') {
text += message.message.content
.map((c) => (c.type === 'text' ? c.text : ''))
.join('');
} else if (message.type === 'result') {
this.sessionId = message.session_id;
costUsd = message.total_cost_usd;
durationMs = message.duration_ms;
if ('usage' in message) {
rawUsage = message.usage;
usage = {
promptTokens:
(message.usage.cache_creation_input_tokens ?? 0) +
(message.usage.cache_read_input_tokens ?? 0) +
(message.usage.input_tokens ?? 0),
completionTokens: message.usage.output_tokens ?? 0
};
}
if (message.subtype === 'error_max_turns') {
finishReason = 'length';
} else if (message.subtype === 'error_during_execution') {
finishReason = 'error';
}
} else if (message.type === 'system' && message.subtype === 'init') {
this.sessionId = message.session_id;
}
}
} catch (error) {
if (error instanceof AbortError) {
throw options.abortSignal?.aborted ? options.abortSignal.reason : error;
}
// Check for authentication errors
if (
error.message?.includes('not logged in') ||
error.message?.includes('authentication') ||
error.exitCode === 401
) {
throw createAuthenticationError({
message:
error.message ||
'Authentication failed. Please ensure Claude Code CLI is properly authenticated.'
});
}
// Wrap other errors with API call error
throw createAPICallError({
message: error.message || 'Claude Code CLI error',
code: error.code,
exitCode: error.exitCode,
stderr: error.stderr,
promptExcerpt: messagesPrompt.substring(0, 200),
isRetryable: error.code === 'ENOENT' || error.code === 'ECONNREFUSED'
});
}
// Extract JSON if in object-json mode
if (options.mode?.type === 'object-json' && text) {
text = extractJson(text);
}
return {
text: text || undefined,
usage,
finishReason,
rawCall: {
rawPrompt: messagesPrompt,
rawSettings: queryOptions
},
warnings: warnings.length > 0 ? warnings : undefined,
response: {
id: generateId(),
timestamp: new Date(),
modelId: this.modelId
},
request: {
body: messagesPrompt
},
providerMetadata: {
'claude-code': {
...(this.sessionId !== undefined && { sessionId: this.sessionId }),
...(costUsd !== undefined && { costUsd }),
...(durationMs !== undefined && { durationMs }),
...(rawUsage !== undefined && { rawUsage })
}
}
};
}
/**
* Stream text using Claude Code
* @param {Object} options - Stream options
* @returns {Promise<Object>}
*/
async doStream(options) {
await loadClaudeCodeModule();
const { messagesPrompt } = convertToClaudeCodeMessages(
options.prompt,
options.mode
);
const abortController = new AbortController();
if (options.abortSignal) {
options.abortSignal.addEventListener('abort', () =>
abortController.abort()
);
}
const queryOptions = {
model: this.getModel(),
abortController,
resume: this.sessionId,
pathToClaudeCodeExecutable: this.settings.pathToClaudeCodeExecutable,
customSystemPrompt: this.settings.customSystemPrompt,
appendSystemPrompt: this.settings.appendSystemPrompt,
maxTurns: this.settings.maxTurns,
maxThinkingTokens: this.settings.maxThinkingTokens,
cwd: this.settings.cwd,
executable: this.settings.executable,
executableArgs: this.settings.executableArgs,
permissionMode: this.settings.permissionMode,
permissionPromptToolName: this.settings.permissionPromptToolName,
continue: this.settings.continue,
allowedTools: this.settings.allowedTools,
disallowedTools: this.settings.disallowedTools,
mcpServers: this.settings.mcpServers
};
const warnings = this.generateUnsupportedWarnings(options);
const stream = new ReadableStream({
start: async (controller) => {
try {
const response = query({
prompt: messagesPrompt,
options: queryOptions
});
let usage = { promptTokens: 0, completionTokens: 0 };
let accumulatedText = '';
for await (const message of response) {
if (message.type === 'assistant') {
const text = message.message.content
.map((c) => (c.type === 'text' ? c.text : ''))
.join('');
if (text) {
accumulatedText += text;
// In object-json mode, we need to accumulate the full text
// and extract JSON at the end, so don't stream individual deltas
if (options.mode?.type !== 'object-json') {
controller.enqueue({
type: 'text-delta',
textDelta: text
});
}
}
} else if (message.type === 'result') {
let rawUsage;
if ('usage' in message) {
rawUsage = message.usage;
usage = {
promptTokens:
(message.usage.cache_creation_input_tokens ?? 0) +
(message.usage.cache_read_input_tokens ?? 0) +
(message.usage.input_tokens ?? 0),
completionTokens: message.usage.output_tokens ?? 0
};
}
let finishReason = 'stop';
if (message.subtype === 'error_max_turns') {
finishReason = 'length';
} else if (message.subtype === 'error_during_execution') {
finishReason = 'error';
}
// Store session ID in the model instance
this.sessionId = message.session_id;
// In object-json mode, extract JSON and send the full text at once
if (options.mode?.type === 'object-json' && accumulatedText) {
const extractedJson = extractJson(accumulatedText);
controller.enqueue({
type: 'text-delta',
textDelta: extractedJson
});
}
controller.enqueue({
type: 'finish',
finishReason,
usage,
providerMetadata: {
'claude-code': {
sessionId: message.session_id,
...(message.total_cost_usd !== undefined && {
costUsd: message.total_cost_usd
}),
...(message.duration_ms !== undefined && {
durationMs: message.duration_ms
}),
...(rawUsage !== undefined && { rawUsage })
}
}
});
} else if (
message.type === 'system' &&
message.subtype === 'init'
) {
// Store session ID for future use
this.sessionId = message.session_id;
// Emit response metadata when session is initialized
controller.enqueue({
type: 'response-metadata',
id: message.session_id,
timestamp: new Date(),
modelId: this.modelId
});
}
}
controller.close();
} catch (error) {
let errorToEmit;
if (error instanceof AbortError) {
errorToEmit = options.abortSignal?.aborted
? options.abortSignal.reason
: error;
} else if (
error.message?.includes('not logged in') ||
error.message?.includes('authentication') ||
error.exitCode === 401
) {
errorToEmit = createAuthenticationError({
message:
error.message ||
'Authentication failed. Please ensure Claude Code CLI is properly authenticated.'
});
} else {
errorToEmit = createAPICallError({
message: error.message || 'Claude Code CLI error',
code: error.code,
exitCode: error.exitCode,
stderr: error.stderr,
promptExcerpt: messagesPrompt.substring(0, 200),
isRetryable:
error.code === 'ENOENT' || error.code === 'ECONNREFUSED'
});
}
// Emit error as a stream part
controller.enqueue({
type: 'error',
error: errorToEmit
});
controller.close();
}
}
});
return {
stream,
rawCall: {
rawPrompt: messagesPrompt,
rawSettings: queryOptions
},
warnings: warnings.length > 0 ? warnings : undefined,
request: {
body: messagesPrompt
}
};
}
}

View File

@@ -0,0 +1,139 @@
/**
* @fileoverview Converts AI SDK prompt format to Claude Code message format
*/
/**
* Convert AI SDK prompt to Claude Code messages format
* @param {Array} prompt - AI SDK prompt array
* @param {Object} [mode] - Generation mode
* @param {string} mode.type - Mode type ('regular', 'object-json', 'object-tool')
* @returns {{messagesPrompt: string, systemPrompt?: string}}
*/
export function convertToClaudeCodeMessages(prompt, mode) {
const messages = [];
let systemPrompt;
for (const message of prompt) {
switch (message.role) {
case 'system':
systemPrompt = message.content;
break;
case 'user':
if (typeof message.content === 'string') {
messages.push(message.content);
} else {
// Handle multi-part content
const textParts = message.content
.filter((part) => part.type === 'text')
.map((part) => part.text)
.join('\n');
if (textParts) {
messages.push(textParts);
}
// Note: Image parts are not supported by Claude Code CLI
const imageParts = message.content.filter(
(part) => part.type === 'image'
);
if (imageParts.length > 0) {
console.warn(
'Claude Code CLI does not support image inputs. Images will be ignored.'
);
}
}
break;
case 'assistant':
if (typeof message.content === 'string') {
messages.push(`Assistant: ${message.content}`);
} else {
const textParts = message.content
.filter((part) => part.type === 'text')
.map((part) => part.text)
.join('\n');
if (textParts) {
messages.push(`Assistant: ${textParts}`);
}
// Handle tool calls if present
const toolCalls = message.content.filter(
(part) => part.type === 'tool-call'
);
if (toolCalls.length > 0) {
// For now, we'll just note that tool calls were made
messages.push(`Assistant: [Tool calls made]`);
}
}
break;
case 'tool':
// Tool results could be included in the conversation
messages.push(
`Tool Result (${message.content[0].toolName}): ${JSON.stringify(
message.content[0].result
)}`
);
break;
}
}
// For the SDK, we need to provide a single prompt string
// Format the conversation history properly
// Combine system prompt with messages
let finalPrompt = '';
// Add system prompt at the beginning if present
if (systemPrompt) {
finalPrompt = systemPrompt;
}
if (messages.length === 0) {
return { messagesPrompt: finalPrompt, systemPrompt };
}
// Format messages
const formattedMessages = [];
for (let i = 0; i < messages.length; i++) {
const msg = messages[i];
// Check if this is a user or assistant message based on content
if (msg.startsWith('Assistant:') || msg.startsWith('Tool Result')) {
formattedMessages.push(msg);
} else {
// User messages
formattedMessages.push(`Human: ${msg}`);
}
}
// Combine system prompt with messages
if (finalPrompt) {
finalPrompt = finalPrompt + '\n\n' + formattedMessages.join('\n\n');
} else {
finalPrompt = formattedMessages.join('\n\n');
}
// For JSON mode, add explicit instruction to ensure JSON output
if (mode?.type === 'object-json') {
// Make the JSON instruction even more explicit
finalPrompt = `${finalPrompt}
CRITICAL INSTRUCTION: You MUST respond with ONLY valid JSON. Follow these rules EXACTLY:
1. Start your response with an opening brace {
2. End your response with a closing brace }
3. Do NOT include any text before the opening brace
4. Do NOT include any text after the closing brace
5. Do NOT use markdown code blocks or backticks
6. Do NOT include explanations or commentary
7. The ENTIRE response must be valid JSON that can be parsed with JSON.parse()
Begin your response with { and end with }`;
}
return {
messagesPrompt: finalPrompt,
systemPrompt
};
}

View File

@@ -0,0 +1,73 @@
/**
* @fileoverview Type definitions for Claude Code AI SDK provider
* These JSDoc types mirror the TypeScript interfaces from the original provider
*/
/**
* Claude Code provider settings
* @typedef {Object} ClaudeCodeSettings
* @property {string} [pathToClaudeCodeExecutable='claude'] - Custom path to Claude Code CLI executable
* @property {string} [customSystemPrompt] - Custom system prompt to use
* @property {string} [appendSystemPrompt] - Append additional content to the system prompt
* @property {number} [maxTurns] - Maximum number of turns for the conversation
* @property {number} [maxThinkingTokens] - Maximum thinking tokens for the model
* @property {string} [cwd] - Working directory for CLI operations
* @property {'bun'|'deno'|'node'} [executable='node'] - JavaScript runtime to use
* @property {string[]} [executableArgs] - Additional arguments for the JavaScript runtime
* @property {'default'|'acceptEdits'|'bypassPermissions'|'plan'} [permissionMode='default'] - Permission mode for tool usage
* @property {string} [permissionPromptToolName] - Custom tool name for permission prompts
* @property {boolean} [continue] - Continue the most recent conversation
* @property {string} [resume] - Resume a specific session by ID
* @property {string[]} [allowedTools] - Tools to explicitly allow during execution (e.g., ['Read', 'LS', 'Bash(git log:*)'])
* @property {string[]} [disallowedTools] - Tools to disallow during execution (e.g., ['Write', 'Edit', 'Bash(rm:*)'])
* @property {Object.<string, MCPServerConfig>} [mcpServers] - MCP server configuration
* @property {boolean} [verbose] - Enable verbose logging for debugging
*/
/**
* MCP Server configuration
* @typedef {Object} MCPServerConfig
* @property {'stdio'|'sse'} [type='stdio'] - Server type
* @property {string} command - Command to execute (for stdio type)
* @property {string[]} [args] - Arguments for the command
* @property {Object.<string, string>} [env] - Environment variables
* @property {string} url - URL for SSE type servers
* @property {Object.<string, string>} [headers] - Headers for SSE type servers
*/
/**
* Model ID type - either 'opus', 'sonnet', or any string
* @typedef {'opus'|'sonnet'|string} ClaudeCodeModelId
*/
/**
* Language model options
* @typedef {Object} ClaudeCodeLanguageModelOptions
* @property {ClaudeCodeModelId} id - The model ID
* @property {ClaudeCodeSettings} [settings] - Optional settings
*/
/**
* Error metadata for Claude Code errors
* @typedef {Object} ClaudeCodeErrorMetadata
* @property {string} [code] - Error code
* @property {number} [exitCode] - Process exit code
* @property {string} [stderr] - Standard error output
* @property {string} [promptExcerpt] - Excerpt of the prompt that caused the error
*/
/**
* Claude Code provider interface
* @typedef {Object} ClaudeCodeProvider
* @property {function(ClaudeCodeModelId, ClaudeCodeSettings=): Object} languageModel - Create a language model
* @property {function(ClaudeCodeModelId, ClaudeCodeSettings=): Object} chat - Alias for languageModel
* @property {function(string): never} textEmbeddingModel - Throws NoSuchModelError (not supported)
*/
/**
* Claude Code provider settings
* @typedef {Object} ClaudeCodeProviderSettings
* @property {ClaudeCodeSettings} [defaultSettings] - Default settings to use for all models
*/
export {}; // This ensures the file is treated as a module

View File

@@ -13,3 +13,4 @@ export { OllamaAIProvider } from './ollama.js';
export { BedrockAIProvider } from './bedrock.js';
export { AzureProvider } from './azure.js';
export { VertexAIProvider } from './google-vertex.js';
export { ClaudeCodeProvider } from './claude-code.js';

59
src/constants/profiles.js Normal file
View File

@@ -0,0 +1,59 @@
/**
* @typedef {'claude' | 'cline' | 'codex' | 'cursor' | 'roo' | 'trae' | 'windsurf' | 'vscode'} RulesProfile
*/
/**
* Available rule profiles for project initialization and rules command
*
* ⚠️ SINGLE SOURCE OF TRUTH: This is the authoritative list of all supported rule profiles.
* This constant is used directly throughout the codebase (previously aliased as PROFILE_NAMES).
*
* @type {RulesProfile[]}
* @description Defines possible rule profile sets:
* - claude: Claude Code integration
* - cline: Cline IDE rules
* - codex: Codex integration
* - cursor: Cursor IDE rules
* - roo: Roo Code IDE rules
* - trae: Trae IDE rules
* - vscode: VS Code with GitHub Copilot integration
* - windsurf: Windsurf IDE rules
*
* To add a new rule profile:
* 1. Add the profile name to this array
* 2. Create a profile file in src/profiles/{profile}.js
* 3. Export it as {profile}Profile in src/profiles/index.js
*/
export const RULE_PROFILES = [
'claude',
'cline',
'codex',
'cursor',
'roo',
'trae',
'vscode',
'windsurf'
];
/**
* Centralized enum for all supported Roo agent modes
* @type {string[]}
* @description Available Roo Code IDE modes for rule generation
*/
export const ROO_MODES = [
'architect',
'ask',
'orchestrator',
'code',
'debug',
'test'
];
/**
* Check if a given rule profile is valid
* @param {string} rulesProfile - The rule profile to check
* @returns {boolean} True if the rule profile is valid, false otherwise
*/
export function isValidRulesProfile(rulesProfile) {
return RULE_PROFILES.includes(rulesProfile);
}

View File

@@ -0,0 +1,33 @@
/**
* Provider validation constants
* Defines which providers should be validated against the supported-models.json file
*/
// Providers that have predefined model lists and should be validated
export const VALIDATED_PROVIDERS = [
'anthropic',
'openai',
'google',
'perplexity',
'xai',
'mistral'
];
// Custom providers object for easy named access
export const CUSTOM_PROVIDERS = {
AZURE: 'azure',
VERTEX: 'vertex',
BEDROCK: 'bedrock',
OPENROUTER: 'openrouter',
OLLAMA: 'ollama',
CLAUDE_CODE: 'claude-code'
};
// Custom providers array (for backward compatibility and iteration)
export const CUSTOM_PROVIDERS_ARRAY = Object.values(CUSTOM_PROVIDERS);
// All known providers (for reference)
export const ALL_PROVIDERS = [
...VALIDATED_PROVIDERS,
...CUSTOM_PROVIDERS_ARRAY
];

View File

@@ -0,0 +1,25 @@
/**
* @typedef {'add' | 'remove'} RulesAction
*/
/**
* Individual rules action constants
*/
export const RULES_ACTIONS = {
ADD: 'add',
REMOVE: 'remove'
};
/**
* Special rules command (not a CRUD operation)
*/
export const RULES_SETUP_ACTION = 'setup';
/**
* Check if a given action is a valid rules action
* @param {string} action - The action to check
* @returns {boolean} True if the action is valid, false otherwise
*/
export function isValidRulesAction(action) {
return Object.values(RULES_ACTIONS).includes(action);
}

View File

@@ -0,0 +1,249 @@
// Base profile factory for rule-transformer
import path from 'path';
/**
* Creates a standardized profile configuration for different editors
* @param {Object} editorConfig - Editor-specific configuration
* @param {string} editorConfig.name - Profile name (e.g., 'cursor', 'vscode')
* @param {string} [editorConfig.displayName] - Display name for the editor (defaults to name)
* @param {string} editorConfig.url - Editor website URL
* @param {string} editorConfig.docsUrl - Editor documentation URL
* @param {string} editorConfig.profileDir - Directory for profile configuration
* @param {string} [editorConfig.rulesDir] - Directory for rules files (defaults to profileDir/rules)
* @param {boolean} [editorConfig.mcpConfig=true] - Whether to create MCP configuration
* @param {string} [editorConfig.mcpConfigName='mcp.json'] - Name of MCP config file
* @param {string} [editorConfig.fileExtension='.mdc'] - Source file extension
* @param {string} [editorConfig.targetExtension='.md'] - Target file extension
* @param {Object} [editorConfig.toolMappings={}] - Tool name mappings
* @param {Array} [editorConfig.customReplacements=[]] - Custom text replacements
* @param {Object} [editorConfig.customFileMap={}] - Custom file name mappings
* @param {boolean} [editorConfig.supportsRulesSubdirectories=false] - Whether to use taskmaster/ subdirectory for taskmaster-specific rules (only Cursor uses this by default)
* @param {Function} [editorConfig.onAdd] - Lifecycle hook for profile addition
* @param {Function} [editorConfig.onRemove] - Lifecycle hook for profile removal
* @param {Function} [editorConfig.onPostConvert] - Lifecycle hook for post-conversion
* @returns {Object} - Complete profile configuration
*/
export function createProfile(editorConfig) {
const {
name,
displayName = name,
url,
docsUrl,
profileDir,
rulesDir = `${profileDir}/rules`,
mcpConfig = true,
mcpConfigName = 'mcp.json',
fileExtension = '.mdc',
targetExtension = '.md',
toolMappings = {},
customReplacements = [],
customFileMap = {},
supportsRulesSubdirectories = false,
onAdd,
onRemove,
onPostConvert
} = editorConfig;
const mcpConfigPath = `${profileDir}/${mcpConfigName}`;
// Standard file mapping with custom overrides
// Use taskmaster subdirectory only if profile supports it
const taskmasterPrefix = supportsRulesSubdirectories ? 'taskmaster/' : '';
const defaultFileMap = {
'cursor_rules.mdc': `${name.toLowerCase()}_rules${targetExtension}`,
'dev_workflow.mdc': `${taskmasterPrefix}dev_workflow${targetExtension}`,
'self_improve.mdc': `self_improve${targetExtension}`,
'taskmaster.mdc': `${taskmasterPrefix}taskmaster${targetExtension}`
};
const fileMap = { ...defaultFileMap, ...customFileMap };
// Base global replacements that work for all editors
const baseGlobalReplacements = [
// Handle URLs in any context
{ from: /cursor\.so/gi, to: url },
{ from: /cursor\s*\.\s*so/gi, to: url },
{ from: /https?:\/\/cursor\.so/gi, to: `https://${url}` },
{ from: /https?:\/\/www\.cursor\.so/gi, to: `https://www.${url}` },
// Handle tool references
{ from: /\bedit_file\b/gi, to: toolMappings.edit_file || 'edit_file' },
{
from: /\bsearch tool\b/gi,
to: `${toolMappings.search || 'search'} tool`
},
{ from: /\bSearch Tool\b/g, to: `${toolMappings.search || 'Search'} Tool` },
// Handle basic terms with proper case handling
{
from: /\bcursor\b/gi,
to: (match) =>
match.charAt(0) === 'C' ? displayName : name.toLowerCase()
},
{ from: /Cursor/g, to: displayName },
{ from: /CURSOR/g, to: displayName.toUpperCase() },
// Handle file extensions if different
...(targetExtension !== fileExtension
? [
{
from: new RegExp(`\\${fileExtension}(?!\\])\\b`, 'g'),
to: targetExtension
}
]
: []),
// Handle documentation URLs
{ from: /docs\.cursor\.com/gi, to: docsUrl },
// Custom editor-specific replacements
...customReplacements
];
// Standard tool mappings
const defaultToolMappings = {
search: 'search',
read_file: 'read_file',
edit_file: 'edit_file',
create_file: 'create_file',
run_command: 'run_command',
terminal_command: 'terminal_command',
use_mcp: 'use_mcp',
switch_mode: 'switch_mode',
...toolMappings
};
// Create conversion config
const conversionConfig = {
// Profile name replacements
profileTerms: [
{ from: /cursor\.so/g, to: url },
{ from: /\[cursor\.so\]/g, to: `[${url}]` },
{ from: /href="https:\/\/cursor\.so/g, to: `href="https://${url}` },
{ from: /\(https:\/\/cursor\.so/g, to: `(https://${url}` },
{
from: /\bcursor\b/gi,
to: (match) => (match === 'Cursor' ? displayName : name.toLowerCase())
},
{ from: /Cursor/g, to: displayName }
],
// File extension replacements
fileExtensions:
targetExtension !== fileExtension
? [
{
from: new RegExp(`\\${fileExtension}\\b`, 'g'),
to: targetExtension
}
]
: [],
// Documentation URL replacements
docUrls: [
{
from: new RegExp(`https:\\/\\/docs\\.cursor\\.com\\/[^\\s)'\"]+`, 'g'),
to: (match) => match.replace('docs.cursor.com', docsUrl)
},
{
from: new RegExp(`https:\\/\\/${docsUrl}\\/`, 'g'),
to: `https://${docsUrl}/`
}
],
// Tool references - direct replacements
toolNames: defaultToolMappings,
// Tool references in context - more specific replacements
toolContexts: Object.entries(defaultToolMappings).flatMap(
([original, mapped]) => [
{
from: new RegExp(`\\b${original} tool\\b`, 'g'),
to: `${mapped} tool`
},
{ from: new RegExp(`\\bthe ${original}\\b`, 'g'), to: `the ${mapped}` },
{ from: new RegExp(`\\bThe ${original}\\b`, 'g'), to: `The ${mapped}` },
{
from: new RegExp(`\\bCursor ${original}\\b`, 'g'),
to: `${displayName} ${mapped}`
}
]
),
// Tool group and category names
toolGroups: [
{ from: /\bSearch tools\b/g, to: 'Read Group tools' },
{ from: /\bEdit tools\b/g, to: 'Edit Group tools' },
{ from: /\bRun tools\b/g, to: 'Command Group tools' },
{ from: /\bMCP servers\b/g, to: 'MCP Group tools' },
{ from: /\bSearch Group\b/g, to: 'Read Group' },
{ from: /\bEdit Group\b/g, to: 'Edit Group' },
{ from: /\bRun Group\b/g, to: 'Command Group' }
],
// File references in markdown links
fileReferences: {
pathPattern: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
replacement: (match, text, filePath) => {
const baseName = path.basename(filePath, '.mdc');
const newFileName =
fileMap[`${baseName}.mdc`] || `${baseName}${targetExtension}`;
// Update the link text to match the new filename (strip directory path for display)
const newLinkText = path.basename(newFileName);
// For Cursor, keep the mdc: protocol; for others, use standard relative paths
if (name.toLowerCase() === 'cursor') {
return `[${newLinkText}](mdc:${rulesDir}/${newFileName})`;
} else {
return `[${newLinkText}](${rulesDir}/${newFileName})`;
}
}
}
};
function getTargetRuleFilename(sourceFilename) {
if (fileMap[sourceFilename]) {
return fileMap[sourceFilename];
}
return targetExtension !== fileExtension
? sourceFilename.replace(
new RegExp(`\\${fileExtension}$`),
targetExtension
)
: sourceFilename;
}
return {
profileName: name, // Use name for programmatic access (tests expect this)
displayName: displayName, // Keep displayName for UI purposes
profileDir,
rulesDir,
mcpConfig,
mcpConfigName,
mcpConfigPath,
supportsRulesSubdirectories,
fileMap,
globalReplacements: baseGlobalReplacements,
conversionConfig,
getTargetRuleFilename,
// Optional lifecycle hooks
...(onAdd && { onAddRulesProfile: onAdd }),
...(onRemove && { onRemoveRulesProfile: onRemove }),
...(onPostConvert && { onPostConvertRulesProfile: onPostConvert })
};
}
// Common tool mappings for editors that share similar tool sets
export const COMMON_TOOL_MAPPINGS = {
// Most editors (Cursor, Cline, Windsurf) keep original tool names
STANDARD: {},
// Roo Code uses different tool names
ROO_STYLE: {
edit_file: 'apply_diff',
search: 'search_files',
create_file: 'write_to_file',
run_command: 'execute_command',
terminal_command: 'execute_command',
use_mcp: 'use_mcp_tool'
}
};

59
src/profiles/claude.js Normal file
View File

@@ -0,0 +1,59 @@
// Claude Code profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { isSilentMode, log } from '../../scripts/modules/utils.js';
// Lifecycle functions for Claude Code profile
function onAddRulesProfile(targetDir, assetsDir) {
// Use the provided assets directory to find the source file
const sourceFile = path.join(assetsDir, 'AGENTS.md');
const destFile = path.join(targetDir, 'CLAUDE.md');
if (fs.existsSync(sourceFile)) {
try {
fs.copyFileSync(sourceFile, destFile);
log('debug', `[Claude] Copied AGENTS.md to ${destFile}`);
} catch (err) {
log('error', `[Claude] Failed to copy AGENTS.md: ${err.message}`);
}
}
}
function onRemoveRulesProfile(targetDir) {
const claudeFile = path.join(targetDir, 'CLAUDE.md');
if (fs.existsSync(claudeFile)) {
try {
fs.rmSync(claudeFile, { force: true });
log('debug', `[Claude] Removed CLAUDE.md from ${claudeFile}`);
} catch (err) {
log('error', `[Claude] Failed to remove CLAUDE.md: ${err.message}`);
}
}
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
onAddRulesProfile(targetDir, assetsDir);
}
// Simple filename function
function getTargetRuleFilename(sourceFilename) {
return sourceFilename;
}
// Simple profile configuration - bypasses base-profile system
export const claudeProfile = {
profileName: 'claude',
displayName: 'Claude Code',
profileDir: '.', // Root directory
rulesDir: '.', // No rules directory needed
mcpConfig: false, // No MCP config needed
mcpConfigName: null,
mcpConfigPath: null,
conversionConfig: {},
fileMap: {},
globalReplacements: [],
getTargetRuleFilename,
onAddRulesProfile,
onRemoveRulesProfile,
onPostConvertRulesProfile
};

20
src/profiles/cline.js Normal file
View File

@@ -0,0 +1,20 @@
// Cline conversion profile for rule-transformer
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
// Create and export cline profile using the base factory
export const clineProfile = createProfile({
name: 'cline',
displayName: 'Cline',
url: 'cline.bot',
docsUrl: 'docs.cline.bot',
profileDir: '.clinerules',
rulesDir: '.clinerules',
mcpConfig: false,
mcpConfigName: 'cline_mcp_settings.json',
fileExtension: '.mdc',
targetExtension: '.md',
toolMappings: COMMON_TOOL_MAPPINGS.STANDARD, // Cline uses standard tool names
customFileMap: {
'cursor_rules.mdc': 'cline_rules.md'
}
});

59
src/profiles/codex.js Normal file
View File

@@ -0,0 +1,59 @@
// Codex profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { isSilentMode, log } from '../../scripts/modules/utils.js';
// Lifecycle functions for Codex profile
function onAddRulesProfile(targetDir, assetsDir) {
// Use the provided assets directory to find the source file
const sourceFile = path.join(assetsDir, 'AGENTS.md');
const destFile = path.join(targetDir, 'AGENTS.md');
if (fs.existsSync(sourceFile)) {
try {
fs.copyFileSync(sourceFile, destFile);
log('debug', `[Codex] Copied AGENTS.md to ${destFile}`);
} catch (err) {
log('error', `[Codex] Failed to copy AGENTS.md: ${err.message}`);
}
}
}
function onRemoveRulesProfile(targetDir) {
const agentsFile = path.join(targetDir, 'AGENTS.md');
if (fs.existsSync(agentsFile)) {
try {
fs.rmSync(agentsFile, { force: true });
log('debug', `[Codex] Removed AGENTS.md from ${agentsFile}`);
} catch (err) {
log('error', `[Codex] Failed to remove AGENTS.md: ${err.message}`);
}
}
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
onAddRulesProfile(targetDir, assetsDir);
}
// Simple filename function
function getTargetRuleFilename(sourceFilename) {
return sourceFilename;
}
// Simple profile configuration - bypasses base-profile system
export const codexProfile = {
profileName: 'codex',
displayName: 'Codex',
profileDir: '.', // Root directory
rulesDir: '.', // No rules directory needed
mcpConfig: false, // No MCP config needed
mcpConfigName: null,
mcpConfigPath: null,
conversionConfig: {},
fileMap: {},
globalReplacements: [],
getTargetRuleFilename,
onAddRulesProfile,
onRemoveRulesProfile,
onPostConvertRulesProfile
};

21
src/profiles/cursor.js Normal file
View File

@@ -0,0 +1,21 @@
// Cursor conversion profile for rule-transformer
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
// Create and export cursor profile using the base factory
export const cursorProfile = createProfile({
name: 'cursor',
displayName: 'Cursor',
url: 'cursor.so',
docsUrl: 'docs.cursor.com',
profileDir: '.cursor',
rulesDir: '.cursor/rules',
mcpConfig: true,
mcpConfigName: 'mcp.json',
fileExtension: '.mdc',
targetExtension: '.mdc', // Cursor keeps .mdc extension
toolMappings: COMMON_TOOL_MAPPINGS.STANDARD,
supportsRulesSubdirectories: true,
customFileMap: {
'cursor_rules.mdc': 'cursor_rules.mdc' // Keep the same name for cursor
}
});

9
src/profiles/index.js Normal file
View File

@@ -0,0 +1,9 @@
// Profile exports for centralized importing
export { claudeProfile } from './claude.js';
export { clineProfile } from './cline.js';
export { codexProfile } from './codex.js';
export { cursorProfile } from './cursor.js';
export { rooProfile } from './roo.js';
export { traeProfile } from './trae.js';
export { vscodeProfile } from './vscode.js';
export { windsurfProfile } from './windsurf.js';

129
src/profiles/roo.js Normal file
View File

@@ -0,0 +1,129 @@
// Roo Code conversion profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { isSilentMode, log } from '../../scripts/modules/utils.js';
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
import { ROO_MODES } from '../constants/profiles.js';
// Lifecycle functions for Roo profile
function onAddRulesProfile(targetDir, assetsDir) {
// Use the provided assets directory to find the roocode directory
const sourceDir = path.join(assetsDir, 'roocode');
if (!fs.existsSync(sourceDir)) {
log('error', `[Roo] Source directory does not exist: ${sourceDir}`);
return;
}
copyRecursiveSync(sourceDir, targetDir);
log('debug', `[Roo] Copied roocode directory to ${targetDir}`);
const rooModesDir = path.join(sourceDir, '.roo');
// Copy .roomodes to project root
const roomodesSrc = path.join(sourceDir, '.roomodes');
const roomodesDest = path.join(targetDir, '.roomodes');
if (fs.existsSync(roomodesSrc)) {
try {
fs.copyFileSync(roomodesSrc, roomodesDest);
log('debug', `[Roo] Copied .roomodes to ${roomodesDest}`);
} catch (err) {
log('error', `[Roo] Failed to copy .roomodes: ${err.message}`);
}
}
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`);
if (fs.existsSync(src)) {
try {
const destDir = path.dirname(dest);
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
fs.copyFileSync(src, dest);
log('debug', `[Roo] Copied ${mode}-rules to ${dest}`);
} catch (err) {
log('error', `[Roo] Failed to copy ${src} to ${dest}: ${err.message}`);
}
}
}
}
function copyRecursiveSync(src, dest) {
const exists = fs.existsSync(src);
const stats = exists && fs.statSync(src);
const isDirectory = exists && stats.isDirectory();
if (isDirectory) {
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
fs.readdirSync(src).forEach((childItemName) => {
copyRecursiveSync(
path.join(src, childItemName),
path.join(dest, childItemName)
);
});
} else {
fs.copyFileSync(src, dest);
}
}
function onRemoveRulesProfile(targetDir) {
const roomodesPath = path.join(targetDir, '.roomodes');
if (fs.existsSync(roomodesPath)) {
try {
fs.rmSync(roomodesPath, { force: true });
log('debug', `[Roo] Removed .roomodes from ${roomodesPath}`);
} catch (err) {
log('error', `[Roo] Failed to remove .roomodes: ${err.message}`);
}
}
const rooDir = path.join(targetDir, '.roo');
if (fs.existsSync(rooDir)) {
fs.readdirSync(rooDir).forEach((entry) => {
if (entry.startsWith('rules-')) {
const modeDir = path.join(rooDir, entry);
try {
fs.rmSync(modeDir, { recursive: true, force: true });
log('debug', `[Roo] Removed ${entry} directory from ${modeDir}`);
} catch (err) {
log('error', `[Roo] Failed to remove ${modeDir}: ${err.message}`);
}
}
});
if (fs.readdirSync(rooDir).length === 0) {
try {
fs.rmSync(rooDir, { recursive: true, force: true });
log('debug', `[Roo] Removed empty .roo directory from ${rooDir}`);
} catch (err) {
log('error', `[Roo] Failed to remove .roo directory: ${err.message}`);
}
}
}
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
onAddRulesProfile(targetDir, assetsDir);
}
// Create and export roo profile using the base factory
export const rooProfile = createProfile({
name: 'roo',
displayName: 'Roo Code',
url: 'roocode.com',
docsUrl: 'docs.roocode.com',
profileDir: '.roo',
rulesDir: '.roo/rules',
mcpConfig: true,
mcpConfigName: 'mcp.json',
fileExtension: '.mdc',
targetExtension: '.md',
toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE,
customFileMap: {
'cursor_rules.mdc': 'roo_rules.md'
},
onAdd: onAddRulesProfile,
onRemove: onRemoveRulesProfile,
onPostConvert: onPostConvertRulesProfile
});
// Export lifecycle functions separately to avoid naming conflicts
export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };

17
src/profiles/trae.js Normal file
View File

@@ -0,0 +1,17 @@
// Trae conversion profile for rule-transformer
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
// Create and export trae profile using the base factory
export const traeProfile = createProfile({
name: 'trae',
displayName: 'Trae',
url: 'trae.ai',
docsUrl: 'docs.trae.ai',
profileDir: '.trae',
rulesDir: '.trae/rules',
mcpConfig: false,
mcpConfigName: 'trae_mcp_settings.json',
fileExtension: '.mdc',
targetExtension: '.md',
toolMappings: COMMON_TOOL_MAPPINGS.STANDARD // Trae uses standard tool names
});

41
src/profiles/vscode.js Normal file
View File

@@ -0,0 +1,41 @@
// VS Code conversion profile for rule-transformer
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
// Create and export vscode profile using the base factory
export const vscodeProfile = createProfile({
name: 'vscode',
displayName: 'VS Code',
url: 'code.visualstudio.com',
docsUrl: 'code.visualstudio.com/docs',
profileDir: '.vscode', // MCP config location
rulesDir: '.github/instructions', // VS Code instructions location
mcpConfig: true,
mcpConfigName: 'mcp.json',
fileExtension: '.mdc',
targetExtension: '.md',
toolMappings: COMMON_TOOL_MAPPINGS.STANDARD, // VS Code uses standard tool names
customFileMap: {
'cursor_rules.mdc': 'vscode_rules.md' // Rename cursor_rules to vscode_rules
},
customReplacements: [
// Core VS Code directory structure changes
{ from: /\.cursor\/rules/g, to: '.github/instructions' },
{ from: /\.cursor\/mcp\.json/g, to: '.vscode/mcp.json' },
// Fix any remaining vscode/rules references that might be created during transformation
{ from: /\.vscode\/rules/g, to: '.github/instructions' },
// VS Code custom instructions format - use applyTo with quoted patterns instead of globs
{ from: /^globs:\s*(.+)$/gm, to: 'applyTo: "$1"' },
// Essential markdown link transformations for VS Code structure
{
from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
to: '[$1](.github/instructions/$2.md)'
},
// VS Code specific terminology
{ from: /rules directory/g, to: 'instructions directory' },
{ from: /cursor rules/gi, to: 'VS Code instructions' }
]
});

17
src/profiles/windsurf.js Normal file
View File

@@ -0,0 +1,17 @@
// Windsurf conversion profile for rule-transformer
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
// Create and export windsurf profile using the base factory
export const windsurfProfile = createProfile({
name: 'windsurf',
displayName: 'Windsurf',
url: 'windsurf.com',
docsUrl: 'docs.windsurf.com',
profileDir: '.windsurf',
rulesDir: '.windsurf/rules',
mcpConfig: true,
mcpConfigName: 'mcp.json',
fileExtension: '.mdc',
targetExtension: '.md',
toolMappings: COMMON_TOOL_MAPPINGS.STANDARD // Windsurf uses standard tool names
});

100
src/ui/confirm.js Normal file
View File

@@ -0,0 +1,100 @@
import chalk from 'chalk';
import boxen from 'boxen';
/**
* Confirm removing profile rules (destructive operation)
* @param {string[]} profiles - Array of profile names to remove
* @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise
*/
async function confirmProfilesRemove(profiles) {
const profileList = profiles
.map((b) => b.charAt(0).toUpperCase() + b.slice(1))
.join(', ');
console.log(
boxen(
chalk.yellow(
`WARNING: This will selectively remove Task Master components for: ${profileList}.
What will be removed:
• Task Master specific rule files (e.g., cursor_rules.mdc, taskmaster.mdc, etc.)
• Task Master MCP server configuration (if no other MCP servers exist)
What will be preserved:
• Your existing custom rule files
• Other MCP server configurations
• The profile directory itself (unless completely empty after removal)
The .[profile] directory will only be removed if ALL of the following are true:
• All rules in the directory were Task Master rules (no custom rules)
• No other files or folders exist in the profile directory
• The MCP configuration was completely removed (no other servers)
Are you sure you want to proceed?`
),
{ padding: 1, borderColor: 'yellow', borderStyle: 'round' }
)
);
const inquirer = await import('inquirer');
const { confirm } = await inquirer.default.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Type y to confirm selective removal, or n to abort:',
default: false
}
]);
return confirm;
}
/**
* Confirm removing ALL remaining profile rules (extremely critical operation)
* @param {string[]} profiles - Array of profile names to remove
* @param {string[]} remainingProfiles - Array of profiles that would be left after removal
* @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise
*/
async function confirmRemoveAllRemainingProfiles(profiles, remainingProfiles) {
const profileList = profiles
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
.join(', ');
console.log(
boxen(
chalk.red.bold(
`⚠️ CRITICAL WARNING: REMOVING ALL TASK MASTER RULE PROFILES ⚠️\n\n` +
`You are about to remove Task Master components for: ${profileList}\n` +
`This will leave your project with NO Task Master rule profiles remaining!\n\n` +
`What will be removed:\n` +
`• All Task Master specific rule files\n` +
`• Task Master MCP server configurations\n` +
`• Profile directories (only if completely empty after removal)\n\n` +
`What will be preserved:\n` +
`• Your existing custom rule files\n` +
`• Other MCP server configurations\n` +
`• Profile directories with custom content\n\n` +
`This could impact Task Master functionality but will preserve your custom configurations.\n\n` +
`Are you absolutely sure you want to proceed?`
),
{
padding: 1,
borderColor: 'red',
borderStyle: 'double',
title: '🚨 CRITICAL OPERATION',
titleAlignment: 'center'
}
)
);
const inquirer = await import('inquirer');
const { confirm } = await inquirer.default.prompt([
{
type: 'confirm',
name: 'confirm',
message:
'Type y to confirm removing ALL Task Master rule profiles, or n to abort:',
default: false
}
]);
return confirm;
}
export { confirmProfilesRemove, confirmRemoveAllRemainingProfiles };

View File

@@ -0,0 +1,264 @@
import fs from 'fs';
import path from 'path';
import { log } from '../../scripts/modules/utils.js';
// Return JSON with existing mcp.json formatting style
function formatJSONWithTabs(obj) {
let json = JSON.stringify(obj, null, '\t');
json = json.replace(
/(\[\n\t+)([^[\]]+?)(\n\t+\])/g,
(match, openBracket, content, closeBracket) => {
// Only convert to single line if content doesn't contain nested objects/arrays
if (!content.includes('{') && !content.includes('[')) {
const singleLineContent = content
.replace(/\n\t+/g, ' ')
.replace(/\s+/g, ' ')
.trim();
return `[${singleLineContent}]`;
}
return match;
}
);
return json;
}
// Structure matches project conventions (see scripts/init.js)
export function setupMCPConfiguration(projectDir, mcpConfigPath) {
// Handle null mcpConfigPath (e.g., for Claude/Codex profiles)
if (!mcpConfigPath) {
log(
'debug',
'[MCP Config] No mcpConfigPath provided, skipping MCP configuration setup'
);
return;
}
// Build the full path to the MCP config file
const mcpPath = path.join(projectDir, mcpConfigPath);
const configDir = path.dirname(mcpPath);
log('info', `Setting up MCP configuration at ${mcpPath}...`);
// New MCP config to be added - references the installed package
const newMCPServer = {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai'],
env: {
ANTHROPIC_API_KEY: 'ANTHROPIC_API_KEY_HERE',
PERPLEXITY_API_KEY: 'PERPLEXITY_API_KEY_HERE',
OPENAI_API_KEY: 'OPENAI_API_KEY_HERE',
GOOGLE_API_KEY: 'GOOGLE_API_KEY_HERE',
XAI_API_KEY: 'XAI_API_KEY_HERE',
OPENROUTER_API_KEY: 'OPENROUTER_API_KEY_HERE',
MISTRAL_API_KEY: 'MISTRAL_API_KEY_HERE',
AZURE_OPENAI_API_KEY: 'AZURE_OPENAI_API_KEY_HERE',
OLLAMA_API_KEY: 'OLLAMA_API_KEY_HERE'
}
}
};
// Create config directory if it doesn't exist
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
if (fs.existsSync(mcpPath)) {
log(
'info',
'MCP configuration file already exists, checking for existing task-master-ai...'
);
try {
// Read existing config
const mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
// Initialize mcpServers if it doesn't exist
if (!mcpConfig.mcpServers) {
mcpConfig.mcpServers = {};
}
// Check if any existing server configuration already has task-master-ai in its args
const hasMCPString = Object.values(mcpConfig.mcpServers).some(
(server) =>
server.args &&
Array.isArray(server.args) &&
server.args.some(
(arg) => typeof arg === 'string' && arg.includes('task-master-ai')
)
);
if (hasMCPString) {
log(
'info',
'Found existing task-master-ai MCP configuration in mcp.json, leaving untouched'
);
return; // Exit early, don't modify the existing configuration
}
// Add the task-master-ai server if it doesn't exist
if (!mcpConfig.mcpServers['task-master-ai']) {
mcpConfig.mcpServers['task-master-ai'] = newMCPServer['task-master-ai'];
log(
'info',
'Added task-master-ai server to existing MCP configuration'
);
} else {
log('info', 'task-master-ai server already configured in mcp.json');
}
// Write the updated configuration
fs.writeFileSync(mcpPath, formatJSONWithTabs(mcpConfig) + '\n');
log('success', 'Updated MCP configuration file');
} catch (error) {
log('error', `Failed to update MCP configuration: ${error.message}`);
// Create a backup before potentially modifying
const backupPath = `${mcpPath}.backup-${Date.now()}`;
if (fs.existsSync(mcpPath)) {
fs.copyFileSync(mcpPath, backupPath);
log('info', `Created backup of existing mcp.json at ${backupPath}`);
}
// Create new configuration
const newMCPConfig = {
mcpServers: newMCPServer
};
fs.writeFileSync(mcpPath, formatJSONWithTabs(newMCPConfig) + '\n');
log(
'warn',
'Created new MCP configuration file (backup of original file was created if it existed)'
);
}
} else {
// If mcp.json doesn't exist, create it
const newMCPConfig = {
mcpServers: newMCPServer
};
fs.writeFileSync(mcpPath, formatJSONWithTabs(newMCPConfig) + '\n');
log('success', `Created MCP configuration file at ${mcpPath}`);
}
// Add note to console about MCP integration
log('info', 'MCP server will use the installed task-master-ai package');
}
/**
* Remove Task Master MCP server configuration from an existing mcp.json file
* Only removes Task Master entries, preserving other MCP servers
* @param {string} projectDir - Target project directory
* @param {string} mcpConfigPath - Relative path to MCP config file (e.g., '.cursor/mcp.json')
* @returns {Object} Result object with success status and details
*/
export function removeTaskMasterMCPConfiguration(projectDir, mcpConfigPath) {
// Handle null mcpConfigPath (e.g., for Claude/Codex profiles)
if (!mcpConfigPath) {
return {
success: true,
removed: false,
deleted: false,
error: null,
hasOtherServers: false
};
}
const mcpPath = path.join(projectDir, mcpConfigPath);
let result = {
success: false,
removed: false,
deleted: false,
error: null,
hasOtherServers: false
};
if (!fs.existsSync(mcpPath)) {
result.success = true;
result.removed = false;
log('debug', `[MCP Config] MCP config file does not exist: ${mcpPath}`);
return result;
}
try {
// Read existing config
const mcpConfig = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
if (!mcpConfig.mcpServers) {
result.success = true;
result.removed = false;
log('debug', `[MCP Config] No mcpServers section found in: ${mcpPath}`);
return result;
}
// Check if Task Master is configured
const hasTaskMaster =
mcpConfig.mcpServers['task-master-ai'] ||
Object.values(mcpConfig.mcpServers).some(
(server) =>
server.args &&
Array.isArray(server.args) &&
server.args.some(
(arg) => typeof arg === 'string' && arg.includes('task-master-ai')
)
);
if (!hasTaskMaster) {
result.success = true;
result.removed = false;
log(
'debug',
`[MCP Config] Task Master not found in MCP config: ${mcpPath}`
);
return result;
}
// Remove task-master-ai server
delete mcpConfig.mcpServers['task-master-ai'];
// Also remove any servers that have task-master-ai in their args
Object.keys(mcpConfig.mcpServers).forEach((serverName) => {
const server = mcpConfig.mcpServers[serverName];
if (
server.args &&
Array.isArray(server.args) &&
server.args.some(
(arg) => typeof arg === 'string' && arg.includes('task-master-ai')
)
) {
delete mcpConfig.mcpServers[serverName];
log(
'debug',
`[MCP Config] Removed server '${serverName}' containing task-master-ai`
);
}
});
// Check if there are other MCP servers remaining
const remainingServers = Object.keys(mcpConfig.mcpServers);
result.hasOtherServers = remainingServers.length > 0;
if (result.hasOtherServers) {
// Write back the modified config with remaining servers
fs.writeFileSync(mcpPath, formatJSONWithTabs(mcpConfig) + '\n');
result.success = true;
result.removed = true;
result.deleted = false;
log(
'info',
`[MCP Config] Removed Task Master from MCP config, preserving other servers: ${remainingServers.join(', ')}`
);
} else {
// No other servers, delete the entire file
fs.rmSync(mcpPath, { force: true });
result.success = true;
result.removed = true;
result.deleted = true;
log(
'info',
`[MCP Config] Removed MCP config file (no other servers remaining): ${mcpPath}`
);
}
} catch (error) {
result.error = error.message;
log(
'error',
`[MCP Config] Failed to remove Task Master from MCP config: ${error.message}`
);
}
return result;
}

283
src/utils/profiles.js Normal file
View File

@@ -0,0 +1,283 @@
/**
* Profiles Utility
* Consolidated utilities for profile detection, setup, and summary generation
*/
import fs from 'fs';
import path from 'path';
import inquirer from 'inquirer';
import chalk from 'chalk';
import boxen from 'boxen';
import { log } from '../../scripts/modules/utils.js';
import { getRulesProfile } from './rule-transformer.js';
import { RULE_PROFILES } from '../constants/profiles.js';
// =============================================================================
// PROFILE DETECTION
// =============================================================================
/**
* Detect which profiles are currently installed in the project
* @param {string} projectRoot - Project root directory
* @returns {string[]} Array of installed profile names
*/
export function getInstalledProfiles(projectRoot) {
const installedProfiles = [];
for (const profileName of RULE_PROFILES) {
const profileConfig = getRulesProfile(profileName);
if (!profileConfig) continue;
// Check if the profile directory exists
const profileDir = path.join(projectRoot, profileConfig.profileDir);
const rulesDir = path.join(projectRoot, profileConfig.rulesDir);
// A profile is considered installed if either the profile dir or rules dir exists
if (fs.existsSync(profileDir) || fs.existsSync(rulesDir)) {
installedProfiles.push(profileName);
}
}
return installedProfiles;
}
/**
* Check if removing the specified profiles would result in no profiles remaining
* @param {string} projectRoot - Project root directory
* @param {string[]} profilesToRemove - Array of profile names to remove
* @returns {boolean} True if removal would result in no profiles remaining
*/
export function wouldRemovalLeaveNoProfiles(projectRoot, profilesToRemove) {
const installedProfiles = getInstalledProfiles(projectRoot);
const remainingProfiles = installedProfiles.filter(
(profile) => !profilesToRemove.includes(profile)
);
return remainingProfiles.length === 0 && installedProfiles.length > 0;
}
// =============================================================================
// PROFILE SETUP
// =============================================================================
/**
* Get the display name for a profile
*/
function getProfileDisplayName(name) {
const profile = getRulesProfile(name);
return profile?.displayName || name.charAt(0).toUpperCase() + name.slice(1);
}
// Note: Profile choices are now generated dynamically within runInteractiveProfilesSetup()
// to ensure proper alphabetical sorting and pagination configuration
/**
* Launches an interactive prompt for selecting which rule profiles to include in your project.
*
* This function dynamically lists all available profiles (from RULE_PROFILES) and presents them as checkboxes.
* The user must select at least one profile (no defaults are pre-selected). The result is an array of selected profile names.
*
* Used by both project initialization (init) and the CLI 'task-master rules setup' command.
*
* @returns {Promise<string[]>} Array of selected profile names (e.g., ['cursor', 'windsurf'])
*/
export async function runInteractiveProfilesSetup() {
// Generate the profile list dynamically with proper display names, alphabetized
const profileDescriptions = RULE_PROFILES.map((profileName) => {
const displayName = getProfileDisplayName(profileName);
const profile = getRulesProfile(profileName);
// Determine description based on profile type
let description;
if (Object.keys(profile.fileMap).length === 0) {
// Simple profiles (Claude, Codex) - specify the target file
const targetFileName =
profileName === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
description = `Integration guide (${targetFileName})`;
} else {
// Full profiles with rules - check if they have MCP config
const hasMcpConfig = profile.mcpConfig === true;
if (hasMcpConfig) {
// Special case for Roo to mention agent modes
if (profileName === 'roo') {
description = 'Rule profile, MCP config, and agent modes';
} else {
description = 'Rule profile and MCP config';
}
} else {
description = 'Rule profile';
}
}
return {
profileName,
displayName,
description
};
}).sort((a, b) => a.displayName.localeCompare(b.displayName));
const profileListText = profileDescriptions
.map(
({ displayName, description }) =>
`${chalk.white('• ')}${chalk.yellow(displayName)}${chalk.white(` - ${description}`)}`
)
.join('\n');
console.log(
boxen(
`${chalk.white.bold('Rule Profiles Setup')}\n\n${chalk.white(
'Rule profiles help enforce best practices and conventions for Task Master.\n' +
'Each profile provides coding guidelines tailored for specific AI coding environments.\n\n'
)}${chalk.cyan('Available Profiles:')}\n${profileListText}`,
{
padding: 1,
borderColor: 'blue',
borderStyle: 'round',
margin: { top: 1, bottom: 1 }
}
)
);
// Generate choices in the same order as the display text above
const sortedChoices = profileDescriptions.map(
({ profileName, displayName }) => ({
name: displayName,
value: profileName
})
);
const ruleProfilesQuestion = {
type: 'checkbox',
name: 'ruleProfiles',
message: 'Which rule profiles would you like to add to your project?',
choices: sortedChoices,
pageSize: sortedChoices.length, // Show all options without pagination
loop: false, // Disable loop scrolling
validate: (input) => input.length > 0 || 'You must select at least one.'
};
const { ruleProfiles } = await inquirer.prompt([ruleProfilesQuestion]);
return ruleProfiles;
}
// =============================================================================
// PROFILE SUMMARY
// =============================================================================
/**
* Generate appropriate summary message for a profile based on its type
* @param {string} profileName - Name of the profile
* @param {Object} addResult - Result object with success/failed counts
* @returns {string} Formatted summary message
*/
export function generateProfileSummary(profileName, addResult) {
const profileConfig = getRulesProfile(profileName);
const isSimpleProfile = Object.keys(profileConfig.fileMap).length === 0;
if (isSimpleProfile) {
// Simple profiles like Claude and Codex only copy AGENTS.md
const targetFileName = profileName === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
return `Summary for ${profileName}: Integration guide copied to ${targetFileName}`;
} else {
return `Summary for ${profileName}: ${addResult.success} rules added, ${addResult.failed} failed.`;
}
}
/**
* Generate appropriate summary message for profile removal
* @param {string} profileName - Name of the profile
* @param {Object} removeResult - Result object from removal operation
* @returns {string} Formatted summary message
*/
export function generateProfileRemovalSummary(profileName, removeResult) {
const profileConfig = getRulesProfile(profileName);
const isSimpleProfile = Object.keys(profileConfig.fileMap).length === 0;
if (removeResult.skipped) {
return `Summary for ${profileName}: Skipped (default or protected files)`;
}
if (removeResult.error && !removeResult.success) {
return `Summary for ${profileName}: Failed to remove - ${removeResult.error}`;
}
if (isSimpleProfile) {
// Simple profiles like Claude and Codex only have an integration guide
const targetFileName = profileName === 'claude' ? 'CLAUDE.md' : 'AGENTS.md';
return `Summary for ${profileName}: Integration guide (${targetFileName}) removed`;
} else {
// Full profiles have rules directories and potentially MCP configs
const baseMessage = `Summary for ${profileName}: Rules directory removed`;
if (removeResult.notice) {
return `${baseMessage} (${removeResult.notice})`;
}
return baseMessage;
}
}
/**
* Categorize profiles and generate final summary statistics
* @param {Array} addResults - Array of add result objects
* @returns {Object} Object with categorized profiles and totals
*/
export function categorizeProfileResults(addResults) {
const successfulProfiles = [];
const simpleProfiles = [];
let totalSuccess = 0;
let totalFailed = 0;
addResults.forEach((r) => {
totalSuccess += r.success;
totalFailed += r.failed;
const profileConfig = getRulesProfile(r.profileName);
const isSimpleProfile = Object.keys(profileConfig.fileMap).length === 0;
if (isSimpleProfile) {
// Simple profiles are successful if they completed without error
simpleProfiles.push(r.profileName);
} else if (r.success > 0) {
// Full profiles are successful if they added rules
successfulProfiles.push(r.profileName);
}
});
return {
successfulProfiles,
simpleProfiles,
allSuccessfulProfiles: [...successfulProfiles, ...simpleProfiles],
totalSuccess,
totalFailed
};
}
/**
* Categorize removal results and generate final summary statistics
* @param {Array} removalResults - Array of removal result objects
* @returns {Object} Object with categorized removal results
*/
export function categorizeRemovalResults(removalResults) {
const successfulRemovals = [];
const skippedRemovals = [];
const failedRemovals = [];
const removalsWithNotices = [];
removalResults.forEach((result) => {
if (result.success) {
successfulRemovals.push(result.profileName);
} else if (result.skipped) {
skippedRemovals.push(result.profileName);
} else if (result.error) {
failedRemovals.push(result);
}
if (result.notice) {
removalsWithNotices.push(result);
}
});
return {
successfulRemovals,
skippedRemovals,
failedRemovals,
removalsWithNotices
};
}

View File

@@ -0,0 +1,503 @@
/**
* Rule Transformer Module
* Handles conversion of Cursor rules to profile rules
*
* This module procedurally generates .{profile}/rules files from assets/rules files,
* eliminating the need to maintain both sets of files manually.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { log } from '../../scripts/modules/utils.js';
// Import the shared MCP configuration helper
import {
setupMCPConfiguration,
removeTaskMasterMCPConfiguration
} from './create-mcp-config.js';
// Import profile constants (single source of truth)
import { RULE_PROFILES } from '../constants/profiles.js';
// --- Profile Imports ---
import * as profilesModule from '../profiles/index.js';
export function isValidProfile(profile) {
return RULE_PROFILES.includes(profile);
}
/**
* Get rule profile by name
* @param {string} name - Profile name
* @returns {Object|null} Profile object or null if not found
*/
export function getRulesProfile(name) {
if (!isValidProfile(name)) {
return null;
}
// Get the profile from the imported profiles module
const profileKey = `${name}Profile`;
const profile = profilesModule[profileKey];
if (!profile) {
throw new Error(
`Profile not found: static import missing for '${name}'. Valid profiles: ${RULE_PROFILES.join(', ')}`
);
}
return profile;
}
/**
* Replace basic Cursor terms with profile equivalents
*/
function replaceBasicTerms(content, conversionConfig) {
let result = content;
// Apply profile term replacements
conversionConfig.profileTerms.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
// Apply file extension replacements
conversionConfig.fileExtensions.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
return result;
}
/**
* Replace Cursor tool references with profile tool equivalents
*/
function replaceToolReferences(content, conversionConfig) {
let result = content;
// Basic pattern for direct tool name replacements
const toolNames = conversionConfig.toolNames;
const toolReferencePattern = new RegExp(
`\\b(${Object.keys(toolNames).join('|')})\\b`,
'g'
);
// Apply direct tool name replacements
result = result.replace(toolReferencePattern, (match, toolName) => {
return toolNames[toolName] || toolName;
});
// Apply contextual tool replacements
conversionConfig.toolContexts.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
// Apply tool group replacements
conversionConfig.toolGroups.forEach((pattern) => {
result = result.replace(pattern.from, pattern.to);
});
return result;
}
/**
* Update documentation URLs to point to profile documentation
*/
function updateDocReferences(content, conversionConfig) {
let result = content;
// Apply documentation URL replacements
conversionConfig.docUrls.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
return result;
}
/**
* Update file references in markdown links
*/
function updateFileReferences(content, conversionConfig) {
const { pathPattern, replacement } = conversionConfig.fileReferences;
return content.replace(pathPattern, replacement);
}
/**
* Transform rule content to profile-specific rules
* @param {string} content - The content to transform
* @param {Object} conversionConfig - The conversion configuration
* @param {Object} globalReplacements - Global text replacements
* @returns {string} - The transformed content
*/
function transformRuleContent(content, conversionConfig, globalReplacements) {
let result = content;
// Apply all transformations in appropriate order
result = updateFileReferences(result, conversionConfig);
result = replaceBasicTerms(result, conversionConfig);
result = replaceToolReferences(result, conversionConfig);
result = updateDocReferences(result, conversionConfig);
// Apply any global/catch-all replacements from the profile
// Super aggressive failsafe pass to catch any variations we might have missed
// This ensures critical transformations are applied even in contexts we didn't anticipate
globalReplacements.forEach((pattern) => {
if (typeof pattern.to === 'function') {
result = result.replace(pattern.from, pattern.to);
} else {
result = result.replace(pattern.from, pattern.to);
}
});
return result;
}
/**
* Convert a Cursor rule file to a profile-specific rule file
* @param {string} sourcePath - Path to the source .mdc file
* @param {string} targetPath - Path to the target file
* @param {Object} profile - The profile configuration
* @returns {boolean} - Success status
*/
export function convertRuleToProfileRule(sourcePath, targetPath, profile) {
const { conversionConfig, globalReplacements } = profile;
try {
// Read source content
const content = fs.readFileSync(sourcePath, 'utf8');
// Transform content
const transformedContent = transformRuleContent(
content,
conversionConfig,
globalReplacements
);
// Ensure target directory exists
const targetDir = path.dirname(targetPath);
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Write transformed content
fs.writeFileSync(targetPath, transformedContent);
return true;
} catch (error) {
console.error(`Error converting rule file: ${error.message}`);
return false;
}
}
/**
* Convert all Cursor rules to profile rules for a specific profile
*/
export function convertAllRulesToProfileRules(projectDir, profile) {
// Handle simple profiles (Claude, Codex) that just copy files to root
const isSimpleProfile = Object.keys(profile.fileMap).length === 0;
if (isSimpleProfile) {
// For simple profiles, just call their post-processing hook and return
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const assetsDir = path.join(__dirname, '..', '..', 'assets');
if (typeof profile.onPostConvertRulesProfile === 'function') {
profile.onPostConvertRulesProfile(projectDir, assetsDir);
}
return { success: 1, failed: 0 };
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const sourceDir = path.join(__dirname, '..', '..', 'assets', 'rules');
const targetDir = path.join(projectDir, profile.rulesDir);
// Ensure target directory exists
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Setup MCP configuration if enabled
if (profile.mcpConfig !== false) {
setupMCPConfiguration(projectDir, profile.mcpConfigPath);
}
let success = 0;
let failed = 0;
// Use fileMap to determine which files to copy
const sourceFiles = Object.keys(profile.fileMap);
for (const sourceFile of sourceFiles) {
try {
const sourcePath = path.join(sourceDir, sourceFile);
// Check if source file exists
if (!fs.existsSync(sourcePath)) {
log(
'warn',
`[Rule Transformer] Source file not found: ${sourceFile}, skipping`
);
continue;
}
const targetFilename = profile.fileMap[sourceFile];
const targetPath = path.join(targetDir, targetFilename);
// Ensure target subdirectory exists (for rules like taskmaster/dev_workflow.md)
const targetFileDir = path.dirname(targetPath);
if (!fs.existsSync(targetFileDir)) {
fs.mkdirSync(targetFileDir, { recursive: true });
}
// Read source content
let content = fs.readFileSync(sourcePath, 'utf8');
// Apply transformations
content = transformRuleContent(
content,
profile.conversionConfig,
profile.globalReplacements
);
// Write to target
fs.writeFileSync(targetPath, content, 'utf8');
success++;
log(
'debug',
`[Rule Transformer] Converted ${sourceFile} -> ${targetFilename} for ${profile.profileName}`
);
} catch (error) {
failed++;
log(
'error',
`[Rule Transformer] Failed to convert ${sourceFile} for ${profile.profileName}: ${error.message}`
);
}
}
// Call post-processing hook if defined (e.g., for Roo's rules-*mode* folders)
if (typeof profile.onPostConvertRulesProfile === 'function') {
const assetsDir = path.join(__dirname, '..', '..', 'assets');
profile.onPostConvertRulesProfile(projectDir, assetsDir);
}
return { success, failed };
}
/**
* Remove only Task Master specific files from a profile, leaving other existing rules intact
* @param {string} projectDir - Target project directory
* @param {Object} profile - Profile configuration
* @returns {Object} Result object
*/
export function removeProfileRules(projectDir, profile) {
const targetDir = path.join(projectDir, profile.rulesDir);
const profileDir = path.join(projectDir, profile.profileDir);
const result = {
profileName: profile.profileName,
success: false,
skipped: false,
error: null,
filesRemoved: [],
mcpResult: null,
profileDirRemoved: false,
notice: null
};
try {
// Handle simple profiles (Claude, Codex) that just copy files to root
const isSimpleProfile = Object.keys(profile.fileMap).length === 0;
if (isSimpleProfile) {
// For simple profiles, just call their removal hook and return
if (typeof profile.onRemoveRulesProfile === 'function') {
profile.onRemoveRulesProfile(projectDir);
}
result.success = true;
log(
'debug',
`[Rule Transformer] Successfully removed ${profile.profileName} files from ${projectDir}`
);
return result;
}
// Check if profile directory exists at all (for full profiles)
if (!fs.existsSync(profileDir)) {
result.success = true;
result.skipped = true;
log(
'debug',
`[Rule Transformer] Profile directory does not exist: ${profileDir}`
);
return result;
}
// 1. Remove only Task Master specific files from the rules directory
let hasOtherRulesFiles = false;
if (fs.existsSync(targetDir)) {
const taskmasterFiles = Object.values(profile.fileMap);
const removedFiles = [];
// Helper function to recursively check and remove Task Master files
function processDirectory(dirPath, relativePath = '') {
const items = fs.readdirSync(dirPath);
for (const item of items) {
const itemPath = path.join(dirPath, item);
const relativeItemPath = relativePath
? path.join(relativePath, item)
: item;
const stat = fs.statSync(itemPath);
if (stat.isDirectory()) {
// Recursively process subdirectory
processDirectory(itemPath, relativeItemPath);
// Check if directory is empty after processing and remove if so
try {
const remainingItems = fs.readdirSync(itemPath);
if (remainingItems.length === 0) {
fs.rmSync(itemPath, { recursive: true, force: true });
log(
'debug',
`[Rule Transformer] Removed empty directory: ${relativeItemPath}`
);
}
} catch (error) {
// Directory might have been removed already, ignore
}
} else if (stat.isFile()) {
if (taskmasterFiles.includes(relativeItemPath)) {
// This is a Task Master file, remove it
fs.rmSync(itemPath, { force: true });
removedFiles.push(relativeItemPath);
log(
'debug',
`[Rule Transformer] Removed Task Master file: ${relativeItemPath}`
);
} else {
// This is not a Task Master file, leave it
hasOtherRulesFiles = true;
log(
'debug',
`[Rule Transformer] Preserved existing file: ${relativeItemPath}`
);
}
}
}
}
// Process the rules directory recursively
processDirectory(targetDir);
result.filesRemoved = removedFiles;
// Only remove the rules directory if it's empty after removing Task Master files
const remainingFiles = fs.readdirSync(targetDir);
if (remainingFiles.length === 0) {
fs.rmSync(targetDir, { recursive: true, force: true });
log(
'debug',
`[Rule Transformer] Removed empty rules directory: ${targetDir}`
);
} else if (hasOtherRulesFiles) {
result.notice = `Preserved ${remainingFiles.length} existing rule files in ${profile.rulesDir}`;
log('info', `[Rule Transformer] ${result.notice}`);
}
}
// 2. Handle MCP configuration - only remove Task Master, preserve other servers
if (profile.mcpConfig !== false) {
result.mcpResult = removeTaskMasterMCPConfiguration(
projectDir,
profile.mcpConfigPath
);
if (result.mcpResult.hasOtherServers) {
if (!result.notice) {
result.notice = 'Preserved other MCP server configurations';
} else {
result.notice += '; preserved other MCP server configurations';
}
}
}
// 3. Call removal hook if defined (e.g., Roo's custom cleanup)
if (typeof profile.onRemoveRulesProfile === 'function') {
profile.onRemoveRulesProfile(projectDir);
}
// 4. Only remove profile directory if:
// - It's completely empty after all operations, AND
// - All rules removed were Task Master rules (no existing rules preserved), AND
// - MCP config was completely deleted (not just Task Master removed), AND
// - No other files or folders exist in the profile directory
if (fs.existsSync(profileDir)) {
const remaining = fs.readdirSync(profileDir);
const allRulesWereTaskMaster = !hasOtherRulesFiles;
const mcpConfigCompletelyDeleted = result.mcpResult?.deleted === true;
// Check if there are any other files or folders beyond what we expect
const hasOtherFilesOrFolders = remaining.length > 0;
if (
remaining.length === 0 &&
allRulesWereTaskMaster &&
(profile.mcpConfig === false || mcpConfigCompletelyDeleted) &&
!hasOtherFilesOrFolders
) {
fs.rmSync(profileDir, { recursive: true, force: true });
result.profileDirRemoved = true;
log(
'debug',
`[Rule Transformer] Removed profile directory: ${profileDir} (completely empty, all rules were Task Master rules, and MCP config was completely removed)`
);
} else {
// Determine what was preserved and why
const preservationReasons = [];
if (hasOtherFilesOrFolders) {
preservationReasons.push(
`${remaining.length} existing files/folders`
);
}
if (hasOtherRulesFiles) {
preservationReasons.push('existing rule files');
}
if (result.mcpResult?.hasOtherServers) {
preservationReasons.push('other MCP server configurations');
}
const preservationMessage = `Preserved ${preservationReasons.join(', ')} in ${profile.profileDir}`;
if (!result.notice) {
result.notice = preservationMessage;
} else if (!result.notice.includes('Preserved')) {
result.notice += `; ${preservationMessage.toLowerCase()}`;
}
log('info', `[Rule Transformer] ${preservationMessage}`);
}
}
result.success = true;
log(
'debug',
`[Rule Transformer] Successfully removed ${profile.profileName} Task Master files from ${projectDir}`
);
} catch (error) {
result.error = error.message;
log(
'error',
`[Rule Transformer] Failed to remove ${profile.profileName} rules: ${error.message}`
);
}
return result;
}

View File

@@ -333,8 +333,8 @@ log_step() {
log_step "Initializing Task Master project (non-interactive)"
task-master init -y --name="E2E Test $TIMESTAMP" --description="Automated E2E test run"
if [ ! -f ".taskmasterconfig" ]; then
log_error "Initialization failed: .taskmasterconfig not found."
if [ ! -f ".taskmaster/config.json" ]; then
log_error "Initialization failed: .taskmaster/config.json not found."
exit 1
fi
log_success "Project initialized."
@@ -344,8 +344,8 @@ log_step() {
exit_status_prd=$?
echo "$cmd_output_prd"
extract_and_sum_cost "$cmd_output_prd"
if [ $exit_status_prd -ne 0 ] || [ ! -s "tasks/tasks.json" ]; then
log_error "Parsing PRD failed: tasks/tasks.json not found or is empty. Exit status: $exit_status_prd"
if [ $exit_status_prd -ne 0 ] || [ ! -s ".taskmaster/tasks/tasks.json" ]; then
log_error "Parsing PRD failed: .taskmaster/tasks/tasks.json not found or is empty. Exit status: $exit_status_prd"
exit 1
else
log_success "PRD parsed successfully."
@@ -386,6 +386,95 @@ log_step() {
task-master list --with-subtasks > task_list_after_changes.log
log_success "Task list after changes saved to task_list_after_changes.log"
# === Start New Test Section: Tag-Aware Expand Testing ===
log_step "Creating additional tag for expand testing"
task-master add-tag feature-expand --description="Tag for testing expand command with tag preservation"
log_success "Created feature-expand tag."
log_step "Adding task to feature-expand tag"
task-master add-task --tag=feature-expand --prompt="Test task for tag-aware expansion" --priority=medium
# Get the new task ID dynamically
new_expand_task_id=$(jq -r '.["feature-expand"].tasks[-1].id' .taskmaster/tasks/tasks.json)
log_success "Added task $new_expand_task_id to feature-expand tag."
log_step "Verifying tags exist before expand test"
task-master tags > tags_before_expand.log
tag_count_before=$(jq 'keys | length' .taskmaster/tasks/tasks.json)
log_success "Tag count before expand: $tag_count_before"
log_step "Expanding task in feature-expand tag (testing tag corruption fix)"
cmd_output_expand_tagged=$(task-master expand --tag=feature-expand --id="$new_expand_task_id" 2>&1)
exit_status_expand_tagged=$?
echo "$cmd_output_expand_tagged"
extract_and_sum_cost "$cmd_output_expand_tagged"
if [ $exit_status_expand_tagged -ne 0 ]; then
log_error "Tagged expand failed. Exit status: $exit_status_expand_tagged"
else
log_success "Tagged expand completed."
fi
log_step "Verifying tag preservation after expand"
task-master tags > tags_after_expand.log
tag_count_after=$(jq 'keys | length' .taskmaster/tasks/tasks.json)
if [ "$tag_count_before" -eq "$tag_count_after" ]; then
log_success "Tag count preserved: $tag_count_after (no corruption detected)"
else
log_error "Tag corruption detected! Before: $tag_count_before, After: $tag_count_after"
fi
log_step "Verifying master tag still exists and has tasks"
master_task_count=$(jq -r '.master.tasks | length' .taskmaster/tasks/tasks.json 2>/dev/null || echo "0")
if [ "$master_task_count" -gt "0" ]; then
log_success "Master tag preserved with $master_task_count tasks"
else
log_error "Master tag corrupted or empty after tagged expand"
fi
log_step "Verifying feature-expand tag has expanded subtasks"
expanded_subtask_count=$(jq -r ".\"feature-expand\".tasks[] | select(.id == $new_expand_task_id) | .subtasks | length" .taskmaster/tasks/tasks.json 2>/dev/null || echo "0")
if [ "$expanded_subtask_count" -gt "0" ]; then
log_success "Expand successful: $expanded_subtask_count subtasks created in feature-expand tag"
else
log_error "Expand failed: No subtasks found in feature-expand tag"
fi
log_step "Testing force expand with tag preservation"
cmd_output_force_expand=$(task-master expand --tag=feature-expand --id="$new_expand_task_id" --force 2>&1)
exit_status_force_expand=$?
echo "$cmd_output_force_expand"
extract_and_sum_cost "$cmd_output_force_expand"
# Verify tags still preserved after force expand
tag_count_after_force=$(jq 'keys | length' .taskmaster/tasks/tasks.json)
if [ "$tag_count_before" -eq "$tag_count_after_force" ]; then
log_success "Force expand preserved all tags"
else
log_error "Force expand caused tag corruption"
fi
log_step "Testing expand --all with tag preservation"
# Add another task to feature-expand for expand-all testing
task-master add-task --tag=feature-expand --prompt="Second task for expand-all testing" --priority=low
second_expand_task_id=$(jq -r '.["feature-expand"].tasks[-1].id' .taskmaster/tasks/tasks.json)
cmd_output_expand_all=$(task-master expand --tag=feature-expand --all 2>&1)
exit_status_expand_all=$?
echo "$cmd_output_expand_all"
extract_and_sum_cost "$cmd_output_expand_all"
# Verify tags preserved after expand-all
tag_count_after_all=$(jq 'keys | length' .taskmaster/tasks/tasks.json)
if [ "$tag_count_before" -eq "$tag_count_after_all" ]; then
log_success "Expand --all preserved all tags"
else
log_error "Expand --all caused tag corruption"
fi
log_success "Completed expand --all tag preservation test."
# === End New Test Section: Tag-Aware Expand Testing ===
# === Test Model Commands ===
log_step "Checking initial model configuration"
task-master models > models_initial_config.log
@@ -626,7 +715,7 @@ log_step() {
# Find the next available task ID dynamically instead of hardcoding 11, 12
# Assuming tasks are added sequentially and we didn't remove any core tasks yet
last_task_id=$(jq '[.tasks[].id] | max' tasks/tasks.json)
last_task_id=$(jq '[.master.tasks[].id] | max' .taskmaster/tasks/tasks.json)
manual_task_id=$((last_task_id + 1))
ai_task_id=$((manual_task_id + 1))
@@ -747,30 +836,30 @@ log_step() {
task-master list --with-subtasks > task_list_after_clear_all.log
log_success "Task list after clear-all saved. (Manual/LLM check recommended to verify subtasks removed)"
log_step "Expanding Task 1 again (to have subtasks for next test)"
task-master expand --id=1
log_success "Attempted to expand Task 1 again."
# Verify 1.1 exists again
if ! jq -e '.tasks[] | select(.id == 1) | .subtasks[] | select(.id == 1)' tasks/tasks.json > /dev/null; then
log_error "Subtask 1.1 not found in tasks.json after re-expanding Task 1."
log_step "Expanding Task 3 again (to have subtasks for next test)"
task-master expand --id=3
log_success "Attempted to expand Task 3."
# Verify 3.1 exists
if ! jq -e '.master.tasks[] | select(.id == 3) | .subtasks[] | select(.id == 1)' .taskmaster/tasks/tasks.json > /dev/null; then
log_error "Subtask 3.1 not found in tasks.json after expanding Task 3."
exit 1
fi
log_step "Adding dependency: Task 3 depends on Subtask 1.1"
task-master add-dependency --id=3 --depends-on=1.1
log_success "Added dependency 3 -> 1.1."
log_step "Adding dependency: Task 4 depends on Subtask 3.1"
task-master add-dependency --id=4 --depends-on=3.1
log_success "Added dependency 4 -> 3.1."
log_step "Showing Task 3 details (after adding subtask dependency)"
task-master show 3 > task_3_details_after_dep_add.log
log_success "Task 3 details saved. (Manual/LLM check recommended for dependency [1.1])"
log_step "Showing Task 4 details (after adding subtask dependency)"
task-master show 4 > task_4_details_after_dep_add.log
log_success "Task 4 details saved. (Manual/LLM check recommended for dependency [3.1])"
log_step "Removing dependency: Task 3 depends on Subtask 1.1"
task-master remove-dependency --id=3 --depends-on=1.1
log_success "Removed dependency 3 -> 1.1."
log_step "Removing dependency: Task 4 depends on Subtask 3.1"
task-master remove-dependency --id=4 --depends-on=3.1
log_success "Removed dependency 4 -> 3.1."
log_step "Showing Task 3 details (after removing subtask dependency)"
task-master show 3 > task_3_details_after_dep_remove.log
log_success "Task 3 details saved. (Manual/LLM check recommended to verify dependency removed)"
log_step "Showing Task 4 details (after removing subtask dependency)"
task-master show 4 > task_4_details_after_dep_remove.log
log_success "Task 4 details saved. (Manual/LLM check recommended to verify dependency removed)"
# === End New Test Section ===

View File

@@ -0,0 +1,95 @@
import { jest } from '@jest/globals';
// Mock the base provider to avoid circular dependencies
jest.unstable_mockModule('../../src/ai-providers/base-provider.js', () => ({
BaseAIProvider: class {
constructor() {
this.name = 'Base Provider';
}
handleError(context, error) {
throw error;
}
}
}));
// Mock the claude-code SDK to simulate it not being installed
jest.unstable_mockModule('@anthropic-ai/claude-code', () => {
throw new Error("Cannot find module '@anthropic-ai/claude-code'");
});
// Import after mocking
const { ClaudeCodeProvider } = await import(
'../../src/ai-providers/claude-code.js'
);
describe('Claude Code Optional Dependency Integration', () => {
describe('when @anthropic-ai/claude-code is not installed', () => {
it('should allow provider instantiation', () => {
// Provider should instantiate without error
const provider = new ClaudeCodeProvider();
expect(provider).toBeDefined();
expect(provider.name).toBe('Claude Code');
});
it('should allow client creation', () => {
const provider = new ClaudeCodeProvider();
// Client creation should work
const client = provider.getClient({});
expect(client).toBeDefined();
expect(typeof client).toBe('function');
});
it('should fail with clear error when trying to use the model', async () => {
const provider = new ClaudeCodeProvider();
const client = provider.getClient({});
const model = client('opus');
// The actual usage should fail with the lazy loading error
await expect(
model.doGenerate({
prompt: [{ role: 'user', content: 'Hello' }],
mode: { type: 'regular' }
})
).rejects.toThrow(
"Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider."
);
});
it('should provide helpful error message for streaming', async () => {
const provider = new ClaudeCodeProvider();
const client = provider.getClient({});
const model = client('sonnet');
await expect(
model.doStream({
prompt: [{ role: 'user', content: 'Hello' }],
mode: { type: 'regular' }
})
).rejects.toThrow(
"Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider."
);
});
});
describe('provider behavior', () => {
it('should not require API key', () => {
const provider = new ClaudeCodeProvider();
// Should not throw
expect(() => provider.validateAuth()).not.toThrow();
expect(() => provider.validateAuth({ apiKey: null })).not.toThrow();
});
it('should work with ai-services-unified when provider is configured', async () => {
// This tests that the provider can be selected but will fail appropriately
// when the actual model is used
const provider = new ClaudeCodeProvider();
expect(provider).toBeDefined();
// In real usage, ai-services-unified would:
// 1. Get the provider instance (works)
// 2. Call provider.getClient() (works)
// 3. Create a model (works)
// 4. Try to generate (fails with clear error)
});
});
});

View File

@@ -625,19 +625,38 @@ describe('MCP Server Direct Functions', () => {
// For successful cases, record that functions were called but don't make real calls
mockEnableSilentMode();
// Mock expandAllTasks
// Mock expandAllTasks - now returns a structured object instead of undefined
const mockExpandAll = jest.fn().mockImplementation(async () => {
// Just simulate success without any real operations
return undefined; // expandAllTasks doesn't return anything
// Return the new structured response that matches the actual implementation
return {
success: true,
expandedCount: 2,
failedCount: 0,
skippedCount: 1,
tasksToExpand: 3,
telemetryData: {
timestamp: new Date().toISOString(),
commandName: 'expand-all-tasks',
totalCost: 0.05,
totalTokens: 1000,
inputTokens: 600,
outputTokens: 400
}
};
});
// Call mock expandAllTasks
await mockExpandAll(
args.num,
args.research || false,
args.prompt || '',
args.force || false,
{ mcpLog: mockLogger, session: options.session }
// Call mock expandAllTasks with the correct signature
const result = await mockExpandAll(
args.file, // tasksPath
args.num, // numSubtasks
args.research || false, // useResearch
args.prompt || '', // additionalContext
args.force || false, // force
{
mcpLog: mockLogger,
session: options.session,
projectRoot: args.projectRoot
}
);
mockDisableSilentMode();
@@ -645,13 +664,14 @@ describe('MCP Server Direct Functions', () => {
return {
success: true,
data: {
message: 'Successfully expanded all pending tasks with subtasks',
message: `Expand all operation completed. Expanded: ${result.expandedCount}, Failed: ${result.failedCount}, Skipped: ${result.skippedCount}`,
details: {
numSubtasks: args.num,
research: args.research || false,
prompt: args.prompt || '',
force: args.force || false
}
expandedCount: result.expandedCount,
failedCount: result.failedCount,
skippedCount: result.skippedCount,
tasksToExpand: result.tasksToExpand
},
telemetryData: result.telemetryData
}
};
}
@@ -671,10 +691,13 @@ describe('MCP Server Direct Functions', () => {
// 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(result.data.message).toMatch(/Expand all operation completed/);
expect(result.data.details.expandedCount).toBe(2);
expect(result.data.details.failedCount).toBe(0);
expect(result.data.details.skippedCount).toBe(1);
expect(result.data.details.tasksToExpand).toBe(3);
expect(result.data.telemetryData).toBeDefined();
expect(result.data.telemetryData.commandName).toBe('expand-all-tasks');
expect(mockEnableSilentMode).toHaveBeenCalled();
expect(mockDisableSilentMode).toHaveBeenCalled();
});
@@ -695,7 +718,8 @@ describe('MCP Server Direct Functions', () => {
// Assert
expect(result.success).toBe(true);
expect(result.data.details.research).toBe(true);
expect(result.data.details.expandedCount).toBe(2);
expect(result.data.telemetryData).toBeDefined();
expect(mockEnableSilentMode).toHaveBeenCalled();
expect(mockDisableSilentMode).toHaveBeenCalled();
});
@@ -715,7 +739,8 @@ describe('MCP Server Direct Functions', () => {
// Assert
expect(result.success).toBe(true);
expect(result.data.details.force).toBe(true);
expect(result.data.details.expandedCount).toBe(2);
expect(result.data.telemetryData).toBeDefined();
expect(mockEnableSilentMode).toHaveBeenCalled();
expect(mockDisableSilentMode).toHaveBeenCalled();
});
@@ -735,11 +760,77 @@ describe('MCP Server Direct Functions', () => {
// Assert
expect(result.success).toBe(true);
expect(result.data.details.prompt).toBe(
'Additional context for subtasks'
);
expect(result.data.details.expandedCount).toBe(2);
expect(result.data.telemetryData).toBeDefined();
expect(mockEnableSilentMode).toHaveBeenCalled();
expect(mockDisableSilentMode).toHaveBeenCalled();
});
test('should handle case with no eligible tasks', async () => {
// Arrange
const args = {
projectRoot: testProjectRoot,
file: testTasksPath,
num: 3
};
// Act - Mock the scenario where no tasks are eligible for expansion
async function testNoEligibleTasks(args, mockLogger, options = {}) {
mockEnableSilentMode();
const mockExpandAll = jest.fn().mockImplementation(async () => {
return {
success: true,
expandedCount: 0,
failedCount: 0,
skippedCount: 0,
tasksToExpand: 0,
telemetryData: null,
message: 'No tasks eligible for expansion.'
};
});
const result = await mockExpandAll(
args.file,
args.num,
false,
'',
false,
{
mcpLog: mockLogger,
session: options.session,
projectRoot: args.projectRoot
},
'json'
);
mockDisableSilentMode();
return {
success: true,
data: {
message: result.message,
details: {
expandedCount: result.expandedCount,
failedCount: result.failedCount,
skippedCount: result.skippedCount,
tasksToExpand: result.tasksToExpand
},
telemetryData: result.telemetryData
}
};
}
const result = await testNoEligibleTasks(args, mockLogger, {
session: mockSession
});
// Assert
expect(result.success).toBe(true);
expect(result.data.message).toBe('No tasks eligible for expansion.');
expect(result.data.details.expandedCount).toBe(0);
expect(result.data.details.tasksToExpand).toBe(0);
expect(result.data.telemetryData).toBeNull();
});
});
});

View File

@@ -0,0 +1,55 @@
import fs from 'fs';
import path from 'path';
describe('Claude Profile Initialization Functionality', () => {
let claudeProfileContent;
beforeAll(() => {
const claudeJsPath = path.join(
process.cwd(),
'src',
'profiles',
'claude.js'
);
claudeProfileContent = fs.readFileSync(claudeJsPath, 'utf8');
});
test('claude.js is a simple profile with correct configuration', () => {
expect(claudeProfileContent).toContain("profileName: 'claude'");
expect(claudeProfileContent).toContain("displayName: 'Claude Code'");
expect(claudeProfileContent).toContain("profileDir: '.'");
expect(claudeProfileContent).toContain("rulesDir: '.'");
});
test('claude.js has no MCP configuration', () => {
expect(claudeProfileContent).toContain('mcpConfig: false');
expect(claudeProfileContent).toContain('mcpConfigName: null');
expect(claudeProfileContent).toContain('mcpConfigPath: null');
});
test('claude.js has empty file map (simple profile)', () => {
expect(claudeProfileContent).toContain('fileMap: {}');
expect(claudeProfileContent).toContain('conversionConfig: {}');
expect(claudeProfileContent).toContain('globalReplacements: []');
});
test('claude.js has lifecycle functions for file management', () => {
expect(claudeProfileContent).toContain('function onAddRulesProfile');
expect(claudeProfileContent).toContain('function onRemoveRulesProfile');
expect(claudeProfileContent).toContain(
'function onPostConvertRulesProfile'
);
});
test('claude.js copies AGENTS.md to CLAUDE.md', () => {
expect(claudeProfileContent).toContain("'AGENTS.md'");
expect(claudeProfileContent).toContain("'CLAUDE.md'");
expect(claudeProfileContent).toContain('copyFileSync');
});
test('claude.js has proper error handling', () => {
expect(claudeProfileContent).toContain('try {');
expect(claudeProfileContent).toContain('} catch (err) {');
expect(claudeProfileContent).toContain("log('error'");
});
});

View File

@@ -0,0 +1,53 @@
import fs from 'fs';
import path from 'path';
describe('Cline Profile Initialization Functionality', () => {
let clineProfileContent;
beforeAll(() => {
const clineJsPath = path.join(process.cwd(), 'src', 'profiles', 'cline.js');
clineProfileContent = fs.readFileSync(clineJsPath, 'utf8');
});
test('cline.js uses factory pattern with correct configuration', () => {
expect(clineProfileContent).toContain("name: 'cline'");
expect(clineProfileContent).toContain("displayName: 'Cline'");
expect(clineProfileContent).toContain("rulesDir: '.clinerules'");
expect(clineProfileContent).toContain("profileDir: '.clinerules'");
});
test('cline.js configures .mdc to .md extension mapping', () => {
expect(clineProfileContent).toContain("fileExtension: '.mdc'");
expect(clineProfileContent).toContain("targetExtension: '.md'");
});
test('cline.js uses standard tool mappings', () => {
expect(clineProfileContent).toContain('COMMON_TOOL_MAPPINGS.STANDARD');
// Should contain comment about standard tool names
expect(clineProfileContent).toContain('standard tool names');
});
test('cline.js contains correct URL configuration', () => {
expect(clineProfileContent).toContain("url: 'cline.bot'");
expect(clineProfileContent).toContain("docsUrl: 'docs.cline.bot'");
});
test('cline.js has MCP configuration disabled', () => {
expect(clineProfileContent).toContain('mcpConfig: false');
expect(clineProfileContent).toContain(
"mcpConfigName: 'cline_mcp_settings.json'"
);
});
test('cline.js has custom file mapping for cursor_rules.mdc', () => {
expect(clineProfileContent).toContain('customFileMap:');
expect(clineProfileContent).toContain(
"'cursor_rules.mdc': 'cline_rules.md'"
);
});
test('cline.js uses createProfile factory function', () => {
expect(clineProfileContent).toContain('createProfile');
expect(clineProfileContent).toContain('export const clineProfile');
});
});

View File

@@ -0,0 +1,54 @@
import fs from 'fs';
import path from 'path';
describe('Codex Profile Initialization Functionality', () => {
let codexProfileContent;
beforeAll(() => {
const codexJsPath = path.join(process.cwd(), 'src', 'profiles', 'codex.js');
codexProfileContent = fs.readFileSync(codexJsPath, 'utf8');
});
test('codex.js is a simple profile with correct configuration', () => {
expect(codexProfileContent).toContain("profileName: 'codex'");
expect(codexProfileContent).toContain("displayName: 'Codex'");
expect(codexProfileContent).toContain("profileDir: '.'");
expect(codexProfileContent).toContain("rulesDir: '.'");
});
test('codex.js has no MCP configuration', () => {
expect(codexProfileContent).toContain('mcpConfig: false');
expect(codexProfileContent).toContain('mcpConfigName: null');
expect(codexProfileContent).toContain('mcpConfigPath: null');
});
test('codex.js has empty file map (simple profile)', () => {
expect(codexProfileContent).toContain('fileMap: {}');
expect(codexProfileContent).toContain('conversionConfig: {}');
expect(codexProfileContent).toContain('globalReplacements: []');
});
test('codex.js has lifecycle functions for file management', () => {
expect(codexProfileContent).toContain('function onAddRulesProfile');
expect(codexProfileContent).toContain('function onRemoveRulesProfile');
expect(codexProfileContent).toContain('function onPostConvertRulesProfile');
});
test('codex.js copies AGENTS.md to AGENTS.md (same filename)', () => {
expect(codexProfileContent).toContain("'AGENTS.md'");
expect(codexProfileContent).toContain('copyFileSync');
// Should copy to the same filename (AGENTS.md)
expect(codexProfileContent).toMatch(/destFile.*AGENTS\.md/);
});
test('codex.js has proper error handling', () => {
expect(codexProfileContent).toContain('try {');
expect(codexProfileContent).toContain('} catch (err) {');
expect(codexProfileContent).toContain("log('error'");
});
test('codex.js removes AGENTS.md on profile removal', () => {
expect(codexProfileContent).toContain('rmSync');
expect(codexProfileContent).toContain('force: true');
});
});

View File

@@ -0,0 +1,44 @@
import fs from 'fs';
import path from 'path';
describe('Cursor Profile Initialization Functionality', () => {
let cursorProfileContent;
beforeAll(() => {
const cursorJsPath = path.join(
process.cwd(),
'src',
'profiles',
'cursor.js'
);
cursorProfileContent = fs.readFileSync(cursorJsPath, 'utf8');
});
test('cursor.js uses factory pattern with correct configuration', () => {
expect(cursorProfileContent).toContain("name: 'cursor'");
expect(cursorProfileContent).toContain("displayName: 'Cursor'");
expect(cursorProfileContent).toContain("rulesDir: '.cursor/rules'");
expect(cursorProfileContent).toContain("profileDir: '.cursor'");
});
test('cursor.js preserves .mdc extension in both input and output', () => {
expect(cursorProfileContent).toContain("fileExtension: '.mdc'");
expect(cursorProfileContent).toContain("targetExtension: '.mdc'");
// Should preserve cursor_rules.mdc filename
expect(cursorProfileContent).toContain(
"'cursor_rules.mdc': 'cursor_rules.mdc'"
);
});
test('cursor.js uses standard tool mappings (no tool renaming)', () => {
expect(cursorProfileContent).toContain('COMMON_TOOL_MAPPINGS.STANDARD');
// Should not contain custom tool mappings since cursor keeps original names
expect(cursorProfileContent).not.toContain('edit_file');
expect(cursorProfileContent).not.toContain('apply_diff');
});
test('cursor.js contains correct URL configuration', () => {
expect(cursorProfileContent).toContain("url: 'cursor.so'");
expect(cursorProfileContent).toContain("docsUrl: 'docs.cursor.com'");
});
});

View File

@@ -0,0 +1,112 @@
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { execSync } from 'child_process';
describe('Roo Files Inclusion in Package', () => {
// This test verifies that the required Roo files are included in the final package
test('package.json includes assets/** in the "files" array for Roo source files', () => {
// Read the package.json file
const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Check if assets/** is included in the files array (which contains Roo files)
expect(packageJson.files).toContain('assets/**');
});
test('roo.js profile contains logic for Roo directory creation and file copying', () => {
// Read the roo.js profile file
const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js');
const rooJsContent = fs.readFileSync(rooJsPath, 'utf8');
// Check for the main handler function
expect(
rooJsContent.includes('onAddRulesProfile(targetDir, assetsDir)')
).toBe(true);
// Check for general recursive copy of assets/roocode
expect(
rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)')
).toBe(true);
// Check for updated path handling
expect(rooJsContent.includes("path.join(assetsDir, 'roocode')")).toBe(true);
// Check for .roomodes file copying logic (source and destination paths)
expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe(
true
);
expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe(
true
);
// Check for mode-specific rule file copying logic
expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true);
expect(
rooJsContent.includes(
'path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)'
)
).toBe(true);
expect(
rooJsContent.includes(
"path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)"
)
).toBe(true);
// Check for import of ROO_MODES from profiles.js instead of local definition
expect(
rooJsContent.includes(
"import { ROO_MODES } from '../constants/profiles.js'"
)
).toBe(true);
// Verify ROO_MODES is used in the for loop
expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true);
// Verify mode variable is used in the template strings (this confirms modes are being processed)
expect(rooJsContent.includes('rules-${mode}')).toBe(true);
expect(rooJsContent.includes('${mode}-rules')).toBe(true);
// Verify that the ROO_MODES constant is properly imported and used
// We should be able to find the template literals that use the mode variable
expect(rooJsContent.includes('`rules-${mode}`')).toBe(true);
expect(rooJsContent.includes('`${mode}-rules`')).toBe(true);
expect(rooJsContent.includes('Copied ${mode}-rules to ${dest}')).toBe(true);
// Also verify that the expected mode names are defined in the imported constant
// by checking that the import is from the correct file that contains all 6 modes
const profilesConstantsPath = path.join(
process.cwd(),
'src',
'constants',
'profiles.js'
);
const profilesContent = fs.readFileSync(profilesConstantsPath, 'utf8');
// Check that ROO_MODES is exported and contains all expected modes
expect(profilesContent.includes('export const ROO_MODES')).toBe(true);
const expectedModes = [
'architect',
'ask',
'orchestrator',
'code',
'debug',
'test'
];
expectedModes.forEach((mode) => {
expect(profilesContent.includes(`'${mode}'`)).toBe(true);
});
});
test('source Roo files exist in assets directory', () => {
// Verify that the source files for Roo integration exist
expect(
fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roo'))
).toBe(true);
expect(
fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roomodes'))
).toBe(true);
});
});

View File

@@ -0,0 +1,70 @@
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
describe('Roo Profile Initialization Functionality', () => {
let rooProfileContent;
beforeAll(() => {
// Read the roo.js profile file content once for all tests
const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js');
rooProfileContent = fs.readFileSync(rooJsPath, 'utf8');
});
test('roo.js profile ensures Roo directory structure via onAddRulesProfile', () => {
// Check if onAddRulesProfile function exists
expect(rooProfileContent).toContain(
'onAddRulesProfile(targetDir, assetsDir)'
);
// Check for the general copy of assets/roocode which includes .roo base structure
expect(rooProfileContent).toContain(
"const sourceDir = path.join(assetsDir, 'roocode');"
);
expect(rooProfileContent).toContain(
'copyRecursiveSync(sourceDir, targetDir);'
);
// Check for the specific .roo modes directory handling
expect(rooProfileContent).toContain(
"const rooModesDir = path.join(sourceDir, '.roo');"
);
// Check for import of ROO_MODES from profiles.js instead of local definition
expect(rooProfileContent).toContain(
"import { ROO_MODES } from '../constants/profiles.js';"
);
});
test('roo.js profile copies .roomodes file via onAddRulesProfile', () => {
expect(rooProfileContent).toContain(
'onAddRulesProfile(targetDir, assetsDir)'
);
// Check for the specific .roomodes copy logic
expect(rooProfileContent).toContain(
"const roomodesSrc = path.join(sourceDir, '.roomodes');"
);
expect(rooProfileContent).toContain(
"const roomodesDest = path.join(targetDir, '.roomodes');"
);
expect(rooProfileContent).toContain(
'fs.copyFileSync(roomodesSrc, roomodesDest);'
);
});
test('roo.js profile copies mode-specific rule files via onAddRulesProfile', () => {
expect(rooProfileContent).toContain(
'onAddRulesProfile(targetDir, assetsDir)'
);
expect(rooProfileContent).toContain('for (const mode of ROO_MODES)');
// Check for the specific mode rule file copy logic
expect(rooProfileContent).toContain(
'const src = path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`);'
);
expect(rooProfileContent).toContain(
"const dest = path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`);"
);
});
});

View File

@@ -0,0 +1,98 @@
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { execSync } from 'child_process';
describe('Rules Files Inclusion in Package', () => {
// This test verifies that the required rules files are included in the final package
test('package.json includes assets/** in the "files" array for rules source files', () => {
// Read the package.json file
const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Check if assets/** is included in the files array (which contains rules files)
expect(packageJson.files).toContain('assets/**');
});
test('source rules files exist in assets/rules directory', () => {
// Verify that the actual rules files exist
const rulesDir = path.join(process.cwd(), 'assets', 'rules');
expect(fs.existsSync(rulesDir)).toBe(true);
// Check for the 4 files that currently exist
const expectedFiles = [
'dev_workflow.mdc',
'taskmaster.mdc',
'self_improve.mdc',
'cursor_rules.mdc'
];
expectedFiles.forEach((file) => {
const filePath = path.join(rulesDir, file);
expect(fs.existsSync(filePath)).toBe(true);
});
});
test('roo.js profile contains logic for Roo directory creation and file copying', () => {
// Read the roo.js profile file
const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js');
const rooJsContent = fs.readFileSync(rooJsPath, 'utf8');
// Check for the main handler function
expect(
rooJsContent.includes('onAddRulesProfile(targetDir, assetsDir)')
).toBe(true);
// Check for general recursive copy of assets/roocode
expect(
rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)')
).toBe(true);
// Check for updated path handling
expect(rooJsContent.includes("path.join(assetsDir, 'roocode')")).toBe(true);
// Check for .roomodes file copying logic (source and destination paths)
expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe(
true
);
expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe(
true
);
// Check for mode-specific rule file copying logic
expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true);
expect(
rooJsContent.includes(
'path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)'
)
).toBe(true);
expect(
rooJsContent.includes(
"path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)"
)
).toBe(true);
// Check for import of ROO_MODES from profiles.js
expect(
rooJsContent.includes(
"import { ROO_MODES } from '../constants/profiles.js'"
)
).toBe(true);
// Verify mode variable is used in the template strings (this confirms modes are being processed)
expect(rooJsContent.includes('rules-${mode}')).toBe(true);
expect(rooJsContent.includes('${mode}-rules')).toBe(true);
});
test('source Roo files exist in assets directory', () => {
// Verify that the source files for Roo integration exist
expect(
fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roo'))
).toBe(true);
expect(
fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roomodes'))
).toBe(true);
});
});

View File

@@ -0,0 +1,41 @@
import fs from 'fs';
import path from 'path';
describe('Trae Profile Initialization Functionality', () => {
let traeProfileContent;
beforeAll(() => {
const traeJsPath = path.join(process.cwd(), 'src', 'profiles', 'trae.js');
traeProfileContent = fs.readFileSync(traeJsPath, 'utf8');
});
test('trae.js uses factory pattern with correct configuration', () => {
expect(traeProfileContent).toContain("name: 'trae'");
expect(traeProfileContent).toContain("displayName: 'Trae'");
expect(traeProfileContent).toContain("rulesDir: '.trae/rules'");
expect(traeProfileContent).toContain("profileDir: '.trae'");
});
test('trae.js configures .mdc to .md extension mapping', () => {
expect(traeProfileContent).toContain("fileExtension: '.mdc'");
expect(traeProfileContent).toContain("targetExtension: '.md'");
});
test('trae.js uses standard tool mappings', () => {
expect(traeProfileContent).toContain('COMMON_TOOL_MAPPINGS.STANDARD');
// Should contain comment about standard tool names
expect(traeProfileContent).toContain('standard tool names');
});
test('trae.js contains correct URL configuration', () => {
expect(traeProfileContent).toContain("url: 'trae.ai'");
expect(traeProfileContent).toContain("docsUrl: 'docs.trae.ai'");
});
test('trae.js has MCP configuration disabled', () => {
expect(traeProfileContent).toContain('mcpConfig: false');
expect(traeProfileContent).toContain(
"mcpConfigName: 'trae_mcp_settings.json'"
);
});
});

Some files were not shown because too many files have changed in this diff Show More