Compare commits
4 Commits
feat/imple
...
fix/more.n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee2d9921c2 | ||
|
|
fff2172a30 | ||
|
|
5fa7d2ecaa | ||
|
|
8e9d00e03d |
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add Kiro editor rule profile support
|
|
||||||
|
|
||||||
- Add support for Kiro IDE with custom rule files and MCP configuration
|
|
||||||
- Generate rule files in `.kiro/steering/` directory with markdown format
|
|
||||||
- Include MCP server configuration with enhanced file inclusion patterns
|
|
||||||
5
.changeset/claude-code-json-truncation.md
Normal file
5
.changeset/claude-code-json-truncation.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"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.
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
"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.
|
|
||||||
5
.changeset/cuddly-baboons-invent.md
Normal file
5
.changeset/cuddly-baboons-invent.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"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.
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
"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.
|
|
||||||
9
.changeset/grok-4-support.md
Normal file
9
.changeset/grok-4-support.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"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)
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
"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
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
feat: Add Zed editor rule profile with agent rules and MCP config
|
|
||||||
|
|
||||||
- Resolves #637
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add Amp rule profile with AGENT.md and MCP config
|
|
||||||
8
.changeset/quick-laws-cover.md
Normal file
8
.changeset/quick-laws-cover.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"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
|
||||||
5
.changeset/some-lies-grin.md
Normal file
5
.changeset/some-lies-grin.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for MCP Sampling as AI provider, requires no API key, uses the client LLM provider
|
||||||
5
.changeset/spicy-badgers-fail.md
Normal file
5
.changeset/spicy-badgers-fail.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Unify and streamline profile system architecture for improved maintainability
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Add MCP configuration support to Claude Code rules
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
"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
|
|
||||||
5
.changeset/tender-ads-joke.md
Normal file
5
.changeset/tender-ads-joke.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"task-master-ai": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Added Groq provider support
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Correct MCP server name and use 'Add to Cursor' button with updated placeholder keys.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": minor
|
|
||||||
---
|
|
||||||
|
|
||||||
Add OpenCode profile with AGENTS.md and MCP config
|
|
||||||
|
|
||||||
- Resolves #965
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"task-master-ai": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Add missing API keys to .env.example and README.md
|
|
||||||
130
.claude/commands/tm/index.md
Normal file
130
.claude/commands/tm/index.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# 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.
|
||||||
@@ -1,146 +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`
|
|
||||||
- `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.
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
reviews:
|
|
||||||
profile: assertive
|
|
||||||
poem: false
|
|
||||||
auto_review:
|
|
||||||
base_branches:
|
|
||||||
- rc
|
|
||||||
- beta
|
|
||||||
- alpha
|
|
||||||
- production
|
|
||||||
- next
|
|
||||||
@@ -8,7 +8,6 @@ GROQ_API_KEY=YOUR_GROQ_KEY_HERE
|
|||||||
OPENROUTER_API_KEY=YOUR_OPENROUTER_KEY_HERE
|
OPENROUTER_API_KEY=YOUR_OPENROUTER_KEY_HERE
|
||||||
XAI_API_KEY=YOUR_XAI_KEY_HERE
|
XAI_API_KEY=YOUR_XAI_KEY_HERE
|
||||||
AZURE_OPENAI_API_KEY=YOUR_AZURE_KEY_HERE
|
AZURE_OPENAI_API_KEY=YOUR_AZURE_KEY_HERE
|
||||||
OLLAMA_API_KEY=YOUR_OLLAMA_API_KEY_HERE
|
|
||||||
|
|
||||||
# Google Vertex AI Configuration
|
# Google Vertex AI Configuration
|
||||||
VERTEX_PROJECT_ID=your-gcp-project-id
|
VERTEX_PROJECT_ID=your-gcp-project-id
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
{
|
{
|
||||||
"models": {
|
"models": {
|
||||||
"main": {
|
"main": {
|
||||||
"provider": "anthropic",
|
"provider": "groq",
|
||||||
"modelId": "claude-3-7-sonnet-20250219",
|
"modelId": "llama-3.1-8b-instant",
|
||||||
"maxTokens": 120000,
|
"maxTokens": 131072,
|
||||||
"temperature": 0.2
|
"temperature": 0.2
|
||||||
},
|
},
|
||||||
"research": {
|
"research": {
|
||||||
"provider": "perplexity",
|
"provider": "groq",
|
||||||
"modelId": "sonar",
|
"modelId": "llama-3.3-70b-versatile",
|
||||||
"maxTokens": 8700,
|
"maxTokens": 32768,
|
||||||
"temperature": 0.1
|
"temperature": 0.1
|
||||||
},
|
},
|
||||||
"fallback": {
|
"fallback": {
|
||||||
"provider": "anthropic",
|
"provider": "anthropic",
|
||||||
"modelId": "claude-3-5-sonnet-20241022",
|
"modelId": "claude-3-7-sonnet-20250219",
|
||||||
"maxTokens": 8192,
|
"maxTokens": 128000,
|
||||||
"temperature": 0.2
|
"temperature": 0.2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
23
.taskmaster/tasks/task_001_test-tag.txt
Normal file
23
.taskmaster/tasks/task_001_test-tag.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 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.
|
||||||
File diff suppressed because one or more lines are too long
50
CHANGELOG.md
50
CHANGELOG.md
@@ -1,55 +1,5 @@
|
|||||||
# task-master-ai
|
# 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
|
## 0.19.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -25,7 +25,11 @@ For more detailed information, check out the documentation in the `docs` directo
|
|||||||
|
|
||||||
#### Quick Install for Cursor 1.0+ (One-Click)
|
#### Quick Install for Cursor 1.0+ (One-Click)
|
||||||
|
|
||||||
[](https://cursor.com/install-mcp?name=task-master-ai&config=eyJjb21tYW5kIjoibnB4IC15IC0tcGFja2FnZT10YXNrLW1hc3Rlci1haSB0YXNrLW1hc3Rlci1haSIsImVudiI6eyJBTlRIUk9QSUNfQVBJX0tFWSI6IllPVVJfQU5USFJPUElDX0FQSV9LRVlfSEVSRSIsIlBFUlBMRVhJVFlfQVBJX0tFWSI6IllPVVJfUEVSUExFWElUWV9BUElfS0VZX0hFUkUiLCJPUEVOQUlfQVBJX0tFWSI6IllPVVJfT1BFTkFJX0tFWV9IRVJFIiwiR09PR0xFX0FQSV9LRVkiOiJZT1VSX0dPT0dMRV9LRVlfSEVSRSIsIk1JU1RSQUxfQVBJX0tFWSI6IllPVVJfTUlTVFJBTF9LRVlfSEVSRSIsIkdST1FfQVBJX0tFWSI6IllPVVJfR1JPUV9LRVlfSEVSRSIsIk9QRU5ST1VURVJfQVBJX0tFWSI6IllPVVJfT1BFTlJPVVRFUl9LRVlfSEVSRSIsIlhBSV9BUElfS0VZIjoiWU9VUl9YQUlfS0VZX0hFUkUiLCJBWlVSRV9PUEVOQUlfQVBJX0tFWSI6IllPVVJfQVpVUkVfS0VZX0hFUkUiLCJPTExBTUFfQVBJX0tFWSI6IllPVVJfT0xMQU1BX0FQSV9LRVlfSEVSRSJ9fQ%3D%3D)
|
📋 Click the copy button (top-right of code block) then paste into your browser:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cursor://anysphere.cursor-deeplink/mcp/install?name=taskmaster-ai&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIi0tcGFja2FnZT10YXNrLW1hc3Rlci1haSIsInRhc2stbWFzdGVyLWFpIl0sImVudiI6eyJBTlRIUk9QSUNfQVBJX0tFWSI6IllPVVJfQU5USFJPUElDX0FQSV9LRVlfSEVSRSIsIlBFUlBMRVhJVFlfQVBJX0tFWSI6IllPVVJfUEVSUExFWElUWV9BUElfS0VZX0hFUkUiLCJPUEVOQUlfQVBJX0tFWSI6IllPVVJfT1BFTkFJX0tFWV9IRVJFIiwiR09PR0xFX0FQSV9LRVkiOiJZT1VSX0dPT0dMRV9LRVlfSEVSRSIsIk1JU1RSQUxfQVBJX0tFWSI6IllPVVJfTUlTVFJBTF9LRVlfSEVSRSIsIk9QRU5ST1VURVJfQVBJX0tFWSI6IllPVVJfT1BFTlJPVVRFUl9LRVlfSEVSRSIsIlhBSV9BUElfS0VZIjoiWU9VUl9YQUlfS0VZX0hFUkUiLCJBWlVSRV9PUEVOQUlfQVBJX0tFWSI6IllPVVJfQVpVUkVfS0VZX0hFUkUiLCJPTExBTUFfQVBJX0tFWSI6IllPVVJfT0xMQU1BX0FQSV9LRVlfSEVSRSJ9fQo=
|
||||||
|
```
|
||||||
|
|
||||||
> **Note:** After clicking the link, you'll still need to add your API keys to the configuration. The link installs the MCP server with placeholder keys that you'll need to replace with your actual API keys.
|
> **Note:** After clicking the link, you'll still need to add your API keys to the configuration. The link installs the MCP server with placeholder keys that you'll need to replace with your actual API keys.
|
||||||
|
|
||||||
@@ -69,7 +73,7 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"task-master-ai": {
|
"taskmaster-ai": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
||||||
"env": {
|
"env": {
|
||||||
@@ -78,7 +82,6 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
|||||||
"OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE",
|
"OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE",
|
||||||
"GOOGLE_API_KEY": "YOUR_GOOGLE_KEY_HERE",
|
"GOOGLE_API_KEY": "YOUR_GOOGLE_KEY_HERE",
|
||||||
"MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE",
|
"MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE",
|
||||||
"GROQ_API_KEY": "YOUR_GROQ_KEY_HERE",
|
|
||||||
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
|
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
|
||||||
"XAI_API_KEY": "YOUR_XAI_KEY_HERE",
|
"XAI_API_KEY": "YOUR_XAI_KEY_HERE",
|
||||||
"AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE",
|
"AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE",
|
||||||
@@ -98,7 +101,7 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"servers": {
|
"servers": {
|
||||||
"task-master-ai": {
|
"taskmaster-ai": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
||||||
"env": {
|
"env": {
|
||||||
@@ -107,11 +110,9 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
|||||||
"OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE",
|
"OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE",
|
||||||
"GOOGLE_API_KEY": "YOUR_GOOGLE_KEY_HERE",
|
"GOOGLE_API_KEY": "YOUR_GOOGLE_KEY_HERE",
|
||||||
"MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE",
|
"MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE",
|
||||||
"GROQ_API_KEY": "YOUR_GROQ_KEY_HERE",
|
|
||||||
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
|
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
|
||||||
"XAI_API_KEY": "YOUR_XAI_KEY_HERE",
|
"XAI_API_KEY": "YOUR_XAI_KEY_HERE",
|
||||||
"AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE",
|
"AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE"
|
||||||
"OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE"
|
|
||||||
},
|
},
|
||||||
"type": "stdio"
|
"type": "stdio"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
console.log('hello world');
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
{
|
|
||||||
"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. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Task Master AI - Agent Integration Guide
|
# Task Master AI - Claude Code Integration Guide
|
||||||
|
|
||||||
## Essential Commands
|
## Essential Commands
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
# API Keys (Required to enable respective provider)
|
# API Keys (Required to enable respective provider)
|
||||||
ANTHROPIC_API_KEY="your_anthropic_api_key_here" # Required: Format: sk-ant-api03-...
|
ANTHROPIC_API_KEY="your_anthropic_api_key_here" # Required: Format: sk-ant-api03-...
|
||||||
PERPLEXITY_API_KEY="your_perplexity_api_key_here" # Optional: Format: pplx-...
|
PERPLEXITY_API_KEY="your_perplexity_api_key_here" # Optional: Format: pplx-...
|
||||||
OPENAI_API_KEY="your_openai_api_key_here" # Optional, for OpenAI models. Format: sk-proj-...
|
OPENAI_API_KEY="your_openai_api_key_here" # Optional, for OpenAI/OpenRouter models. Format: sk-proj-...
|
||||||
GOOGLE_API_KEY="your_google_api_key_here" # Optional, for Google Gemini models.
|
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.
|
MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral AI models.
|
||||||
XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models.
|
XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models.
|
||||||
GROQ_API_KEY="YOUR_GROQ_KEY_HERE" # Optional, for Groq models.
|
|
||||||
OPENROUTER_API_KEY="YOUR_OPENROUTER_KEY_HERE" # Optional, for OpenRouter models.
|
|
||||||
AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmaster/config.json).
|
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.
|
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_...
|
GITHUB_API_KEY="your_github_api_key_here" # Optional: For GitHub import/export features. Format: ghp_... or github_pat_...
|
||||||
@@ -4,7 +4,30 @@ Taskmaster uses two primary methods for configuration:
|
|||||||
|
|
||||||
1. **`.taskmaster/config.json` File (Recommended - New Structure)**
|
1. **`.taskmaster/config.json` File (Recommended - New Structure)**
|
||||||
|
|
||||||
- This JSON file stores most configuration settings, including AI model selections, parameters, logging levels, and project defaults.
|
- 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.
|
||||||
- **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`.
|
- **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`.
|
- **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.
|
- **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.
|
||||||
@@ -45,12 +68,11 @@ Taskmaster uses two primary methods for configuration:
|
|||||||
"azureBaseURL": "https://your-endpoint.azure.com/openai/deployments",
|
"azureBaseURL": "https://your-endpoint.azure.com/openai/deployments",
|
||||||
"vertexProjectId": "your-gcp-project-id",
|
"vertexProjectId": "your-gcp-project-id",
|
||||||
"vertexLocation": "us-central1",
|
"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)**
|
2. **Legacy `.taskmasterconfig` File (Backward Compatibility)**
|
||||||
|
|
||||||
@@ -176,6 +198,8 @@ node scripts/init.js
|
|||||||
|
|
||||||
### MCP (Model Context Protocol) Provider
|
### 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**:
|
1. **Prerequisites**:
|
||||||
- An active MCP session with sampling capability
|
- An active MCP session with sampling capability
|
||||||
- MCP client with sampling support (e.g. VS Code)
|
- MCP client with sampling support (e.g. VS Code)
|
||||||
@@ -214,24 +238,12 @@ node scripts/init.js
|
|||||||
- Must be running in an MCP context (session must be available)
|
- Must be running in an MCP context (session must be available)
|
||||||
- Session must provide `clientCapabilities.sampling` capability
|
- Session must provide `clientCapabilities.sampling` capability
|
||||||
|
|
||||||
6. **Best Practices**:
|
5. **Best Practices**:
|
||||||
- Always configure a non-MCP fallback provider
|
- Always configure a non-MCP fallback provider
|
||||||
- Use `mcp` for main/research roles when in MCP environments
|
- Use `mcp` for main/research roles when in MCP environments
|
||||||
- Test sampling capability before production use
|
- Test sampling capability before production use
|
||||||
|
|
||||||
7. **Setup Commands**:
|
6. **Troubleshooting**:
|
||||||
```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
|
- "MCP provider requires session context" → Ensure running in MCP environment
|
||||||
- See the [MCP Provider Guide](./mcp-provider-guide.md) for detailed troubleshooting
|
- See the [MCP Provider Guide](./mcp-provider-guide.md) for detailed troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Available Models as of July 16, 2025
|
# Available Models as of July 10, 2025
|
||||||
|
|
||||||
## Main Models
|
## Main Models
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
| xai | grok-3 | — | 3 | 15 |
|
| xai | grok-3 | — | 3 | 15 |
|
||||||
| xai | grok-3-fast | — | 5 | 25 |
|
| xai | grok-3-fast | — | 5 | 25 |
|
||||||
| xai | grok-4 | — | 3 | 15 |
|
| 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.3-70b-versatile | 0.55 | 0.59 | 0.79 |
|
||||||
| groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
|
| groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
|
||||||
| groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
|
| groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
|
||||||
@@ -145,7 +144,6 @@
|
|||||||
| xai | grok-3 | — | 3 | 15 |
|
| xai | grok-3 | — | 3 | 15 |
|
||||||
| xai | grok-3-fast | — | 5 | 25 |
|
| xai | grok-3-fast | — | 5 | 25 |
|
||||||
| xai | grok-4 | — | 3 | 15 |
|
| 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.3-70b-versatile | 0.55 | 0.59 | 0.79 |
|
||||||
| groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
|
| groq | llama-3.1-8b-instant | 0.32 | 0.05 | 0.08 |
|
||||||
| groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
|
| groq | llama-4-scout | 0.45 | 0.11 | 0.34 |
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ export async function addTaskDirect(args, log, context = {}) {
|
|||||||
},
|
},
|
||||||
'json', // outputFormat
|
'json', // outputFormat
|
||||||
manualTaskData, // Pass the manual task data
|
manualTaskData, // Pass the manual task data
|
||||||
false // research flag is false for manual creation
|
false, // research flag is false for manual creation
|
||||||
|
projectRoot // Pass projectRoot
|
||||||
);
|
);
|
||||||
newTaskId = result.newTaskId;
|
newTaskId = result.newTaskId;
|
||||||
telemetryData = result.telemetryData;
|
telemetryData = result.telemetryData;
|
||||||
|
|||||||
37
package-lock.json
generated
37
package-lock.json
generated
@@ -1,17 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "task-master-ai",
|
"name": "task-master-ai",
|
||||||
"version": "0.20.0",
|
"version": "0.19.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "task-master-ai",
|
"name": "task-master-ai",
|
||||||
"version": "0.20.0",
|
"version": "0.19.0",
|
||||||
"license": "MIT WITH Commons-Clause",
|
"license": "MIT WITH Commons-Clause",
|
||||||
"workspaces": [
|
|
||||||
"apps/*",
|
|
||||||
"."
|
|
||||||
],
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "^2.2.9",
|
"@ai-sdk/amazon-bedrock": "^2.2.9",
|
||||||
"@ai-sdk/anthropic": "^1.2.10",
|
"@ai-sdk/anthropic": "^1.2.10",
|
||||||
@@ -84,13 +80,6 @@
|
|||||||
"ai-sdk-provider-gemini-cli": "^0.0.4"
|
"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": {
|
"node_modules/@ai-sdk/amazon-bedrock": {
|
||||||
"version": "2.2.10",
|
"version": "2.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/amazon-bedrock/-/amazon-bedrock-2.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/amazon-bedrock/-/amazon-bedrock-2.2.10.tgz",
|
||||||
@@ -7351,10 +7340,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/extension": {
|
|
||||||
"resolved": "apps/extension",
|
|
||||||
"link": true
|
|
||||||
},
|
|
||||||
"node_modules/external-editor": {
|
"node_modules/external-editor": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||||
@@ -12992,10 +12977,6 @@
|
|||||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
"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": {
|
"node_modules/term-size": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
|
||||||
@@ -13204,20 +13185,6 @@
|
|||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/uint8array-extras": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "task-master-ai",
|
"name": "task-master-ai",
|
||||||
"version": "0.20.0",
|
"version": "0.19.0",
|
||||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
"task-master-mcp": "mcp-server/server.js",
|
"task-master-mcp": "mcp-server/server.js",
|
||||||
"task-master-ai": "mcp-server/server.js"
|
"task-master-ai": "mcp-server/server.js"
|
||||||
},
|
},
|
||||||
"workspaces": ["apps/*", "."],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
||||||
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",
|
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",
|
||||||
|
|||||||
@@ -8,48 +8,47 @@
|
|||||||
|
|
||||||
// --- Core Dependencies ---
|
// --- Core Dependencies ---
|
||||||
import {
|
import {
|
||||||
MODEL_MAP,
|
|
||||||
getAzureBaseURL,
|
|
||||||
getBaseUrlForRole,
|
|
||||||
getBedrockBaseURL,
|
|
||||||
getDebugFlag,
|
|
||||||
getFallbackModelId,
|
|
||||||
getFallbackProvider,
|
|
||||||
getMainModelId,
|
|
||||||
getMainProvider,
|
getMainProvider,
|
||||||
getOllamaBaseURL,
|
getMainModelId,
|
||||||
getParametersForRole,
|
|
||||||
getResearchModelId,
|
|
||||||
getResearchProvider,
|
getResearchProvider,
|
||||||
|
getResearchModelId,
|
||||||
|
getFallbackProvider,
|
||||||
|
getFallbackModelId,
|
||||||
|
getParametersForRole,
|
||||||
getResponseLanguage,
|
getResponseLanguage,
|
||||||
getUserId,
|
getUserId,
|
||||||
getVertexLocation,
|
MODEL_MAP,
|
||||||
getVertexProjectId,
|
getDebugFlag,
|
||||||
|
getBaseUrlForRole,
|
||||||
isApiKeySet,
|
isApiKeySet,
|
||||||
|
getOllamaBaseURL,
|
||||||
|
getAzureBaseURL,
|
||||||
|
getBedrockBaseURL,
|
||||||
|
getVertexProjectId,
|
||||||
|
getVertexLocation,
|
||||||
providersWithoutApiKeys
|
providersWithoutApiKeys
|
||||||
} from './config-manager.js';
|
} from './config-manager.js';
|
||||||
import {
|
import {
|
||||||
findProjectRoot,
|
|
||||||
getCurrentTag,
|
|
||||||
log,
|
log,
|
||||||
resolveEnvVariable
|
findProjectRoot,
|
||||||
|
resolveEnvVariable,
|
||||||
|
getCurrentTag
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
|
||||||
// Import provider classes
|
// Import provider classes
|
||||||
import {
|
import {
|
||||||
AnthropicAIProvider,
|
AnthropicAIProvider,
|
||||||
AzureProvider,
|
|
||||||
BedrockAIProvider,
|
|
||||||
ClaudeCodeProvider,
|
|
||||||
GeminiCliProvider,
|
|
||||||
GoogleAIProvider,
|
|
||||||
GroqProvider,
|
|
||||||
OllamaAIProvider,
|
|
||||||
OpenAIProvider,
|
|
||||||
OpenRouterAIProvider,
|
|
||||||
PerplexityAIProvider,
|
PerplexityAIProvider,
|
||||||
|
GoogleAIProvider,
|
||||||
|
OpenAIProvider,
|
||||||
|
XAIProvider,
|
||||||
|
OpenRouterAIProvider,
|
||||||
|
OllamaAIProvider,
|
||||||
|
BedrockAIProvider,
|
||||||
|
AzureProvider,
|
||||||
VertexAIProvider,
|
VertexAIProvider,
|
||||||
XAIProvider
|
ClaudeCodeProvider,
|
||||||
|
GeminiCliProvider
|
||||||
} from '../../src/ai-providers/index.js';
|
} from '../../src/ai-providers/index.js';
|
||||||
|
|
||||||
// Import the provider registry
|
// Import the provider registry
|
||||||
@@ -62,7 +61,6 @@ const PROVIDERS = {
|
|||||||
google: new GoogleAIProvider(),
|
google: new GoogleAIProvider(),
|
||||||
openai: new OpenAIProvider(),
|
openai: new OpenAIProvider(),
|
||||||
xai: new XAIProvider(),
|
xai: new XAIProvider(),
|
||||||
groq: new GroqProvider(),
|
|
||||||
openrouter: new OpenRouterAIProvider(),
|
openrouter: new OpenRouterAIProvider(),
|
||||||
ollama: new OllamaAIProvider(),
|
ollama: new OllamaAIProvider(),
|
||||||
bedrock: new BedrockAIProvider(),
|
bedrock: new BedrockAIProvider(),
|
||||||
|
|||||||
@@ -2353,14 +2353,10 @@ ${result.result}
|
|||||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||||
.action(async (taskId, options) => {
|
.action(async (taskId, options) => {
|
||||||
// Initialize TaskMaster
|
// Initialize TaskMaster
|
||||||
const initOptions = {
|
const taskMaster = initTaskMaster({
|
||||||
tasksPath: options.file || true
|
tasksPath: options.file || true,
|
||||||
};
|
complexityReportPath: options.report || false
|
||||||
// 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 idArg = taskId || options.id;
|
||||||
const statusFilter = options.status;
|
const statusFilter = options.status;
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { AI_COMMAND_NAMES } from '../../src/constants/commands.js';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { log, findProjectRoot, resolveEnvVariable, isEmpty } from './utils.js';
|
||||||
import {
|
import {
|
||||||
LEGACY_CONFIG_FILE,
|
LEGACY_CONFIG_FILE,
|
||||||
TASKMASTER_DIR
|
TASKMASTER_DIR
|
||||||
} from '../../src/constants/paths.js';
|
} from '../../src/constants/paths.js';
|
||||||
|
import { findConfigPath } from '../../src/utils/path-utils.js';
|
||||||
import {
|
import {
|
||||||
ALL_PROVIDERS,
|
VALIDATED_PROVIDERS,
|
||||||
CUSTOM_PROVIDERS,
|
CUSTOM_PROVIDERS,
|
||||||
CUSTOM_PROVIDERS_ARRAY,
|
CUSTOM_PROVIDERS_ARRAY,
|
||||||
VALIDATED_PROVIDERS
|
ALL_PROVIDERS
|
||||||
} from '../../src/constants/providers.js';
|
} from '../../src/constants/providers.js';
|
||||||
import { findConfigPath } from '../../src/utils/path-utils.js';
|
import { AI_COMMAND_NAMES } from '../../src/constants/commands.js';
|
||||||
import { findProjectRoot, isEmpty, log, resolveEnvVariable } from './utils.js';
|
|
||||||
|
|
||||||
// Calculate __dirname in ESM
|
// Calculate __dirname in ESM
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -641,7 +641,6 @@ function isApiKeySet(providerName, session = null, projectRoot = null) {
|
|||||||
azure: 'AZURE_OPENAI_API_KEY',
|
azure: 'AZURE_OPENAI_API_KEY',
|
||||||
openrouter: 'OPENROUTER_API_KEY',
|
openrouter: 'OPENROUTER_API_KEY',
|
||||||
xai: 'XAI_API_KEY',
|
xai: 'XAI_API_KEY',
|
||||||
groq: 'GROQ_API_KEY',
|
|
||||||
vertex: 'GOOGLE_API_KEY', // Vertex uses the same key as Google
|
vertex: 'GOOGLE_API_KEY', // Vertex uses the same key as Google
|
||||||
'claude-code': 'CLAUDE_CODE_API_KEY', // Not actually used, but included for consistency
|
'claude-code': 'CLAUDE_CODE_API_KEY', // Not actually used, but included for consistency
|
||||||
bedrock: 'AWS_ACCESS_KEY_ID' // Bedrock uses AWS credentials
|
bedrock: 'AWS_ACCESS_KEY_ID' // Bedrock uses AWS credentials
|
||||||
@@ -727,10 +726,6 @@ function getMcpApiKeyStatus(providerName, projectRoot = null) {
|
|||||||
apiKeyToCheck = mcpEnv.XAI_API_KEY;
|
apiKeyToCheck = mcpEnv.XAI_API_KEY;
|
||||||
placeholderValue = 'YOUR_XAI_API_KEY_HERE';
|
placeholderValue = 'YOUR_XAI_API_KEY_HERE';
|
||||||
break;
|
break;
|
||||||
case 'groq':
|
|
||||||
apiKeyToCheck = mcpEnv.GROQ_API_KEY;
|
|
||||||
placeholderValue = 'YOUR_GROQ_API_KEY_HERE';
|
|
||||||
break;
|
|
||||||
case 'ollama':
|
case 'ollama':
|
||||||
return true; // No key needed
|
return true; // No key needed
|
||||||
case 'claude-code':
|
case 'claude-code':
|
||||||
|
|||||||
@@ -295,16 +295,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groq": [
|
"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",
|
"id": "llama-3.3-70b-versatile",
|
||||||
"swe_score": 0.55,
|
"swe_score": 0.55,
|
||||||
|
|||||||
@@ -561,6 +561,16 @@ async function addTask(
|
|||||||
writeJSON(tasksPath, rawData, projectRoot, targetTag);
|
writeJSON(tasksPath, rawData, projectRoot, targetTag);
|
||||||
report('DEBUG: tasks.json written.', 'debug');
|
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)
|
// Show success message - only for text output (CLI)
|
||||||
if (outputFormat === 'text') {
|
if (outputFormat === 'text') {
|
||||||
const table = new Table({
|
const table = new Table({
|
||||||
|
|||||||
@@ -461,44 +461,19 @@ async function expandTask(
|
|||||||
`${combinedAdditionalContext}\n\n# Project Context\n\n${gatheredContext}`.trim();
|
`${combinedAdditionalContext}\n\n# Project Context\n\n${gatheredContext}`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure expansionPrompt is a string (handle both string and object formats)
|
|
||||||
let expansionPromptText = undefined;
|
|
||||||
if (taskAnalysis?.expansionPrompt) {
|
|
||||||
if (typeof taskAnalysis.expansionPrompt === 'string') {
|
|
||||||
expansionPromptText = taskAnalysis.expansionPrompt;
|
|
||||||
} else if (
|
|
||||||
typeof taskAnalysis.expansionPrompt === 'object' &&
|
|
||||||
taskAnalysis.expansionPrompt.text
|
|
||||||
) {
|
|
||||||
expansionPromptText = taskAnalysis.expansionPrompt.text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure gatheredContext is a string (handle both string and object formats)
|
|
||||||
let gatheredContextText = gatheredContext;
|
|
||||||
if (typeof gatheredContext === 'object' && gatheredContext !== null) {
|
|
||||||
if (gatheredContext.data) {
|
|
||||||
gatheredContextText = gatheredContext.data;
|
|
||||||
} else if (gatheredContext.text) {
|
|
||||||
gatheredContextText = gatheredContext.text;
|
|
||||||
} else {
|
|
||||||
gatheredContextText = JSON.stringify(gatheredContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const promptParams = {
|
const promptParams = {
|
||||||
task: task,
|
task: task,
|
||||||
subtaskCount: finalSubtaskCount,
|
subtaskCount: finalSubtaskCount,
|
||||||
nextSubtaskId: nextSubtaskId,
|
nextSubtaskId: nextSubtaskId,
|
||||||
additionalContext: additionalContext,
|
additionalContext: additionalContext,
|
||||||
complexityReasoningContext: complexityReasoningContext,
|
complexityReasoningContext: complexityReasoningContext,
|
||||||
gatheredContext: gatheredContextText || '',
|
gatheredContext: gatheredContext,
|
||||||
useResearch: useResearch,
|
useResearch: useResearch,
|
||||||
expansionPrompt: expansionPromptText || undefined
|
expansionPrompt: taskAnalysis?.expansionPrompt || undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
let variantKey = 'default';
|
let variantKey = 'default';
|
||||||
if (expansionPromptText) {
|
if (taskAnalysis?.expansionPrompt) {
|
||||||
variantKey = 'complexity-report';
|
variantKey = 'complexity-report';
|
||||||
logger.info(
|
logger.info(
|
||||||
`Using expansion prompt from complexity report for task ${task.id}.`
|
`Using expansion prompt from complexity report for task ${task.id}.`
|
||||||
|
|||||||
@@ -190,45 +190,8 @@ function parseUpdatedTaskFromText(text, expectedTaskId, logFn, isMCP) {
|
|||||||
throw new Error('Parsed AI response is not a valid JSON object.');
|
throw new Error('Parsed AI response is not a valid JSON object.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preprocess the task to ensure subtasks have proper structure
|
|
||||||
const preprocessedTask = {
|
|
||||||
...parsedTask,
|
|
||||||
status: parsedTask.status || 'pending',
|
|
||||||
dependencies: Array.isArray(parsedTask.dependencies)
|
|
||||||
? parsedTask.dependencies
|
|
||||||
: [],
|
|
||||||
details:
|
|
||||||
typeof parsedTask.details === 'string'
|
|
||||||
? parsedTask.details
|
|
||||||
: String(parsedTask.details || ''),
|
|
||||||
testStrategy:
|
|
||||||
typeof parsedTask.testStrategy === 'string'
|
|
||||||
? parsedTask.testStrategy
|
|
||||||
: String(parsedTask.testStrategy || ''),
|
|
||||||
// Ensure subtasks is an array and each subtask has required fields
|
|
||||||
subtasks: Array.isArray(parsedTask.subtasks)
|
|
||||||
? parsedTask.subtasks.map((subtask) => ({
|
|
||||||
...subtask,
|
|
||||||
title: subtask.title || '',
|
|
||||||
description: subtask.description || '',
|
|
||||||
status: subtask.status || 'pending',
|
|
||||||
dependencies: Array.isArray(subtask.dependencies)
|
|
||||||
? subtask.dependencies
|
|
||||||
: [],
|
|
||||||
details:
|
|
||||||
typeof subtask.details === 'string'
|
|
||||||
? subtask.details
|
|
||||||
: String(subtask.details || ''),
|
|
||||||
testStrategy:
|
|
||||||
typeof subtask.testStrategy === 'string'
|
|
||||||
? subtask.testStrategy
|
|
||||||
: String(subtask.testStrategy || '')
|
|
||||||
}))
|
|
||||||
: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validate the parsed task object using Zod
|
// Validate the parsed task object using Zod
|
||||||
const validationResult = updatedTaskSchema.safeParse(preprocessedTask);
|
const validationResult = updatedTaskSchema.safeParse(parsedTask);
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
report('error', 'Parsed task object failed Zod validation.');
|
report('error', 'Parsed task object failed Zod validation.');
|
||||||
validationResult.error.errors.forEach((err) => {
|
validationResult.error.errors.forEach((err) => {
|
||||||
|
|||||||
@@ -196,18 +196,7 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preprocess tasks to ensure required fields have proper defaults
|
const validationResult = updatedTaskArraySchema.safeParse(parsedTasks);
|
||||||
const preprocessedTasks = parsedTasks.map((task) => ({
|
|
||||||
...task,
|
|
||||||
// Ensure subtasks is always an array (not null or undefined)
|
|
||||||
subtasks: Array.isArray(task.subtasks) ? task.subtasks : [],
|
|
||||||
// Ensure status has a default value if missing
|
|
||||||
status: task.status || 'pending',
|
|
||||||
// Ensure dependencies is always an array
|
|
||||||
dependencies: Array.isArray(task.dependencies) ? task.dependencies : []
|
|
||||||
}));
|
|
||||||
|
|
||||||
const validationResult = updatedTaskArraySchema.safeParse(preprocessedTasks);
|
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
report('error', 'Parsed task array failed Zod validation.');
|
report('error', 'Parsed task array failed Zod validation.');
|
||||||
validationResult.error.errors.forEach((err) => {
|
validationResult.error.errors.forEach((err) => {
|
||||||
@@ -453,17 +442,7 @@ async function updateTasks(
|
|||||||
data.tasks.forEach((task, index) => {
|
data.tasks.forEach((task, index) => {
|
||||||
if (updatedTasksMap.has(task.id)) {
|
if (updatedTasksMap.has(task.id)) {
|
||||||
// Only update if the task was part of the set sent to AI
|
// Only update if the task was part of the set sent to AI
|
||||||
const updatedTask = updatedTasksMap.get(task.id);
|
data.tasks[index] = updatedTasksMap.get(task.id);
|
||||||
// Merge the updated task with the existing one to preserve fields like subtasks
|
|
||||||
data.tasks[index] = {
|
|
||||||
...task, // Keep all existing fields
|
|
||||||
...updatedTask, // Override with updated fields
|
|
||||||
// Ensure subtasks field is preserved if not provided by AI
|
|
||||||
subtasks:
|
|
||||||
updatedTask.subtasks !== undefined
|
|
||||||
? updatedTask.subtasks
|
|
||||||
: task.subtasks
|
|
||||||
};
|
|
||||||
actualUpdateCount++;
|
actualUpdateCount++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,14 +14,6 @@ export class GroqProvider extends BaseAIProvider {
|
|||||||
this.name = 'Groq';
|
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.
|
* Creates and returns a Groq client instance.
|
||||||
* @param {object} params - Parameters for client initialization
|
* @param {object} params - Parameters for client initialization
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {'amp' | 'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'kiro' | 'opencode' | 'roo' | 'trae' | 'windsurf' | 'vscode' | 'zed'} RulesProfile
|
* @typedef {'claude' | 'cline' | 'codex' | 'cursor' | 'gemini' | 'roo' | 'trae' | 'windsurf' | 'vscode'} RulesProfile
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,19 +10,15 @@
|
|||||||
*
|
*
|
||||||
* @type {RulesProfile[]}
|
* @type {RulesProfile[]}
|
||||||
* @description Defines possible rule profile sets:
|
* @description Defines possible rule profile sets:
|
||||||
* - amp: Amp Code integration
|
|
||||||
* - claude: Claude Code integration
|
* - claude: Claude Code integration
|
||||||
* - cline: Cline IDE rules
|
* - cline: Cline IDE rules
|
||||||
* - codex: Codex integration
|
* - codex: Codex integration
|
||||||
* - cursor: Cursor IDE rules
|
* - cursor: Cursor IDE rules
|
||||||
* - gemini: Gemini integration
|
* - gemini: Gemini integration
|
||||||
* - kiro: Kiro IDE rules
|
|
||||||
* - opencode: OpenCode integration
|
|
||||||
* - roo: Roo Code IDE rules
|
* - roo: Roo Code IDE rules
|
||||||
* - trae: Trae IDE rules
|
* - trae: Trae IDE rules
|
||||||
* - vscode: VS Code with GitHub Copilot integration
|
* - vscode: VS Code with GitHub Copilot integration
|
||||||
* - windsurf: Windsurf IDE rules
|
* - windsurf: Windsurf IDE rules
|
||||||
* - zed: Zed IDE rules
|
|
||||||
*
|
*
|
||||||
* To add a new rule profile:
|
* To add a new rule profile:
|
||||||
* 1. Add the profile name to this array
|
* 1. Add the profile name to this array
|
||||||
@@ -30,19 +26,15 @@
|
|||||||
* 3. Export it as {profile}Profile in src/profiles/index.js
|
* 3. Export it as {profile}Profile in src/profiles/index.js
|
||||||
*/
|
*/
|
||||||
export const RULE_PROFILES = [
|
export const RULE_PROFILES = [
|
||||||
'amp',
|
|
||||||
'claude',
|
'claude',
|
||||||
'cline',
|
'cline',
|
||||||
'codex',
|
'codex',
|
||||||
'cursor',
|
'cursor',
|
||||||
'gemini',
|
'gemini',
|
||||||
'kiro',
|
|
||||||
'opencode',
|
|
||||||
'roo',
|
'roo',
|
||||||
'trae',
|
'trae',
|
||||||
'vscode',
|
'vscode',
|
||||||
'windsurf',
|
'windsurf'
|
||||||
'zed'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,277 +0,0 @@
|
|||||||
// 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 };
|
|
||||||
@@ -46,9 +46,7 @@ export function createProfile(editorConfig) {
|
|||||||
onPostConvert
|
onPostConvert
|
||||||
} = editorConfig;
|
} = editorConfig;
|
||||||
|
|
||||||
const mcpConfigPath = mcpConfigName
|
const mcpConfigPath = mcpConfigName ? `${profileDir}/${mcpConfigName}` : null;
|
||||||
? path.join(profileDir, mcpConfigName)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Standard file mapping with custom overrides
|
// Standard file mapping with custom overrides
|
||||||
// Use taskmaster subdirectory only if profile supports it
|
// Use taskmaster subdirectory only if profile supports it
|
||||||
|
|||||||
@@ -59,63 +59,6 @@ function onAddRulesProfile(targetDir, assetsDir) {
|
|||||||
`[Claude] An error occurred during directory copy: ${err.message}`
|
`[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) {
|
function onRemoveRulesProfile(targetDir) {
|
||||||
@@ -124,146 +67,11 @@ function onRemoveRulesProfile(targetDir) {
|
|||||||
if (removeDirectoryRecursive(claudeDir)) {
|
if (removeDirectoryRecursive(claudeDir)) {
|
||||||
log('debug', `[Claude] Removed .claude directory from ${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) {
|
function onPostConvertRulesProfile(targetDir, assetsDir) {
|
||||||
// For Claude, post-convert is the same as add since we don't transform rules
|
// For Claude, post-convert is the same as add since we don't transform rules
|
||||||
onAddRulesProfile(targetDir, assetsDir);
|
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
|
// Create and export claude profile using the base factory
|
||||||
@@ -274,10 +82,11 @@ export const claudeProfile = createProfile({
|
|||||||
docsUrl: 'docs.anthropic.com/en/docs/claude-code',
|
docsUrl: 'docs.anthropic.com/en/docs/claude-code',
|
||||||
profileDir: '.', // Root directory
|
profileDir: '.', // Root directory
|
||||||
rulesDir: '.', // No specific rules directory needed
|
rulesDir: '.', // No specific rules directory needed
|
||||||
mcpConfigName: '.mcp.json', // Place MCP config in project root
|
mcpConfig: false,
|
||||||
|
mcpConfigName: null,
|
||||||
includeDefaultRules: false,
|
includeDefaultRules: false,
|
||||||
fileMap: {
|
fileMap: {
|
||||||
'AGENTS.md': '.taskmaster/CLAUDE.md'
|
'AGENTS.md': 'CLAUDE.md'
|
||||||
},
|
},
|
||||||
onAdd: onAddRulesProfile,
|
onAdd: onAddRulesProfile,
|
||||||
onRemove: onRemoveRulesProfile,
|
onRemove: onRemoveRulesProfile,
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
// Profile exports for centralized importing
|
// Profile exports for centralized importing
|
||||||
export { ampProfile } from './amp.js';
|
|
||||||
export { claudeProfile } from './claude.js';
|
export { claudeProfile } from './claude.js';
|
||||||
export { clineProfile } from './cline.js';
|
export { clineProfile } from './cline.js';
|
||||||
export { codexProfile } from './codex.js';
|
export { codexProfile } from './codex.js';
|
||||||
export { cursorProfile } from './cursor.js';
|
export { cursorProfile } from './cursor.js';
|
||||||
export { geminiProfile } from './gemini.js';
|
export { geminiProfile } from './gemini.js';
|
||||||
export { kiroProfile } from './kiro.js';
|
|
||||||
export { opencodeProfile } from './opencode.js';
|
|
||||||
export { rooProfile } from './roo.js';
|
export { rooProfile } from './roo.js';
|
||||||
export { traeProfile } from './trae.js';
|
export { traeProfile } from './trae.js';
|
||||||
export { vscodeProfile } from './vscode.js';
|
export { vscodeProfile } from './vscode.js';
|
||||||
export { windsurfProfile } from './windsurf.js';
|
export { windsurfProfile } from './windsurf.js';
|
||||||
export { zedProfile } from './zed.js';
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
// Kiro profile for rule-transformer
|
|
||||||
import { createProfile } from './base-profile.js';
|
|
||||||
|
|
||||||
// Create and export kiro profile using the base factory
|
|
||||||
export const kiroProfile = createProfile({
|
|
||||||
name: 'kiro',
|
|
||||||
displayName: 'Kiro',
|
|
||||||
url: 'kiro.dev',
|
|
||||||
docsUrl: 'kiro.dev/docs',
|
|
||||||
profileDir: '.kiro',
|
|
||||||
rulesDir: '.kiro/steering', // Kiro rules location (full path)
|
|
||||||
mcpConfig: true,
|
|
||||||
mcpConfigName: 'settings/mcp.json', // Create directly in settings subdirectory
|
|
||||||
includeDefaultRules: true, // Include default rules to get all the standard files
|
|
||||||
targetExtension: '.md',
|
|
||||||
fileMap: {
|
|
||||||
// Override specific mappings - the base profile will create:
|
|
||||||
// 'rules/cursor_rules.mdc': 'kiro_rules.md'
|
|
||||||
// 'rules/dev_workflow.mdc': 'dev_workflow.md'
|
|
||||||
// 'rules/self_improve.mdc': 'self_improve.md'
|
|
||||||
// 'rules/taskmaster.mdc': 'taskmaster.md'
|
|
||||||
// We can add additional custom mappings here if needed
|
|
||||||
},
|
|
||||||
customReplacements: [
|
|
||||||
// Core Kiro directory structure changes
|
|
||||||
{ from: /\.cursor\/rules/g, to: '.kiro/steering' },
|
|
||||||
{ from: /\.cursor\/mcp\.json/g, to: '.kiro/settings/mcp.json' },
|
|
||||||
|
|
||||||
// Fix any remaining kiro/rules references that might be created during transformation
|
|
||||||
{ from: /\.kiro\/rules/g, to: '.kiro/steering' },
|
|
||||||
|
|
||||||
// Essential markdown link transformations for Kiro structure
|
|
||||||
{
|
|
||||||
from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
|
|
||||||
to: '[$1](.kiro/steering/$2.md)'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Kiro specific terminology
|
|
||||||
{ from: /rules directory/g, to: 'steering directory' },
|
|
||||||
{ from: /cursor rules/gi, to: 'Kiro steering files' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
// Opencode profile for rule-transformer
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
|
||||||
import { log } from '../../scripts/modules/utils.js';
|
|
||||||
import { createProfile } from './base-profile.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform standard MCP config format to OpenCode format
|
|
||||||
* @param {Object} mcpConfig - Standard MCP configuration object
|
|
||||||
* @returns {Object} - Transformed OpenCode configuration object
|
|
||||||
*/
|
|
||||||
function transformToOpenCodeFormat(mcpConfig) {
|
|
||||||
const openCodeConfig = {
|
|
||||||
$schema: 'https://opencode.ai/config.json'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transform mcpServers to mcp
|
|
||||||
if (mcpConfig.mcpServers) {
|
|
||||||
openCodeConfig.mcp = {};
|
|
||||||
|
|
||||||
for (const [serverName, serverConfig] of Object.entries(
|
|
||||||
mcpConfig.mcpServers
|
|
||||||
)) {
|
|
||||||
// Transform server configuration
|
|
||||||
const transformedServer = {
|
|
||||||
type: 'local'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Combine command and args into single command array
|
|
||||||
if (serverConfig.command && serverConfig.args) {
|
|
||||||
transformedServer.command = [
|
|
||||||
serverConfig.command,
|
|
||||||
...serverConfig.args
|
|
||||||
];
|
|
||||||
} else if (serverConfig.command) {
|
|
||||||
transformedServer.command = [serverConfig.command];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add enabled flag
|
|
||||||
transformedServer.enabled = true;
|
|
||||||
|
|
||||||
// Transform env to environment
|
|
||||||
if (serverConfig.env) {
|
|
||||||
transformedServer.environment = serverConfig.env;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update with transformed config
|
|
||||||
openCodeConfig.mcp[serverName] = transformedServer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return openCodeConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lifecycle function called after MCP config generation to transform to OpenCode format
|
|
||||||
* @param {string} targetDir - Target project directory
|
|
||||||
* @param {string} assetsDir - Assets directory (unused for OpenCode)
|
|
||||||
*/
|
|
||||||
function onPostConvertRulesProfile(targetDir, assetsDir) {
|
|
||||||
const openCodeConfigPath = path.join(targetDir, 'opencode.json');
|
|
||||||
|
|
||||||
if (!fs.existsSync(openCodeConfigPath)) {
|
|
||||||
log('debug', '[OpenCode] No opencode.json found to transform');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Read the generated standard MCP config
|
|
||||||
const mcpConfigContent = fs.readFileSync(openCodeConfigPath, 'utf8');
|
|
||||||
const mcpConfig = JSON.parse(mcpConfigContent);
|
|
||||||
|
|
||||||
// Check if it's already in OpenCode format (has $schema)
|
|
||||||
if (mcpConfig.$schema) {
|
|
||||||
log(
|
|
||||||
'info',
|
|
||||||
'[OpenCode] opencode.json already in OpenCode format, skipping transformation'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform to OpenCode format
|
|
||||||
const openCodeConfig = transformToOpenCodeFormat(mcpConfig);
|
|
||||||
|
|
||||||
// Write back the transformed config with proper formatting
|
|
||||||
fs.writeFileSync(
|
|
||||||
openCodeConfigPath,
|
|
||||||
JSON.stringify(openCodeConfig, null, 2) + '\n'
|
|
||||||
);
|
|
||||||
|
|
||||||
log('info', '[OpenCode] Transformed opencode.json to OpenCode format');
|
|
||||||
log(
|
|
||||||
'debug',
|
|
||||||
`[OpenCode] Added schema, renamed mcpServers->mcp, combined command+args, added type/enabled, renamed env->environment`
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
log(
|
|
||||||
'error',
|
|
||||||
`[OpenCode] Failed to transform opencode.json: ${error.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lifecycle function called when removing OpenCode profile
|
|
||||||
* @param {string} targetDir - Target project directory
|
|
||||||
*/
|
|
||||||
function onRemoveRulesProfile(targetDir) {
|
|
||||||
const openCodeConfigPath = path.join(targetDir, 'opencode.json');
|
|
||||||
|
|
||||||
if (!fs.existsSync(openCodeConfigPath)) {
|
|
||||||
log('debug', '[OpenCode] No opencode.json found to clean up');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Read the current config
|
|
||||||
const configContent = fs.readFileSync(openCodeConfigPath, 'utf8');
|
|
||||||
const config = JSON.parse(configContent);
|
|
||||||
|
|
||||||
// Check if it has the mcp section and taskmaster-ai server
|
|
||||||
if (config.mcp && config.mcp['taskmaster-ai']) {
|
|
||||||
// Remove taskmaster-ai server
|
|
||||||
delete config.mcp['taskmaster-ai'];
|
|
||||||
|
|
||||||
// Check if there are other MCP servers
|
|
||||||
const remainingServers = Object.keys(config.mcp);
|
|
||||||
|
|
||||||
if (remainingServers.length === 0) {
|
|
||||||
// No other servers, remove entire mcp section
|
|
||||||
delete config.mcp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if config is now empty (only has $schema)
|
|
||||||
const remainingKeys = Object.keys(config).filter(
|
|
||||||
(key) => key !== '$schema'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (remainingKeys.length === 0) {
|
|
||||||
// Config only has schema left, remove entire file
|
|
||||||
fs.rmSync(openCodeConfigPath, { force: true });
|
|
||||||
log('info', '[OpenCode] Removed empty opencode.json file');
|
|
||||||
} else {
|
|
||||||
// Write back the modified config
|
|
||||||
fs.writeFileSync(
|
|
||||||
openCodeConfigPath,
|
|
||||||
JSON.stringify(config, null, 2) + '\n'
|
|
||||||
);
|
|
||||||
log(
|
|
||||||
'info',
|
|
||||||
'[OpenCode] Removed TaskMaster from opencode.json, preserved other configurations'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log('debug', '[OpenCode] TaskMaster not found in opencode.json');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log(
|
|
||||||
'error',
|
|
||||||
`[OpenCode] Failed to clean up opencode.json: ${error.message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and export opencode profile using the base factory
|
|
||||||
export const opencodeProfile = createProfile({
|
|
||||||
name: 'opencode',
|
|
||||||
displayName: 'OpenCode',
|
|
||||||
url: 'opencode.ai',
|
|
||||||
docsUrl: 'opencode.ai/docs/',
|
|
||||||
profileDir: '.', // Root directory
|
|
||||||
rulesDir: '.', // Root directory for AGENTS.md
|
|
||||||
mcpConfigName: 'opencode.json', // Override default 'mcp.json'
|
|
||||||
includeDefaultRules: false,
|
|
||||||
fileMap: {
|
|
||||||
'AGENTS.md': 'AGENTS.md'
|
|
||||||
},
|
|
||||||
onPostConvert: onPostConvertRulesProfile,
|
|
||||||
onRemove: onRemoveRulesProfile
|
|
||||||
});
|
|
||||||
|
|
||||||
// Export lifecycle functions separately to avoid naming conflicts
|
|
||||||
export { onPostConvertRulesProfile, onRemoveRulesProfile };
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
// Zed 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 Zed format
|
|
||||||
* @param {Object} mcpConfig - Standard MCP configuration object
|
|
||||||
* @returns {Object} - Transformed Zed configuration object
|
|
||||||
*/
|
|
||||||
function transformToZedFormat(mcpConfig) {
|
|
||||||
const zedConfig = {};
|
|
||||||
|
|
||||||
// Transform mcpServers to context_servers
|
|
||||||
if (mcpConfig.mcpServers) {
|
|
||||||
zedConfig['context_servers'] = mcpConfig.mcpServers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preserve any other existing settings
|
|
||||||
for (const [key, value] of Object.entries(mcpConfig)) {
|
|
||||||
if (key !== 'mcpServers') {
|
|
||||||
zedConfig[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return zedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifecycle functions for Zed profile
|
|
||||||
function onAddRulesProfile(targetDir, assetsDir) {
|
|
||||||
// MCP transformation will be handled in onPostConvertRulesProfile
|
|
||||||
// File copying is handled by the base profile via fileMap
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRemoveRulesProfile(targetDir) {
|
|
||||||
// Clean up .rules (Zed uses .rules directly in root)
|
|
||||||
const userRulesFile = path.join(targetDir, '.rules');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Remove Task Master .rules
|
|
||||||
if (fs.existsSync(userRulesFile)) {
|
|
||||||
fs.rmSync(userRulesFile, { force: true });
|
|
||||||
log('debug', `[Zed] Removed ${userRulesFile}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
log('error', `[Zed] Failed to remove Zed instructions: ${err.message}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MCP Removal: Remove context_servers section
|
|
||||||
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
|
|
||||||
|
|
||||||
if (!fs.existsSync(mcpConfigPath)) {
|
|
||||||
log('debug', '[Zed] No .zed/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 context_servers section and task-master-ai server
|
|
||||||
if (
|
|
||||||
config['context_servers'] &&
|
|
||||||
config['context_servers']['task-master-ai']
|
|
||||||
) {
|
|
||||||
// Remove task-master-ai server
|
|
||||||
delete config['context_servers']['task-master-ai'];
|
|
||||||
|
|
||||||
// Check if there are other MCP servers in context_servers
|
|
||||||
const remainingServers = Object.keys(config['context_servers']);
|
|
||||||
|
|
||||||
if (remainingServers.length === 0) {
|
|
||||||
// No other servers, remove entire context_servers section
|
|
||||||
delete config['context_servers'];
|
|
||||||
log('debug', '[Zed] Removed empty context_servers 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', '[Zed] Removed empty settings.json file');
|
|
||||||
|
|
||||||
// Check if .zed directory is empty
|
|
||||||
const zedDirPath = path.join(targetDir, '.zed');
|
|
||||||
if (fs.existsSync(zedDirPath)) {
|
|
||||||
const remainingContents = fs.readdirSync(zedDirPath);
|
|
||||||
if (remainingContents.length === 0) {
|
|
||||||
fs.rmSync(zedDirPath, { recursive: true, force: true });
|
|
||||||
log('debug', '[Zed] Removed empty .zed directory');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Write back the modified config
|
|
||||||
fs.writeFileSync(
|
|
||||||
mcpConfigPath,
|
|
||||||
JSON.stringify(config, null, '\t') + '\n'
|
|
||||||
);
|
|
||||||
log(
|
|
||||||
'info',
|
|
||||||
'[Zed] Removed TaskMaster from settings.json, preserved other configurations'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log('debug', '[Zed] TaskMaster not found in context_servers');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log('error', `[Zed] Failed to clean up settings.json: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPostConvertRulesProfile(targetDir, assetsDir) {
|
|
||||||
// Handle .rules setup (same as onAddRulesProfile)
|
|
||||||
onAddRulesProfile(targetDir, assetsDir);
|
|
||||||
|
|
||||||
// Transform MCP config to Zed format
|
|
||||||
const mcpConfigPath = path.join(targetDir, '.zed', 'settings.json');
|
|
||||||
|
|
||||||
if (!fs.existsSync(mcpConfigPath)) {
|
|
||||||
log('debug', '[Zed] No .zed/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 Zed format (has context_servers)
|
|
||||||
if (mcpConfig['context_servers']) {
|
|
||||||
log(
|
|
||||||
'info',
|
|
||||||
'[Zed] settings.json already in Zed format, skipping transformation'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform to Zed format
|
|
||||||
const zedConfig = transformToZedFormat(mcpConfig);
|
|
||||||
|
|
||||||
// Write back the transformed config with proper formatting
|
|
||||||
fs.writeFileSync(
|
|
||||||
mcpConfigPath,
|
|
||||||
JSON.stringify(zedConfig, null, '\t') + '\n'
|
|
||||||
);
|
|
||||||
|
|
||||||
log('info', '[Zed] Transformed settings.json to Zed format');
|
|
||||||
log('debug', '[Zed] Renamed mcpServers to context_servers');
|
|
||||||
} catch (error) {
|
|
||||||
log('error', `[Zed] Failed to transform settings.json: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and export zed profile using the base factory
|
|
||||||
export const zedProfile = createProfile({
|
|
||||||
name: 'zed',
|
|
||||||
displayName: 'Zed',
|
|
||||||
url: 'zed.dev',
|
|
||||||
docsUrl: 'zed.dev/docs',
|
|
||||||
profileDir: '.zed',
|
|
||||||
rulesDir: '.',
|
|
||||||
mcpConfig: true,
|
|
||||||
mcpConfigName: 'settings.json',
|
|
||||||
includeDefaultRules: false,
|
|
||||||
fileMap: {
|
|
||||||
'AGENTS.md': '.rules'
|
|
||||||
},
|
|
||||||
onAdd: onAddRulesProfile,
|
|
||||||
onRemove: onRemoveRulesProfile,
|
|
||||||
onPostConvert: onPostConvertRulesProfile
|
|
||||||
});
|
|
||||||
|
|
||||||
// Export lifecycle functions separately to avoid naming conflicts
|
|
||||||
export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
|
|
||||||
@@ -113,12 +113,12 @@ export async function runInteractiveProfilesSetup() {
|
|||||||
const hasMcpConfig = profile.mcpConfig === true;
|
const hasMcpConfig = profile.mcpConfig === true;
|
||||||
|
|
||||||
if (!profile.includeDefaultRules) {
|
if (!profile.includeDefaultRules) {
|
||||||
// Integration guide profiles (claude, codex, gemini, opencode, zed, amp) - don't include standard coding rules
|
// Integration guide profiles (claude, codex, gemini) - don't include standard coding rules
|
||||||
if (profileName === 'claude') {
|
if (profileName === 'claude') {
|
||||||
description = 'Integration guide with Task Master slash commands';
|
description = 'Integration guide with Task Master slash commands';
|
||||||
} else if (profileName === 'codex') {
|
} else if (profileName === 'codex') {
|
||||||
description = 'Comprehensive Task Master integration guide';
|
description = 'Comprehensive Task Master integration guide';
|
||||||
} else if (hasMcpConfig) {
|
} else if (profileName === 'gemini') {
|
||||||
description = 'Integration guide and MCP config';
|
description = 'Integration guide and MCP config';
|
||||||
} else {
|
} else {
|
||||||
description = 'Integration guide';
|
description = 'Integration guide';
|
||||||
@@ -199,7 +199,7 @@ export function generateProfileSummary(profileName, addResult) {
|
|||||||
const profileConfig = getRulesProfile(profileName);
|
const profileConfig = getRulesProfile(profileName);
|
||||||
|
|
||||||
if (!profileConfig.includeDefaultRules) {
|
if (!profileConfig.includeDefaultRules) {
|
||||||
// Integration guide profiles (claude, codex, gemini, amp)
|
// Integration guide profiles (claude, codex, gemini)
|
||||||
return `Summary for ${profileName}: Integration guide installed.`;
|
return `Summary for ${profileName}: Integration guide installed.`;
|
||||||
} else {
|
} else {
|
||||||
// Rule profiles with coding guidelines
|
// Rule profiles with coding guidelines
|
||||||
@@ -225,7 +225,7 @@ export function generateProfileRemovalSummary(profileName, removeResult) {
|
|||||||
const profileConfig = getRulesProfile(profileName);
|
const profileConfig = getRulesProfile(profileName);
|
||||||
|
|
||||||
if (!profileConfig.includeDefaultRules) {
|
if (!profileConfig.includeDefaultRules) {
|
||||||
// Integration guide profiles (claude, codex, gemini, amp)
|
// Integration guide profiles (claude, codex, gemini)
|
||||||
const baseMessage = `Summary for ${profileName}: Integration guide removed`;
|
const baseMessage = `Summary for ${profileName}: Integration guide removed`;
|
||||||
if (removeResult.notice) {
|
if (removeResult.notice) {
|
||||||
return `${baseMessage} (${removeResult.notice})`;
|
return `${baseMessage} (${removeResult.notice})`;
|
||||||
|
|||||||
@@ -1,346 +0,0 @@
|
|||||||
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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -21,22 +21,19 @@ describe('Claude Profile Initialization Functionality', () => {
|
|||||||
expect(claudeProfileContent).toContain("displayName: 'Claude Code'");
|
expect(claudeProfileContent).toContain("displayName: 'Claude Code'");
|
||||||
expect(claudeProfileContent).toContain("profileDir: '.'"); // non-default
|
expect(claudeProfileContent).toContain("profileDir: '.'"); // non-default
|
||||||
expect(claudeProfileContent).toContain("rulesDir: '.'"); // non-default
|
expect(claudeProfileContent).toContain("rulesDir: '.'"); // non-default
|
||||||
expect(claudeProfileContent).toContain("mcpConfigName: '.mcp.json'"); // non-default
|
expect(claudeProfileContent).toContain('mcpConfig: false'); // non-default
|
||||||
expect(claudeProfileContent).toContain('includeDefaultRules: false'); // non-default
|
expect(claudeProfileContent).toContain('includeDefaultRules: false'); // non-default
|
||||||
expect(claudeProfileContent).toContain(
|
expect(claudeProfileContent).toContain("'AGENTS.md': 'CLAUDE.md'");
|
||||||
"'AGENTS.md': '.taskmaster/CLAUDE.md'"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check the final computed properties on the profile object
|
// Check the final computed properties on the profile object
|
||||||
expect(claudeProfile.profileName).toBe('claude');
|
expect(claudeProfile.profileName).toBe('claude');
|
||||||
expect(claudeProfile.displayName).toBe('Claude Code');
|
expect(claudeProfile.displayName).toBe('Claude Code');
|
||||||
expect(claudeProfile.profileDir).toBe('.');
|
expect(claudeProfile.profileDir).toBe('.');
|
||||||
expect(claudeProfile.rulesDir).toBe('.');
|
expect(claudeProfile.rulesDir).toBe('.');
|
||||||
expect(claudeProfile.mcpConfig).toBe(true); // default from base profile
|
expect(claudeProfile.mcpConfig).toBe(false);
|
||||||
expect(claudeProfile.mcpConfigName).toBe('.mcp.json'); // explicitly set
|
expect(claudeProfile.mcpConfigName).toBe(null); // computed
|
||||||
expect(claudeProfile.mcpConfigPath).toBe('.mcp.json'); // computed
|
|
||||||
expect(claudeProfile.includeDefaultRules).toBe(false);
|
expect(claudeProfile.includeDefaultRules).toBe(false);
|
||||||
expect(claudeProfile.fileMap['AGENTS.md']).toBe('.taskmaster/CLAUDE.md');
|
expect(claudeProfile.fileMap['AGENTS.md']).toBe('CLAUDE.md');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('claude.js has lifecycle functions for file management', () => {
|
test('claude.js has lifecycle functions for file management', () => {
|
||||||
@@ -47,11 +44,9 @@ describe('Claude Profile Initialization Functionality', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('claude.js handles .claude directory and .taskmaster/CLAUDE.md import in lifecycle functions', () => {
|
test('claude.js handles .claude directory in lifecycle functions', () => {
|
||||||
expect(claudeProfileContent).toContain('.claude');
|
expect(claudeProfileContent).toContain('.claude');
|
||||||
expect(claudeProfileContent).toContain('copyRecursiveSync');
|
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', () => {
|
test('claude.js has proper error handling in lifecycle functions', () => {
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { opencodeProfile } from '../../../src/profiles/opencode.js';
|
|
||||||
|
|
||||||
describe('OpenCode Profile Initialization Functionality', () => {
|
|
||||||
let opencodeProfileContent;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
const opencodeJsPath = path.join(
|
|
||||||
process.cwd(),
|
|
||||||
'src',
|
|
||||||
'profiles',
|
|
||||||
'opencode.js'
|
|
||||||
);
|
|
||||||
opencodeProfileContent = fs.readFileSync(opencodeJsPath, 'utf8');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('opencode.js has correct asset-only profile configuration', () => {
|
|
||||||
// Check for explicit, non-default values in the source file
|
|
||||||
expect(opencodeProfileContent).toContain("name: 'opencode'");
|
|
||||||
expect(opencodeProfileContent).toContain("displayName: 'OpenCode'");
|
|
||||||
expect(opencodeProfileContent).toContain("url: 'opencode.ai'");
|
|
||||||
expect(opencodeProfileContent).toContain("docsUrl: 'opencode.ai/docs/'");
|
|
||||||
expect(opencodeProfileContent).toContain("profileDir: '.'"); // non-default
|
|
||||||
expect(opencodeProfileContent).toContain("rulesDir: '.'"); // non-default
|
|
||||||
expect(opencodeProfileContent).toContain("mcpConfigName: 'opencode.json'"); // non-default
|
|
||||||
expect(opencodeProfileContent).toContain('includeDefaultRules: false'); // non-default
|
|
||||||
expect(opencodeProfileContent).toContain("'AGENTS.md': 'AGENTS.md'");
|
|
||||||
|
|
||||||
// Check the final computed properties on the profile object
|
|
||||||
expect(opencodeProfile.profileName).toBe('opencode');
|
|
||||||
expect(opencodeProfile.displayName).toBe('OpenCode');
|
|
||||||
expect(opencodeProfile.profileDir).toBe('.');
|
|
||||||
expect(opencodeProfile.rulesDir).toBe('.');
|
|
||||||
expect(opencodeProfile.mcpConfig).toBe(true); // computed from mcpConfigName
|
|
||||||
expect(opencodeProfile.mcpConfigName).toBe('opencode.json');
|
|
||||||
expect(opencodeProfile.mcpConfigPath).toBe('opencode.json'); // computed
|
|
||||||
expect(opencodeProfile.includeDefaultRules).toBe(false);
|
|
||||||
expect(opencodeProfile.fileMap['AGENTS.md']).toBe('AGENTS.md');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('opencode.js has lifecycle functions for MCP config transformation', () => {
|
|
||||||
expect(opencodeProfileContent).toContain(
|
|
||||||
'function onPostConvertRulesProfile'
|
|
||||||
);
|
|
||||||
expect(opencodeProfileContent).toContain('function onRemoveRulesProfile');
|
|
||||||
expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('opencode.js handles opencode.json transformation in lifecycle functions', () => {
|
|
||||||
expect(opencodeProfileContent).toContain('opencode.json');
|
|
||||||
expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
|
|
||||||
expect(opencodeProfileContent).toContain('$schema');
|
|
||||||
expect(opencodeProfileContent).toContain('mcpServers');
|
|
||||||
expect(opencodeProfileContent).toContain('mcp');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('opencode.js has proper error handling in lifecycle functions', () => {
|
|
||||||
expect(opencodeProfileContent).toContain('try {');
|
|
||||||
expect(opencodeProfileContent).toContain('} catch (error) {');
|
|
||||||
expect(opencodeProfileContent).toContain('log(');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('opencode.js uses custom MCP config name', () => {
|
|
||||||
// OpenCode uses opencode.json instead of mcp.json
|
|
||||||
expect(opencodeProfileContent).toContain("mcpConfigName: 'opencode.json'");
|
|
||||||
// Should not contain mcp.json as a config value (comments are OK)
|
|
||||||
expect(opencodeProfileContent).not.toMatch(
|
|
||||||
/mcpConfigName:\s*['"]mcp\.json['"]/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('opencode.js has transformation logic for OpenCode format', () => {
|
|
||||||
// Check for transformation function
|
|
||||||
expect(opencodeProfileContent).toContain('transformToOpenCodeFormat');
|
|
||||||
|
|
||||||
// Check for specific transformation logic
|
|
||||||
expect(opencodeProfileContent).toContain('mcpServers');
|
|
||||||
expect(opencodeProfileContent).toContain('command');
|
|
||||||
expect(opencodeProfileContent).toContain('args');
|
|
||||||
expect(opencodeProfileContent).toContain('environment');
|
|
||||||
expect(opencodeProfileContent).toContain('enabled');
|
|
||||||
expect(opencodeProfileContent).toContain('type');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -177,13 +177,6 @@ jest.unstable_mockModule('../../src/ai-providers/index.js', () => ({
|
|||||||
getRequiredApiKeyName: jest.fn(() => 'XAI_API_KEY'),
|
getRequiredApiKeyName: jest.fn(() => 'XAI_API_KEY'),
|
||||||
isRequiredApiKey: jest.fn(() => true)
|
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(() => ({
|
OpenRouterAIProvider: jest.fn(() => ({
|
||||||
generateText: jest.fn(),
|
generateText: jest.fn(),
|
||||||
streamText: jest.fn(),
|
streamText: jest.fn(),
|
||||||
|
|||||||
@@ -1,299 +0,0 @@
|
|||||||
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;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,7 +2,6 @@ import { jest } from '@jest/globals';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import { claudeProfile } from '../../../src/profiles/claude.js';
|
|
||||||
|
|
||||||
// Mock external modules
|
// Mock external modules
|
||||||
jest.mock('child_process', () => ({
|
jest.mock('child_process', () => ({
|
||||||
@@ -78,22 +77,11 @@ describe('Claude Profile Integration', () => {
|
|||||||
expect(mkdirCalls).toHaveLength(0);
|
expect(mkdirCalls).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('supports MCP configuration when using rule transformer', () => {
|
test('does not create MCP configuration files', () => {
|
||||||
// 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
|
// Act
|
||||||
mockCreateClaudeStructure();
|
mockCreateClaudeStructure();
|
||||||
|
|
||||||
// Assert - The mock function should not create MCP config files
|
// Assert - Claude profile should not create any MCP config files
|
||||||
// (This is expected since the mock doesn't use the rule transformer)
|
|
||||||
const writeFileCalls = fs.writeFileSync.mock.calls;
|
const writeFileCalls = fs.writeFileSync.mock.calls;
|
||||||
const mcpConfigCalls = writeFileCalls.filter(
|
const mcpConfigCalls = writeFileCalls.filter(
|
||||||
(call) =>
|
(call) =>
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import { jest } from '@jest/globals';
|
|
||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import os from 'os';
|
|
||||||
|
|
||||||
// Mock external modules
|
|
||||||
jest.mock('child_process', () => ({
|
|
||||||
execSync: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Mock console methods
|
|
||||||
jest.mock('console', () => ({
|
|
||||||
log: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
warn: jest.fn(),
|
|
||||||
error: jest.fn(),
|
|
||||||
clear: jest.fn()
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('Kiro Integration', () => {
|
|
||||||
let tempDir;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
// Create a temporary directory for testing
|
|
||||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
|
|
||||||
|
|
||||||
// Spy on fs methods
|
|
||||||
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
|
|
||||||
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
|
|
||||||
if (filePath.toString().includes('mcp.json')) {
|
|
||||||
return JSON.stringify({ mcpServers: {} }, null, 2);
|
|
||||||
}
|
|
||||||
return '{}';
|
|
||||||
});
|
|
||||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
|
|
||||||
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
// Clean up the temporary directory
|
|
||||||
try {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Error cleaning up: ${err.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test function that simulates the createProjectStructure behavior for Kiro files
|
|
||||||
function mockCreateKiroStructure() {
|
|
||||||
// This function simulates the actual kiro profile creation logic
|
|
||||||
// It explicitly calls the mocked fs methods to ensure consistency with the test environment
|
|
||||||
|
|
||||||
// Simulate directory creation calls - these will call the mocked mkdirSync
|
|
||||||
fs.mkdirSync(path.join(tempDir, '.kiro'), { recursive: true });
|
|
||||||
fs.mkdirSync(path.join(tempDir, '.kiro', 'steering'), { recursive: true });
|
|
||||||
fs.mkdirSync(path.join(tempDir, '.kiro', 'settings'), { recursive: true });
|
|
||||||
|
|
||||||
// Create MCP config file at .kiro/settings/mcp.json
|
|
||||||
// This will call the mocked writeFileSync
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
|
|
||||||
JSON.stringify({ mcpServers: {} }, null, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create kiro rule files in steering directory
|
|
||||||
// All these will call the mocked writeFileSync
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
|
|
||||||
'# Kiro Rules\n\nKiro-specific rules and instructions.'
|
|
||||||
);
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
|
|
||||||
'# Development Workflow\n\nDevelopment workflow instructions.'
|
|
||||||
);
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
|
|
||||||
'# Self Improvement\n\nSelf improvement guidelines.'
|
|
||||||
);
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
|
|
||||||
'# Task Master\n\nTask Master integration instructions.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
test('creates all required .kiro directories', () => {
|
|
||||||
// Act
|
|
||||||
mockCreateKiroStructure();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.kiro'), {
|
|
||||||
recursive: true
|
|
||||||
});
|
|
||||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
||||||
path.join(tempDir, '.kiro', 'steering'),
|
|
||||||
{
|
|
||||||
recursive: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
|
||||||
path.join(tempDir, '.kiro', 'settings'),
|
|
||||||
{
|
|
||||||
recursive: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates Kiro mcp.json with mcpServers format', () => {
|
|
||||||
// Act
|
|
||||||
mockCreateKiroStructure();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
||||||
path.join(tempDir, '.kiro', 'settings', 'mcp.json'),
|
|
||||||
JSON.stringify({ mcpServers: {} }, null, 2)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('creates rule files in steering directory', () => {
|
|
||||||
// Act
|
|
||||||
mockCreateKiroStructure();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'kiro_rules.md'),
|
|
||||||
'# Kiro Rules\n\nKiro-specific rules and instructions.'
|
|
||||||
);
|
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'dev_workflow.md'),
|
|
||||||
'# Development Workflow\n\nDevelopment workflow instructions.'
|
|
||||||
);
|
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'self_improve.md'),
|
|
||||||
'# Self Improvement\n\nSelf improvement guidelines.'
|
|
||||||
);
|
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
||||||
path.join(tempDir, '.kiro', 'steering', 'taskmaster.md'),
|
|
||||||
'# Task Master\n\nTask Master integration instructions.'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user