Compare commits

..

19 Commits

Author SHA1 Message Date
Ralph Khreish
b724048033 chore: remove all defaults 2025-07-16 19:52:41 +03:00
Ralph Khreish
68f759f215 chore: more coderabbit reviews 2025-07-16 19:26:25 +03:00
Ralph Khreish
2ac7f20601 chore: improve coderabbit config 2025-07-16 19:18:37 +03:00
Ralph Khreish
e91609a373 chore: fix coderabbit config 2025-07-16 19:06:37 +03:00
Ralph Khreish
9fea22cbc3 chore: add coderabbit configuration 2025-07-16 18:30:15 +03:00
Joe Danziger
6d05e8622c feat: Add Amp rule profile with AGENT.md and MCP config (#973)
* Amp profile + tests

* generatlize to Agent instead of Claude Code to support any agent

* add changeset

* unnecessary tab formatting

* fix exports

* fix formatting
2025-07-16 14:44:37 +02:00
github-actions[bot]
c3272736fb docs: Auto-update and format models.md 2025-07-16 12:42:21 +00:00
Ben Vargas
fedfd6a0f4 feat: complete Groq provider integration and add Kimi K2 model (#978)
* feat: complete Groq provider integration and add Kimi K2 model

- Add missing getRequiredApiKeyName() method to GroqProvider class
- Register GroqProvider in ai-services-unified.js PROVIDERS object
- Add Groq API key handling to config-manager.js (isApiKeySet and getMcpApiKeyStatus)
- Add GROQ_API_KEY to env.example with format hint
- Add moonshotai/kimi-k2-instruct model to Groq provider ($1/$3 per 1M tokens, 16k max)
- Fix import sorting for linting compliance
- Add GroqProvider mock to ai-services-unified tests

Fixes missing implementation pieces that prevented Groq provider from working.

* chore: improve changeset

---------

Co-authored-by: Ben Vargas <ben@example.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
2025-07-16 14:42:12 +02:00
Ben Vargas
ab2e946087 fix: show command no longer requires complexity report to exist (#979)
Co-authored-by: Ben Vargas <ben@example.com>
2025-07-16 09:24:06 +02:00
Joe Danziger
cc4fe205fb feat(profiles): Add MCP configuration to Claude Code rules (#980)
* add .mcp.json with claude profile

* add changeset

* update changeset

* update test
2025-07-16 09:07:33 +02:00
Ralph Khreish
36dc129328 chore: create extension scaffolding (#989)
* chore: create extension scaffolding

* chore: fix workspace for changeset

* chore: fix package-lock
2025-07-16 09:00:33 +02:00
Ralph Khreish
7b4803a479 fix: task master (tm) custom slash commands w/ proper syntax (#968)
* feat: add task master (tm) custom slash commands

Add comprehensive task management system integration via custom slash commands.
Includes commands for:
- Project initialization and setup
- Task parsing from PRD documents
- Task creation, update, and removal
- Subtask management
- Dependency tracking and validation
- Complexity analysis and task expansion
- Project status and reporting
- Workflow automation

This provides a complete task management workflow directly within Claude Code.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: add changeset

---------

Co-authored-by: neno-is-ooo <204701868+neno-is-ooo@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-14 15:49:16 +02:00
Ben Vargas
f662654afb fix: prevent CLAUDE.md overwrite by using imports (#949)
* fix: prevent CLAUDE.md overwrite by using imports

- Copy Task Master instructions to .taskmaster/CLAUDE.md
- Add import section to user's CLAUDE.md instead of overwriting
- Preserve existing user content
- Clean removal of Task Master content on uninstall

Closes #929

* chore: add changeset for Claude import fix
2025-07-14 10:29:36 +02:00
github-actions[bot]
a8e2d728c9 Version Packages 2025-07-14 11:28:48 +03:00
Ralph Khreish
baf9bd545a fix: add-task fixes (#960)
- remove task-generation
- fix mcp add-task generation in manual mode
2025-07-12 08:19:21 +02:00
Ralph Khreish
fbea48d8ec chore: fix configuration file 2025-07-12 00:54:16 +03:00
Ralph Khreish
d0fe7dc25a chore: add the deleted tasks.json back 2025-07-12 00:44:14 +03:00
Ralph Khreish
f380b8e86c chore: revert config.json to what it was initially 2025-07-12 00:42:52 +03:00
github-actions[bot]
bd89061a1d chore: rc version bump 2025-07-11 16:18:25 +00:00
87 changed files with 7400 additions and 363 deletions

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Recover from `@anthropic-ai/claude-code` JSON truncation bug that caused Task Master to crash when handling large (>8 kB) structured responses. The CLI/SDK still truncates, but Task Master now detects the error, preserves buffered text, and returns a usable response instead of throwing.

View File

@@ -0,0 +1,12 @@
---
"task-master-ai": patch
---
Prevent CLAUDE.md overwrite by using Claude Code's import feature
- Task Master now creates its instructions in `.taskmaster/CLAUDE.md` instead of overwriting the user's `CLAUDE.md`
- Adds an import section to the user's CLAUDE.md that references the Task Master instructions
- Preserves existing user content in CLAUDE.md files
- Provides clean uninstall that only removes Task Master's additions
**Breaking Change**: Task Master instructions for Claude Code are now stored in `.taskmaster/CLAUDE.md` and imported into the main CLAUDE.md file. Users who previously had Task Master content directly in their CLAUDE.md will need to run `task-master rules remove claude` followed by `task-master rules add claude` to migrate to the new structure.

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Updating dependency ai-sdk-provider-gemini-cli to 0.0.4 to address breaking change Google made to Gemini CLI and add better 'api-key' in addition to 'gemini-api-key' AI-SDK compatibility.

View File

@@ -0,0 +1,7 @@
---
"task-master-ai": patch
---
Fix: show command no longer requires complexity report file to exist
The `tm show` command was incorrectly requiring the complexity report file to exist even when not needed. Now it only validates the complexity report path when a custom report file is explicitly provided via the -r/--report option.

View File

@@ -1,9 +0,0 @@
---
"task-master-ai": minor
---
Add support for xAI Grok 4 model
- Add grok-4 model to xAI provider with $3/$15 per 1M token pricing
- Enable main, fallback, and research roles for grok-4
- Max tokens set to 131,072 (matching other xAI models)

View File

@@ -0,0 +1,10 @@
---
"task-master-ai": minor
---
Complete Groq provider integration and add MoonshotAI Kimi K2 model support
- Fixed Groq provider registration
- Added Groq API key validation
- Added GROQ_API_KEY to .env.example
- Added moonshotai/kimi-k2-instruct model with $1/$3 per 1M token pricing and 16k max output

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": minor
---
Add Amp rule profile with AGENT.md and MCP config

View File

@@ -1,8 +0,0 @@
---
"task-master-ai": minor
---
Add stricter validation and clearer feedback for task priority when adding new tasks
- if a task priority is invalid, it will default to medium
- made taks priority case-insensitive, essentially making HIGH and high the same value

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
Add support for MCP Sampling as AI provider, requires no API key, uses the client LLM provider

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": patch
---
Unify and streamline profile system architecture for improved maintainability

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Add MCP configuration support to Claude Code rules

View File

@@ -0,0 +1,7 @@
---
"task-master-ai": patch
---
Fixed the comprehensive taskmaster system integration via custom slash commands with proper syntax
- Provide claude clode with a complete set of of commands that can trigger task master events directly within Claude Code

View File

@@ -1,5 +0,0 @@
---
"task-master-ai": minor
---
Added Groq provider support

View File

@@ -1,130 +0,0 @@
# Task Master Command Reference
Comprehensive command structure for Task Master integration with Claude Code.
## Command Organization
Commands are organized hierarchically to match Task Master's CLI structure while providing enhanced Claude Code integration.
## Project Setup & Configuration
### `/project:tm/init`
- `index` - Initialize new project (handles PRD files intelligently)
- `quick` - Quick setup with auto-confirmation (-y flag)
### `/project:tm/models`
- `index` - View current AI model configuration
- `setup` - Interactive model configuration
- `set-main` - Set primary generation model
- `set-research` - Set research model
- `set-fallback` - Set fallback model
## Task Generation
### `/project:tm/parse-prd`
- `index` - Generate tasks from PRD document
- `with-research` - Enhanced parsing with research mode
### `/project:tm/generate`
- Create individual task files from tasks.json
## Task Management
### `/project:tm/list`
- `index` - Smart listing with natural language filters
- `with-subtasks` - Include subtasks in hierarchical view
- `by-status` - Filter by specific status
### `/project:tm/set-status`
- `to-pending` - Reset task to pending
- `to-in-progress` - Start working on task
- `to-done` - Mark task complete
- `to-review` - Submit for review
- `to-deferred` - Defer task
- `to-cancelled` - Cancel task
### `/project:tm/sync-readme`
- Export tasks to README.md with formatting
### `/project:tm/update`
- `index` - Update tasks with natural language
- `from-id` - Update multiple tasks from a starting point
- `single` - Update specific task
### `/project:tm/add-task`
- `index` - Add new task with AI assistance
### `/project:tm/remove-task`
- `index` - Remove task with confirmation
## Subtask Management
### `/project:tm/add-subtask`
- `index` - Add new subtask to parent
- `from-task` - Convert existing task to subtask
### `/project:tm/remove-subtask`
- Remove subtask (with optional conversion)
### `/project:tm/clear-subtasks`
- `index` - Clear subtasks from specific task
- `all` - Clear all subtasks globally
## Task Analysis & Breakdown
### `/project:tm/analyze-complexity`
- Analyze and generate expansion recommendations
### `/project:tm/complexity-report`
- Display complexity analysis report
### `/project:tm/expand`
- `index` - Break down specific task
- `all` - Expand all eligible tasks
- `with-research` - Enhanced expansion
## Task Navigation
### `/project:tm/next`
- Intelligent next task recommendation
### `/project:tm/show`
- Display detailed task information
### `/project:tm/status`
- Comprehensive project dashboard
## Dependency Management
### `/project:tm/add-dependency`
- Add task dependency
### `/project:tm/remove-dependency`
- Remove task dependency
### `/project:tm/validate-dependencies`
- Check for dependency issues
### `/project:tm/fix-dependencies`
- Automatically fix dependency problems
## Usage Patterns
### Natural Language
Most commands accept natural language arguments:
```
/project:tm/add-task create user authentication system
/project:tm/update mark all API tasks as high priority
/project:tm/list show blocked tasks
```
### ID-Based Commands
Commands requiring IDs intelligently parse from $ARGUMENTS:
```
/project:tm/show 45
/project:tm/expand 23
/project:tm/set-status/to-done 67
```
### Smart Defaults
Commands provide intelligent defaults and suggestions based on context.

View File

@@ -0,0 +1,146 @@
# Task Master Command Reference
Comprehensive command structure for Task Master integration with Claude Code.
## Command Organization
Commands are organized hierarchically to match Task Master's CLI structure while providing enhanced Claude Code integration.
## Project Setup & Configuration
### `/project:tm/init`
- `init-project` - Initialize new project (handles PRD files intelligently)
- `init-project-quick` - Quick setup with auto-confirmation (-y flag)
### `/project:tm/models`
- `view-models` - View current AI model configuration
- `setup-models` - Interactive model configuration
- `set-main` - Set primary generation model
- `set-research` - Set research model
- `set-fallback` - Set fallback model
## Task Generation
### `/project:tm/parse-prd`
- `parse-prd` - Generate tasks from PRD document
- `parse-prd-with-research` - Enhanced parsing with research mode
### `/project:tm/generate`
- `generate-tasks` - Create individual task files from tasks.json
## Task Management
### `/project:tm/list`
- `list-tasks` - Smart listing with natural language filters
- `list-tasks-with-subtasks` - Include subtasks in hierarchical view
- `list-tasks-by-status` - Filter by specific status
### `/project:tm/set-status`
- `to-pending` - Reset task to pending
- `to-in-progress` - Start working on task
- `to-done` - Mark task complete
- `to-review` - Submit for review
- `to-deferred` - Defer task
- `to-cancelled` - Cancel task
### `/project:tm/sync-readme`
- `sync-readme` - Export tasks to README.md with formatting
### `/project:tm/update`
- `update-task` - Update tasks with natural language
- `update-tasks-from-id` - Update multiple tasks from a starting point
- `update-single-task` - Update specific task
### `/project:tm/add-task`
- `add-task` - Add new task with AI assistance
### `/project:tm/remove-task`
- `remove-task` - Remove task with confirmation
## Subtask Management
### `/project:tm/add-subtask`
- `add-subtask` - Add new subtask to parent
- `convert-task-to-subtask` - Convert existing task to subtask
### `/project:tm/remove-subtask`
- `remove-subtask` - Remove subtask (with optional conversion)
### `/project:tm/clear-subtasks`
- `clear-subtasks` - Clear subtasks from specific task
- `clear-all-subtasks` - Clear all subtasks globally
## Task Analysis & Breakdown
### `/project:tm/analyze-complexity`
- `analyze-complexity` - Analyze and generate expansion recommendations
### `/project:tm/complexity-report`
- `complexity-report` - Display complexity analysis report
### `/project:tm/expand`
- `expand-task` - Break down specific task
- `expand-all-tasks` - Expand all eligible tasks
- `with-research` - Enhanced expansion
## Task Navigation
### `/project:tm/next`
- `next-task` - Intelligent next task recommendation
### `/project:tm/show`
- `show-task` - Display detailed task information
### `/project:tm/status`
- `project-status` - Comprehensive project dashboard
## Dependency Management
### `/project:tm/add-dependency`
- `add-dependency` - Add task dependency
### `/project:tm/remove-dependency`
- `remove-dependency` - Remove task dependency
### `/project:tm/validate-dependencies`
- `validate-dependencies` - Check for dependency issues
### `/project:tm/fix-dependencies`
- `fix-dependencies` - Automatically fix dependency problems
## Workflows & Automation
### `/project:tm/workflows`
- `smart-workflow` - Context-aware intelligent workflow execution
- `command-pipeline` - Chain multiple commands together
- `auto-implement-tasks` - Advanced auto-implementation with code generation
## Utilities
### `/project:tm/utils`
- `analyze-project` - Deep project analysis and insights
### `/project:tm/setup`
- `install-taskmaster` - Comprehensive installation guide
- `quick-install-taskmaster` - One-line global installation
## Usage Patterns
### Natural Language
Most commands accept natural language arguments:
```
/project:tm/add-task create user authentication system
/project:tm/update mark all API tasks as high priority
/project:tm/list show blocked tasks
```
### ID-Based Commands
Commands requiring IDs intelligently parse from $ARGUMENTS:
```
/project:tm/show 45
/project:tm/expand 23
/project:tm/set-status/to-done 67
```
### Smart Defaults
Commands provide intelligent defaults and suggestions based on context.

10
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,10 @@
reviews:
profile: assertive
poem: false
auto_review:
base_branches:
- rc
- beta
- alpha
- production
- next

View File

@@ -1,21 +1,21 @@
{
"models": {
"main": {
"provider": "groq",
"modelId": "llama-3.1-8b-instant",
"maxTokens": 131072,
"provider": "anthropic",
"modelId": "claude-3-7-sonnet-20250219",
"maxTokens": 120000,
"temperature": 0.2
},
"research": {
"provider": "groq",
"modelId": "llama-3.3-70b-versatile",
"maxTokens": 32768,
"provider": "perplexity",
"modelId": "sonar",
"maxTokens": 8700,
"temperature": 0.1
},
"fallback": {
"provider": "anthropic",
"modelId": "claude-3-7-sonnet-20250219",
"maxTokens": 128000,
"modelId": "claude-3-5-sonnet-20241022",
"maxTokens": 8192,
"temperature": 0.2
}
},

View File

@@ -1,23 +0,0 @@
# Task ID: 1
# Title: Implement TTS Flag for Taskmaster Commands
# Status: pending
# Dependencies: 16 (Not found)
# Priority: medium
# Description: Add text-to-speech functionality to taskmaster commands with configurable voice options and audio output settings.
# Details:
Implement TTS functionality including:
- Add --tts flag to all relevant taskmaster commands (list, show, generate, etc.)
- Integrate with system TTS engines (Windows SAPI, macOS say command, Linux espeak/festival)
- Create TTS configuration options in the configuration management system
- Add voice selection options (male/female, different languages if available)
- Implement audio output settings (volume, speed, pitch)
- Add TTS-specific error handling for cases where TTS is unavailable
- Create fallback behavior when TTS fails (silent failure or text output)
- Support for reading task titles, descriptions, and status updates aloud
- Add option to read entire task lists or individual task details
- Implement TTS for command confirmations and error messages
- Create TTS output formatting to make spoken text more natural (removing markdown, formatting numbers/dates appropriately)
- Add configuration option to enable/disable TTS globally
# Test Strategy:
Test TTS functionality across different operating systems (Windows, macOS, Linux). Verify that the --tts flag works with all major commands. Test voice configuration options and ensure audio output settings are properly applied. Test error handling when TTS services are unavailable. Verify that text formatting for speech is natural and understandable. Test with various task content types including special characters, code snippets, and long descriptions. Ensure TTS can be disabled and enabled through configuration.

5648
.taskmaster/tasks/tasks.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,55 @@
# task-master-ai
## 0.20.0
### Minor Changes
- [#950](https://github.com/eyaltoledano/claude-task-master/pull/950) [`699e9ee`](https://github.com/eyaltoledano/claude-task-master/commit/699e9eefb5d687b256e9402d686bdd5e3a358b4a) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Add support for xAI Grok 4 model
- Add grok-4 model to xAI provider with $3/$15 per 1M token pricing
- Enable main, fallback, and research roles for grok-4
- Max tokens set to 131,072 (matching other xAI models)
- [#946](https://github.com/eyaltoledano/claude-task-master/pull/946) [`5f009a5`](https://github.com/eyaltoledano/claude-task-master/commit/5f009a5e1fc10e37be26f5135df4b7f44a9c5320) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add stricter validation and clearer feedback for task priority when adding new tasks
- if a task priority is invalid, it will default to medium
- made taks priority case-insensitive, essentially making HIGH and high the same value
- [#863](https://github.com/eyaltoledano/claude-task-master/pull/863) [`b530657`](https://github.com/eyaltoledano/claude-task-master/commit/b53065713c8da0ae6f18eb2655397aa975004923) Thanks [@OrenMe](https://github.com/OrenMe)! - Add support for MCP Sampling as AI provider, requires no API key, uses the client LLM provider
- [#930](https://github.com/eyaltoledano/claude-task-master/pull/930) [`98d1c97`](https://github.com/eyaltoledano/claude-task-master/commit/98d1c974361a56ddbeb772b1272986b9d3913459) Thanks [@OmarElKadri](https://github.com/OmarElKadri)! - Added Groq provider support
### Patch Changes
- [#958](https://github.com/eyaltoledano/claude-task-master/pull/958) [`6c88a4a`](https://github.com/eyaltoledano/claude-task-master/commit/6c88a4a749083e3bd2d073a9240799771774495a) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Recover from `@anthropic-ai/claude-code` JSON truncation bug that caused Task Master to crash when handling large (>8 kB) structured responses. The CLI/SDK still truncates, but Task Master now detects the error, preserves buffered text, and returns a usable response instead of throwing.
- [#958](https://github.com/eyaltoledano/claude-task-master/pull/958) [`3334e40`](https://github.com/eyaltoledano/claude-task-master/commit/3334e409ae659d5223bb136ae23fd22c5e219073) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Updating dependency ai-sdk-provider-gemini-cli to 0.0.4 to address breaking change Google made to Gemini CLI and add better 'api-key' in addition to 'gemini-api-key' AI-SDK compatibility.
- [#853](https://github.com/eyaltoledano/claude-task-master/pull/853) [`95c299d`](https://github.com/eyaltoledano/claude-task-master/commit/95c299df642bd8e6d75f8fa5110ac705bcc72edf) Thanks [@joedanz](https://github.com/joedanz)! - Unify and streamline profile system architecture for improved maintainability
## 0.20.0-rc.0
### Minor Changes
- [#950](https://github.com/eyaltoledano/claude-task-master/pull/950) [`699e9ee`](https://github.com/eyaltoledano/claude-task-master/commit/699e9eefb5d687b256e9402d686bdd5e3a358b4a) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Add support for xAI Grok 4 model
- Add grok-4 model to xAI provider with $3/$15 per 1M token pricing
- Enable main, fallback, and research roles for grok-4
- Max tokens set to 131,072 (matching other xAI models)
- [#946](https://github.com/eyaltoledano/claude-task-master/pull/946) [`5f009a5`](https://github.com/eyaltoledano/claude-task-master/commit/5f009a5e1fc10e37be26f5135df4b7f44a9c5320) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add stricter validation and clearer feedback for task priority when adding new tasks
- if a task priority is invalid, it will default to medium
- made taks priority case-insensitive, essentially making HIGH and high the same value
- [#863](https://github.com/eyaltoledano/claude-task-master/pull/863) [`b530657`](https://github.com/eyaltoledano/claude-task-master/commit/b53065713c8da0ae6f18eb2655397aa975004923) Thanks [@OrenMe](https://github.com/OrenMe)! - Add support for MCP Sampling as AI provider, requires no API key, uses the client LLM provider
- [#930](https://github.com/eyaltoledano/claude-task-master/pull/930) [`98d1c97`](https://github.com/eyaltoledano/claude-task-master/commit/98d1c974361a56ddbeb772b1272986b9d3913459) Thanks [@OmarElKadri](https://github.com/OmarElKadri)! - Added Groq provider support
### Patch Changes
- [#916](https://github.com/eyaltoledano/claude-task-master/pull/916) [`6c88a4a`](https://github.com/eyaltoledano/claude-task-master/commit/6c88a4a749083e3bd2d073a9240799771774495a) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Recover from `@anthropic-ai/claude-code` JSON truncation bug that caused Task Master to crash when handling large (>8 kB) structured responses. The CLI/SDK still truncates, but Task Master now detects the error, preserves buffered text, and returns a usable response instead of throwing.
- [#916](https://github.com/eyaltoledano/claude-task-master/pull/916) [`3334e40`](https://github.com/eyaltoledano/claude-task-master/commit/3334e409ae659d5223bb136ae23fd22c5e219073) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Updating dependency ai-sdk-provider-gemini-cli to 0.0.4 to address breaking change Google made to Gemini CLI and add better 'api-key' in addition to 'gemini-api-key' AI-SDK compatibility.
- [#853](https://github.com/eyaltoledano/claude-task-master/pull/853) [`95c299d`](https://github.com/eyaltoledano/claude-task-master/commit/95c299df642bd8e6d75f8fa5110ac705bcc72edf) Thanks [@joedanz](https://github.com/joedanz)! - Unify and streamline profile system architecture for improved maintainability
## 0.19.0
### Minor Changes

View File

@@ -0,0 +1,15 @@
{
"name": "extension",
"version": "0.20.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"typescript": "^5.8.3"
}
}

View File

@@ -0,0 +1 @@
console.log('hello world');

View File

@@ -0,0 +1,113 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "libReplacement": true, /* Enable lib replacement. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@@ -1,4 +1,4 @@
# Task Master AI - Claude Code Integration Guide
# Task Master AI - Agent Integration Guide
## Essential Commands

View File

@@ -5,6 +5,7 @@ OPENAI_API_KEY="your_openai_api_key_here" # Optional, for OpenAI/Ope
GOOGLE_API_KEY="your_google_api_key_here" # Optional, for Google Gemini models.
MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral AI models.
XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models.
GROQ_API_KEY="your_groq_api_key_here" # Optional, for Groq models. Format: gsk_...
AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmaster/config.json).
OLLAMA_API_KEY="your_ollama_api_key_here" # Optional: For remote Ollama servers that require authentication.
GITHUB_API_KEY="your_github_api_key_here" # Optional: For GitHub import/export features. Format: ghp_... or github_pat_...

View File

@@ -4,30 +4,7 @@ Taskmaster uses two primary methods for configuration:
1. **`.taskmaster/config.json` File (Recommended - New Structure)**
- This JSON file stores most configuration settings, including A5. **Usage Requirements**:
8. **Troubleshooting**:
- "MCP provider requires session context" → Ensure running in MCP environment
- See the [MCP Provider Guide](./mcp-provider-guide.md) for detailed troubleshootingust be running in an MCP context (session must be available)
- Session must provide `clientCapabilities.sampling` capability
6. **Best Practices**:
- Always configure a non-MCP fallback provider
- Use `mcp` for main/research roles when in MCP environments
- Test sampling capability before production use
7. **Setup Commands**:
```bash
# Set MCP provider for main role
task-master models set-main --provider mcp --model claude-3-5-sonnet-20241022
# Set MCP provider for research role
task-master models set-research --provider mcp --model claude-3-opus-20240229
# Verify configuration
task-master models list
```
8. **Troubleshooting**:lections, parameters, logging levels, and project defaults.
- This JSON file stores most configuration settings, including AI model selections, parameters, logging levels, and project defaults.
- **Location:** This file is created in the `.taskmaster/` directory when you run the `task-master models --setup` interactive setup or initialize a new project with `task-master init`.
- **Migration:** Existing projects with `.taskmasterconfig` in the root will continue to work, but should be migrated to the new structure using `task-master migrate`.
- **Management:** Use the `task-master models --setup` command (or `models` MCP tool) to interactively create and manage this file. You can also set specific models directly using `task-master models --set-<role>=<model_id>`, adding `--ollama` or `--openrouter` flags for custom models. Manual editing is possible but not recommended unless you understand the structure.
@@ -68,11 +45,12 @@ Taskmaster uses two primary methods for configuration:
"azureBaseURL": "https://your-endpoint.azure.com/openai/deployments",
"vertexProjectId": "your-gcp-project-id",
"vertexLocation": "us-central1",
"responseLanguage": "English"
"responseLanguage": "English"
}
}
```
> For MCP-specific setup and troubleshooting, see [Provider-Specific Configuration](#provider-specific-configuration).
2. **Legacy `.taskmasterconfig` File (Backward Compatibility)**
@@ -198,8 +176,6 @@ node scripts/init.js
### MCP (Model Context Protocol) Provider
The MCP provider enables Task Master to use MCP servers as AI providers. This is particularly useful when running Task Master within MCP-compatible development environments like Claude Desktop or Cursor.
1. **Prerequisites**:
- An active MCP session with sampling capability
- MCP client with sampling support (e.g. VS Code)
@@ -238,12 +214,24 @@ The MCP provider enables Task Master to use MCP servers as AI providers. This is
- Must be running in an MCP context (session must be available)
- Session must provide `clientCapabilities.sampling` capability
5. **Best Practices**:
6. **Best Practices**:
- Always configure a non-MCP fallback provider
- Use `mcp` for main/research roles when in MCP environments
- Test sampling capability before production use
6. **Troubleshooting**:
7. **Setup Commands**:
```bash
# Set MCP provider for main role
task-master models set-main --provider mcp --model claude-3-5-sonnet-20241022
# Set MCP provider for research role
task-master models set-research --provider mcp --model claude-3-opus-20240229
# Verify configuration
task-master models list
```
8. **Troubleshooting**:
- "MCP provider requires session context" → Ensure running in MCP environment
- See the [MCP Provider Guide](./mcp-provider-guide.md) for detailed troubleshooting

View File

@@ -1,4 +1,4 @@
# Available Models as of July 10, 2025
# Available Models as of July 16, 2025
## Main Models
@@ -32,6 +32,7 @@
| xai | grok-3 | — | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| xai | grok-4 | — | 3 | 15 |
| groq | moonshotai/kimi-k2-instruct | 0.66 | 1 | 3 |
| groq | llama-3.3-70b-versatile | 0.55 | 0.59 | 0.79 |
| groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
| groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
@@ -144,6 +145,7 @@
| xai | grok-3 | — | 3 | 15 |
| xai | grok-3-fast | — | 5 | 25 |
| xai | grok-4 | — | 3 | 15 |
| groq | moonshotai/kimi-k2-instruct | 0.66 | 1 | 3 |
| groq | llama-3.3-70b-versatile | 0.55 | 0.59 | 0.79 |
| groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
| groq | llama-4-scout | 0.45 | 0.11 | 0.34 |

View File

@@ -125,8 +125,7 @@ export async function addTaskDirect(args, log, context = {}) {
},
'json', // outputFormat
manualTaskData, // Pass the manual task data
false, // research flag is false for manual creation
projectRoot // Pass projectRoot
false // research flag is false for manual creation
);
newTaskId = result.newTaskId;
telemetryData = result.telemetryData;

37
package-lock.json generated
View File

@@ -1,13 +1,17 @@
{
"name": "task-master-ai",
"version": "0.19.0",
"version": "0.20.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "task-master-ai",
"version": "0.19.0",
"version": "0.20.0",
"license": "MIT WITH Commons-Clause",
"workspaces": [
"apps/*",
"."
],
"dependencies": {
"@ai-sdk/amazon-bedrock": "^2.2.9",
"@ai-sdk/anthropic": "^1.2.10",
@@ -80,6 +84,13 @@
"ai-sdk-provider-gemini-cli": "^0.0.4"
}
},
"apps/extension": {
"version": "0.20.0",
"license": "ISC",
"devDependencies": {
"typescript": "^5.8.3"
}
},
"node_modules/@ai-sdk/amazon-bedrock": {
"version": "2.2.10",
"resolved": "https://registry.npmjs.org/@ai-sdk/amazon-bedrock/-/amazon-bedrock-2.2.10.tgz",
@@ -7340,6 +7351,10 @@
"dev": true,
"license": "MIT"
},
"node_modules/extension": {
"resolved": "apps/extension",
"link": true
},
"node_modules/external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -12977,6 +12992,10 @@
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/task-master-ai": {
"resolved": "",
"link": true
},
"node_modules/term-size": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
@@ -13185,6 +13204,20 @@
"node": ">= 0.6"
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uint8array-extras": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "task-master-ai",
"version": "0.19.0",
"version": "0.20.0",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js",
"type": "module",
@@ -9,6 +9,7 @@
"task-master-mcp": "mcp-server/server.js",
"task-master-ai": "mcp-server/server.js"
},
"workspaces": ["apps/*", "."],
"scripts": {
"test": "node --experimental-vm-modules node_modules/.bin/jest",
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",

View File

@@ -8,47 +8,48 @@
// --- Core Dependencies ---
import {
getMainProvider,
getMainModelId,
getResearchProvider,
getResearchModelId,
getFallbackProvider,
MODEL_MAP,
getAzureBaseURL,
getBaseUrlForRole,
getBedrockBaseURL,
getDebugFlag,
getFallbackModelId,
getFallbackProvider,
getMainModelId,
getMainProvider,
getOllamaBaseURL,
getParametersForRole,
getResearchModelId,
getResearchProvider,
getResponseLanguage,
getUserId,
MODEL_MAP,
getDebugFlag,
getBaseUrlForRole,
isApiKeySet,
getOllamaBaseURL,
getAzureBaseURL,
getBedrockBaseURL,
getVertexProjectId,
getVertexLocation,
getVertexProjectId,
isApiKeySet,
providersWithoutApiKeys
} from './config-manager.js';
import {
log,
findProjectRoot,
resolveEnvVariable,
getCurrentTag
getCurrentTag,
log,
resolveEnvVariable
} from './utils.js';
// Import provider classes
import {
AnthropicAIProvider,
PerplexityAIProvider,
GoogleAIProvider,
OpenAIProvider,
XAIProvider,
OpenRouterAIProvider,
OllamaAIProvider,
BedrockAIProvider,
AzureProvider,
VertexAIProvider,
BedrockAIProvider,
ClaudeCodeProvider,
GeminiCliProvider
GeminiCliProvider,
GoogleAIProvider,
GroqProvider,
OllamaAIProvider,
OpenAIProvider,
OpenRouterAIProvider,
PerplexityAIProvider,
VertexAIProvider,
XAIProvider
} from '../../src/ai-providers/index.js';
// Import the provider registry
@@ -61,6 +62,7 @@ const PROVIDERS = {
google: new GoogleAIProvider(),
openai: new OpenAIProvider(),
xai: new XAIProvider(),
groq: new GroqProvider(),
openrouter: new OpenRouterAIProvider(),
ollama: new OllamaAIProvider(),
bedrock: new BedrockAIProvider(),

View File

@@ -2353,10 +2353,14 @@ ${result.result}
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (taskId, options) => {
// Initialize TaskMaster
const taskMaster = initTaskMaster({
tasksPath: options.file || true,
complexityReportPath: options.report || false
});
const initOptions = {
tasksPath: options.file || true
};
// Only pass complexityReportPath if user provided a custom path
if (options.report && options.report !== COMPLEXITY_REPORT_FILE) {
initOptions.complexityReportPath = options.report;
}
const taskMaster = initTaskMaster(initOptions);
const idArg = taskId || options.id;
const statusFilter = options.status;

View File

@@ -1,21 +1,21 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import chalk from 'chalk';
import { z } from 'zod';
import { fileURLToPath } from 'url';
import { log, findProjectRoot, resolveEnvVariable, isEmpty } from './utils.js';
import { AI_COMMAND_NAMES } from '../../src/constants/commands.js';
import {
LEGACY_CONFIG_FILE,
TASKMASTER_DIR
} from '../../src/constants/paths.js';
import { findConfigPath } from '../../src/utils/path-utils.js';
import {
VALIDATED_PROVIDERS,
ALL_PROVIDERS,
CUSTOM_PROVIDERS,
CUSTOM_PROVIDERS_ARRAY,
ALL_PROVIDERS
VALIDATED_PROVIDERS
} from '../../src/constants/providers.js';
import { AI_COMMAND_NAMES } from '../../src/constants/commands.js';
import { findConfigPath } from '../../src/utils/path-utils.js';
import { findProjectRoot, isEmpty, log, resolveEnvVariable } from './utils.js';
// Calculate __dirname in ESM
const __filename = fileURLToPath(import.meta.url);
@@ -641,6 +641,7 @@ function isApiKeySet(providerName, session = null, projectRoot = null) {
azure: 'AZURE_OPENAI_API_KEY',
openrouter: 'OPENROUTER_API_KEY',
xai: 'XAI_API_KEY',
groq: 'GROQ_API_KEY',
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
@@ -726,6 +727,10 @@ function getMcpApiKeyStatus(providerName, projectRoot = null) {
apiKeyToCheck = mcpEnv.XAI_API_KEY;
placeholderValue = 'YOUR_XAI_API_KEY_HERE';
break;
case 'groq':
apiKeyToCheck = mcpEnv.GROQ_API_KEY;
placeholderValue = 'YOUR_GROQ_API_KEY_HERE';
break;
case 'ollama':
return true; // No key needed
case 'claude-code':

View File

@@ -295,6 +295,16 @@
}
],
"groq": [
{
"id": "moonshotai/kimi-k2-instruct",
"swe_score": 0.66,
"cost_per_1m_tokens": {
"input": 1.0,
"output": 3.0
},
"allowed_roles": ["main", "fallback"],
"max_tokens": 16384
},
{
"id": "llama-3.3-70b-versatile",
"swe_score": 0.55,

View File

@@ -561,16 +561,6 @@ async function addTask(
writeJSON(tasksPath, rawData, projectRoot, targetTag);
report('DEBUG: tasks.json written.', 'debug');
// Generate markdown task files
report('Generating task files...', 'info');
report('DEBUG: Calling generateTaskFiles...', 'debug');
// Pass mcpLog if available to generateTaskFiles
await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
projectRoot,
tag: targetTag
});
report('DEBUG: generateTaskFiles finished.', 'debug');
// Show success message - only for text output (CLI)
if (outputFormat === 'text') {
const table = new Table({

View File

@@ -14,6 +14,14 @@ export class GroqProvider extends BaseAIProvider {
this.name = 'Groq';
}
/**
* Returns the environment variable name required for this provider's API key.
* @returns {string} The environment variable name for the Groq API key
*/
getRequiredApiKeyName() {
return 'GROQ_API_KEY';
}
/**
* Creates and returns a Groq client instance.
* @param {object} params - Parameters for client initialization

View File

@@ -1,5 +1,5 @@
/**
* @typedef {'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'roo' | 'trae' | 'windsurf' | 'vscode'} RulesProfile
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'roo' | 'trae' | 'windsurf' | 'vscode'} RulesProfile
*/
/**
@@ -10,6 +10,7 @@
*
* @type {RulesProfile[]}
* @description Defines possible rule profile sets:
* - amp: Amp Code integration
* - claude: Claude Code integration
* - cline: Cline IDE rules
* - codex: Codex integration
@@ -26,6 +27,7 @@
* 3. Export it as {profile}Profile in src/profiles/index.js
*/
export const RULE_PROFILES = [
'amp',
'claude',
'cline',
'codex',

277
src/profiles/amp.js Normal file
View File

@@ -0,0 +1,277 @@
// Amp profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { isSilentMode, log } from '../../scripts/modules/utils.js';
import { createProfile } from './base-profile.js';
/**
* Transform standard MCP config format to Amp format
* @param {Object} mcpConfig - Standard MCP configuration object
* @returns {Object} - Transformed Amp configuration object
*/
function transformToAmpFormat(mcpConfig) {
const ampConfig = {};
// Transform mcpServers to amp.mcpServers
if (mcpConfig.mcpServers) {
ampConfig['amp.mcpServers'] = mcpConfig.mcpServers;
}
// Preserve any other existing settings
for (const [key, value] of Object.entries(mcpConfig)) {
if (key !== 'mcpServers') {
ampConfig[key] = value;
}
}
return ampConfig;
}
// Lifecycle functions for Amp profile
function onAddRulesProfile(targetDir, assetsDir) {
// Handle AGENT.md import for non-destructive integration (Amp uses AGENT.md, copies from AGENTS.md)
const sourceFile = path.join(assetsDir, 'AGENTS.md');
const userAgentFile = path.join(targetDir, 'AGENT.md');
const taskMasterAgentFile = path.join(targetDir, '.taskmaster', 'AGENT.md');
const importLine = '@./.taskmaster/AGENT.md';
const importSection = `\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n${importLine}`;
if (fs.existsSync(sourceFile)) {
try {
// Ensure .taskmaster directory exists
const taskMasterDir = path.join(targetDir, '.taskmaster');
if (!fs.existsSync(taskMasterDir)) {
fs.mkdirSync(taskMasterDir, { recursive: true });
}
// Copy Task Master instructions to .taskmaster/AGENT.md
fs.copyFileSync(sourceFile, taskMasterAgentFile);
log(
'debug',
`[Amp] Created Task Master instructions at ${taskMasterAgentFile}`
);
// Handle user's AGENT.md
if (fs.existsSync(userAgentFile)) {
// Check if import already exists
const content = fs.readFileSync(userAgentFile, 'utf8');
if (!content.includes(importLine)) {
// Append import section at the end
const updatedContent = content.trim() + '\n' + importSection + '\n';
fs.writeFileSync(userAgentFile, updatedContent);
log(
'info',
`[Amp] Added Task Master import to existing ${userAgentFile}`
);
} else {
log(
'info',
`[Amp] Task Master import already present in ${userAgentFile}`
);
}
} else {
// Create minimal AGENT.md with the import section
const minimalContent = `# Amp Instructions\n${importSection}\n`;
fs.writeFileSync(userAgentFile, minimalContent);
log('info', `[Amp] Created ${userAgentFile} with Task Master import`);
}
} catch (err) {
log('error', `[Amp] Failed to set up Amp instructions: ${err.message}`);
}
}
// MCP transformation will be handled in onPostConvertRulesProfile
}
function onRemoveRulesProfile(targetDir) {
// Clean up AGENT.md import (Amp uses AGENT.md, not AGENTS.md)
const userAgentFile = path.join(targetDir, 'AGENT.md');
const taskMasterAgentFile = path.join(targetDir, '.taskmaster', 'AGENT.md');
const importLine = '@./.taskmaster/AGENT.md';
try {
// Remove Task Master AGENT.md from .taskmaster
if (fs.existsSync(taskMasterAgentFile)) {
fs.rmSync(taskMasterAgentFile, { force: true });
log('debug', `[Amp] Removed ${taskMasterAgentFile}`);
}
// Clean up import from user's AGENT.md
if (fs.existsSync(userAgentFile)) {
const content = fs.readFileSync(userAgentFile, 'utf8');
const lines = content.split('\n');
const filteredLines = [];
let skipNextLines = 0;
// Remove the Task Master section
for (let i = 0; i < lines.length; i++) {
if (skipNextLines > 0) {
skipNextLines--;
continue;
}
// Check if this is the start of our Task Master section
if (lines[i].includes('## Task Master AI Instructions')) {
// Skip this line and the next two lines (bold text and import)
skipNextLines = 2;
continue;
}
// Also remove standalone import lines (for backward compatibility)
if (lines[i].trim() === importLine) {
continue;
}
filteredLines.push(lines[i]);
}
// Join back and clean up excessive newlines
let updatedContent = filteredLines
.join('\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
// Check if file only contained our minimal template
if (updatedContent === '# Amp Instructions' || updatedContent === '') {
// File only contained our import, remove it
fs.rmSync(userAgentFile, { force: true });
log('debug', `[Amp] Removed empty ${userAgentFile}`);
} else {
// Write back without the import
fs.writeFileSync(userAgentFile, updatedContent + '\n');
log('debug', `[Amp] Removed Task Master import from ${userAgentFile}`);
}
}
} catch (err) {
log('error', `[Amp] Failed to remove Amp instructions: ${err.message}`);
}
// MCP Removal: Remove amp.mcpServers section
const mcpConfigPath = path.join(targetDir, '.vscode', 'settings.json');
if (!fs.existsSync(mcpConfigPath)) {
log('debug', '[Amp] No .vscode/settings.json found to clean up');
return;
}
try {
// Read the current config
const configContent = fs.readFileSync(mcpConfigPath, 'utf8');
const config = JSON.parse(configContent);
// Check if it has the amp.mcpServers section and task-master-ai server
if (
config['amp.mcpServers'] &&
config['amp.mcpServers']['task-master-ai']
) {
// Remove task-master-ai server
delete config['amp.mcpServers']['task-master-ai'];
// Check if there are other MCP servers in amp.mcpServers
const remainingServers = Object.keys(config['amp.mcpServers']);
if (remainingServers.length === 0) {
// No other servers, remove entire amp.mcpServers section
delete config['amp.mcpServers'];
log('debug', '[Amp] Removed empty amp.mcpServers section');
}
// Check if config is now empty
const remainingKeys = Object.keys(config);
if (remainingKeys.length === 0) {
// Config is empty, remove entire file
fs.rmSync(mcpConfigPath, { force: true });
log('info', '[Amp] Removed empty settings.json file');
// Check if .vscode directory is empty
const vscodeDirPath = path.join(targetDir, '.vscode');
if (fs.existsSync(vscodeDirPath)) {
const remainingContents = fs.readdirSync(vscodeDirPath);
if (remainingContents.length === 0) {
fs.rmSync(vscodeDirPath, { recursive: true, force: true });
log('debug', '[Amp] Removed empty .vscode directory');
}
}
} else {
// Write back the modified config
fs.writeFileSync(
mcpConfigPath,
JSON.stringify(config, null, '\t') + '\n'
);
log(
'info',
'[Amp] Removed TaskMaster from settings.json, preserved other configurations'
);
}
} else {
log('debug', '[Amp] TaskMaster not found in amp.mcpServers');
}
} catch (error) {
log('error', `[Amp] Failed to clean up settings.json: ${error.message}`);
}
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
// Handle AGENT.md setup (same as onAddRulesProfile)
onAddRulesProfile(targetDir, assetsDir);
// Transform MCP config to Amp format
const mcpConfigPath = path.join(targetDir, '.vscode', 'settings.json');
if (!fs.existsSync(mcpConfigPath)) {
log('debug', '[Amp] No .vscode/settings.json found to transform');
return;
}
try {
// Read the generated standard MCP config
const mcpConfigContent = fs.readFileSync(mcpConfigPath, 'utf8');
const mcpConfig = JSON.parse(mcpConfigContent);
// Check if it's already in Amp format (has amp.mcpServers)
if (mcpConfig['amp.mcpServers']) {
log(
'info',
'[Amp] settings.json already in Amp format, skipping transformation'
);
return;
}
// Transform to Amp format
const ampConfig = transformToAmpFormat(mcpConfig);
// Write back the transformed config with proper formatting
fs.writeFileSync(
mcpConfigPath,
JSON.stringify(ampConfig, null, '\t') + '\n'
);
log('info', '[Amp] Transformed settings.json to Amp format');
log('debug', '[Amp] Renamed mcpServers to amp.mcpServers');
} catch (error) {
log('error', `[Amp] Failed to transform settings.json: ${error.message}`);
}
}
// Create and export amp profile using the base factory
export const ampProfile = createProfile({
name: 'amp',
displayName: 'Amp',
url: 'ampcode.com',
docsUrl: 'ampcode.com/manual',
profileDir: '.vscode',
rulesDir: '.',
mcpConfig: true,
mcpConfigName: 'settings.json',
includeDefaultRules: false,
fileMap: {
'AGENTS.md': '.taskmaster/AGENT.md'
},
onAdd: onAddRulesProfile,
onRemove: onRemoveRulesProfile,
onPostConvert: onPostConvertRulesProfile
});
// Export lifecycle functions separately to avoid naming conflicts
export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };

View File

@@ -46,7 +46,9 @@ export function createProfile(editorConfig) {
onPostConvert
} = editorConfig;
const mcpConfigPath = mcpConfigName ? `${profileDir}/${mcpConfigName}` : null;
const mcpConfigPath = mcpConfigName
? path.join(profileDir, mcpConfigName)
: null;
// Standard file mapping with custom overrides
// Use taskmaster subdirectory only if profile supports it

View File

@@ -59,6 +59,63 @@ function onAddRulesProfile(targetDir, assetsDir) {
`[Claude] An error occurred during directory copy: ${err.message}`
);
}
// Handle CLAUDE.md import for non-destructive integration
const sourceFile = path.join(assetsDir, 'AGENTS.md');
const userClaudeFile = path.join(targetDir, 'CLAUDE.md');
const taskMasterClaudeFile = path.join(targetDir, '.taskmaster', 'CLAUDE.md');
const importLine = '@./.taskmaster/CLAUDE.md';
const importSection = `\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main CLAUDE.md file.**\n${importLine}`;
if (fs.existsSync(sourceFile)) {
try {
// Ensure .taskmaster directory exists
const taskMasterDir = path.join(targetDir, '.taskmaster');
if (!fs.existsSync(taskMasterDir)) {
fs.mkdirSync(taskMasterDir, { recursive: true });
}
// Copy Task Master instructions to .taskmaster/CLAUDE.md
fs.copyFileSync(sourceFile, taskMasterClaudeFile);
log(
'debug',
`[Claude] Created Task Master instructions at ${taskMasterClaudeFile}`
);
// Handle user's CLAUDE.md
if (fs.existsSync(userClaudeFile)) {
// Check if import already exists
const content = fs.readFileSync(userClaudeFile, 'utf8');
if (!content.includes(importLine)) {
// Append import section at the end
const updatedContent = content.trim() + '\n' + importSection + '\n';
fs.writeFileSync(userClaudeFile, updatedContent);
log(
'info',
`[Claude] Added Task Master import to existing ${userClaudeFile}`
);
} else {
log(
'info',
`[Claude] Task Master import already present in ${userClaudeFile}`
);
}
} else {
// Create minimal CLAUDE.md with the import section
const minimalContent = `# Claude Code Instructions\n${importSection}\n`;
fs.writeFileSync(userClaudeFile, minimalContent);
log(
'info',
`[Claude] Created ${userClaudeFile} with Task Master import`
);
}
} catch (err) {
log(
'error',
`[Claude] Failed to set up Claude instructions: ${err.message}`
);
}
}
}
function onRemoveRulesProfile(targetDir) {
@@ -67,11 +124,146 @@ function onRemoveRulesProfile(targetDir) {
if (removeDirectoryRecursive(claudeDir)) {
log('debug', `[Claude] Removed .claude directory from ${claudeDir}`);
}
// Clean up CLAUDE.md import
const userClaudeFile = path.join(targetDir, 'CLAUDE.md');
const taskMasterClaudeFile = path.join(targetDir, '.taskmaster', 'CLAUDE.md');
const importLine = '@./.taskmaster/CLAUDE.md';
try {
// Remove Task Master CLAUDE.md from .taskmaster
if (fs.existsSync(taskMasterClaudeFile)) {
fs.rmSync(taskMasterClaudeFile, { force: true });
log('debug', `[Claude] Removed ${taskMasterClaudeFile}`);
}
// Clean up import from user's CLAUDE.md
if (fs.existsSync(userClaudeFile)) {
const content = fs.readFileSync(userClaudeFile, 'utf8');
const lines = content.split('\n');
const filteredLines = [];
let skipNextLines = 0;
// Remove the Task Master section
for (let i = 0; i < lines.length; i++) {
if (skipNextLines > 0) {
skipNextLines--;
continue;
}
// Check if this is the start of our Task Master section
if (lines[i].includes('## Task Master AI Instructions')) {
// Skip this line and the next two lines (bold text and import)
skipNextLines = 2;
continue;
}
// Also remove standalone import lines (for backward compatibility)
if (lines[i].trim() === importLine) {
continue;
}
filteredLines.push(lines[i]);
}
// Join back and clean up excessive newlines
let updatedContent = filteredLines
.join('\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
// Check if file only contained our minimal template
if (
updatedContent === '# Claude Code Instructions' ||
updatedContent === ''
) {
// File only contained our import, remove it
fs.rmSync(userClaudeFile, { force: true });
log('debug', `[Claude] Removed empty ${userClaudeFile}`);
} else {
// Write back without the import
fs.writeFileSync(userClaudeFile, updatedContent + '\n');
log(
'debug',
`[Claude] Removed Task Master import from ${userClaudeFile}`
);
}
}
} catch (err) {
log(
'error',
`[Claude] Failed to remove Claude instructions: ${err.message}`
);
}
}
/**
* Transform standard MCP config format to Claude format
* @param {Object} mcpConfig - Standard MCP configuration object
* @returns {Object} - Transformed Claude configuration object
*/
function transformToClaudeFormat(mcpConfig) {
const claudeConfig = {};
// Transform mcpServers to servers (keeping the same structure but adding type)
if (mcpConfig.mcpServers) {
claudeConfig.mcpServers = {};
for (const [serverName, serverConfig] of Object.entries(
mcpConfig.mcpServers
)) {
// Transform server configuration with type as first key
const reorderedServer = {};
// Add type: "stdio" as the first key
reorderedServer.type = 'stdio';
// Then add the rest of the properties in order
if (serverConfig.command) reorderedServer.command = serverConfig.command;
if (serverConfig.args) reorderedServer.args = serverConfig.args;
if (serverConfig.env) reorderedServer.env = serverConfig.env;
// Add any other properties that might exist
Object.keys(serverConfig).forEach((key) => {
if (!['command', 'args', 'env', 'type'].includes(key)) {
reorderedServer[key] = serverConfig[key];
}
});
claudeConfig.mcpServers[serverName] = reorderedServer;
}
}
return claudeConfig;
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
// For Claude, post-convert is the same as add since we don't transform rules
onAddRulesProfile(targetDir, assetsDir);
// Transform MCP configuration to Claude format
const mcpConfigPath = path.join(targetDir, '.mcp.json');
if (fs.existsSync(mcpConfigPath)) {
try {
const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
const claudeConfig = transformToClaudeFormat(mcpConfig);
// Write back the transformed configuration
fs.writeFileSync(
mcpConfigPath,
JSON.stringify(claudeConfig, null, '\t') + '\n'
);
log(
'debug',
`[Claude] Transformed MCP configuration to Claude format at ${mcpConfigPath}`
);
} catch (err) {
log(
'error',
`[Claude] Failed to transform MCP configuration: ${err.message}`
);
}
}
}
// Create and export claude profile using the base factory
@@ -82,11 +274,10 @@ export const claudeProfile = createProfile({
docsUrl: 'docs.anthropic.com/en/docs/claude-code',
profileDir: '.', // Root directory
rulesDir: '.', // No specific rules directory needed
mcpConfig: false,
mcpConfigName: null,
mcpConfigName: '.mcp.json', // Place MCP config in project root
includeDefaultRules: false,
fileMap: {
'AGENTS.md': 'CLAUDE.md'
'AGENTS.md': '.taskmaster/CLAUDE.md'
},
onAdd: onAddRulesProfile,
onRemove: onRemoveRulesProfile,

View File

@@ -1,4 +1,5 @@
// Profile exports for centralized importing
export { ampProfile } from './amp.js';
export { claudeProfile } from './claude.js';
export { clineProfile } from './cline.js';
export { codexProfile } from './codex.js';

View File

@@ -113,13 +113,15 @@ export async function runInteractiveProfilesSetup() {
const hasMcpConfig = profile.mcpConfig === true;
if (!profile.includeDefaultRules) {
// Integration guide profiles (claude, codex, gemini) - don't include standard coding rules
// Integration guide profiles (claude, codex, gemini, amp) - don't include standard coding rules
if (profileName === 'claude') {
description = 'Integration guide with Task Master slash commands';
} else if (profileName === 'codex') {
description = 'Comprehensive Task Master integration guide';
} else if (profileName === 'gemini') {
description = 'Integration guide and MCP config';
} else if (profileName === 'amp') {
description = 'Integration guide and MCP config';
} else {
description = 'Integration guide';
}
@@ -199,7 +201,7 @@ export function generateProfileSummary(profileName, addResult) {
const profileConfig = getRulesProfile(profileName);
if (!profileConfig.includeDefaultRules) {
// Integration guide profiles (claude, codex, gemini)
// Integration guide profiles (claude, codex, gemini, amp)
return `Summary for ${profileName}: Integration guide installed.`;
} else {
// Rule profiles with coding guidelines
@@ -225,7 +227,7 @@ export function generateProfileRemovalSummary(profileName, removeResult) {
const profileConfig = getRulesProfile(profileName);
if (!profileConfig.includeDefaultRules) {
// Integration guide profiles (claude, codex, gemini)
// Integration guide profiles (claude, codex, gemini, amp)
const baseMessage = `Summary for ${profileName}: Integration guide removed`;
if (removeResult.notice) {
return `${baseMessage} (${removeResult.notice})`;

View File

@@ -0,0 +1,346 @@
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { getRulesProfile } from '../../../src/utils/rule-transformer.js';
import { convertAllRulesToProfileRules } from '../../../src/utils/rule-transformer.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
describe('Amp Profile Init Functionality', () => {
let tempDir;
let ampProfile;
beforeEach(() => {
// Create temporary directory for testing
tempDir = fs.mkdtempSync(path.join(__dirname, 'temp-amp-'));
// Get the Amp profile
ampProfile = getRulesProfile('amp');
});
afterEach(() => {
// Clean up temporary directory
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
describe('Profile Configuration', () => {
test('should have correct profile metadata', () => {
expect(ampProfile).toBeDefined();
expect(ampProfile.profileName).toBe('amp');
expect(ampProfile.displayName).toBe('Amp');
expect(ampProfile.profileDir).toBe('.vscode');
expect(ampProfile.rulesDir).toBe('.');
expect(ampProfile.mcpConfig).toBe(true);
expect(ampProfile.mcpConfigName).toBe('settings.json');
expect(ampProfile.mcpConfigPath).toBe('.vscode/settings.json');
expect(ampProfile.includeDefaultRules).toBe(false);
});
test('should have correct file mapping', () => {
expect(ampProfile.fileMap).toBeDefined();
expect(ampProfile.fileMap['AGENTS.md']).toBe('.taskmaster/AGENT.md');
});
test('should have lifecycle functions', () => {
expect(typeof ampProfile.onAddRulesProfile).toBe('function');
expect(typeof ampProfile.onRemoveRulesProfile).toBe('function');
expect(typeof ampProfile.onPostConvertRulesProfile).toBe('function');
});
});
describe('AGENT.md Handling', () => {
test('should create AGENT.md with import when none exists', () => {
// Create mock AGENTS.md source
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
fs.writeFileSync(
path.join(assetsDir, 'AGENTS.md'),
'Task Master instructions'
);
// Call onAddRulesProfile
ampProfile.onAddRulesProfile(tempDir, assetsDir);
// Check that AGENT.md was created with import
const agentFile = path.join(tempDir, 'AGENT.md');
expect(fs.existsSync(agentFile)).toBe(true);
const content = fs.readFileSync(agentFile, 'utf8');
expect(content).toContain('# Amp Instructions');
expect(content).toContain('## Task Master AI Instructions');
expect(content).toContain('@./.taskmaster/AGENT.md');
// Check that .taskmaster/AGENT.md was created
const taskMasterAgent = path.join(tempDir, '.taskmaster', 'AGENT.md');
expect(fs.existsSync(taskMasterAgent)).toBe(true);
});
test('should append import to existing AGENT.md', () => {
// Create existing AGENT.md
const existingContent =
'# My Existing Amp Instructions\n\nSome content here.';
fs.writeFileSync(path.join(tempDir, 'AGENT.md'), existingContent);
// Create mock AGENTS.md source
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
fs.writeFileSync(
path.join(assetsDir, 'AGENTS.md'),
'Task Master instructions'
);
// Call onAddRulesProfile
ampProfile.onAddRulesProfile(tempDir, assetsDir);
// Check that import was appended
const agentFile = path.join(tempDir, 'AGENT.md');
const content = fs.readFileSync(agentFile, 'utf8');
expect(content).toContain('# My Existing Amp Instructions');
expect(content).toContain('Some content here.');
expect(content).toContain('## Task Master AI Instructions');
expect(content).toContain('@./.taskmaster/AGENT.md');
});
test('should not duplicate import if already exists', () => {
// Create AGENT.md with existing import
const existingContent =
"# My Amp Instructions\n\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n@./.taskmaster/AGENT.md";
fs.writeFileSync(path.join(tempDir, 'AGENT.md'), existingContent);
// Create mock AGENTS.md source
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
fs.writeFileSync(
path.join(assetsDir, 'AGENTS.md'),
'Task Master instructions'
);
// Call onAddRulesProfile
ampProfile.onAddRulesProfile(tempDir, assetsDir);
// Check that import was not duplicated
const agentFile = path.join(tempDir, 'AGENT.md');
const content = fs.readFileSync(agentFile, 'utf8');
const importCount = (content.match(/@\.\/.taskmaster\/AGENT\.md/g) || [])
.length;
expect(importCount).toBe(1);
});
});
describe('MCP Configuration', () => {
test('should rename mcpServers to amp.mcpServers', () => {
// Create .vscode directory and settings.json with mcpServers
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
const initialConfig = {
mcpServers: {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai']
}
}
};
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
JSON.stringify(initialConfig, null, '\t')
);
// Call onPostConvertRulesProfile (which should transform mcpServers to amp.mcpServers)
ampProfile.onPostConvertRulesProfile(
tempDir,
path.join(tempDir, 'assets')
);
// Check that mcpServers was renamed to amp.mcpServers
const settingsFile = path.join(vscodeDirPath, 'settings.json');
const content = fs.readFileSync(settingsFile, 'utf8');
const config = JSON.parse(content);
expect(config.mcpServers).toBeUndefined();
expect(config['amp.mcpServers']).toBeDefined();
expect(config['amp.mcpServers']['task-master-ai']).toBeDefined();
});
test('should not rename if amp.mcpServers already exists', () => {
// Create .vscode directory and settings.json with both mcpServers and amp.mcpServers
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
const initialConfig = {
mcpServers: {
'some-other-server': {
command: 'other-command'
}
},
'amp.mcpServers': {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai']
}
}
};
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
JSON.stringify(initialConfig, null, '\t')
);
// Call onAddRulesProfile
ampProfile.onAddRulesProfile(tempDir, path.join(tempDir, 'assets'));
// Check that both sections remain unchanged
const settingsFile = path.join(vscodeDirPath, 'settings.json');
const content = fs.readFileSync(settingsFile, 'utf8');
const config = JSON.parse(content);
expect(config.mcpServers).toBeDefined();
expect(config.mcpServers['some-other-server']).toBeDefined();
expect(config['amp.mcpServers']).toBeDefined();
expect(config['amp.mcpServers']['task-master-ai']).toBeDefined();
});
});
describe('Removal Functionality', () => {
test('should remove AGENT.md import and clean up files', () => {
// Setup: Create AGENT.md with import and .taskmaster/AGENT.md
const agentContent =
"# My Amp Instructions\n\nSome content.\n\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n@./.taskmaster/AGENT.md\n";
fs.writeFileSync(path.join(tempDir, 'AGENT.md'), agentContent);
fs.mkdirSync(path.join(tempDir, '.taskmaster'), { recursive: true });
fs.writeFileSync(
path.join(tempDir, '.taskmaster', 'AGENT.md'),
'Task Master instructions'
);
// Call onRemoveRulesProfile
ampProfile.onRemoveRulesProfile(tempDir);
// Check that .taskmaster/AGENT.md was removed
expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
false
);
// Check that import was removed from AGENT.md
const remainingContent = fs.readFileSync(
path.join(tempDir, 'AGENT.md'),
'utf8'
);
expect(remainingContent).not.toContain('## Task Master AI Instructions');
expect(remainingContent).not.toContain('@./.taskmaster/AGENT.md');
expect(remainingContent).toContain('# My Amp Instructions');
expect(remainingContent).toContain('Some content.');
});
test('should remove empty AGENT.md if only contained import', () => {
// Setup: Create AGENT.md with only import
const agentContent =
"# Amp Instructions\n\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n@./.taskmaster/AGENT.md";
fs.writeFileSync(path.join(tempDir, 'AGENT.md'), agentContent);
fs.mkdirSync(path.join(tempDir, '.taskmaster'), { recursive: true });
fs.writeFileSync(
path.join(tempDir, '.taskmaster', 'AGENT.md'),
'Task Master instructions'
);
// Call onRemoveRulesProfile
ampProfile.onRemoveRulesProfile(tempDir);
// Check that AGENT.md was removed
expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(false);
});
test('should remove amp.mcpServers section from settings.json', () => {
// Setup: Create .vscode/settings.json with amp.mcpServers and other settings
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
const initialConfig = {
'amp.mcpServers': {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai']
}
},
'other.setting': 'value'
};
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
JSON.stringify(initialConfig, null, '\t')
);
// Call onRemoveRulesProfile
ampProfile.onRemoveRulesProfile(tempDir);
// Check that amp.mcpServers was removed but other settings remain
const settingsFile = path.join(vscodeDirPath, 'settings.json');
expect(fs.existsSync(settingsFile)).toBe(true);
const content = fs.readFileSync(settingsFile, 'utf8');
const config = JSON.parse(content);
expect(config['amp.mcpServers']).toBeUndefined();
expect(config['other.setting']).toBe('value');
});
test('should remove settings.json and .vscode directory if empty after removal', () => {
// Setup: Create .vscode/settings.json with only amp.mcpServers
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
const initialConfig = {
'amp.mcpServers': {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai']
}
}
};
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
JSON.stringify(initialConfig, null, '\t')
);
// Call onRemoveRulesProfile
ampProfile.onRemoveRulesProfile(tempDir);
// Check that settings.json and .vscode directory were removed
expect(fs.existsSync(path.join(vscodeDirPath, 'settings.json'))).toBe(
false
);
expect(fs.existsSync(vscodeDirPath)).toBe(false);
});
});
describe('Full Integration', () => {
test('should work with convertAllRulesToProfileRules', () => {
// This test ensures the profile works with the full rule transformer
const result = convertAllRulesToProfileRules(tempDir, ampProfile);
expect(result.success).toBeGreaterThan(0);
expect(result.failed).toBe(0);
// Check that .taskmaster/AGENT.md was created
expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
true
);
// Check that AGENT.md was created with import
expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);
const agentContent = fs.readFileSync(
path.join(tempDir, 'AGENT.md'),
'utf8'
);
expect(agentContent).toContain('@./.taskmaster/AGENT.md');
});
});
});

View File

@@ -21,19 +21,22 @@ describe('Claude Profile Initialization Functionality', () => {
expect(claudeProfileContent).toContain("displayName: 'Claude Code'");
expect(claudeProfileContent).toContain("profileDir: '.'"); // non-default
expect(claudeProfileContent).toContain("rulesDir: '.'"); // non-default
expect(claudeProfileContent).toContain('mcpConfig: false'); // non-default
expect(claudeProfileContent).toContain("mcpConfigName: '.mcp.json'"); // non-default
expect(claudeProfileContent).toContain('includeDefaultRules: false'); // non-default
expect(claudeProfileContent).toContain("'AGENTS.md': 'CLAUDE.md'");
expect(claudeProfileContent).toContain(
"'AGENTS.md': '.taskmaster/CLAUDE.md'"
);
// Check the final computed properties on the profile object
expect(claudeProfile.profileName).toBe('claude');
expect(claudeProfile.displayName).toBe('Claude Code');
expect(claudeProfile.profileDir).toBe('.');
expect(claudeProfile.rulesDir).toBe('.');
expect(claudeProfile.mcpConfig).toBe(false);
expect(claudeProfile.mcpConfigName).toBe(null); // computed
expect(claudeProfile.mcpConfig).toBe(true); // default from base profile
expect(claudeProfile.mcpConfigName).toBe('.mcp.json'); // explicitly set
expect(claudeProfile.mcpConfigPath).toBe('.mcp.json'); // computed
expect(claudeProfile.includeDefaultRules).toBe(false);
expect(claudeProfile.fileMap['AGENTS.md']).toBe('CLAUDE.md');
expect(claudeProfile.fileMap['AGENTS.md']).toBe('.taskmaster/CLAUDE.md');
});
test('claude.js has lifecycle functions for file management', () => {
@@ -44,9 +47,11 @@ describe('Claude Profile Initialization Functionality', () => {
);
});
test('claude.js handles .claude directory in lifecycle functions', () => {
test('claude.js handles .claude directory and .taskmaster/CLAUDE.md import in lifecycle functions', () => {
expect(claudeProfileContent).toContain('.claude');
expect(claudeProfileContent).toContain('copyRecursiveSync');
expect(claudeProfileContent).toContain('.taskmaster/CLAUDE.md');
expect(claudeProfileContent).toContain('@./.taskmaster/CLAUDE.md');
});
test('claude.js has proper error handling in lifecycle functions', () => {

View File

@@ -177,6 +177,13 @@ jest.unstable_mockModule('../../src/ai-providers/index.js', () => ({
getRequiredApiKeyName: jest.fn(() => 'XAI_API_KEY'),
isRequiredApiKey: jest.fn(() => true)
})),
GroqProvider: jest.fn(() => ({
generateText: jest.fn(),
streamText: jest.fn(),
generateObject: jest.fn(),
getRequiredApiKeyName: jest.fn(() => 'GROQ_API_KEY'),
isRequiredApiKey: jest.fn(() => true)
})),
OpenRouterAIProvider: jest.fn(() => ({
generateText: jest.fn(),
streamText: jest.fn(),

View File

@@ -0,0 +1,299 @@
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { getRulesProfile } from '../../../src/utils/rule-transformer.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
describe('Amp Profile Integration', () => {
let tempDir;
let ampProfile;
beforeEach(() => {
// Create temporary directory for testing
tempDir = fs.mkdtempSync(path.join(__dirname, 'temp-amp-unit-'));
// Get the Amp profile
ampProfile = getRulesProfile('amp');
});
afterEach(() => {
// Clean up temporary directory
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
});
describe('Profile Structure', () => {
test('should have expected profile structure', () => {
expect(ampProfile).toBeDefined();
expect(ampProfile.profileName).toBe('amp');
expect(ampProfile.displayName).toBe('Amp');
expect(ampProfile.profileDir).toBe('.vscode');
expect(ampProfile.rulesDir).toBe('.');
expect(ampProfile.mcpConfig).toBe(true);
expect(ampProfile.mcpConfigName).toBe('settings.json');
expect(ampProfile.mcpConfigPath).toBe('.vscode/settings.json');
expect(ampProfile.includeDefaultRules).toBe(false);
});
test('should have correct file mapping', () => {
expect(ampProfile.fileMap).toEqual({
'AGENTS.md': '.taskmaster/AGENT.md'
});
});
test('should not create unnecessary directories', () => {
// Unlike profiles that copy entire directories, Amp should only create what's needed
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
fs.writeFileSync(
path.join(assetsDir, 'AGENTS.md'),
'Task Master instructions'
);
// Call onAddRulesProfile
ampProfile.onAddRulesProfile(tempDir, assetsDir);
// Should only have created .taskmaster directory and AGENT.md
expect(fs.existsSync(path.join(tempDir, '.taskmaster'))).toBe(true);
expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);
// Should not have created any other directories (like .claude)
expect(fs.existsSync(path.join(tempDir, '.amp'))).toBe(false);
expect(fs.existsSync(path.join(tempDir, '.claude'))).toBe(false);
});
});
describe('AGENT.md Import Logic', () => {
test('should handle missing source file gracefully', () => {
// Call onAddRulesProfile without creating source file
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
// Should not throw error
expect(() => {
ampProfile.onAddRulesProfile(tempDir, assetsDir);
}).not.toThrow();
// Should not create any files
expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(false);
expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
false
);
});
test('should preserve existing content when adding import', () => {
// Create existing AGENT.md with specific content
const existingContent =
'# My Custom Amp Setup\n\nThis is my custom configuration.\n\n## Custom Section\n\nSome custom rules here.';
fs.writeFileSync(path.join(tempDir, 'AGENT.md'), existingContent);
// Create mock source
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
fs.writeFileSync(
path.join(assetsDir, 'AGENTS.md'),
'Task Master instructions'
);
// Call onAddRulesProfile
ampProfile.onAddRulesProfile(tempDir, assetsDir);
// Check that existing content is preserved
const updatedContent = fs.readFileSync(
path.join(tempDir, 'AGENT.md'),
'utf8'
);
expect(updatedContent).toContain('# My Custom Amp Setup');
expect(updatedContent).toContain('This is my custom configuration.');
expect(updatedContent).toContain('## Custom Section');
expect(updatedContent).toContain('Some custom rules here.');
expect(updatedContent).toContain('@./.taskmaster/AGENT.md');
});
});
describe('MCP Configuration Handling', () => {
test('should handle missing .vscode directory gracefully', () => {
// Call onAddRulesProfile without .vscode directory
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
// Should not throw error
expect(() => {
ampProfile.onAddRulesProfile(tempDir, assetsDir);
}).not.toThrow();
});
test('should handle malformed JSON gracefully', () => {
// Create .vscode directory with malformed JSON
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
'{ malformed json'
);
// Should not throw error
expect(() => {
ampProfile.onAddRulesProfile(tempDir, path.join(tempDir, 'assets'));
}).not.toThrow();
});
test('should preserve other VS Code settings when renaming', () => {
// Create .vscode/settings.json with various settings
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
const initialConfig = {
'editor.fontSize': 14,
'editor.tabSize': 2,
mcpServers: {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai']
}
},
'workbench.colorTheme': 'Dark+'
};
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
JSON.stringify(initialConfig, null, '\t')
);
// Call onPostConvertRulesProfile (which handles MCP transformation)
ampProfile.onPostConvertRulesProfile(
tempDir,
path.join(tempDir, 'assets')
);
// Check that other settings are preserved
const settingsFile = path.join(vscodeDirPath, 'settings.json');
const content = fs.readFileSync(settingsFile, 'utf8');
const config = JSON.parse(content);
expect(config['editor.fontSize']).toBe(14);
expect(config['editor.tabSize']).toBe(2);
expect(config['workbench.colorTheme']).toBe('Dark+');
expect(config['amp.mcpServers']).toBeDefined();
expect(config.mcpServers).toBeUndefined();
});
});
describe('Removal Logic', () => {
test('should handle missing files gracefully during removal', () => {
// Should not throw error when removing non-existent files
expect(() => {
ampProfile.onRemoveRulesProfile(tempDir);
}).not.toThrow();
});
test('should handle malformed JSON gracefully during removal', () => {
// Create .vscode directory with malformed JSON
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
'{ malformed json'
);
// Should not throw error
expect(() => {
ampProfile.onRemoveRulesProfile(tempDir);
}).not.toThrow();
});
test('should preserve .vscode directory if it contains other files', () => {
// Create .vscode directory with amp.mcpServers and other files
const vscodeDirPath = path.join(tempDir, '.vscode');
fs.mkdirSync(vscodeDirPath, { recursive: true });
const initialConfig = {
'amp.mcpServers': {
'task-master-ai': {
command: 'npx',
args: ['-y', '--package=task-master-ai', 'task-master-ai']
}
}
};
fs.writeFileSync(
path.join(vscodeDirPath, 'settings.json'),
JSON.stringify(initialConfig, null, '\t')
);
// Create another file in .vscode
fs.writeFileSync(path.join(vscodeDirPath, 'launch.json'), '{}');
// Call onRemoveRulesProfile
ampProfile.onRemoveRulesProfile(tempDir);
// Check that .vscode directory is preserved
expect(fs.existsSync(vscodeDirPath)).toBe(true);
expect(fs.existsSync(path.join(vscodeDirPath, 'launch.json'))).toBe(true);
});
});
describe('Lifecycle Function Integration', () => {
test('should have all required lifecycle functions', () => {
expect(typeof ampProfile.onAddRulesProfile).toBe('function');
expect(typeof ampProfile.onRemoveRulesProfile).toBe('function');
expect(typeof ampProfile.onPostConvertRulesProfile).toBe('function');
});
test('onPostConvertRulesProfile should behave like onAddRulesProfile', () => {
// Create mock source
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
fs.writeFileSync(
path.join(assetsDir, 'AGENTS.md'),
'Task Master instructions'
);
// Call onPostConvertRulesProfile
ampProfile.onPostConvertRulesProfile(tempDir, assetsDir);
// Should have same result as onAddRulesProfile
expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
true
);
expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);
const agentContent = fs.readFileSync(
path.join(tempDir, 'AGENT.md'),
'utf8'
);
expect(agentContent).toContain('@./.taskmaster/AGENT.md');
});
});
describe('Error Handling', () => {
test('should handle file system errors gracefully', () => {
// Mock fs.writeFileSync to throw an error
const originalWriteFileSync = fs.writeFileSync;
fs.writeFileSync = jest.fn().mockImplementation(() => {
throw new Error('Permission denied');
});
// Create mock source
const assetsDir = path.join(tempDir, 'assets');
fs.mkdirSync(assetsDir, { recursive: true });
originalWriteFileSync.call(
fs,
path.join(assetsDir, 'AGENTS.md'),
'Task Master instructions'
);
// Should not throw error
expect(() => {
ampProfile.onAddRulesProfile(tempDir, assetsDir);
}).not.toThrow();
// Restore original function
fs.writeFileSync = originalWriteFileSync;
});
});
});

View File

@@ -2,6 +2,7 @@ import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { claudeProfile } from '../../../src/profiles/claude.js';
// Mock external modules
jest.mock('child_process', () => ({
@@ -77,11 +78,22 @@ describe('Claude Profile Integration', () => {
expect(mkdirCalls).toHaveLength(0);
});
test('does not create MCP configuration files', () => {
test('supports MCP configuration when using rule transformer', () => {
// This test verifies that the Claude profile is configured to support MCP
// The actual MCP file creation is handled by the rule transformer
// Assert - Claude profile should now support MCP configuration
expect(claudeProfile.mcpConfig).toBe(true);
expect(claudeProfile.mcpConfigName).toBe('.mcp.json');
expect(claudeProfile.mcpConfigPath).toBe('.mcp.json');
});
test('mock function does not create MCP configuration files', () => {
// Act
mockCreateClaudeStructure();
// Assert - Claude profile should not create any MCP config files
// Assert - The mock function should not create MCP config files
// (This is expected since the mock doesn't use the rule transformer)
const writeFileCalls = fs.writeFileSync.mock.calls;
const mcpConfigCalls = writeFileCalls.filter(
(call) =>

View File

@@ -92,7 +92,12 @@ describe('MCP Configuration Validation', () => {
RULE_PROFILES.forEach((profileName) => {
const profile = getRulesProfile(profileName);
if (profile.mcpConfig !== false) {
expect(profile.mcpConfigPath).toMatch(/^\.[\w-]+\/[\w_.]+$/);
// Claude profile uses root directory (.), so its path is just '.mcp.json'
if (profileName === 'claude') {
expect(profile.mcpConfigPath).toBe('.mcp.json');
} else {
expect(profile.mcpConfigPath).toMatch(/^\.[\w-]+\/[\w_.]+$/);
}
}
});
});
@@ -123,17 +128,13 @@ describe('MCP Configuration Validation', () => {
});
test('should have null config name for non-MCP profiles', () => {
const clineProfile = getRulesProfile('cline');
expect(clineProfile.mcpConfigName).toBe(null);
// Only codex, cline, and trae profiles should have null config names
const nonMcpProfiles = ['codex', 'cline', 'trae'];
const traeProfile = getRulesProfile('trae');
expect(traeProfile.mcpConfigName).toBe(null);
const claudeProfile = getRulesProfile('claude');
expect(claudeProfile.mcpConfigName).toBe(null);
const codexProfile = getRulesProfile('codex');
expect(codexProfile.mcpConfigName).toBe(null);
for (const profileName of nonMcpProfiles) {
const profile = getRulesProfile(profileName);
expect(profile.mcpConfigName).toBe(null);
}
});
});
@@ -142,6 +143,8 @@ describe('MCP Configuration Validation', () => {
const profileDirs = new Set();
// Profiles that use root directory (can share the same directory)
const rootProfiles = ['claude', 'codex', 'gemini'];
// Profiles that intentionally share the same directory
const sharedDirectoryProfiles = ['amp', 'vscode']; // Both use .vscode
RULE_PROFILES.forEach((profileName) => {
const profile = getRulesProfile(profileName);
@@ -151,10 +154,18 @@ describe('MCP Configuration Validation', () => {
expect(profile.rulesDir).toBe('.');
}
// Profile directories should be unique (except for root profiles)
if (!rootProfiles.includes(profileName) || profile.profileDir !== '.') {
expect(profileDirs.has(profile.profileDir)).toBe(false);
profileDirs.add(profile.profileDir);
// Profile directories should be unique (except for root profiles and shared directory profiles)
if (
!rootProfiles.includes(profileName) &&
!sharedDirectoryProfiles.includes(profileName)
) {
if (profile.profileDir !== '.') {
expect(profileDirs.has(profile.profileDir)).toBe(false);
profileDirs.add(profile.profileDir);
}
} else if (sharedDirectoryProfiles.includes(profileName)) {
// Shared directory profiles should use .vscode
expect(profile.profileDir).toBe('.vscode');
}
});
});
@@ -185,17 +196,19 @@ describe('MCP Configuration Validation', () => {
describe('MCP Configuration Creation Logic', () => {
test('should indicate which profiles require MCP configuration creation', () => {
// Get all profiles that have MCP configuration enabled
const mcpEnabledProfiles = RULE_PROFILES.filter((profileName) => {
const profile = getRulesProfile(profileName);
return profile.mcpConfig !== false;
});
// Verify expected MCP-enabled profiles
expect(mcpEnabledProfiles).toContain('claude');
expect(mcpEnabledProfiles).toContain('cursor');
expect(mcpEnabledProfiles).toContain('gemini');
expect(mcpEnabledProfiles).toContain('roo');
expect(mcpEnabledProfiles).toContain('vscode');
expect(mcpEnabledProfiles).toContain('windsurf');
expect(mcpEnabledProfiles).not.toContain('claude');
expect(mcpEnabledProfiles).not.toContain('cline');
expect(mcpEnabledProfiles).not.toContain('codex');
expect(mcpEnabledProfiles).not.toContain('trae');
@@ -215,18 +228,25 @@ describe('MCP Configuration Validation', () => {
describe('MCP Configuration Path Usage Verification', () => {
test('should verify that rule transformer functions use mcpConfigPath correctly', () => {
// This test verifies that the mcpConfigPath property exists and is properly formatted
// for use with the setupMCPConfiguration function
RULE_PROFILES.forEach((profileName) => {
const profile = getRulesProfile(profileName);
if (profile.mcpConfig !== false) {
// Verify the path is properly formatted for path.join usage
expect(profile.mcpConfigPath.startsWith('/')).toBe(false);
expect(profile.mcpConfigPath).toContain('/');
// Claude profile uses root directory (.), so its path is just '.mcp.json'
if (profileName === 'claude') {
expect(profile.mcpConfigPath).toBe('.mcp.json');
} else {
expect(profile.mcpConfigPath).toContain('/');
}
// Verify it matches the expected pattern: profileDir/configName
const expectedPath = `${profile.profileDir}/${profile.mcpConfigName}`;
expect(profile.mcpConfigPath).toBe(expectedPath);
// For Claude, path.join('.', '.mcp.json') returns '.mcp.json'
const normalizedExpected =
profileName === 'claude' ? '.mcp.json' : expectedPath;
expect(profile.mcpConfigPath).toBe(normalizedExpected);
}
});
});
@@ -250,20 +270,19 @@ describe('MCP Configuration Validation', () => {
describe('MCP Configuration Function Integration', () => {
test('should verify that setupMCPConfiguration receives the correct mcpConfigPath parameter', () => {
// This test verifies the integration between rule transformer and mcp-utils
RULE_PROFILES.forEach((profileName) => {
const profile = getRulesProfile(profileName);
if (profile.mcpConfig !== false) {
// Verify that the mcpConfigPath can be used directly with setupMCPConfiguration
// The function signature is: setupMCPConfiguration(projectDir, mcpConfigPath)
expect(profile.mcpConfigPath).toBeDefined();
expect(typeof profile.mcpConfigPath).toBe('string');
// Verify the path structure is correct for the new function signature
const parts = profile.mcpConfigPath.split('/');
expect(parts).toHaveLength(2); // Should be profileDir/configName
expect(parts[0]).toBe(profile.profileDir);
expect(parts[1]).toBe(profile.mcpConfigName);
if (profileName === 'claude') {
// Claude profile uses root directory, so path is just '.mcp.json'
expect(profile.mcpConfigPath).toBe('.mcp.json');
} else {
const parts = profile.mcpConfigPath.split('/');
expect(parts).toHaveLength(2); // Should be profileDir/configName
expect(parts[0]).toBe(profile.profileDir);
expect(parts[1]).toBe(profile.mcpConfigName);
}
}
});
});
@@ -271,7 +290,9 @@ describe('MCP Configuration Validation', () => {
describe('MCP configuration validation', () => {
const mcpProfiles = ['cursor', 'gemini', 'roo', 'windsurf', 'vscode'];
const nonMcpProfiles = ['claude', 'codex', 'cline', 'trae'];
const nonMcpProfiles = ['codex', 'cline', 'trae'];
const profilesWithLifecycle = ['claude'];
const profilesWithoutLifecycle = ['codex'];
test.each(mcpProfiles)(
'should have valid MCP config for %s profile',
@@ -296,6 +317,7 @@ describe('MCP Configuration Validation', () => {
describe('Profile structure validation', () => {
const mcpProfiles = [
'amp',
'cursor',
'gemini',
'roo',
@@ -304,7 +326,7 @@ describe('MCP Configuration Validation', () => {
'trae',
'vscode'
];
const profilesWithLifecycle = ['claude'];
const profilesWithLifecycle = ['amp', 'claude'];
const profilesWithoutLifecycle = ['codex'];
test.each(mcpProfiles)(

View File

@@ -3,6 +3,7 @@ import {
getRulesProfile
} from '../../../src/utils/rule-transformer.js';
import { RULE_PROFILES } from '../../../src/constants/profiles.js';
import path from 'path';
describe('Rule Transformer - General', () => {
describe('Profile Configuration Validation', () => {
@@ -166,29 +167,28 @@ describe('Rule Transformer - General', () => {
// Check types based on MCP configuration
expect(typeof profileConfig.mcpConfig).toBe('boolean');
if (profileConfig.mcpConfig === false) {
// Profiles without MCP configuration
expect(profileConfig.mcpConfigName).toBe(null);
expect(profileConfig.mcpConfigPath).toBe(null);
} else {
// Profiles with MCP configuration
expect(typeof profileConfig.mcpConfigName).toBe('string');
expect(typeof profileConfig.mcpConfigPath).toBe('string');
if (profileConfig.mcpConfig !== false) {
// Check that mcpConfigPath is properly constructed
expect(profileConfig.mcpConfigPath).toBe(
`${profileConfig.profileDir}/${profileConfig.mcpConfigName}`
const expectedPath = path.join(
profileConfig.profileDir,
profileConfig.mcpConfigName
);
expect(profileConfig.mcpConfigPath).toBe(expectedPath);
}
});
});
it('should have correct MCP configuration for each profile', () => {
const expectedConfigs = {
amp: {
mcpConfig: true,
mcpConfigName: 'settings.json',
expectedPath: '.vscode/settings.json'
},
claude: {
mcpConfig: false,
mcpConfigName: null,
expectedPath: null
mcpConfig: true,
mcpConfigName: '.mcp.json',
expectedPath: '.mcp.json'
},
cline: {
mcpConfig: false,
@@ -245,25 +245,19 @@ describe('Rule Transformer - General', () => {
it('should have consistent profileDir and mcpConfigPath relationship', () => {
RULE_PROFILES.forEach((profile) => {
const profileConfig = getRulesProfile(profile);
if (profileConfig.mcpConfig === false) {
// Profiles without MCP configuration have null mcpConfigPath
expect(profileConfig.mcpConfigPath).toBe(null);
} else {
if (profileConfig.mcpConfig !== false) {
// Profiles with MCP configuration should have valid paths
// The mcpConfigPath should start with the profileDir
expect(profileConfig.mcpConfigPath).toMatch(
new RegExp(
`^${profileConfig.profileDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`
)
);
// The mcpConfigPath should end with the mcpConfigName
expect(profileConfig.mcpConfigPath).toMatch(
new RegExp(
`${profileConfig.mcpConfigName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`
)
);
if (profile === 'claude') {
// Claude uses root directory (.), so path.join('.', '.mcp.json') = '.mcp.json'
expect(profileConfig.mcpConfigPath).toBe('.mcp.json');
} else {
expect(profileConfig.mcpConfigPath).toMatch(
new RegExp(
`^${profileConfig.profileDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`
)
);
}
}
});
});