feat: Hamster Integration - Cloud Storage, Sentry Telemetry, and Major CLI UX Improvements (#1452)

This commit is contained in:
Eyal Toledano
2025-11-30 06:35:49 -05:00
committed by GitHub
parent 9e934650b7
commit 4046b3ca44
352 changed files with 16204 additions and 3779 deletions

View File

@@ -0,0 +1,28 @@
---
"task-master-ai": minor
---
Add Hamster integration for `parse-prd` command
Your tasks are only as good as the context behind them. Now when you run `parse-prd`, you can choose to bring your PRD to Hamster instead of parsing locally.
**New Workflow Choice**
- **Parse locally**: PRD becomes a task list in a local JSON file - great for quick prototyping and vibing solo
- **Bring it to Hamster**: PRD becomes a living brief connected to your team, codebase, and agents
**Why Hamster?**
- Tasks live in a central place with real-time sync across your team
- Collaborate on your PRD/brief together, generate tasks on Hamster, bring them into Taskmaster
- No API keys needed - Hamster handles all AI inference, just need a Hamster account
**Hamster Integration**
- OAuth login flow when choosing Hamster (same as export command)
- Create brief directly from PRD content with auto-generated title/description
- Progress bar showing task generation phases (analyzing → generating → processing)
- Invite teammates during brief creation
- Auto-set context to new brief when complete
**Quality of Life**
- Clickable brief URL and team invite URL in terminal
- Shows task count as they're generated
- Graceful fallback if generation takes longer than expected

View File

@@ -0,0 +1,27 @@
---
"task-master-ai": minor
---
Redesign `tm init` with clearer workflow selection and reduced noise
Choose how you want to plan: Solo with Taskmaster or Together with Hamster. The new init flow guides you through setup with context-appropriate options and cleaner output.
**New Workflow Selection**
- Clear framing: "You need a plan before you execute. How do you want to build it?"
- **Solo (Taskmaster)**: Parse PRD → structured tasks → AI agent executes with control
- **Together (Hamster)**: Team writes brief → Hamster refines → aligned execution with Taskmaster
**Cleaner Experience**
- Optional AI IDE rules setup (Y/n prompt instead of always showing)
- 15+ log messages moved to debug level - much less noise
- Skip Git prompts when using Hamster (not needed for cloud storage)
- Skip AI model configuration for Hamster (uses Hamster's AI)
**Hamster Integration**
- OAuth login flow when choosing Hamster workflow
- Context-aware guidance based on your workflow choice
**Quality of Life**
- Run `tm rules --setup` anytime if you declined during init
- Use `--yes` flag for fully non-interactive setup
- Use `--rules cursor,windsurf` to specify rules upfront

View File

@@ -0,0 +1,25 @@
---
"task-master-ai": minor
---
Introduce `tm export` command - bring Task Master to your team
Share your task plans with teammates by exporting local tasks to collaborative briefs on Hamster. Select which tags to export, invite team members, and start collaborating instantly.
**New `tm export` Command**
- Export your local tasks to shareable team briefs
- Hamster will reverse engineer your PRD based on your tasks (reverse parse prd!)
- Select multiple tags to export in one go, import all tasks across tags to Hamster
- Hamster will generate brief titles and descriptions from your task content
- Automatically sets your CLI context to the new brief
- All AI calls handled by Hamster, zero API keys needed - just a Hamster account!
**Team Collaboration**
- Invite teammates during export with `-I, --invite` flag
- Add up to 10 team members by email
- See invitation status: sent, already a member, or error
**Quality of Life Improvements**
- New `tm login` / `tm logout` shortcuts
- Display ID shortcuts: `tm show ham31` now works (normalizes to HAM-31)
- Better task rendering with proper HTML/Markdown support

View File

@@ -0,0 +1,14 @@
---
'task-master-ai': patch
---
Add Sentry integration for error tracking and AI telemetry monitoring
- **Sentry Integration**: Added comprehensive error tracking and AI operation monitoring using Sentry with Vercel AI SDK integration
- **AI Telemetry**: All AI operations (generateText, streamText, generateObject, streamObject) now automatically track spans, token usage, prompts, and responses
- **MCP Server Instrumentation**: Wrapped FastMCP server with `Sentry.wrapMcpServerWithSentry()` to automatically capture spans for all MCP tool interactions
- **Privacy Controls**: Added `anonymousTelemetry` config option (default: true) allowing local storage users to opt out of telemetry
- **Complete Coverage**: Telemetry enabled for all AI commands including parse-prd, expand, update-task, analyze-complexity, and research
- **Internal Telemetry**: Sentry DSN is hardcoded internally for Task Master's telemetry (not user-configurable)
- **Dual Initialization**: Automatic Sentry initialization in both CLI (scripts/dev.js) and MCP Server (mcp-server/src/index.js) with full MCP instrumentation

View File

@@ -0,0 +1,28 @@
---
"task-master-ai": minor
---
Add simpler positional syntax and Hamster-aware UI improvements
- **Simpler command syntax**: Use positional arguments without flags
- `tm update-task 1 Added implementation` (no quotes needed for multi-word prompts)
- `tm status 1 done` (new alias for set-status) or `tm set-status 1,1.1,2 in-progress`
- `tm list done` or `tm list in-progress` or `tm list all` (shortcut for --with-subtasks)
- **Hamster-aware help**: Context-specific command list when connected to Hamster
- Shows only relevant commands for Hamster workflow
- Beautiful boxed section headers with improved spacing
- Clear usage examples with new positional syntax
- Better visual alignment and cleaner formatting
- **Progress indicators**: Added loading spinner to `update-task` when connected to Hamster
- Shows "Updating task X on Hamster..." during AI processing
- Cleaner, more responsive UX for long-running operations
- **Improved context display**: Show 'Brief: [name]' instead of 'tag: [name]' when connected to Hamster
- **Cleaner Hamster updates**: Simplified update display (removed redundant Mode/Prompt info)
- **Smart messaging**: "NO TASKS AVAILABLE" warning only shows when literally no tasks exist
- Removed misleading messages when tasks are just completed/in-progress/blocked
- Better UX for filtered task lists
- **Updated help everywhere**: Regular help menu now shows new positional argument syntax
- All suggested actions updated across commands
- Consistent syntax in all UI components
- **Auto-detection**: Automatically detects Hamster connection for better UX
- **Backward compatible**: All old flag syntax still works (`--id`, `--status`, etc.)

139
.claude/commands/go/ham.md Normal file
View File

@@ -0,0 +1,139 @@
# Hamster (Go ham!) Run Task Workflow
This command initiates the HAM (Hamster Automated Management) workflow for task execution.
## Usage
```
/go:ham [task-id]
```
- `task-id` (optional): Specific task identifier to work on (e.g., "1", "1.2", "2.3.1")
- If provided, start working on that specific task immediately
- If omitted, automatically identify the next available task
## Process
When the user invokes this command, follow these steps:
### 1. Task Selection
#### If task-id is provided ($ARGUMENTS is not empty):
```bash
tm show $ARGUMENTS
```
Start working on the specified task immediately, skipping to step 3.
#### If no task-id is provided ($ARGUMENTS is empty):
```bash
tm list
```
Display all tasks with their current status to provide context.
### 2. Identify Next Task (only if no task-id provided)
Determine which task should be worked on next based on:
- Dependencies
- Priority
- Current status (pending tasks only)
### 3. Show Task Details (only if task wasn't specified in step 1)
```bash
tm show <task-id>
```
Display the full details of the identified task including:
- Title and description
- Dependencies
- Test strategy
- Subtasks (if any)
### 4. Kickoff Workflow
Based on the task type, follow the appropriate workflow:
#### For Main Tasks (e.g., "1", "2", "3")
- Review the task's subtasks
- If no subtasks exist, suggest expanding the task first
- Identify the first pending subtask
- Begin implementation following the subtask's requirements
#### For Subtasks (e.g., "1.1", "2.3")
- Mark the subtask as in-progress:
```bash
tm set-status --id=<subtask-id> --status=in-progress
```
- Review the task details and requirements
- Check for related code files or dependencies
- Create an implementation plan
- Begin implementation following project conventions
### 5. Implementation Guidelines
Follow these principles during implementation:
1. **Understand First**: Read related files and understand the current architecture
2. **Plan**: Create a mental model or brief plan before coding
3. **Follow Conventions**: Adhere to project structure and coding standards
4. **Test As You Go**: Validate changes incrementally
5. **Stay Focused**: Complete the current subtask before moving to the next
### 6. Task Completion
When the subtask is complete:
```bash
tm set-status --id=<subtask-id> --status=done
```
Then automatically check for the next available task by repeating from step 2.
## Example Flows
### With Specific Task ID
```
User: "/go:ham 1.2"
1. Claude runs: tm show 1.2
→ Displays full task details
2. Claude analyzes the task and creates an implementation plan
3. Claude marks task in-progress: tm set-status --id=1.2 --status=in-progress
4. Claude begins implementation following the task requirements
5. Upon completion, Claude runs: tm set-status --id=1.2 --status=done
6. Claude automatically identifies next task with tm list
```
### Without Specific Task ID (Auto-discovery)
```
User: "/go:ham"
1. Claude runs: tm list
2. Claude identifies next available task (e.g., 1.2)
3. Claude runs: tm show 1.2
→ Displays full task details
4. Claude analyzes the task and creates an implementation plan
5. Claude marks task in-progress: tm set-status --id=1.2 --status=in-progress
6. Claude begins implementation following the task requirements
7. Upon completion, Claude runs: tm set-status --id=1.2 --status=done
8. Claude automatically identifies next task with tm list
```
## Notes
- Always verify task dependencies are complete before starting
- If a task is blocked, mark it as such and move to the next available task
- Keep the user informed of progress at each major step
- Ask for clarification if task requirements are unclear
- Follow the project's CLAUDE.md and .cursor/rules/* guidelines at all times
- Unlike the usual Taskmaster process, do not bother using update-task nor update-subtask as they do not work with Hamster tasks yet.
- Use only `tm list`, `tm show <sub/task id>` and `tm set status` - other commands don't yet work with it.
- Do not use the MCP tools when connected with Hamster briefs - that is not yet up to date.
- Use `.cursor/rules/git_workflow.mdc` as a guide for the workflow
- When starting a task, mark it as in-progress. You can mark multiple task statuses at once with comma separation (i.e. `tm set-status -i 1,1.1 -s in-progress`)
- Read the task, then if it has subtasks, begin implementing the subtasks one at a time.
- When the subtask is done, run lint and typecheck, mark the task as done if it passes, and commit.
- Continue until all subtasks are done, then run a final lint and typecheck (`npm lint` and `npm typecheck`) and create a PR using `gh` cli for that Task.
- Keep committing to the same PR as long as the scope is maintained. An entire task list (brief) might fit into a single PR but not if it ends up being huge. It is preferred for everything to land in one PR if it is possible, otherwise commit to different PRs that build on top of the previous ones. Confirm with the human when doing this.
- When the parent task is completed, ensure you mark is as done.
- When the first task is done, repeat this process for all tasks until all tasks are done.
- If you run into an issue where the JWT seems expired or commands don't work, ensure you use `tm auth refresh` to refresh the token and if that does not work, use `tm context <brief url>` to reconnect the context. If you do not have the brief url, ask the user for it (perhaps use it at the beginning)
You're a fast hamster. Go go go.

View File

@@ -0,0 +1,133 @@
Fix PR review comments: PR # $ARGUMENTS
This command collects all review comments from a GitHub PR (including CodeRabbit, human reviewers, and other bots), consolidates them by author and severity, shows them to you for approval, then implements the approved fixes.
Steps:
1. **Collect PR comments**
- Run: `gh pr view $ARGUMENTS --comments` to get ALL comments (no truncation)
- Parse and extract all review comments from:
- PR review comments (file-level)
- General comments
- Review threads
- Include author information for each comment
- IMPORTANT: Do NOT use `head`, `tail`, or any truncation - we need complete comment history
2. **Consolidate comments**
- Group comments by:
- Author (CodeRabbit, human reviewers, other bots)
- Severity (🚨 Critical, ⚠️ Important, 💡 Suggestion, Info)
- Category (Security, Performance, Best Practices, Style, etc.)
- Remove duplicates and group similar issues
- Present in a clear, numbered list format showing author for each
3. **Show consolidated issues for approval**
- Display the organized list with:
- Issue number for reference
- Severity indicator
- File location
- Description
- Suggested fix
- Ask: "Which issues would you like me to fix? (Enter numbers separated by commas, or 'all' for everything)"
- Wait for user confirmation
4. **Implement approved fixes**
- For each approved issue:
- Read the relevant file(s)
- Implement the suggested fix
- Log what was changed
5. **Validate changes**
- Run: `pnpm typecheck`
- If fails: review errors, fix them, retry
- Run: `pnpm lint`
- If fails: review errors, fix them, retry
- Continue until both pass
6. **Commit and push**
- Stage changes: `git add .`
- Create commit: `git commit -m "fix: address review comments from PR #$ARGUMENTS"`
- Push: `git push`
- Confirm completion with summary of fixes applied
Notes:
- If no review comments found, inform user and exit
- If typecheck/lint fails after fixes, show errors and ask for guidance
- Keep fixes focused on reviewers' specific suggestions
- Preserve existing code style and patterns
- Group related fixes in the commit message if many changes
- Treat all reviewers equally - human and bot feedback both matter
You previously got all the PR comments in a temporary JSON file and then ran something like this;
cat > /tmp/parse_comments.js << 'EOF'
const fs = require('fs');
const comments = JSON.parse(fs.readFileSync('/tmp/all-pr-comments.json', 'utf8'));
const byFile = {};
const bySeverity = {
critical: [],
important: [],
suggestion: [],
info: []
};
comments.forEach((c, idx) => {
const file = c.path;
const author = c.user.login;
const line = c.line || c.original_line || 'N/A';
const body = c.body;
if (!byFile[file]) byFile[file] = [];
const comment = {
num: idx + 1,
author,
line,
body: body.substring(0, 200) + (body.length > 200 ? '...' : ''),
fullBody: body
};
byFile[file].push(comment);
// Categorize by severity
const lower = body.toLowerCase();
if (lower.includes('critical') || lower.includes('security') || lower.includes('bug:')) {
bySeverity.critical.push({...comment, file});
} else if (lower.includes('important') || lower.includes('error') || lower.includes('fail')) {
bySeverity.important.push({...comment, file});
} else if (lower.includes('suggestion') || lower.includes('consider') || lower.includes('recommend')) {
bySeverity.suggestion.push({...comment, file});
} else {
bySeverity.info.push({...comment, file});
}
});
console.log('\n=== SUMMARY BY SEVERITY ===\n');
console.log(`🚨 Critical: ${bySeverity.critical.length}`);
console.log(`⚠️ Important: ${bySeverity.important.length}`);
console.log(`💡 Suggestion: ${bySeverity.suggestion.length}`);
console.log(` Info: ${bySeverity.info.length}`);
console.log('\n=== SUMMARY BY FILE ===\n');
Object.entries(byFile)
.sort((a, b) => b[1].length - a[1].length)
.forEach(([file, comments]) => {
console.log(`${file}: ${comments.length} comments`);
});
console.log('\n=== CRITICAL ISSUES ===\n');
bySeverity.critical.forEach(c => {
console.log(`\n#${c.num} [${c.author}] ${c.file}:${c.line}`);
console.log(c.body);
});
console.log('\n=== IMPORTANT ISSUES ===\n');
bySeverity.important.slice(0, 10).forEach(c => {
console.log(`\n#${c.num} [${c.author}] ${c.file}:${c.line}`);
console.log(c.body);
});
EOF
node /tmp/parse_comments.js
And got a nice report you could act on.

127
.claude/hamster.md Normal file
View File

@@ -0,0 +1,127 @@
# Hamster Integration Workflow
This guide outlines the process for working with tasks imported from Hamster briefs using Task Master. When connected to a Hamster brief, follow these specific guidelines to ensure proper task management and workflow execution.
## Command Restrictions
### Supported Commands
Use only these Task Master CLI commands when working with Hamster briefs:
```bash
tm list # List all tasks
tm show <sub/task id> --json # Show task details (--json saves tokens)
tm set-status # Update task status
tm auth refresh # Refresh authentication token
tm context <brief url> # Reconnect to Hamster brief context
```
### Unsupported Commands
- Do not use MCP tools when connected with Hamster briefs - they are not yet up to date with Hamster integration
- Do not use other Task Master CLI commands that haven't been verified to work with Hamster integration
## Task Workflow Process
### Starting a Task
```bash
# Mark task and subtasks as in-progress when starting
tm set-status -i 1,1.1 -s in-progress
# Multiple tasks/subtasks can be marked at once using comma separation
tm set-status -i 1,1.1,1.2,2 -s in-progress
```
### Task Implementation Flow
1. **Read the Task**: Use `tm show <id> --json` to understand the task requirements
2. **Check for Subtasks**: If the task has subtasks, implement them one at a time
3. **Implement Subtask**: Complete the subtask implementation
4. **Verify Quality**: Run lint and typecheck before marking as done
```bash
npm run lint
npm run typecheck
```
5. **Mark Complete**: If verification passes, mark the subtask as done
```bash
tm set-status -i 1.1 -s done
```
6. **Commit Changes**: Commit the completed subtask work
7. **Repeat**: Continue until all subtasks are complete
### Parent Task Completion
- After all subtasks are done, run final verification:
```bash
npm run lint
npm run typecheck
```
- Mark the parent task as done:
```bash
tm set-status -i 1 -s done
```
- Move to the next task and repeat the process
## Multiple Task Context
### Viewing Multiple Tasks
```bash
# Use comma-separated IDs to get context from multiple tasks
tm show 1,1.1,2,2.1 --json
# This is more efficient than calling tm show multiple times
```
### Parallel Subtask Execution
- **When to Parallelize**: If a task has subtasks that can be completed in parallel
- **Requirements**:
- Ensure work/files to adjust are **not the same** across subtasks
- Spawn sub-agents for each parallel subtask
- Verify clear separation of work before parallelizing
- **Example**: If subtask 1.1 modifies `src/api.js` and subtask 1.2 modifies `src/utils.js`, these can run in parallel
## Pull Request Management
### PR Creation Strategy
- **Preferred Approach**: Keep everything in one PR if scope remains manageable
- **When to Split**: Create separate PRs if the work becomes too large
- **Multi-PR Strategy**: If splitting is necessary:
- Create PRs that build on top of previous ones
- **Always confirm with the human** before creating multiple PRs
- **PR Creation**: Use GitHub CLI after completing a task:
```bash
gh pr create --title "Task X: [Task Title]" --body "Description"
```
### Committing to PRs
- Keep committing to the same PR as long as the scope is maintained
- An entire task list (brief) might fit into a single PR
- If scope grows too large, discuss with human before creating new PRs
## Authentication & Context Management
### Token Refresh
```bash
# Refresh token if JWT seems expired or commands don't work
tm auth refresh
# If refresh doesn't work, reconnect context
tm context <brief url>
```
### Context Reconnection
- **When Needed**: If commands stop working or authentication fails
- **Required Information**: Brief URL (ask user if not available)
- **Best Practice**: Store brief URL at the beginning of the session
- **Command**: `tm context <brief url>`
## Integration with Git Workflow
When working with Hamster briefs, follow standard Git workflow patterns:
- Create task-specific branches: `task-XXX`
- Commit subtask work incrementally
- Create PRs after task completion
- Follow commit message standards
## Key Principles
- **Incremental Progress**: Work through subtasks one at a time
- **Quality Gates**: Always run lint and typecheck before marking tasks as done
- **Clear Communication**: Confirm with human before splitting work across multiple PRs
- **Efficient Context Gathering**: Use comma-separated IDs when viewing multiple tasks
- **Authentication Management**: Proactively refresh tokens and reconnect context when needed

329
.cursor/commands/goham.md Normal file
View File

@@ -0,0 +1,329 @@
# Start Working with Hamster Brief
End-to-end workflow for working on tasks from a connected Hamster brief. All tasks from the brief are worked on in a single branch, with one PR created at the end.
## Step 1: Verify Connection & Authentication
```bash
# Check current context and authentication status
tm context
```
If not connected or authentication fails:
- Get brief URL from user if not available
- Connect: `tm context <brief url>`
- Refresh token if needed: `tm auth refresh`
## Step 2: List Available Tasks
```bash
# View all tasks from the brief
tm list
```
Review the task list to understand what needs to be done. Note the total number of tasks.
## Step 3: Initialize Git Branch for Brief
```bash
# Ensure you're on dev branch and pull latest
git checkout dev
git pull origin dev
# Create a single branch for the entire brief (e.g., hamster-brief-YYYY-MM-DD or brief-specific name)
git checkout -b hamster-brief
# Verify branch creation
git branch
```
**Note**: This branch will be used for ALL tasks in the brief. Do not create separate branches per task.
## Step 4: Task Loop (Repeat for Each Task)
Work through all tasks sequentially in the same branch:
### 4.1: Read Task Details
```bash
# Get detailed information about the task
tm show 1
# If task has subtasks, examine them all
tm show 1,1.1,1.2,1.3 # Adjust IDs as needed
```
### 4.2: Log Initial Context
```bash
# Document task understanding and initial findings
tm update-task -i 1 --append --prompt="Starting task implementation.
Initial context:
- Task requirements: [summarize key requirements]
- Dependencies identified: [list any dependencies]
- Files that may need modification: [list relevant files]
- Approach planned: [brief implementation approach]"
```
### 4.3: Mark Task as In-Progress
```bash
# Mark task and first subtask (if exists) as in-progress
tm set-status -i 1,1.1 -s in-progress
```
### 4.4: Subtask Implementation Loop
For each subtask (1.1, 1.2, 1.3, etc.):
#### 4.4.1: Read Subtask Details
```bash
tm show 1.1 # Replace with current subtask ID
```
#### 4.4.2: Log Research & Context Gathering
```bash
# Document findings during implementation
tm update-task -i 1 --append --prompt="Subtask 1.1 - Context gathered:
- Code exploration findings: [what you discovered]
- Implementation approach: [how you plan to implement]
- Key decisions made: [important choices]
- Challenges encountered: [any blockers or issues]"
```
#### 4.4.3: Implement Subtask
- Write code following the subtask requirements
- Make necessary changes to files
#### 4.4.4: Quality Verification
```bash
# Run linting
pnpm lint
# Run type checking
pnpm typecheck
# If either fails, fix issues and re-run until both pass
```
#### 4.4.5: CodeRabbit Review
```bash
# Generate code review (wait for plain text results)
coderabbit --prompt-only
# Review the output and address any critical issues if needed
```
#### 4.4.6: Log Implementation Completion
```bash
# Document what was completed
tm update-task -i 1 --append --prompt="Subtask 1.1 - Implementation complete:
- Files modified: [list files changed]
- Key changes: [summary of implementation]
- CodeRabbit feedback addressed: [if any issues were fixed]
- Ready for commit"
```
#### 4.4.7: Commit Subtask Work
```bash
# Stage changes
git add .
# Commit with detailed message following git_workflow.mdc format
git commit -m "feat(task-1): Complete subtask 1.1 - [Subtask Title]
- Implementation details
- Key changes made
- Files modified: [list files]
- CodeRabbit review completed
Subtask 1.1: [Brief description of what was accomplished]
Relates to Task 1: [Main task title]"
```
#### 4.4.8: Mark Subtask as Done
```bash
tm set-status -i 1.1 -s done
```
#### 4.4.9: Move to Next Subtask
Repeat steps 4.4.1 through 4.4.8 for the next subtask (1.2, 1.3, etc.)
### 4.5: Complete Parent Task
After all subtasks are complete:
#### 4.5.1: Final Quality Checks
```bash
# Final linting
pnpm lint
# Final type checking
pnpm typecheck
# Final CodeRabbit review
coderabbit --prompt-only
# Address any remaining issues if critical
```
#### 4.5.2: Log Task Completion
```bash
# Document final task completion
tm update-task -i 1 --append --prompt="Task 1 - Complete:
- All subtasks completed: [list all subtasks]
- Final verification passed: lint, typecheck, CodeRabbit review
- Files changed: [comprehensive list]
- Committed to brief branch"
```
#### 4.5.3: Mark Parent Task as Done
```bash
tm set-status -i 1 -s done
```
**Note**: Do NOT push or create PR yet. Continue to next task in the same branch.
### 4.6: Move to Next Task
```bash
# Verify remaining tasks
tm list
# Continue with next task (e.g., Task 2)
# Repeat steps 4.1 through 4.5 for Task 2, then Task 3, etc.
```
## Step 5: Complete All Tasks
Continue working through all tasks (Steps 4.1-4.6) until all tasks in the brief are complete. All work is committed to the same `hamster-brief` branch.
## Step 6: Final Verification & PR Creation
After ALL tasks are complete:
### 6.1: Verify All Tasks Complete
```bash
# Verify all tasks are done
tm list
# Should show all tasks with status 'done'
```
### 6.2: Final Quality Checks
```bash
# Final comprehensive checks
pnpm lint
pnpm typecheck
coderabbit --prompt-only
# Address any remaining issues if critical
```
### 6.3: Push Branch
```bash
# Push the brief branch to remote
git push origin hamster-brief
```
### 6.4: Create Pull Request to Dev
```bash
# Get all task titles (adjust task IDs as needed)
# Create comprehensive PR description
gh pr create \
--base dev \
--title "Hamster Brief: Complete Implementation" \
--body "## Brief Overview
Completed all tasks from Hamster brief.
## Tasks Completed
- [x] Task 1: [Task 1 title]
- Subtasks: 1.1, 1.2, 1.3
- [x] Task 2: [Task 2 title]
- Subtasks: 2.1, 2.2
- [x] Task 3: [Task 3 title]
- [Continue listing all tasks]
## Implementation Summary
- Total tasks: [number]
- Total subtasks: [number]
- Files modified: [comprehensive list]
- All quality checks passed
## Quality Checks
- ✅ Linting passed (pnpm lint)
- ✅ Type checking passed (pnpm typecheck)
- ✅ CodeRabbit review completed for all changes
## Testing
- [ ] Manual testing completed
- [ ] All checks passing
Complete implementation of Hamster brief tasks"
```
## Step 7: Cleanup
```bash
# After PR is merged, switch back to dev
git checkout dev
git pull origin dev
# Delete local branch (optional)
git branch -d hamster-brief
```
## Important Notes
- **Use ONLY**: `tm list`, `tm show <id>`, `tm set-status`, `tm update-task`, `tm auth refresh`, `tm context <brief url>`
- **DON'T use MCP tools** - not compatible with Hamster integration
- **Single branch per brief**: All tasks work in the same branch (`hamster-brief`)
- **Single PR per brief**: One PR created after all tasks are complete
- **Always target dev branch** - never main branch
- **Regular logging**: Use `tm update-task -i <id> --append` frequently to document:
- Context gathered during exploration
- Implementation decisions made
- Challenges encountered
- Completion status
- **Quality gates**: Never skip lint, typecheck, or CodeRabbit review
- **Commit format**: Follow git_workflow.mdc commit message standards
- **PR format**: Always use `--base dev` when creating PRs
## Workflow Summary
```
1. Verify connection → tm context
2. List tasks → tm list
3. Create single branch → git checkout -b hamster-brief
4. For each task (in same branch):
a. Read task → tm show X
b. Log context → tm update-task -i X --append
c. Mark in-progress → tm set-status -i X,X.Y -s in-progress
d. For each subtask:
- Read → tm show X.Y
- Log context → tm update-task -i X --append
- Implement code
- Verify → pnpm lint && pnpm typecheck
- Review → coderabbit --prompt-only
- Log completion → tm update-task -i X --append
- Commit → git commit (following git_workflow.mdc format)
- Mark done → tm set-status -i X.Y -s done
e. Final checks → pnpm lint && pnpm typecheck && coderabbit --prompt-only
f. Log completion → tm update-task -i X --append
g. Mark task done → tm set-status -i X -s done
h. Continue to next task (same branch)
5. After ALL tasks complete:
a. Final verification → pnpm lint && pnpm typecheck && coderabbit --prompt-only
b. Push branch → git push origin hamster-brief
c. Create PR → gh pr create --base dev
```
## References
- Full guidelines: [hamster.mdc](mdc:.cursor/rules/hamster.mdc)
- Git workflow: [git_workflow.mdc](mdc:.cursor/rules/git_workflow.mdc)

173
.cursor/rules/hamster.mdc Normal file
View File

@@ -0,0 +1,173 @@
---
description: Guidelines for implementing tasks imported from Hamster using Task Master CLI
globs: "**/*"
alwaysApply: true
---
# Hamster Integration Workflow
This guide outlines the process for working with tasks imported from Hamster briefs using Task Master. When connected to a Hamster brief, follow these specific guidelines to ensure proper task management and workflow execution.
## **Command Restrictions**
### **Supported Commands**
- **✅ DO**: Use only these Task Master CLI commands when working with Hamster briefs:
```bash
tm list # List all tasks
tm show <sub/task id> --json # Show task details
tm set-status # Update task status
tm auth refresh # Refresh authentication token
tm context <brief url> # Reconnect to Hamster brief context
```
### **Unsupported Commands**
- **❌ DON'T**: Use MCP tools when connected with Hamster briefs - they are not yet up to date with Hamster integration
- **❌ DON'T**: Use other Task Master CLI commands that haven't been verified to work with Hamster integration
## **Task Workflow Process**
### **Starting a Task**
```bash
# ✅ DO: Mark task and subtasks as in-progress when starting
tm set-status -i 1,1.1 -s in-progress
# Multiple tasks/subtasks can be marked at once using comma separation
tm set-status -i 1,1.1,1.2,2 -s in-progress
```
### **Task Implementation Flow**
1. **Read the Task**: Use `tm show <id> --json` to understand the task requirements (json used to save tokens, gets you same information)
2. **Check for Subtasks**: If the task has subtasks, implement them one at a time
3. **Implement Subtask**: Complete the subtask implementation
4. **Verify Quality**: Run lint and typecheck before marking as done
```bash
npm lint
npm typecheck
```
5. **Mark Complete**: If verification passes, mark the subtask as done
```bash
tm set-status -i 1.1 -s done
```
6. **Commit Changes**: Commit the completed subtask work
7. **Repeat**: Continue until all subtasks are complete
### **Parent Task Completion**
- After all subtasks are done, run final verification:
```bash
npm lint
npm typecheck
```
- Mark the parent task as done:
```bash
tm set-status -i 1 -s done
```
- Move to the next task and repeat the process
## **Multiple Task Context**
### **Viewing Multiple Tasks**
```bash
# ✅ DO: Use comma-separated IDs to get context from multiple tasks
tm show 1,1.1,2,2.1 --json
# This is more efficient than calling tm show multiple times
```
### **Parallel Subtask Execution**
- **When to Parallelize**: If a task has subtasks that can be completed in parallel
- **Requirements**:
- Ensure work/files to adjust are **not the same** across subtasks
- Spawn sub-agents for each parallel subtask
- Verify clear separation of work before parallelizing
- **Example**: If subtask 1.1 modifies `src/api.js` and subtask 1.2 modifies `src/utils.js`, these can run in parallel
## **Pull Request Management**
### **PR Creation Strategy**
- **Preferred Approach**: Keep everything in one PR if scope remains manageable
- **When to Split**: Create separate PRs if the work becomes too large
- **Multi-PR Strategy**: If splitting is necessary:
- Create PRs that build on top of previous ones
- **Always confirm with the human** before creating multiple PRs
- **PR Creation**: Use GitHub CLI after completing a task:
```bash
gh pr create --title "Task X: [Task Title]" --body "Description"
```
### **Committing to PRs**
- Keep committing to the same PR as long as the scope is maintained
- An entire task list (brief) might fit into a single PR
- If scope grows too large, discuss with human before creating new PRs
## **Authentication & Context Management**
### **Token Refresh**
```bash
# ✅ DO: Refresh token if JWT seems expired or commands don't work
tm auth refresh
# If refresh doesn't work, reconnect context
tm context <brief url>
```
### **Context Reconnection**
- **When Needed**: If commands stop working or authentication fails
- **Required Information**: Brief URL (ask user if not available)
- **Best Practice**: Store brief URL at the beginning of the session
- **Command**: `tm context <brief url>`
## **Integration with Git Workflow**
When working with Hamster briefs, follow the standard Git workflow patterns outlined in [git_workflow.mdc](mdc:.cursor/rules/git_workflow.mdc):
- Create task-specific branches: `task-XXX`
- Commit subtask work incrementally
- Create PRs after task completion
- Follow commit message standards
## **Workflow Summary**
```mermaid
flowchart TD
A[Start: Connect to Hamster Brief] --> B[Get Brief URL]
B --> C[tm list - View Tasks]
C --> D[Select Task]
D --> E[tm show <id> --json - Read Task]
E --> F{Has Subtasks?}
F -->|Yes| G[Select First Subtask]
F -->|No| M[Implement Task]
G --> H[tm set-status -i X.Y -s in-progress]
H --> I[Implement Subtask]
I --> J[npm lint && npm typecheck]
J --> K{Passes?}
K -->|Yes| L[tm set-status -i X.Y -s done]
K -->|No| I
L --> N{More Subtasks?}
N -->|Yes| G
N -->|No| O[Final lint/typecheck]
M --> O
O --> P[tm set-status -i X -s done]
P --> Q[Commit & Create PR]
Q --> R{More Tasks?}
R -->|Yes| D
R -->|No| S[Complete]
style A fill:#e1f5fe
style H fill:#fff3e0
style L fill:#e8f5e8
style Q fill:#fce4ec
```
## **Key Principles**
- **Incremental Progress**: Work through subtasks one at a time
- **Quality Gates**: Always run lint and typecheck before marking tasks as done
- **Clear Communication**: Confirm with human before splitting work across multiple PRs
- **Efficient Context Gathering**: Use comma-separated IDs when viewing multiple tasks
- **Authentication Management**: Proactively refresh tokens and reconnect context when needed
## **Related Rules**
- [Git Workflow](mdc:.cursor/rules/git_workflow.mdc) - Standard Git branching and PR practices
- [Development Workflow](mdc:.cursor/rules/dev_workflow.mdc) - General Task Master development patterns
- [Task Management](mdc:.cursor/rules/tasks.mdc) - Task structure and operations

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname, resolve } from 'node:path';
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { readFileSync, existsSync, writeFileSync } from 'fs';
import { existsSync, readFileSync, writeFileSync } from 'fs';
function parseMetricsTable(content, metricName) {
const lines = content.split('\n');

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { existsSync, unlinkSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { findRootDir, runCommand } from './utils.mjs';

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env node
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { findRootDir, createAndPushTag } from './utils.mjs';
import { createAndPushTag, findRootDir } from './utils.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { join, dirname, resolve } from 'node:path';
import { dirname, join, resolve } from 'node:path';
// Find the root directory by looking for package.json with task-master-ai
export function findRootDir(startDir) {

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { readFileSync, readdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);

6
.gitignore vendored
View File

@@ -102,3 +102,9 @@ apps/extension/vsix-build/
# No longer needed in .gitignore as state is stored globally
.scannerwork
# OS specific
# Task files
tasks.json
tasks/

View File

@@ -7,7 +7,7 @@
```bash
# Project Setup
task-master init # Initialize Task Master in current project
task-master parse-prd .taskmaster/docs/prd.txt # Generate tasks from PRD document
task-master parse-prd .taskmaster/docs/prd.md # Generate tasks from PRD document
task-master models --setup # Configure AI models interactively
# Daily Development Workflow
@@ -40,10 +40,15 @@ task-master validate-dependencies # Check for depende
- `.taskmaster/tasks/tasks.json` - Main task data file (auto-managed)
- `.taskmaster/config.json` - AI model configuration (use `task-master models` to modify)
- `.taskmaster/docs/prd.txt` - Product Requirements Document for parsing
- `.taskmaster/docs/prd.md` - Product Requirements Document for parsing (`.md` extension recommended for better editor support)
- `.taskmaster/tasks/*.txt` - Individual task files (auto-generated from tasks.json)
- `.env` - API keys for CLI usage
**PRD File Format:** While both `.txt` and `.md` extensions work, **`.md` is recommended** because:
- Markdown syntax highlighting in editors improves readability
- Proper rendering when previewing in VS Code, GitHub, or other tools
- Better collaboration through formatted documentation
### Claude Code Integration Files
- `CLAUDE.md` - Auto-loaded context for Claude Code (this file)
@@ -61,11 +66,11 @@ project/
│ │ ├── task-1.md # Individual task files
│ │ └── task-2.md
│ ├── docs/ # Documentation directory
│ │ ├── prd.txt # Product requirements
│ │ ├── prd.md # Product requirements (.md recommended)
│ ├── reports/ # Analysis reports directory
│ │ └── task-complexity-report.json
│ ├── templates/ # Template files
│ │ └── example_prd.txt # Example PRD template
│ │ └── example_prd.md # Example PRD template (.md recommended)
│ └── config.json # AI models & settings
├── .claude/
│ ├── settings.json # Claude Code configuration
@@ -137,8 +142,8 @@ complexity_report; // = task-master complexity-report
# Initialize Task Master
task-master init
# Create or obtain PRD, then parse it
task-master parse-prd .taskmaster/docs/prd.txt
# Create or obtain PRD, then parse it (use .md extension for better editor support)
task-master parse-prd .taskmaster/docs/prd.md
# Analyze complexity and expand tasks
task-master analyze-complexity --research

View File

@@ -0,0 +1,49 @@
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▓▓▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▓▒▒▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▓▒▓▒▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓████▓▓▓▓▓▓▓▒▒▒▓▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓█ ░░▓▓▒ ▒▓▓▓▓▒▒▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██ ▒▓░▒▒▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▓ ░ ██▒ ▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓██▒ ▓█░ ░ ▒██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓░▓ ▒▒▒▒▒░ ▒█▓▒ ▓██████▓█▓▓▓░▓▓████████▒ ▓▓█▒ ▒▒▒▒ ▓░█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓░░ ░▒▓▓▓▒▒▒ ██▓▓ ▒▓ ░ ░▒▓░▒▒░░ ▒ ▓ ▓█▓█ ▒▓███▓ ▒▒█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█ ▒▒█▓▓▒ ▒▒▒ ▒█▒██ ░▒▒▒░░░░░▒▒▒░▒▒▓▓▒▒▒░▓▓▒▓█ ▒░░▒▓█▒▒ █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓░▒▒▒▒▒▒▒░░░░ █▒▒█▓▒▒▒▒▓▓▓▒▒░░░░▒░▒▒▓▓▒▒▒▒▒█ ░▓▓▒▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒░█▒▒▒▒░░ ▓█ ▒▓░▒░▒▓▓▓▓▓▓██▓▓▒░░░▒▓▒▓███▓▓█░▓░░▒▒▒▒▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▓▓▓▓▓▓▓▓▒▒▒▒▓░░▒▓▒▒░▒ ▓▒▒░▒▓▓▓██████▓▓▓▓▒░▒▓▓▓████████▒▓ ▒▒▒▒ ██▓▓▓▓▓█▒▒▓████▓▓▓▓▓
▒▒▒▓░░ ▒▓ ░░▒▒▒▒▒▓▓ ░▒▒░ ░▒▒▒▒▒▒▓████████▓▓▓█▓▒█▓▒▓█▒ ░▓██▓░░▒ ░██▓▒▓▓▓▓▒░▓░█ ▒▓ █▓▓▓
▓▓▒▓ ▓█ ▒░█▒▒▓▒▒▒▒▓██ ░▒▒▒▒▒▒▒▒▒▓▒▓░ ▒▓▒▒▓▓▓▒█▓▓▓▒▒▒██▒▒█▓▓░▓██▓▓▓███▓█ ▒█ ▒█░▒██▓▓
▓ ▓▓░ ▓█ █▒ ▓▒▒▒▓▓▒██░ ░░▒▒▓▓▒▒▒▓▓██████▓▒▓▓▒▓▓▓▓▒▓▓██████████▒ █▓▓▓▓▒ ▓░▒ ▒█▓▒█ ░▓▒░▓
▓▒ ██░ ████▓ ░▒█▒ ▓▓▓░ ░▒▒▒▒▒▒▓▓▒▒▓█░ ▓▓▓▒▓▒▒▒▓▒▓▒▓▒ ▓▓▓▒▒▒░▓▓▒▓█ ▒█▒░▓█▓████▓ ░█▓
░▒▒ ▓██▒▒▒█▒██░ ▓▓▓ ░░▒▒░▒▓▓▒▓▒▓▓ ░█ ▓▓▒▒▒▒▒▒▒▒▓█ ██ ▒▓▓█▓▒ █▓▓▓█ ▒███▒░ ▒█▒▒█▒░▓
░ ░▓███▓▒▒ ▓██░░▓▓▓░░▒▒░▒▒▒▒░▒▒▒▒▒ ▓ ▓▒▒▒▒▓▒▓▒▒▒▓ ▒▓▒▒▒░▒█▒▓██ ▓██▒████▒ ▓▓
▓▓▓░ ████▓█▒▒░▒██▓░░░░░▓ ░ ░ ▒ ░▒▒▓▓▓▓█████▒ ░ ▒▒▒░▓░ ███▓ ░▓▒▓█▓░ ███▓▒
▒▒▒▓▓ ▓██▓▒▒▒░ ▒▓ ░░░░░░▒▒▒█████▓▓ ▒▓▓▓▓██████████▓▓▓▓▓▓████▒▒░ ▒░▓▒▒░ ▒█▓▓▓▓▓
▒▒▒▒▓█▒ ▒▒▓█▓▓ ░▒▒░▒▒▓▓▒▒▒▒▒▒▒▒█████▓▓▓▒▒▓█▓░▒▓█▓▒███▓▒██▒▒▒▒▒▒▓▒░ ▒▓▒▒▒▓▓▓▒▒█▓▒▒▒▒▒
▒▒▒▒▒█ ░▒▒▒▓▒▓▓███ ░▒▒▒▒▒▓▓▒▒▒▓███▓██▓▒▒████▓▒ ░▒▓▓░ ████▒▓████▓▓██▒ ▓▓▓▓▓▓▓▓ ▓█▒▒▒▒▒▒
▒▒▒▒▒█ ░▒▒░▓▓▓▓▓▓█▓░░▒▒▒▒▒▓▓▓█▓▓▒▓▓▓██▓█████████ ▒ ██████▓█▓▒██▓▓█▓▒░▒▒▒▒▒▒▓▒ ▓▓▒▒▒▒▒
▒▒▒▒▒█ ▒▒░▒▒▒▒▓▒▓▒▓ ░▓▓▓██▒▒▒▒▒▒░░▒▓▒░░█▓███▓█████▒▒███████░ ▒▓▓▒▒▒▓▓▓██▒▒▒▒▒▒▓░░█▓▒▒▒▒▒
▒▒▒▒▒█▒░▒░▒▓▒▒▓▒▒▒▓█ ░▒▒▒▒▓▒▒▒▒▒█▓▓▒ ▓███▓▓▓▒▓▓ ▓███▓▓ ▒▒▓▓▓█▒▓▓▓▒ ░▓█▓▒▒▓ █▓▒▒▒▒▒▒
▒▒▒▒▒█ ░▒░░░▓▒▒▒█▒▒▒░ ▒▒▓▒▒▓███▓▓▒▒▒██▒ ░████░░██▒ ▒▓▓▓▓▓▓▓▓█▓▒▓░▒▒▒▒▓▓▒ █▓▒▒▒▒▒▒
▒▒▒▒▒▓ ░▒▒░░▒▓▒▒▒▒▒▒▒ ░▒▒█▒██▓▓▓██▓▒▓▓██▒ ░██ █▓▒▒▓▓██▓██▓▓██ ▒▓▒▒▒▒▒░░█▒▒▒▒▒▒▒
▒▒▒▒▒▓▓ ░▒░░▒░▒▒▒▒▒▒▒░▒░░▓▓▒▒▒▓▓▓▓▓▒▓▓▓██ ██▒▓▓█▓█▒▓▓▓▓▓▒ ▒█▓▒▒▒▒▒ █▓▒▒▒▒▒▒▒
▒▒▒▒▒▒▓▒░▒▒░░▒▒▒▒▓▒▒▒░ █░▒▒▒▒▓▓▒▒▓▒▓▓▓▒▓█ ▒█▓▒░░▒▒░ ██▓▓█▓▓▒█▒▒▓▒▓▓ ▒▒▒▒▒▒▒░ ▓▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓ ▒▒░░░░░░▒▒▒▒▓▒▒ ░▒▓▓▓▓▓▓▓▓▓▓▒▓▓▓██ ▓█▓▓█▓░░██▓▓▓▒▓▒▓▒▓█▒▒ ░▓▓▒▓▒▒▒░▒█▓▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▓ ░░▒▒▒░▒▒▒▒▒▓▒▒░ ░▒▒▓▓▓▓▓▒▒▒▒▒▒▒▓██▓ ▒█▓▒▒▓▒▓▓▓█▓▓░ ▒▓▓▒▒▒░░ █▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▓░░▓▒░░░░░▒▒░▒▒▒▒▒░ ░▒░▒▓▓▒▓▓▓▒▒▒▓████████▓░▒▓▒▓▒▓░░ ░▒▓▒▒▒▒▓▒▒░░▓▓▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▓ ▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒░░░░░▒▒▒▒▒▒▒▒░ ░▒██▓░ ░▓▒▒▒▒░ ▒▒▒█▓▓▒▒▒▒▒▒▒░ ▒▓▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▓█ ░▒▒░░▒░░▒▒▒▒▒▓▒▒▓▓▓▓▒▒▒░░░░▒▒▒▒▒░ ░▒▒░░░░▒▓▓▓▓▓█▓▓▓▒▒▒▒░ ▓▓▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▓ ░ ▒░▒▒▒░▒▒▒▒░▓▓▒▒▒▓▓▒▒▓▓▒▒▓▓▒▒▒░▒▒▒▒▒▒▒▒▓▓▓▓▓▓█▓██▓▓▓▒▓▒▒▒▒▒░ ▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▓█ ░▒░ ░░░░░▒▒▒▒░░▓█▒▒▒▓▓▒▒▓▓▒▒▒▒▒▓▒▓▒▓▒▓▒▒▒▓▒▓███▓▓▓▓▓▒▓▓▒▒░ ░ ▓█▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▓ ▒▒▒░░░░░░▒░ ▒▒▒▒░▒▒▓▓▓██▓▓▓▓▓▓█░█▒▒█▒▓██▓███▓▓▓▓▓▓▓▓▓▓▒██▒▒░ ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▓░░▒▒░░░░░▒▒▒▒▒▒▒▒▒▒▓▒▒▓▓▒▒▓█▓▓██▓█████▓▓▓██▓██▓█▓▓▓▓▓▓▓▓▓▒▒▒▓▓░ █▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▒ ▒▒░░░░▒▒░░░▒▒▒▒▒▒▓▒▒▒▒▒▒██▒█▓▒▓▓▓▓▓▓▓█▓▓▓█▓▒▒▓██▒▒▒▒▒█▓▓▒▓▓▒▓▓ ▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▓ ▒▒░░░░▒░▒▒▒▒▒▒▓▒▒▒▓▓▓▓▓▒▒░▒▒▒▓▓█▓▓▓▓▒▓▓▓▒▒▓▓▓▒▒▒▓▓▓▓▓▓▓██▒▓▓▓▒░░▓▓▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▓▒ ▒▒░░░░▒▒▒▒▒▒▒▒▓▓▓▓▒▓▓▒▒▒▒▓▒▒▒▒▓▓▒▒▒▓▒▓▒▓▒▒▒▓▒▓▒▓▒█▓▓▓▓▓▓▒▓█▓▒▓▓▓░ ▓▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▓ ░▒░░░░░░░▒▒▒▒▒▓▒▓░▒▓▒▒▓▓▒▓▒██▓▓▒▒▓▒▓▓▒▓▓▒█▒▒▒▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓ ░▓▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▓▒ ▒▒░░▒▒▒▒▒▒░▓▓▓▒▒▓▓▓▓▓▓▓▓▓█▒▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▓░ ▒▒░░░░░░░░▒▒▓▒▒▒▒▓▒▒▓▒▒▓▒▓▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓█▓▒▓▓▓▓▓▓▒▓▓░ ▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▓▒░▒░░░░░░░░▒▒▒▒░▓▒▒▒▒▒▓▒▒▓▒▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▒▒▓▒▓▒█▓▒█▓▒▒▓▓▒▒▒▒▓▓ ▓▓▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▓ ▒▒░░▒░░▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▓▓▒▒▓▓▒▓▓▒▓▓▒▓▓▓▓▒▒▒▒▓▒▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▓▒░▓▒▒▒▒▒▒▒▒▒▒
░ ░

View File

@@ -1,26 +1,26 @@
{
"models": {
"main": {
"provider": "anthropic",
"modelId": "claude-sonnet-4-5-20250929",
"maxTokens": 64000,
"provider": "claude-code",
"modelId": "haiku",
"maxTokens": 200000,
"temperature": 0.2
},
"research": {
"provider": "perplexity",
"modelId": "sonar-pro",
"modelId": "sonar-reasoning-pro",
"maxTokens": 8700,
"temperature": 0.1
},
"fallback": {
"provider": "anthropic",
"modelId": "claude-sonnet-4-20250514",
"provider": "claude-code",
"modelId": "sonnet",
"maxTokens": 64000,
"temperature": 0.2
}
},
"global": {
"logLevel": "info",
"logLevel": "silent",
"debug": false,
"defaultNumTasks": 10,
"defaultSubtasks": 5,
@@ -30,6 +30,8 @@
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
"responseLanguage": "English",
"enableCodebaseAnalysis": true,
"enableProxy": false,
"anonymousTelemetry": true,
"userId": "1234567890",
"azureBaseURL": "https://your-endpoint.azure.com/",
"defaultTag": "master"

View File

@@ -0,0 +1,166 @@
---
title: Research Session
query: "should we be doing the taskmaster gateway even though we have a hamster connection now? how might we serve the ai from hamster as a standalone model?"
date: 11/17/2025
time: 7:35:00 PM
timestamp: 2025-11-18T00:35:00.754Z
exchanges: 2
---
# Research Session
## Initial Query
**Question:** should we be doing the taskmaster gateway even though we have a hamster connection now? how might we serve the ai from hamster as a standalone model?
**Response:**
You do not necessarily need to proceed with the Taskmaster Gateway integration if your current **Hamster connection** already provides the required AI capabilities, but the decision depends on your specific project needs, desired features, and architectural preferences. Serving the AI from Hamster as a standalone model is feasible and may offer advantages in control, cost, and flexibility, but it also comes with trade-offs compared to using a managed gateway.
---
## Should You Still Integrate the Taskmaster Gateway?
**Key considerations:**
- **Feature Set:**
The Taskmaster Gateway is designed to provide access to **premium AI-powered test generation, TDD orchestration, and smart git workflows** via API key authentication. If Hamster already delivers these features (or you can implement them), the gateway may be redundant.
- **Centralization and Vendor Lock-in:**
Using the gateway centralizes premium features and may simplify updates, billing, and support. However, it can introduce vendor lock-in and dependency on external uptime and pricing.
- **Local vs. Remote AI:**
The gateway is intended to maintain local file operations while leveraging remote AI intelligence. If Hamster can run locally or on your own infrastructure, you gain more control over data privacy, latency, and cost.
- **Testing and Workflow Integration:**
If your team values seamless integration with **git workflows** and **test orchestration** as provided by the gateway, and these are not easily replicated with Hamster, the gateway may still be valuable.
- **Project Roadmap:**
If Task 102 is high priority and aligns with your long-term goals (e.g., supporting multiple AI backends or offering users a choice), completing the integration could future-proof your platform.
---
## Serving the AI from Hamster as a Standalone Model
If you decide to use Hamster as your primary AI backend, you can serve it as a **standalone model** in several ways:
### 1. **Local API Server**
- **Wrap the Hamster model** in a lightweight HTTP API (e.g., using FastAPI, Flask, or Express).
- Expose endpoints that mimic the Taskmaster Gateway API, allowing your CLI and other tools to interact with Hamster as if it were a remote service.
- This approach enables easy swapping between Hamster and other backends by changing the API endpoint.
### 2. **Direct Integration**
- Integrate Hamster directly into your Taskmaster codebase as a module or service.
- This reduces network overhead and can simplify error handling, but may require more code changes if you later want to support multiple backends.
### 3. **Containerization**
- Package Hamster and its serving API in a Docker container.
- Users can run the container locally or deploy it to their own infrastructure, maintaining isolation and reproducibility.
### 4. **Configuration and Abstraction**
- Add a configuration flag or environment variable to select between Hamster and the Taskmaster Gateway.
- Abstract the AI interaction layer so that switching backends requires minimal code changes.
---
## Actionable Recommendations
- **Evaluate Feature Parity:**
Compare the features of Hamster and the Taskmaster Gateway. If Hamster meets your needs, prioritize serving it as a standalone model.
- **Design for Flexibility:**
Implement an abstraction layer for AI providers. This allows you to support both Hamster and the Taskmaster Gateway (or others) with minimal friction.
- **Document the Setup:**
Clearly document how to run Hamster as a standalone service, including installation, configuration, and API usage. This will help with onboarding and maintenance.
- **Consider User Experience:**
If your users expect plug-and-play access to premium features (as with the gateway), ensure your Hamster integration matches or exceeds this experience.
---
## How This Applies to Your Tasks
- **Task 102 (Taskmaster Gateway Integration):**
If you choose to deprioritize this, document the rationale and ensure stakeholders agree. If you proceed, consider making the gateway optional, with Hamster as a default or fallback.
- **CLI and Directory Structure:**
Ensure that your `.taskmaster/` directory and CLI enhancements (Tasks 95, 57) are compatible with both Hamster and gateway-based workflows.
- **Installation and Configuration:**
Update your installation scripts and documentation (Tasks 64, 65, 31) to support configuration for Hamster as a backend, including any required flags or environment variables.
---
**Summary:**
If Hamster provides all required AI features, you can serve it as a standalone model via a local API or direct integration, potentially making the Taskmaster Gateway optional. Design your system to support backend flexibility, and ensure your documentation and CLI tools reflect this choice.
---
## Follow-up 1
**Question:** so if i have an ai sdk setup from hamster, could i just expose it as an option and let users pay us directly?
**Response:**
Yes, if you have an AI SDK setup from Hamster, you can expose it as an option and allow users to pay you directly—**but there are important technical, legal, and business considerations** to keep in mind for your project.
---
### **How It Works Technically**
- **SDK as a Service Layer:**
You can wrap the Hamster AI SDK in your own API or service layer, exposing endpoints that users interact with. This way, your platform acts as a middleman, routing requests to Hamsters backend and returning results.
- **Billing Integration:**
You can implement your own billing logic (e.g., usage-based or subscription) and charge users directly. Youd then pay Hamster for the underlying API usage (if their pricing is usage-based) or manage your own subscription costs.
- **User Experience:**
Users see your platform as the provider, not Hamster. You control the UX, branding, and support, while Hamster remains the backend engine.
---
### **Key Considerations for Your Project**
- **Pricing Model Alignment:**
Hamster AI offers a **free plan (250 credits/month)** and a **Pro plan (unlimited credits, $3.30/month)**. If you want to charge users, youll need to either:
- Use the Pro plan and resell access (with your own pricing),
- Or use the free plan and limit user access to 250 credits/month (not ideal for heavy usage).
- **Usage Tracking:**
Youll need to track user usage (e.g., tokens, requests) to bill them accurately and ensure you dont exceed Hamsters limits.
- **Legal & Terms of Service:**
Check Hamsters **terms of service** to ensure reselling or white-labeling is allowed. Some AI providers restrict commercial resale or require special agreements.
- **Abstraction Layer:**
To support flexibility (e.g., switching to other AI backends like Claude or Taskmaster Gateway), design an abstraction layer in your code. This lets you swap providers without breaking your user-facing API.
---
### **Actionable Steps for Your Team**
1. **Review Hamsters Terms:**
Confirm if reselling or white-labeling is permitted.
2. **Implement Usage Tracking:**
Add logic to track user requests/tokens and map them to your billing system.
3. **Design Your API Layer:**
Create endpoints that proxy requests to Hamster, with your own authentication and rate limiting.
4. **Set Up Billing:**
Integrate a payment processor (e.g., Stripe) to charge users directly.
5. **Document the Flow:**
Clearly explain to users how billing works and what theyre paying for.
---
### **Trade-offs vs. Taskmaster Gateway**
- **Pros of Using Hamster Directly:**
- Lower cost (especially if you use the Pro plan),
- More control over pricing and user experience,
- No vendor lock-in with Taskmaster.
- **Cons:**
- Youre responsible for billing, support, and compliance,
- Hamster may not offer all the premium features (e.g., advanced test generation, git workflows) that Taskmaster Gateway provides.
---
### **Recommendation**
If Hamster meets your AI needs and their terms allow it, **exposing it as a paid option is feasible and can be a cost-effective way to monetize your platform**. However, ensure you have a clear abstraction layer so you can easily switch providers or add new ones (like Taskmaster Gateway) in the future. This approach gives you flexibility and control while keeping costs low.
---
*Generated by Task Master Research Command*
*Timestamp: 2025-11-18T00:35:00.754Z*

View File

@@ -1,9 +1,59 @@
{
"currentTag": "tdd-phase-1-core-rails",
"lastSwitched": "2025-11-10T19:45:04.383Z",
"branchTagMapping": {
"v017-adds": "v017-adds",
"next": "next"
"currentTag": "tm-core-phase-1",
"lastUpdated": "2025-11-28T02:21:32.160Z",
"metadata": {
"upgradePrompts": {
"triggers": {
"export_attempt": {
"firstShownAt": "2025-11-26T21:02:12.322Z",
"lastShownAt": "2025-11-26T21:02:12.322Z",
"showCount": 1,
"dismissed": false,
"action": "accepted",
"actionAt": "2025-11-28T02:20:29.155Z"
}
},
"metrics": {
"totalTaskCount": 0,
"tagCount": 0,
"listCommandCount": 0,
"tasksWithDependencies": 0
},
"lastUpdated": "2025-11-28T02:20:29.155Z",
"version": "1.0.0"
},
"exportedTags": {
"tm-core-phase-1": {
"briefId": "5f36141d-9749-47b4-b64b-7841a42cf68e",
"briefUrl": "http://localhost:3000/home/hamster/briefs/5f36141d-9749-47b4-b64b-7841a42cf68e",
"exportedAt": "2025-11-27T21:41:38.821Z"
},
"tdd-workflow-phase-0": {
"briefId": "b137627a-0573-4ae6-bda6-afea97e31b4f",
"briefUrl": "http://localhost:3000/home/hamster/briefs/b137627a-0573-4ae6-bda6-afea97e31b4f",
"exportedAt": "2025-11-27T21:51:39.086Z"
},
"autonomous-tdd-git-workflow": {
"briefId": "3f0e8ef0-c79d-4942-92be-0c9f2b6db0eb",
"briefUrl": "http://localhost:3000/home/hamster/briefs/3f0e8ef0-c79d-4942-92be-0c9f2b6db0eb",
"exportedAt": "2025-11-27T21:51:39.557Z"
},
"tdd-phase-1-core-rails": {
"briefId": "c14723fb-d1c1-428f-82f3-676c45479f60",
"briefUrl": "http://localhost:3000/home/hamster/briefs/c14723fb-d1c1-428f-82f3-676c45479f60",
"exportedAt": "2025-11-28T02:20:29.155Z"
},
"tm-start": {
"briefId": "f32d7ba2-1709-4ed1-ad48-e42a10e395dd",
"briefUrl": "http://localhost:3000/home/hamster/briefs/f32d7ba2-1709-4ed1-ad48-e42a10e395dd",
"exportedAt": "2025-11-28T02:21:31.612Z"
},
"cc-kiro-hooks": {
"briefId": "ee743dbe-f2ff-458e-8ff2-6b192e386805",
"briefUrl": "http://localhost:3000/home/hamster/briefs/ee743dbe-f2ff-458e-8ff2-6b192e386805",
"exportedAt": "2025-11-28T02:21:32.160Z"
}
}
},
"migrationNoticeShown": true
}

View File

@@ -0,0 +1,47 @@
<context>
# Overview
[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.]
# Core Features
[List and describe the main features of your product. For each feature, include:
- What it does
- Why it's important
- How it works at a high level]
# User Experience
[Describe the user journey and experience. Include:
- User personas
- Key user flows
- UI/UX considerations]
</context>
<PRD>
# Technical Architecture
[Outline the technical implementation details:
- System components
- Data models
- APIs and integrations
- Infrastructure requirements]
# Development Roadmap
[Break down the development process into phases:
- MVP requirements
- Future enhancements
- Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks]
# Logical Dependency Chain
[Define the logical order of development:
- Which features need to be built first (foundation)
- Getting as quickly as possible to something usable/visible front end that works
- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches]
# Risks and Mitigations
[Identify potential risks and how they'll be addressed:
- Technical challenges
- Figuring out the MVP that we can build upon
- Resource constraints]
# Appendix
[Include any additional information:
- Research findings
- Technical specifications]
</PRD>

View File

@@ -0,0 +1,511 @@
<rpg-method>
# Repository Planning Graph (RPG) Method - PRD Template
This template teaches you (AI or human) how to create structured, dependency-aware PRDs using the RPG methodology from Microsoft Research. The key insight: separate WHAT (functional) from HOW (structural), then connect them with explicit dependencies.
## Core Principles
1. **Dual-Semantics**: Think functional (capabilities) AND structural (code organization) separately, then map them
2. **Explicit Dependencies**: Never assume - always state what depends on what
3. **Topological Order**: Build foundation first, then layers on top
4. **Progressive Refinement**: Start broad, refine iteratively
## How to Use This Template
- Follow the instructions in each `<instruction>` block
- Look at `<example>` blocks to see good vs bad patterns
- Fill in the content sections with your project details
- The AI reading this will learn the RPG method by following along
- Task Master will parse the resulting PRD into dependency-aware tasks
## Recommended Tools for Creating PRDs
When using this template to **create** a PRD (not parse it), use **code-context-aware AI assistants** for best results:
**Why?** The AI needs to understand your existing codebase to make good architectural decisions about modules, dependencies, and integration points.
**Recommended tools:**
- **Claude Code** (claude-code CLI) - Best for structured reasoning and large contexts
- **Cursor/Windsurf** - IDE integration with full codebase context
- **Gemini CLI** (gemini-cli) - Massive context window for large codebases
- **Codex/Grok CLI** - Strong code generation with context awareness
**Note:** Once your PRD is created, `task-master parse-prd` works with any configured AI model - it just needs to read the PRD text itself, not your codebase.
</rpg-method>
---
<overview>
<instruction>
Start with the problem, not the solution. Be specific about:
- What pain point exists?
- Who experiences it?
- Why existing solutions don't work?
- What success looks like (measurable outcomes)?
Keep this section focused - don't jump into implementation details yet.
</instruction>
## Problem Statement
[Describe the core problem. Be concrete about user pain points.]
## Target Users
[Define personas, their workflows, and what they're trying to achieve.]
## Success Metrics
[Quantifiable outcomes. Examples: "80% task completion via autopilot", "< 5% manual intervention rate"]
</overview>
---
<functional-decomposition>
<instruction>
Now think about CAPABILITIES (what the system DOES), not code structure yet.
Step 1: Identify high-level capability domains
- Think: "What major things does this system do?"
- Examples: Data Management, Core Processing, Presentation Layer
Step 2: For each capability, enumerate specific features
- Use explore-exploit strategy:
* Exploit: What features are REQUIRED for core value?
* Explore: What features make this domain COMPLETE?
Step 3: For each feature, define:
- Description: What it does in one sentence
- Inputs: What data/context it needs
- Outputs: What it produces/returns
- Behavior: Key logic or transformations
<example type="good">
Capability: Data Validation
Feature: Schema validation
- Description: Validate JSON payloads against defined schemas
- Inputs: JSON object, schema definition
- Outputs: Validation result (pass/fail) + error details
- Behavior: Iterate fields, check types, enforce constraints
Feature: Business rule validation
- Description: Apply domain-specific validation rules
- Inputs: Validated data object, rule set
- Outputs: Boolean + list of violated rules
- Behavior: Execute rules sequentially, short-circuit on failure
</example>
<example type="bad">
Capability: validation.js
(Problem: This is a FILE, not a CAPABILITY. Mixing structure into functional thinking.)
Capability: Validation
Feature: Make sure data is good
(Problem: Too vague. No inputs/outputs. Not actionable.)
</example>
</instruction>
## Capability Tree
### Capability: [Name]
[Brief description of what this capability domain covers]
#### Feature: [Name]
- **Description**: [One sentence]
- **Inputs**: [What it needs]
- **Outputs**: [What it produces]
- **Behavior**: [Key logic]
#### Feature: [Name]
- **Description**:
- **Inputs**:
- **Outputs**:
- **Behavior**:
### Capability: [Name]
...
</functional-decomposition>
---
<structural-decomposition>
<instruction>
NOW think about code organization. Map capabilities to actual file/folder structure.
Rules:
1. Each capability maps to a module (folder or file)
2. Features within a capability map to functions/classes
3. Use clear module boundaries - each module has ONE responsibility
4. Define what each module exports (public interface)
The goal: Create a clear mapping between "what it does" (functional) and "where it lives" (structural).
<example type="good">
Capability: Data Validation
→ Maps to: src/validation/
├── schema-validator.js (Schema validation feature)
├── rule-validator.js (Business rule validation feature)
└── index.js (Public exports)
Exports:
- validateSchema(data, schema)
- validateRules(data, rules)
</example>
<example type="bad">
Capability: Data Validation
→ Maps to: src/utils.js
(Problem: "utils" is not a clear module boundary. Where do I find validation logic?)
Capability: Data Validation
→ Maps to: src/validation/everything.js
(Problem: One giant file. Features should map to separate files for maintainability.)
</example>
</instruction>
## Repository Structure
```
project-root/
├── src/
│ ├── [module-name]/ # Maps to: [Capability Name]
│ │ ├── [file].js # Maps to: [Feature Name]
│ │ └── index.js # Public exports
│ └── [module-name]/
├── tests/
└── docs/
```
## Module Definitions
### Module: [Name]
- **Maps to capability**: [Capability from functional decomposition]
- **Responsibility**: [Single clear purpose]
- **File structure**:
```
module-name/
├── feature1.js
├── feature2.js
└── index.js
```
- **Exports**:
- `functionName()` - [what it does]
- `ClassName` - [what it does]
</structural-decomposition>
---
<dependency-graph>
<instruction>
This is THE CRITICAL SECTION for Task Master parsing.
Define explicit dependencies between modules. This creates the topological order for task execution.
Rules:
1. List modules in dependency order (foundation first)
2. For each module, state what it depends on
3. Foundation modules should have NO dependencies
4. Every non-foundation module should depend on at least one other module
5. Think: "What must EXIST before I can build this module?"
<example type="good">
Foundation Layer (no dependencies):
- error-handling: No dependencies
- config-manager: No dependencies
- base-types: No dependencies
Data Layer:
- schema-validator: Depends on [base-types, error-handling]
- data-ingestion: Depends on [schema-validator, config-manager]
Core Layer:
- algorithm-engine: Depends on [base-types, error-handling]
- pipeline-orchestrator: Depends on [algorithm-engine, data-ingestion]
</example>
<example type="bad">
- validation: Depends on API
- API: Depends on validation
(Problem: Circular dependency. This will cause build/runtime issues.)
- user-auth: Depends on everything
(Problem: Too many dependencies. Should be more focused.)
</example>
</instruction>
## Dependency Chain
### Foundation Layer (Phase 0)
No dependencies - these are built first.
- **[Module Name]**: [What it provides]
- **[Module Name]**: [What it provides]
### [Layer Name] (Phase 1)
- **[Module Name]**: Depends on [[module-from-phase-0], [module-from-phase-0]]
- **[Module Name]**: Depends on [[module-from-phase-0]]
### [Layer Name] (Phase 2)
- **[Module Name]**: Depends on [[module-from-phase-1], [module-from-foundation]]
[Continue building up layers...]
</dependency-graph>
---
<implementation-roadmap>
<instruction>
Turn the dependency graph into concrete development phases.
Each phase should:
1. Have clear entry criteria (what must exist before starting)
2. Contain tasks that can be parallelized (no inter-dependencies within phase)
3. Have clear exit criteria (how do we know phase is complete?)
4. Build toward something USABLE (not just infrastructure)
Phase ordering follows topological sort of dependency graph.
<example type="good">
Phase 0: Foundation
Entry: Clean repository
Tasks:
- Implement error handling utilities
- Create base type definitions
- Setup configuration system
Exit: Other modules can import foundation without errors
Phase 1: Data Layer
Entry: Phase 0 complete
Tasks:
- Implement schema validator (uses: base types, error handling)
- Build data ingestion pipeline (uses: validator, config)
Exit: End-to-end data flow from input to validated output
</example>
<example type="bad">
Phase 1: Build Everything
Tasks:
- API
- Database
- UI
- Tests
(Problem: No clear focus. Too broad. Dependencies not considered.)
</example>
</instruction>
## Development Phases
### Phase 0: [Foundation Name]
**Goal**: [What foundational capability this establishes]
**Entry Criteria**: [What must be true before starting]
**Tasks**:
- [ ] [Task name] (depends on: [none or list])
- Acceptance criteria: [How we know it's done]
- Test strategy: [What tests prove it works]
- [ ] [Task name] (depends on: [none or list])
**Exit Criteria**: [Observable outcome that proves phase complete]
**Delivers**: [What can users/developers do after this phase?]
---
### Phase 1: [Layer Name]
**Goal**:
**Entry Criteria**: Phase 0 complete
**Tasks**:
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])
- [ ] [Task name] (depends on: [[tasks-from-phase-0]])
**Exit Criteria**:
**Delivers**:
---
[Continue with more phases...]
</implementation-roadmap>
---
<test-strategy>
<instruction>
Define how testing will be integrated throughout development (TDD approach).
Specify:
1. Test pyramid ratios (unit vs integration vs e2e)
2. Coverage requirements
3. Critical test scenarios
4. Test generation guidelines for Surgical Test Generator
This section guides the AI when generating tests during the RED phase of TDD.
<example type="good">
Critical Test Scenarios for Data Validation module:
- Happy path: Valid data passes all checks
- Edge cases: Empty strings, null values, boundary numbers
- Error cases: Invalid types, missing required fields
- Integration: Validator works with ingestion pipeline
</example>
</instruction>
## Test Pyramid
```
/\
/E2E\ ← [X]% (End-to-end, slow, comprehensive)
/------\
/Integration\ ← [Y]% (Module interactions)
/------------\
/ Unit Tests \ ← [Z]% (Fast, isolated, deterministic)
/----------------\
```
## Coverage Requirements
- Line coverage: [X]% minimum
- Branch coverage: [X]% minimum
- Function coverage: [X]% minimum
- Statement coverage: [X]% minimum
## Critical Test Scenarios
### [Module/Feature Name]
**Happy path**:
- [Scenario description]
- Expected: [What should happen]
**Edge cases**:
- [Scenario description]
- Expected: [What should happen]
**Error cases**:
- [Scenario description]
- Expected: [How system handles failure]
**Integration points**:
- [What interactions to test]
- Expected: [End-to-end behavior]
## Test Generation Guidelines
[Specific instructions for Surgical Test Generator about what to focus on, what patterns to follow, project-specific test conventions]
</test-strategy>
---
<architecture>
<instruction>
Describe technical architecture, data models, and key design decisions.
Keep this section AFTER functional/structural decomposition - implementation details come after understanding structure.
</instruction>
## System Components
[Major architectural pieces and their responsibilities]
## Data Models
[Core data structures, schemas, database design]
## Technology Stack
[Languages, frameworks, key libraries]
**Decision: [Technology/Pattern]**
- **Rationale**: [Why chosen]
- **Trade-offs**: [What we're giving up]
- **Alternatives considered**: [What else we looked at]
</architecture>
---
<risks>
<instruction>
Identify risks that could derail development and how to mitigate them.
Categories:
- Technical risks (complexity, unknowns)
- Dependency risks (blocking issues)
- Scope risks (creep, underestimation)
</instruction>
## Technical Risks
**Risk**: [Description]
- **Impact**: [High/Medium/Low - effect on project]
- **Likelihood**: [High/Medium/Low]
- **Mitigation**: [How to address]
- **Fallback**: [Plan B if mitigation fails]
## Dependency Risks
[External dependencies, blocking issues]
## Scope Risks
[Scope creep, underestimation, unclear requirements]
</risks>
---
<appendix>
## References
[Papers, documentation, similar systems]
## Glossary
[Domain-specific terms]
## Open Questions
[Things to resolve during development]
</appendix>
---
<task-master-integration>
# How Task Master Uses This PRD
When you run `task-master parse-prd <file>.txt`, the parser:
1. **Extracts capabilities** → Main tasks
- Each `### Capability:` becomes a top-level task
2. **Extracts features** → Subtasks
- Each `#### Feature:` becomes a subtask under its capability
3. **Parses dependencies** → Task dependencies
- `Depends on: [X, Y]` sets task.dependencies = ["X", "Y"]
4. **Orders by phases** → Task priorities
- Phase 0 tasks = highest priority
- Phase N tasks = lower priority, properly sequenced
5. **Uses test strategy** → Test generation context
- Feeds test scenarios to Surgical Test Generator during implementation
**Result**: A dependency-aware task graph that can be executed in topological order.
## Why RPG Structure Matters
Traditional flat PRDs lead to:
- ❌ Unclear task dependencies
- ❌ Arbitrary task ordering
- ❌ Circular dependencies discovered late
- ❌ Poorly scoped tasks
RPG-structured PRDs provide:
- ✅ Explicit dependency chains
- ✅ Topological execution order
- ✅ Clear module boundaries
- ✅ Validated task graph before implementation
## Tips for Best Results
1. **Spend time on dependency graph** - This is the most valuable section for Task Master
2. **Keep features atomic** - Each feature should be independently testable
3. **Progressive refinement** - Start broad, use `task-master expand` to break down complex tasks
4. **Use research mode** - `task-master parse-prd --research` leverages AI for better task generation
</task-master-integration>

View File

@@ -32,7 +32,8 @@
"figlet": "^1.9.4",
"gradient-string": "^3.0.0",
"inquirer": "^12.5.0",
"ora": "^8.2.0"
"ora": "^8.2.0",
"terminal-link": "^5.0.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",

View File

@@ -9,10 +9,12 @@ import { AuthCommand } from './commands/auth.command.js';
import { AutopilotCommand } from './commands/autopilot/index.js';
import { BriefsCommand } from './commands/briefs.command.js';
import { ContextCommand } from './commands/context.command.js';
import { ExportCommand } from './commands/export.command.js';
import { ExportCommand, ExportTagCommand } from './commands/export.command.js';
import { GenerateCommand } from './commands/generate.command.js';
// Import all commands
import { ListTasksCommand } from './commands/list.command.js';
import { LoginCommand } from './commands/login.command.js';
import { LogoutCommand } from './commands/logout.command.js';
import { NextCommand } from './commands/next.command.js';
import { SetStatusCommand } from './commands/set-status.command.js';
import { ShowCommand } from './commands/show.command.js';
@@ -70,10 +72,16 @@ export class CommandRegistry {
},
{
name: 'export',
description: 'Export tasks to external systems',
description: 'Export tasks to Hamster by creating a new brief',
commandClass: ExportCommand as any,
category: 'task'
},
{
name: 'export-tag',
description: 'Export a specific tag to Hamster',
commandClass: ExportTagCommand as any,
category: 'task'
},
{
name: 'autopilot',
description:
@@ -89,6 +97,18 @@ export class CommandRegistry {
commandClass: AuthCommand as any,
category: 'auth'
},
{
name: 'login',
description: 'Login to Hamster (alias for "auth login")',
commandClass: LoginCommand as any,
category: 'auth'
},
{
name: 'logout',
description: 'Logout from Hamster (alias for "auth logout")',
commandClass: LogoutCommand as any,
category: 'auth'
},
{
name: 'context',
description: 'Manage workspace context (organization/brief)',

View File

@@ -4,6 +4,7 @@
*/
import {
AUTH_TIMEOUT_MS,
type AuthCredentials,
AuthManager,
AuthenticationError
@@ -12,7 +13,13 @@ import chalk from 'chalk';
import { Command } from 'commander';
import inquirer from 'inquirer';
import open from 'open';
import ora, { type Ora } from 'ora';
import ora from 'ora';
import {
AuthCountdownTimer,
displayAuthInstructions,
displayWaitingForAuth,
handleMFAFlow
} from '../utils/auth-ui.js';
import { displayError } from '../utils/error-handler.js';
import * as ui from '../utils/ui.js';
import { ContextCommand } from './context.command.js';
@@ -67,6 +74,7 @@ export class AuthCommand extends Command {
'Authentication token (optional, for SSH/remote environments)'
)
.option('-y, --yes', 'Skip interactive prompts')
.option('--no-header', 'Suppress the Task Master header banner')
.addHelpText(
'after',
`
@@ -77,9 +85,18 @@ Examples:
# Note: MFA prompts cannot be skipped if enabled
`
)
.action(async (token?: string, options?: { yes?: boolean }) => {
await this.executeLogin(token, options?.yes);
});
.action(
async (
token?: string,
options?: { yes?: boolean; header?: boolean }
) => {
await this.executeLogin(
token,
options?.yes,
options?.header !== false
);
}
);
}
/**
@@ -88,7 +105,8 @@ Examples:
private addLogoutCommand(): void {
this.command('logout')
.description('Logout and clear credentials')
.action(async () => {
.option('--no-header', 'Suppress the Task Master header banner')
.action(async (_options?: { header?: boolean }) => {
await this.executeLogout();
});
}
@@ -99,7 +117,8 @@ Examples:
private addStatusCommand(): void {
this.command('status')
.description('Display authentication status')
.action(async () => {
.option('--no-header', 'Suppress the Task Master header banner')
.action(async (_options?: { header?: boolean }) => {
await this.executeStatus();
});
}
@@ -110,7 +129,8 @@ Examples:
private addRefreshCommand(): void {
this.command('refresh')
.description('Refresh authentication token')
.action(async () => {
.option('--no-header', 'Suppress the Task Master header banner')
.action(async (_options?: { header?: boolean }) => {
await this.executeRefresh();
});
}
@@ -130,12 +150,17 @@ Examples:
/**
* Execute login command
* Exported for reuse by login.command.ts
*/
private async executeLogin(token?: string, yes?: boolean): Promise<void> {
async executeLogin(
token?: string,
yes?: boolean,
showHeader: boolean = true
): Promise<void> {
try {
const result = token
? await this.performTokenAuth(token, yes)
: await this.performInteractiveAuth(yes);
? await this.performTokenAuth(token, yes, showHeader)
: await this.performInteractiveAuth(yes, showHeader);
this.setLastResult(result);
if (!result.success) {
@@ -154,8 +179,9 @@ Examples:
/**
* Execute logout command
* Exported for reuse by logout.command.ts
*/
private async executeLogout(): Promise<void> {
async executeLogout(): Promise<void> {
try {
const result = await this.performLogout();
this.setLastResult(result);
@@ -296,8 +322,9 @@ Examples:
/**
* Perform logout
* Exported for reuse by logout.command.ts
*/
private async performLogout(): Promise<AuthResult> {
async performLogout(): Promise<AuthResult> {
try {
await this.authManager.logout();
ui.displaySuccess('Successfully logged out');
@@ -362,9 +389,15 @@ Examples:
/**
* Perform interactive authentication
* Exported for reuse by login.command.ts
*/
private async performInteractiveAuth(yes?: boolean): Promise<AuthResult> {
async performInteractiveAuth(
yes?: boolean,
showHeader: boolean = true
): Promise<AuthResult> {
if (showHeader) {
ui.displayBanner('Task Master Authentication');
}
const isAuthenticated = await this.authManager.hasValidSession();
// Check if already authenticated (skip if --yes is used)
@@ -464,9 +497,10 @@ Examples:
/**
* Authenticate with browser using OAuth 2.0 with PKCE
* Uses shared countdown timer from auth-ui.ts
*/
private async authenticateWithBrowser(): Promise<AuthCredentials> {
let authSpinner: Ora | null = null;
const countdownTimer = new AuthCountdownTimer(AUTH_TIMEOUT_MS);
try {
// Use AuthManager's new unified OAuth flow method with callbacks
@@ -475,43 +509,37 @@ Examples:
openBrowser: async (authUrl) => {
await open(authUrl);
},
timeout: 5 * 60 * 1000, // 5 minutes
timeout: AUTH_TIMEOUT_MS,
// Callback when auth URL is ready
onAuthUrl: (authUrl) => {
// Display authentication instructions
console.log(chalk.blue.bold('\n🔐 Browser Authentication\n'));
console.log(chalk.white(' Opening your browser to authenticate...'));
console.log(chalk.gray(" If the browser doesn't open, visit:"));
console.log(chalk.cyan.underline(` ${authUrl}\n`));
displayAuthInstructions(authUrl);
},
// Callback when waiting for authentication
onWaitingForAuth: () => {
authSpinner = ora({
text: 'Waiting for authentication...',
spinner: 'dots'
}).start();
displayWaitingForAuth();
countdownTimer.start();
},
// Callback on success
onSuccess: () => {
if (authSpinner) {
authSpinner.succeed('Authentication successful!');
}
countdownTimer.stop('success');
},
// Callback on error
onError: () => {
if (authSpinner) {
authSpinner.fail('Authentication failed');
}
countdownTimer.stop('failure');
}
});
return credentials;
} catch (error) {
countdownTimer.stop('failure');
throw error;
} finally {
// Ensure cleanup
countdownTimer.cleanup();
}
}
@@ -534,7 +562,14 @@ Examples:
// Stop spinner without showing failure - MFA is required, not a failure
spinner.stop();
// MFA is required - prompt the user for their MFA code
if (!error.mfaChallenge?.factorId) {
throw new AuthenticationError(
'MFA challenge information missing',
'MFA_VERIFICATION_FAILED'
);
}
// Use shared MFA flow handler
return this.handleMFAVerification(error);
}
@@ -546,7 +581,7 @@ Examples:
/**
* Handle MFA verification flow
* Thin wrapper around @tm/core's verifyMFAWithRetry
* Uses shared MFA utilities from auth-ui.ts
*/
private async handleMFAVerification(
mfaError: AuthenticationError
@@ -558,83 +593,9 @@ Examples:
);
}
const { factorId } = mfaError.mfaChallenge;
console.log(
chalk.yellow(
'\n⚠ Multi-factor authentication is enabled on your account'
)
);
console.log(
chalk.white(
' Please enter the 6-digit code from your authenticator app\n'
)
);
// Use @tm/core's retry logic - presentation layer just handles UI
const result = await this.authManager.verifyMFAWithRetry(
factorId,
async () => {
// Prompt for MFA code
try {
const response = await inquirer.prompt([
{
type: 'input',
name: 'mfaCode',
message: 'Enter your 6-digit MFA code:',
validate: (input: string) => {
const trimmed = (input || '').trim();
if (trimmed.length === 0) {
return 'MFA code cannot be empty';
}
if (!/^\d{6}$/.test(trimmed)) {
return 'MFA code must be exactly 6 digits (0-9)';
}
return true;
}
}
]);
return response.mfaCode.trim();
} catch (error: any) {
// Handle user cancellation (Ctrl+C)
if (
error.name === 'ExitPromptError' ||
error.message?.includes('force closed')
) {
ui.displayWarning(' MFA verification cancelled by user');
throw new AuthenticationError(
'MFA verification cancelled',
'MFA_VERIFICATION_FAILED'
);
}
throw error;
}
},
{
maxAttempts: 3,
onInvalidCode: (_attempt: number, remaining: number) => {
// Callback invoked when invalid code is entered
if (remaining > 0) {
ui.displayError(`Invalid MFA code. Please try again.`);
}
}
}
);
// Handle result from core
if (result.success && result.credentials) {
console.log(chalk.green('\n✓ MFA verification successful!'));
return result.credentials;
}
// Show error with attempt count
throw new AuthenticationError(
`MFA verification failed after ${result.attemptsUsed} attempts`,
'MFA_VERIFICATION_FAILED'
return handleMFAFlow(
this.authManager.verifyMFAWithRetry.bind(this.authManager),
mfaError.mfaChallenge.factorId
);
}
@@ -643,9 +604,12 @@ Examples:
*/
private async performTokenAuth(
token: string,
yes?: boolean
yes?: boolean,
showHeader: boolean = true
): Promise<AuthResult> {
if (showHeader) {
ui.displayBanner('Task Master Authentication');
}
try {
// Authenticate with the token

View File

@@ -5,6 +5,7 @@
import { WorkflowOrchestrator } from '@tm/core';
import { Command } from 'commander';
import inquirer from 'inquirer';
import { getProjectRoot } from '../../utils/project-root.js';
import {
AutopilotBaseOptions,
OutputFormatter,
@@ -12,7 +13,6 @@ import {
hasWorkflowState,
loadWorkflowState
} from './shared.js';
import { getProjectRoot } from '../../utils/project-root.js';
interface AbortOptions extends AutopilotBaseOptions {
force?: boolean;

View File

@@ -4,8 +4,8 @@
import { CommitMessageGenerator, GitAdapter, WorkflowService } from '@tm/core';
import { Command } from 'commander';
import { AutopilotBaseOptions, OutputFormatter } from './shared.js';
import { getProjectRoot } from '../../utils/project-root.js';
import { AutopilotBaseOptions, OutputFormatter } from './shared.js';
type CommitOptions = AutopilotBaseOptions;

View File

@@ -4,13 +4,13 @@
import { WorkflowOrchestrator } from '@tm/core';
import { Command } from 'commander';
import { getProjectRoot } from '../../utils/project-root.js';
import {
AutopilotBaseOptions,
OutputFormatter,
hasWorkflowState,
loadWorkflowState
} from './shared.js';
import { getProjectRoot } from '../../utils/project-root.js';
type ResumeOptions = AutopilotBaseOptions;

View File

@@ -4,6 +4,7 @@
import { type WorkflowContext, createTmCore } from '@tm/core';
import { Command } from 'commander';
import { getProjectRoot } from '../../utils/project-root.js';
import {
AutopilotBaseOptions,
OutputFormatter,
@@ -13,7 +14,6 @@ import {
parseSubtasks,
validateTaskId
} from './shared.js';
import { getProjectRoot } from '../../utils/project-root.js';
interface StartOptions extends AutopilotBaseOptions {
force?: boolean;

View File

@@ -4,13 +4,13 @@
import { WorkflowOrchestrator } from '@tm/core';
import { Command } from 'commander';
import { getProjectRoot } from '../../utils/project-root.js';
import {
AutopilotBaseOptions,
OutputFormatter,
hasWorkflowState,
loadWorkflowState
} from './shared.js';
import { getProjectRoot } from '../../utils/project-root.js';
type StatusOptions = AutopilotBaseOptions;

View File

@@ -3,15 +3,15 @@
* Provides brief-specific commands that only work with API storage
*/
import {
type LogLevel,
type TagInfo,
tryAddTagViaRemote,
tryListTagsViaRemote
} from '@tm/bridge';
import type { TmCore } from '@tm/core';
import readline from 'readline';
import { type LogLevel, type TagInfo, tryAddTagViaRemote } from '@tm/bridge';
import type { Brief, TmCore } from '@tm/core';
import { AuthManager, createTmCore } from '@tm/core';
import chalk from 'chalk';
import { Command } from 'commander';
import inquirer from 'inquirer';
import ora from 'ora';
import { getBriefStatusWithColor } from '../ui/formatters/status-formatters.js';
import { checkAuthentication } from '../utils/auth-helpers.js';
import {
selectBriefFromInput,
@@ -179,29 +179,62 @@ Note: Briefs must be created through the Hamster Studio web interface.
process.exit(1);
}
// Use the bridge to list briefs
const remoteResult = await tryListTagsViaRemote({
projectRoot: process.cwd(),
showMetadata: options?.showMetadata || false,
report: (level: LogLevel, ...args: unknown[]) => {
const message = args[0] as string;
if (level === 'error') ui.displayError(message);
else if (level === 'warn') ui.displayWarning(message);
else if (level === 'info') ui.displayInfo(message);
// Ensure org is selected - prompt if not
const orgId = await this.ensureOrgSelected();
if (!orgId) {
process.exit(1);
}
});
if (!remoteResult) {
throw new Error('Failed to fetch briefs from API');
}
// Fetch briefs directly from AuthManager (bypasses storage layer issues)
const spinner = ora('Fetching briefs...').start();
const briefs = await this.authManager.getBriefs(orgId);
spinner.stop();
// Get current context to determine current brief
const context = this.authManager.getContext();
const currentBriefId = context?.briefId;
// Convert to TagInfo format for display
const tags: TagInfo[] = briefs.map((brief: Brief) => ({
name: brief.document?.title || `Brief ${brief.id.slice(-8)}`,
isCurrent: brief.id === currentBriefId,
taskCount: brief.taskCount || 0,
completedTasks: 0, // Not available from getBriefs
statusBreakdown: {},
created: brief.createdAt,
description: brief.document?.description,
status: brief.status,
briefId: brief.id,
updatedAt: brief.updatedAt
}));
// Sort: current first, then by updatedAt
tags.sort((a, b) => {
if (a.isCurrent) return -1;
if (b.isCurrent) return 1;
return 0;
});
this.setLastResult({
success: remoteResult.success,
success: true,
action: 'list',
briefs: remoteResult.tags,
currentBrief: remoteResult.currentTag,
message: remoteResult.message
briefs: tags,
currentBrief: currentBriefId || null,
message: `Found ${tags.length} brief(s)`
});
// Determine if we should skip table display (when interactive selection follows)
const isInteractive = process.stdout.isTTY;
// If interactive mode and briefs available, show integrated table selection
if (isInteractive && tags.length > 0) {
await this.promptBriefSelection(tags);
} else if (tags.length === 0) {
ui.displayWarning('No briefs found in this organization');
} else {
// Non-interactive: display table
this.displayBriefsTable(tags, options?.showMetadata);
}
} catch (error) {
ui.displayError(`Failed to list briefs: ${(error as Error).message}`);
this.setLastResult({
@@ -213,6 +246,322 @@ Note: Briefs must be created through the Hamster Studio web interface.
}
}
/**
* Ensure an organization is selected, prompting if necessary
*/
private async ensureOrgSelected(): Promise<string | null> {
const context = this.authManager.getContext();
// If org is already selected, return it
if (context?.orgId) {
return context.orgId;
}
// No org selected - check if we can auto-select
const orgs = await this.authManager.getOrganizations();
if (orgs.length === 0) {
ui.displayError(
'No organizations available. Please create or join an organization first.'
);
return null;
}
if (orgs.length === 1) {
// Auto-select the only org
await this.authManager.updateContext({
orgId: orgs[0].id,
orgName: orgs[0].name,
orgSlug: orgs[0].slug
});
console.log(chalk.gray(` Auto-selected organization: ${orgs[0].name}`));
return orgs[0].id;
}
// Multiple orgs - prompt for selection
console.log(chalk.yellow('No organization selected.'));
const response = await inquirer.prompt<{ orgId: string }>([
{
type: 'list',
name: 'orgId',
message: 'Select an organization:',
choices: orgs.map((org) => ({
name: org.name,
value: org.id
}))
}
]);
const selectedOrg = orgs.find((o) => o.id === response.orgId);
if (selectedOrg) {
await this.authManager.updateContext({
orgId: selectedOrg.id,
orgName: selectedOrg.name,
orgSlug: selectedOrg.slug
});
ui.displaySuccess(`Selected organization: ${selectedOrg.name}`);
return selectedOrg.id;
}
return null;
}
/**
* Display briefs in a table format (for non-interactive mode)
*/
private displayBriefsTable(tags: TagInfo[], _showMetadata?: boolean): void {
const Table = require('cli-table3');
const terminalWidth = Math.max(process.stdout.columns || 120, 80);
const usableWidth = Math.floor(terminalWidth * 0.95);
const widths = [0.35, 0.25, 0.2, 0.1, 0.1];
const colWidths = widths.map((w, i) =>
Math.max(Math.floor(usableWidth * w), i === 0 ? 20 : 8)
);
const table = new Table({
head: [
chalk.cyan.bold('Brief Name'),
chalk.cyan.bold('Status'),
chalk.cyan.bold('Updated'),
chalk.cyan.bold('Tasks'),
chalk.cyan.bold('Completed')
],
colWidths: colWidths,
wordWrap: true
});
tags.forEach((tag) => {
const shortId = tag.briefId ? tag.briefId.slice(-8) : 'unknown';
const tagDisplay = tag.isCurrent
? `${chalk.green('●')} ${chalk.green.bold(tag.name)} ${chalk.gray(`(current - ${shortId})`)}`
: ` ${tag.name} ${chalk.gray(`(${shortId})`)}`;
const updatedDate = tag.updatedAt
? new Date(tag.updatedAt).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
: chalk.gray('N/A');
table.push([
tagDisplay,
getBriefStatusWithColor(tag.status, true),
chalk.gray(updatedDate),
chalk.white(String(tag.taskCount || 0)),
chalk.green(String(tag.completedTasks || 0))
]);
});
console.log(table.toString());
}
/**
* Create table-formatted choice for integrated selection
*/
private formatBriefAsTableRow(
brief: TagInfo,
colWidths: {
name: number;
status: number;
updated: number;
tasks: number;
done: number;
}
): string {
const shortId = brief.briefId ? brief.briefId.slice(-8) : 'unknown';
const isCurrent = brief.isCurrent;
// Current indicator
const currentMarker = isCurrent ? chalk.green('●') : ' ';
// Calculate max name length (leave room for marker, spaces, and ID)
const idSuffix = isCurrent ? '(current)' : `(${shortId})`;
const maxNameLen = colWidths.name - 4 - idSuffix.length; // 4 = "● " + " " before id
// Truncate name if too long
let displayName = brief.name;
if (displayName.length > maxNameLen) {
displayName = displayName.substring(0, maxNameLen - 1) + '…';
}
const nameText = isCurrent ? chalk.green.bold(displayName) : displayName;
const idText = isCurrent
? chalk.gray(`(current)`)
: chalk.gray(`(${shortId})`);
// Calculate visual length and pad
const nameVisualLength = 2 + displayName.length + 1 + idSuffix.length;
const namePadding = Math.max(0, colWidths.name - nameVisualLength);
const nameCol = `${currentMarker} ${nameText} ${idText}${' '.repeat(namePadding)}`;
// Status column - fixed width with padding
const statusDisplay = getBriefStatusWithColor(brief.status, true);
const statusVisual = (brief.status || 'unknown').length + 2; // icon + space + status
const statusPadding = Math.max(0, colWidths.status - statusVisual);
const statusCol = `${statusDisplay}${' '.repeat(statusPadding)}`;
// Updated column
const updatedDate = brief.updatedAt
? new Date(brief.updatedAt).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
})
: 'N/A';
const updatedCol = chalk.gray(updatedDate.padEnd(colWidths.updated));
// Tasks column
const tasksCol = chalk.white(
String(brief.taskCount || 0).padStart(colWidths.tasks)
);
// Done column
const doneCol = chalk.green(
String(brief.completedTasks || 0).padStart(colWidths.done)
);
return `${nameCol} ${statusCol} ${updatedCol} ${tasksCol} ${doneCol}`;
}
/**
* Prompt user to select a brief using integrated table selection
*/
private async promptBriefSelection(briefs: TagInfo[]): Promise<void> {
try {
// Check if org is selected (required for context updates)
const context = this.authManager.getContext();
if (!context?.orgId) {
// Don't prompt if no org selected - user needs to set org first
return;
}
// Calculate column widths based on terminal
const terminalWidth = Math.max(process.stdout.columns || 120, 80);
const usableWidth = Math.floor(terminalWidth * 0.95);
const colWidths = {
name: Math.floor(usableWidth * 0.42), // More room for long names
status: Math.floor(usableWidth * 0.14),
updated: Math.floor(usableWidth * 0.16),
tasks: 6,
done: 6
};
// Create table header
const headerLine =
chalk.cyan.bold('Brief Name'.padEnd(colWidths.name)) +
chalk.cyan.bold('Status'.padEnd(colWidths.status)) +
chalk.cyan.bold('Updated'.padEnd(colWidths.updated)) +
chalk.cyan.bold('Tasks'.padStart(colWidths.tasks + 2)) +
chalk.cyan.bold('Done'.padStart(colWidths.done + 2));
const separator = chalk.gray('─'.repeat(usableWidth));
// Build choices as table rows
const choices: any[] = [
new inquirer.Separator(headerLine),
new inquirer.Separator(separator)
];
briefs.forEach((brief) => {
choices.push({
name: this.formatBriefAsTableRow(brief, colWidths),
value: brief.briefId || brief.name,
short: brief.name // Show just name after selection
});
});
// Add separator and cancel option
choices.push(new inquirer.Separator(separator));
choices.push({
name: chalk.dim(' (Cancel - keep current selection)'),
value: null,
short: 'Cancelled'
});
// Set up ESC key handler to cancel
let cancelled = false;
const handleKeypress = (_char: string, key: readline.Key) => {
if (key && key.name === 'escape') {
cancelled = true;
// Send Ctrl+C to cancel the prompt
process.stdin.emit('keypress', '', { name: 'c', ctrl: true });
}
};
// Enable keypress events
if (process.stdin.isTTY) {
readline.emitKeypressEvents(process.stdin);
process.stdin.on('keypress', handleKeypress);
}
let answer: { selectedBrief: string | null };
try {
answer = await inquirer.prompt([
{
type: 'list',
name: 'selectedBrief',
message: 'Select a brief:',
choices: choices,
pageSize: Math.min(briefs.length + 5, 20), // Show all briefs if possible
loop: false
}
]);
} finally {
// Clean up keypress listener
if (process.stdin.isTTY) {
process.stdin.removeListener('keypress', handleKeypress);
}
}
// If ESC was pressed, treat as cancel
if (cancelled) {
return;
}
if (answer.selectedBrief && answer.selectedBrief !== null) {
// Find the selected brief
const selectedBrief = briefs.find(
(b) =>
b.briefId === answer.selectedBrief ||
b.name === answer.selectedBrief
);
if (selectedBrief) {
// Update context with selected brief
await this.authManager.updateContext({
briefId: selectedBrief.briefId || undefined,
briefName: selectedBrief.name,
briefStatus: selectedBrief.status || undefined,
briefUpdatedAt: selectedBrief.updatedAt || undefined
});
ui.displaySuccess(`Selected brief: ${selectedBrief.name}`);
this.setLastResult({
success: true,
action: 'select',
currentBrief: selectedBrief.briefId || selectedBrief.name,
message: `Selected brief: ${selectedBrief.name}`
});
}
}
} catch (error) {
// If user cancels (Ctrl+C), inquirer throws - handle gracefully
if ((error as any).isTtyError) {
// Not a TTY, skip interactive prompt
return;
}
// Other errors - log but don't fail the command
console.error(
chalk.yellow(
`\nNote: Could not prompt for brief selection: ${(error as Error).message}`
)
);
}
}
/**
* Execute select brief interactively or by name/ID
*/

View File

@@ -3,23 +3,23 @@
* Provides a clean interface for workspace context management
*/
import { Command } from 'commander';
import chalk from 'chalk';
import inquirer from 'inquirer';
import ora from 'ora';
import {
AuthManager,
createTmCore,
type TmCore,
type UserContext,
type TmCore
createTmCore
} from '@tm/core';
import * as ui from '../utils/ui.js';
import { checkAuthentication } from '../utils/auth-helpers.js';
import chalk from 'chalk';
import { Command } from 'commander';
import inquirer from 'inquirer';
import ora from 'ora';
import { getBriefStatusWithColor } from '../ui/formatters/status-formatters.js';
import { checkAuthentication } from '../utils/auth-helpers.js';
import {
selectBriefInteractive,
selectBriefFromInput
selectBriefFromInput,
selectBriefInteractive
} from '../utils/brief-selection.js';
import * as ui from '../utils/ui.js';
/**
* Result type from context command
@@ -60,13 +60,17 @@ export class ContextCommand extends Command {
// Accept optional positional argument for brief ID or Hamster URL
this.argument('[briefOrUrl]', 'Brief ID or Hamster brief URL');
// Global option for this command and its subcommands
this.option('--no-header', 'Suppress the header display');
// Default action: if an argument is provided, resolve and set context; else show
this.action(async (briefOrUrl?: string) => {
this.action(async (briefOrUrl?: string, options?: { header?: boolean }) => {
const showHeader = options?.header !== false;
if (briefOrUrl && briefOrUrl.trim().length > 0) {
await this.executeSetFromBriefInput(briefOrUrl.trim());
await this.executeSetFromBriefInput(briefOrUrl.trim(), showHeader);
return;
}
await this.executeShow();
await this.executeShow(showHeader);
});
}
@@ -77,6 +81,7 @@ export class ContextCommand extends Command {
this.command('org')
.description('Select an organization')
.argument('[orgId]', 'Organization ID or slug to select directly')
.option('--no-header', 'Suppress the header display')
.action(async (orgId?: string) => {
await this.executeSelectOrg(orgId);
});
@@ -89,6 +94,7 @@ export class ContextCommand extends Command {
this.command('brief')
.description('Select a brief within the current organization')
.argument('[briefIdOrUrl]', 'Brief ID or Hamster URL to select directly')
.option('--no-header', 'Suppress the header display')
.action(async (briefIdOrUrl?: string) => {
await this.executeSelectBrief(briefIdOrUrl);
});
@@ -100,6 +106,7 @@ export class ContextCommand extends Command {
private addClearCommand(): void {
this.command('clear')
.description('Clear all context selections')
.option('--no-header', 'Suppress the header display')
.action(async () => {
await this.executeClear();
});
@@ -115,6 +122,7 @@ export class ContextCommand extends Command {
.option('--org-name <name>', 'Organization name')
.option('--brief <id>', 'Brief ID')
.option('--brief-name <name>', 'Brief name')
.option('--no-header', 'Suppress the header display')
.action(async (options) => {
await this.executeSet(options);
});
@@ -123,9 +131,9 @@ export class ContextCommand extends Command {
/**
* Execute show current context
*/
private async executeShow(): Promise<void> {
private async executeShow(showHeader: boolean = true): Promise<void> {
try {
const result = await this.displayContext();
const result = await this.displayContext(showHeader);
this.setLastResult(result);
} catch (error: any) {
ui.displayError(`Failed to show context: ${(error as Error).message}`);
@@ -136,7 +144,9 @@ export class ContextCommand extends Command {
/**
* Display current context
*/
private async displayContext(): Promise<ContextResult> {
private async displayContext(
showHeader: boolean = true
): Promise<ContextResult> {
// Check authentication first
const isAuthenticated = await checkAuthentication(this.authManager, {
message:
@@ -153,7 +163,9 @@ export class ContextCommand extends Command {
const context = this.authManager.getContext();
if (showHeader) {
console.log(chalk.cyan('\n🌍 Workspace Context\n'));
}
if (context && (context.orgId || context.briefId)) {
if (context.orgName || context.orgId) {
@@ -507,7 +519,10 @@ export class ContextCommand extends Command {
* Execute setting context from a brief ID or Hamster URL
* All parsing logic is in tm-core
*/
private async executeSetFromBriefInput(input: string): Promise<void> {
private async executeSetFromBriefInput(
input: string,
_showHeader: boolean = true
): Promise<void> {
try {
// Check authentication
if (!(await checkAuthentication(this.authManager))) {

File diff suppressed because it is too large Load Diff

View File

@@ -68,10 +68,17 @@ export class ListTasksCommand extends Command {
constructor(name?: string) {
super(name || 'list');
// Configure the command
// Configure the command with positional status argument
this.description('List tasks with optional filtering')
.alias('ls')
.option('-s, --status <status>', 'Filter by status (comma-separated)')
.argument(
'[status]',
'Filter by status (e.g., pending, done, in-progress) or "all" to show with subtasks'
)
.option(
'-s, --status <status>',
'Filter by status (fallback if not using positional)'
)
.option('-t, --tag <tag>', 'Filter by tag')
.option('--with-subtasks', 'Include subtasks in the output')
.option(
@@ -85,8 +92,24 @@ export class ListTasksCommand extends Command {
'-p, --project <path>',
'Project root directory (auto-detected if not provided)'
)
.action(async (options: ListCommandOptions) => {
await this.executeCommand(options);
.action(async (statusArg?: string, options?: ListCommandOptions) => {
// Handle special "all" keyword to show with subtasks
let status = statusArg || options?.status;
let withSubtasks = options?.withSubtasks || false;
if (statusArg === 'all') {
// "all" means show all tasks with subtasks expanded
status = undefined;
withSubtasks = true;
}
// Prioritize positional argument over option
const mergedOptions: ListCommandOptions = {
...options,
status,
withSubtasks
};
await this.executeCommand(mergedOptions);
});
}
@@ -212,7 +235,7 @@ export class ListTasksCommand extends Command {
case 'text':
default:
this.displayText(result, options.withSubtasks);
this.displayText(result, options.withSubtasks, options.status);
break;
}
}
@@ -260,7 +283,11 @@ export class ListTasksCommand extends Command {
/**
* Display in text format with tables
*/
private displayText(data: ListTasksResult, withSubtasks?: boolean): void {
private displayText(
data: ListTasksResult,
withSubtasks?: boolean,
_statusFilter?: string
): void {
const { tasks, tag, storageType } = data;
// Display header using utility function
@@ -308,6 +335,7 @@ export class ListTasksCommand extends Command {
);
// Display recommended next task section immediately after table
// Don't show "no tasks available" message in list command - that's for tm next
if (nextTask) {
const description = getTaskDescription(nextTask);
@@ -320,9 +348,8 @@ export class ListTasksCommand extends Command {
description,
complexity: nextTask.complexity as number | undefined
});
} else {
displayRecommendedNextTask(undefined);
}
// If no next task, don't show any message - dashboard already shows the info
// Display suggested next steps at the end
displaySuggestedNextSteps();

View File

@@ -0,0 +1,54 @@
/**
* @fileoverview Login command - alias for 'auth login'
* Provides a convenient shorthand for authentication.
*
* This is a thin wrapper that delegates to AuthCommand.
*/
import { Command } from 'commander';
import { AuthCommand } from './auth.command.js';
/**
* LoginCommand - Shorthand alias for 'tm auth login'
* Reuses AuthCommand's login functionality to avoid code duplication.
*/
export class LoginCommand extends Command {
private authCommand: AuthCommand;
constructor(name?: string) {
super(name || 'login');
this.authCommand = new AuthCommand();
this.description('Login to Hamster (alias for "auth login")');
this.argument(
'[token]',
'Authentication token (optional, for SSH/remote environments)'
);
this.option('-y, --yes', 'Skip interactive prompts');
this.addHelpText(
'after',
`
Examples:
$ tm login # Browser-based OAuth flow (interactive)
$ tm login <token> # Token-based authentication
$ tm login <token> -y # Non-interactive token auth (for scripts)
`
);
this.action(async (token?: string, options?: { yes?: boolean }) => {
// Delegate to AuthCommand's executeLogin
await this.authCommand.executeLogin(token, options?.yes, true);
});
}
/**
* Register this command on a program
*/
static register(program: Command): LoginCommand {
const cmd = new LoginCommand();
program.addCommand(cmd);
return cmd;
}
}

View File

@@ -0,0 +1,47 @@
/**
* @fileoverview Logout command - alias for 'auth logout'
* Provides a convenient shorthand for logging out.
*
* This is a thin wrapper that delegates to AuthCommand.
*/
import { Command } from 'commander';
import { AuthCommand } from './auth.command.js';
/**
* LogoutCommand - Shorthand alias for 'tm auth logout'
* Reuses AuthCommand's logout functionality to avoid code duplication.
*/
export class LogoutCommand extends Command {
private authCommand: AuthCommand;
constructor(name?: string) {
super(name || 'logout');
this.authCommand = new AuthCommand();
this.description('Logout from Hamster (alias for "auth logout")');
this.addHelpText(
'after',
`
Examples:
$ tm logout # Clear credentials and logout
`
);
this.action(async () => {
// Delegate to AuthCommand's executeLogout
await this.authCommand.executeLogout();
});
}
/**
* Register this command on a program
*/
static register(program: Command): LogoutCommand {
const cmd = new LogoutCommand();
program.addCommand(cmd);
return cmd;
}
}

View File

@@ -32,6 +32,7 @@ export interface NextTaskResult {
found: boolean;
tag: string;
storageType: Exclude<StorageType, 'auto'>;
hasAnyTasks?: boolean;
}
/**
@@ -135,11 +136,16 @@ export class NextCommand extends Command {
const activeTag = options.tag || this.tmCore.config.getActiveTag();
// Check if there are ANY tasks at all
const allTasks = await this.tmCore.tasks.list({ tag: options.tag });
const hasAnyTasks = allTasks && allTasks.tasks.length > 0;
return {
task,
found: task !== null,
tag: activeTag,
storageType
storageType,
hasAnyTasks
};
}
@@ -182,24 +188,31 @@ export class NextCommand extends Command {
});
if (!result.found || !result.task) {
// No next task available
// Only show warning box if there are literally NO tasks at all
if (!result.hasAnyTasks) {
console.log(
boxen(
chalk.yellow(
'No tasks available to work on. All tasks are either completed, blocked by dependencies, or in progress.'
),
{
boxen(chalk.yellow('No tasks found in this project.'), {
padding: 1,
borderStyle: 'round',
borderColor: 'yellow',
title: '⚠️ NO TASKS AVAILABLE ⚠️',
titleAlignment: 'center'
}
})
);
console.log(
`\n${chalk.dim('Tip: Create tasks with')} ${chalk.cyan('task-master parse-prd')} ${chalk.dim('or')} ${chalk.cyan('task-master add-task')}`
);
} else {
// Tasks exist but none are available to work on - just show a simple message
console.log(
chalk.yellow(
'✓ All tasks are either completed, blocked by dependencies, or in progress.'
)
);
console.log(
`\n${chalk.dim('Tip: Try')} ${chalk.cyan('task-master list --status pending')} ${chalk.dim('to see all pending tasks')}`
`\n${chalk.dim('Tip: Try')} ${chalk.cyan('task-master list')} ${chalk.dim('to see all tasks')}`
);
}
return;
}

View File

@@ -3,7 +3,12 @@
* Extends Commander.Command for better integration with the framework
*/
import { type TaskStatus, type TmCore, createTmCore } from '@tm/core';
import {
type TaskStatus,
type TmCore,
createTmCore,
normalizeDisplayId
} from '@tm/core';
import type { StorageType } from '@tm/core';
import boxen from 'boxen';
import chalk from 'chalk';
@@ -59,15 +64,18 @@ export class SetStatusCommand extends Command {
constructor(name?: string) {
super(name || 'set-status');
// Configure the command
// Configure the command with positional arguments
this.description('Update the status of one or more tasks')
.requiredOption(
'-i, --id <id>',
'Task ID(s) to update (comma-separated for multiple, supports subtasks like 5.2)'
.alias('status')
.argument(
'[id]',
'Task ID(s) - comma-separated for multiple (e.g., 1 or 1,1.1,2)'
)
.requiredOption(
.argument('[status]', `Status - ${VALID_TASK_STATUSES.join(', ')}`)
.option('-i, --id <id>', 'Task ID(s) (fallback if not using positional)')
.option(
'-s, --status <status>',
`New status (${VALID_TASK_STATUSES.join(', ')})`
'Status (fallback if not using positional)'
)
.option('-f, --format <format>', 'Output format (text, json)', 'text')
.option('--silent', 'Suppress output (useful for programmatic usage)')
@@ -75,9 +83,21 @@ export class SetStatusCommand extends Command {
'-p, --project <path>',
'Project root directory (auto-detected if not provided)'
)
.action(async (options: SetStatusCommandOptions) => {
await this.executeCommand(options);
});
.action(
async (
idArg?: string,
statusArg?: string,
options?: SetStatusCommandOptions
) => {
// Prioritize positional arguments over options
const mergedOptions: SetStatusCommandOptions = {
...options,
id: idArg || options?.id,
status: (statusArg as TaskStatus) || options?.status
};
await this.executeCommand(mergedOptions);
}
);
}
/**
@@ -90,13 +110,27 @@ export class SetStatusCommand extends Command {
try {
// Validate required options
if (!options.id) {
console.error(chalk.red('Error: Task ID is required. Use -i or --id'));
console.error(chalk.red('Error: Task ID is required'));
console.log(
chalk.yellow(
'Usage examples:\n' +
' tm set-status 1 done\n' +
' tm set-status 1.2 in-progress\n' +
' tm set-status --id=1 --status=done'
)
);
process.exit(1);
}
if (!options.status) {
console.error(
chalk.red('Error: Status is required. Use -s or --status')
console.error(chalk.red('Error: Status is required'));
console.log(
chalk.yellow(
'Usage examples:\n' +
' tm set-status 1 done\n' +
' tm set-status 1,1.1 in-progress\n' +
' tm set-status --id=1 --status=done'
)
);
process.exit(1);
}
@@ -117,7 +151,10 @@ export class SetStatusCommand extends Command {
});
// Parse task IDs (handle comma-separated values)
const taskIds = options.id.split(',').map((id) => id.trim());
// Normalize display IDs (e.g., "ham31" → "HAM-31")
const taskIds = options.id
.split(',')
.map((id) => normalizeDisplayId(id.trim()));
// Update each task
const updatedTasks: Array<{

View File

@@ -3,7 +3,12 @@
* Extends Commander.Command for better integration with the framework
*/
import { type Task, type TmCore, createTmCore } from '@tm/core';
import {
type Task,
type TmCore,
createTmCore,
normalizeDisplayId
} from '@tm/core';
import type { StorageType, Subtask } from '@tm/core';
import boxen from 'boxen';
import chalk from 'chalk';
@@ -11,8 +16,8 @@ import { Command } from 'commander';
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
import { displayCommandHeader } from '../utils/display-helpers.js';
import { displayError } from '../utils/error-handler.js';
import * as ui from '../utils/ui.js';
import { getProjectRoot } from '../utils/project-root.js';
import * as ui from '../utils/ui.js';
/**
* Options interface for the show command
@@ -102,9 +107,10 @@ export class ShowCommand extends Command {
}
// Check if multiple IDs are provided (comma-separated)
// Normalize display IDs (e.g., "ham31" → "HAM-31")
const taskIds = idArg
.split(',')
.map((id) => id.trim())
.map((id) => normalizeDisplayId(id.trim()))
.filter((id) => id.length > 0);
// Get tasks from core

View File

@@ -3,9 +3,9 @@
* Provides tag/brief management with file and API storage support
*/
import { Command } from 'commander';
import type { TmCore } from '@tm/core';
import { createTmCore, getProjectPaths } from '@tm/core';
import { Command } from 'commander';
import { displayError } from '../utils/index.js';
/**
@@ -28,12 +28,12 @@ import { displayError } from '../utils/index.js';
* Priority: Medium (improves testability, maintainability, and code reuse)
*/
import {
copyTag as legacyCopyTag,
createTag as legacyCreateTag,
deleteTag as legacyDeleteTag,
tags as legacyListTags,
useTag as legacyUseTag,
renameTag as legacyRenameTag,
copyTag as legacyCopyTag
useTag as legacyUseTag
} from '../../../../scripts/modules/task-manager/tag-management.js';
/**

View File

@@ -0,0 +1,187 @@
/**
* @fileoverview Export Validator
* Validates tasks before export to Hamster
*/
import type {
ExportValidationResult,
ExportableTask,
TaskValidationResult
} from './types.js';
/**
* Validates a single task for export
*/
export function validateTask(task: ExportableTask): TaskValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Required: title
if (!task.title || task.title.trim().length === 0) {
errors.push('Task title is required');
}
// Required: id
if (!task.id) {
errors.push('Task ID is required');
}
// Warning: missing description
if (!task.description || task.description.trim().length === 0) {
warnings.push('Task has no description');
}
// Warning: missing status (will default to 'todo')
if (!task.status) {
warnings.push('Task has no status, will default to "todo"');
}
// Warning: missing priority (will default to 'medium')
if (!task.priority) {
warnings.push('Task has no priority, will default to "medium"');
}
// Warning: title too short
if (task.title && task.title.trim().length < 5) {
warnings.push('Task title is very short');
}
// Warning: title too long
if (task.title && task.title.length > 200) {
warnings.push('Task title is very long (>200 chars)');
}
return {
taskId: task.id,
isValid: errors.length === 0,
errors,
warnings
};
}
/**
* Validates multiple tasks for export
*/
export function validateTasks(tasks: ExportableTask[]): ExportValidationResult {
const taskResults = tasks.map(validateTask);
const validTasks = taskResults.filter((r) => r.isValid).length;
const invalidTasks = taskResults.filter((r) => !r.isValid).length;
const allErrors: string[] = [];
const allWarnings: string[] = [];
// Collect all errors and warnings
for (const result of taskResults) {
for (const error of result.errors) {
allErrors.push(`Task ${result.taskId}: ${error}`);
}
for (const warning of result.warnings) {
allWarnings.push(`Task ${result.taskId}: ${warning}`);
}
}
// Check for empty task list
if (tasks.length === 0) {
allErrors.push('No tasks selected for export');
}
// Check for circular dependencies
const circularDeps = detectCircularDependencies(tasks);
if (circularDeps.length > 0) {
for (const cycle of circularDeps) {
allWarnings.push(`Circular dependency detected: ${cycle.join(' → ')}`);
}
}
// Check for missing dependency targets
const missingDeps = detectMissingDependencies(tasks);
if (missingDeps.length > 0) {
for (const { taskId, missingDep } of missingDeps) {
allWarnings.push(
`Task ${taskId} depends on non-existent task ${missingDep}`
);
}
}
return {
isValid: invalidTasks === 0 && allErrors.length === 0,
totalTasks: tasks.length,
validTasks,
invalidTasks,
taskResults,
errors: allErrors,
warnings: allWarnings
};
}
/**
* Detects circular dependencies in tasks
*/
function detectCircularDependencies(tasks: ExportableTask[]): string[][] {
const cycles: string[][] = [];
const taskMap = new Map(tasks.map((t) => [t.id, t]));
function dfs(taskId: string, path: string[], visited: Set<string>): boolean {
if (path.includes(taskId)) {
// Found a cycle
const cycleStart = path.indexOf(taskId);
cycles.push([...path.slice(cycleStart), taskId]);
return true;
}
if (visited.has(taskId)) {
return false;
}
visited.add(taskId);
path.push(taskId);
const task = taskMap.get(taskId);
if (task?.dependencies) {
for (const depId of task.dependencies) {
dfs(depId, path, visited);
}
}
path.pop();
return false;
}
const visited = new Set<string>();
for (const task of tasks) {
if (!visited.has(task.id)) {
dfs(task.id, [], visited);
}
}
return cycles;
}
/**
* Detects dependencies that reference non-existent tasks
*/
function detectMissingDependencies(
tasks: ExportableTask[]
): Array<{ taskId: string; missingDep: string }> {
const taskIds = new Set(tasks.map((t) => t.id));
const missing: Array<{ taskId: string; missingDep: string }> = [];
for (const task of tasks) {
if (task.dependencies) {
for (const depId of task.dependencies) {
if (!taskIds.has(depId)) {
missing.push({ taskId: task.id, missingDep: depId });
}
}
}
}
return missing;
}
/**
* Filters tasks to only include valid ones
*/
export function filterValidTasks(tasks: ExportableTask[]): ExportableTask[] {
return tasks.filter((task) => validateTask(task).isValid);
}

View File

@@ -0,0 +1,39 @@
/**
* @fileoverview Export module exports
* Task export workflow with selection, mapping, and validation
*/
// Types
export type {
ExportableTask,
ExportPreview,
ExportValidationResult,
MappedTask,
TaskSelectionResult,
TaskValidationResult
} from './types.js';
// Task Mapper
export {
flattenTasks,
getDisplayPriority,
getDisplayStatus,
mapPriority,
mapStatus,
mapTask,
mapTasks
} from './task-mapper.js';
// Validator
export {
filterValidTasks,
validateTask,
validateTasks
} from './export-validator.js';
// Task Selector
export {
selectTasks,
showExportPreview,
showUpgradeMessage
} from './task-selector.js';

View File

@@ -0,0 +1,137 @@
/**
* @fileoverview Task Mapper
* Transforms CLI task structure to Hamster Studio format
*/
import type { ExportableTask, MappedTask } from './types.js';
/**
* Status mapping from CLI to Hamster format
*/
const STATUS_MAP: Record<string, MappedTask['status']> = {
pending: 'todo',
'in-progress': 'in_progress',
in_progress: 'in_progress',
done: 'done',
completed: 'done',
blocked: 'blocked',
deferred: 'todo',
cancelled: 'done',
review: 'in_progress'
};
/**
* Priority mapping from CLI to Hamster format
* Note: Hamster uses 'urgent' instead of 'critical'
*/
const PRIORITY_MAP: Record<string, MappedTask['priority']> = {
low: 'low',
medium: 'medium',
high: 'high',
critical: 'urgent',
urgent: 'urgent',
// Numeric priority mapping
'1': 'urgent',
'2': 'high',
'3': 'medium',
'4': 'low'
};
/**
* Maps a single task from CLI format to Hamster format
*/
export function mapTask(task: ExportableTask): MappedTask {
return {
externalId: task.id,
title: task.title,
description: task.description || undefined,
status: mapStatus(task.status),
priority: mapPriority(task.priority),
dependencies: task.dependencies?.length ? task.dependencies : undefined,
metadata: {
originalStatus: task.status,
originalPriority: task.priority,
createdAt: task.createdAt,
updatedAt: task.updatedAt,
source: 'task-master-cli'
}
};
}
/**
* Maps multiple tasks
*/
export function mapTasks(tasks: ExportableTask[]): MappedTask[] {
return tasks.map(mapTask);
}
/**
* Maps CLI status to Hamster status
*/
export function mapStatus(status?: string): MappedTask['status'] {
if (!status) return 'todo';
const normalized = status.toLowerCase().trim();
return STATUS_MAP[normalized] || 'todo';
}
/**
* Maps CLI priority to Hamster priority
*/
export function mapPriority(priority?: string): MappedTask['priority'] {
if (!priority) return 'medium';
const normalized = priority.toLowerCase().trim();
return PRIORITY_MAP[normalized] || 'medium';
}
/**
* Flattens tasks with subtasks into a flat array
* Subtasks get their parent ID prefixed
*/
export function flattenTasks(tasks: ExportableTask[]): ExportableTask[] {
const result: ExportableTask[] = [];
for (const task of tasks) {
result.push(task);
if (task.subtasks?.length) {
for (const subtask of task.subtasks) {
result.push({
...subtask,
id: `${task.id}.${subtask.id}`
});
}
}
}
return result;
}
/**
* Get display-friendly status for preview
*/
export function getDisplayStatus(status?: string): string {
const mapped = mapStatus(status);
const displayMap: Record<string, string> = {
todo: '○ To Do',
in_progress: '◐ In Progress',
done: '● Done',
blocked: '⊘ Blocked'
};
return displayMap[mapped] || '○ To Do';
}
/**
* Get display-friendly priority for preview
*/
export function getDisplayPriority(priority?: string): string {
const mapped = mapPriority(priority);
const displayMap: Record<string, string> = {
low: '↓ Low',
medium: '→ Medium',
high: '↑ High',
urgent: '⚡ Urgent'
};
return displayMap[mapped] || '→ Medium';
}

View File

@@ -0,0 +1,215 @@
/**
* @fileoverview Task Selector
* Interactive task selection interface using Inquirer.js
*/
import boxen from 'boxen';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { validateTasks } from './export-validator.js';
import type { ExportableTask, TaskSelectionResult } from './types.js';
/**
* Choice item for Inquirer checkbox
*/
interface TaskChoice {
name: string;
value: ExportableTask;
checked: boolean;
short: string;
}
/**
* Presents an interactive task selection interface
*/
export async function selectTasks(
tasks: ExportableTask[],
options: {
preselectAll?: boolean;
showStatus?: boolean;
showPriority?: boolean;
} = {}
): Promise<TaskSelectionResult> {
const { preselectAll = true, showStatus = true } = options;
if (tasks.length === 0) {
console.log(chalk.yellow('\nNo tasks available for export.\n'));
return {
selectedTasks: [],
totalAvailable: 0,
cancelled: false
};
}
// Count subtasks for display
const subtaskCount = tasks.reduce(
(sum, t) => sum + (t.subtasks?.length || 0),
0
);
const availableMessage =
subtaskCount > 0
? `${tasks.length} tasks + ${subtaskCount} subtasks available`
: `${tasks.length} available`;
// Build choices for Inquirer
const choices = buildTaskChoices(tasks, { preselectAll, showStatus });
try {
const { selectedTasks } = await inquirer.prompt<{
selectedTasks: ExportableTask[];
}>([
{
type: 'checkbox',
name: 'selectedTasks',
message: `Select tasks to export (${availableMessage}):`,
choices,
pageSize: 12,
loop: false,
validate: (input: ExportableTask[]) => {
if (input.length === 0) {
return 'Please select at least one task';
}
return true;
}
}
]);
return {
selectedTasks,
totalAvailable: tasks.length,
cancelled: false
};
} catch (error: any) {
if (error.isTtyError || error.message?.includes('User force closed')) {
return {
selectedTasks: [],
totalAvailable: tasks.length,
cancelled: true
};
}
throw error;
}
}
/**
* Builds Inquirer checkbox choices from tasks
*/
function buildTaskChoices(
tasks: ExportableTask[],
options: { preselectAll: boolean; showStatus: boolean }
): TaskChoice[] {
return tasks.map((task) => {
const statusIcon = getStatusIcon(task.status);
const title =
task.title.length > 45 ? task.title.substring(0, 42) + '...' : task.title;
return {
name: `${chalk.cyan(task.id.padEnd(6))} ${statusIcon} ${title}`,
value: task,
checked: options.preselectAll,
short: `${task.id}`
};
});
}
/**
* Get compact status icon
*/
function getStatusIcon(status?: string): string {
switch (status?.toLowerCase()) {
case 'done':
case 'completed':
return chalk.green('✓');
case 'in-progress':
case 'in_progress':
return chalk.yellow('◐');
case 'blocked':
return chalk.red('✗');
default:
return chalk.gray('○');
}
}
/**
* Shows a compact preview of what will be exported
*/
export async function showExportPreview(
tasks: ExportableTask[],
_destination?: { briefName?: string; orgName?: string }
): Promise<boolean> {
// Count tasks and subtasks (subtasks are nested inside tasks)
const taskCount = tasks.length;
const subtaskCount = tasks.reduce(
(sum, t) => sum + (t.subtasks?.length || 0),
0
);
const totalCount = taskCount + subtaskCount;
// Compact summary
console.log('');
const taskSummary =
subtaskCount > 0
? `${taskCount} tasks + ${subtaskCount} subtasks (${totalCount} total)`
: `${taskCount} tasks`;
console.log(chalk.white(` ${taskSummary} ready to export`));
// Validation check
const validation = validateTasks(tasks);
if (validation.warnings.length > 0) {
console.log(chalk.yellow(` ${validation.warnings.length} warning(s):`));
for (const warning of validation.warnings) {
console.log(chalk.gray(` - ${warning}`));
}
}
if (!validation.isValid) {
console.log('');
console.log(chalk.red(` Cannot export: ${validation.errors[0]}`));
return false;
}
console.log('');
// Confirmation prompt
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
{
type: 'confirm',
name: 'confirmed',
message: 'Continue?',
default: true
}
]);
return confirmed;
}
/**
* Shows value prop message for Hamster export
*/
export function showUpgradeMessage(tagName?: string): void {
const tagLine = tagName
? chalk.cyan(` Exporting tag: ${chalk.white.bold(tagName)}`)
: '';
const content = [
chalk.white.bold('Exporting your tasks to Hamster'),
'',
chalk.gray('Your tasks will live on Hamster where you can:'),
chalk.white(' • Invite teammates to collaborate on the brief together'),
chalk.white(' • Chat with AI alongside your team in real-time'),
chalk.white(
' • Draft, refine, align briefs and ship them faster together'
),
...(tagLine ? ['', tagLine] : [])
].join('\n');
console.log(
boxen(content, {
padding: { top: 1, bottom: 1, left: 2, right: 2 },
margin: { top: 1, bottom: 1 },
borderStyle: 'round',
borderColor: 'cyan',
dimBorder: true
})
);
}

View File

@@ -0,0 +1,79 @@
/**
* @fileoverview Types for the export workflow
*/
/**
* Task data for export selection
*/
export interface ExportableTask {
id: string;
title: string;
description?: string;
status: string;
priority?: string;
dependencies?: string[];
subtasks?: ExportableTask[];
dueDate?: string;
createdAt?: string;
updatedAt?: string;
}
/**
* Mapped task ready for Hamster API
*/
export interface MappedTask {
externalId: string;
title: string;
description?: string;
status: 'todo' | 'in_progress' | 'done' | 'blocked';
priority: 'low' | 'medium' | 'high' | 'urgent';
dependencies?: string[];
metadata?: Record<string, unknown>;
}
/**
* Validation result for a task
*/
export interface TaskValidationResult {
taskId: string;
isValid: boolean;
errors: string[];
warnings: string[];
}
/**
* Overall validation result
*/
export interface ExportValidationResult {
isValid: boolean;
totalTasks: number;
validTasks: number;
invalidTasks: number;
taskResults: TaskValidationResult[];
errors: string[];
warnings: string[];
}
/**
* Selection result from task selector
*/
export interface TaskSelectionResult {
selectedTasks: ExportableTask[];
totalAvailable: number;
cancelled: boolean;
}
/**
* Export preview data
*/
export interface ExportPreview {
taskCount: number;
tasks: Array<{
id: string;
title: string;
mappedStatus: string;
mappedPriority: string;
}>;
destinationBrief?: string;
destinationOrg?: string;
}

View File

@@ -0,0 +1,10 @@
/**
* @fileoverview Hamster integration utilities
* Functions for integrating with Hamster from the CLI
*/
export {
parsePrdToHamster,
type ParsePrdToHamsterOptions,
type ParsePrdToHamsterResult
} from './parse-prd-to-hamster.js';

View File

@@ -0,0 +1,331 @@
/**
* @fileoverview Parse PRD to Hamster
* Takes a PRD file and creates a brief on Hamster with auto-generated tasks
*/
import fs from 'node:fs/promises';
import path from 'node:path';
import { type TmCore, createTmCore } from '@tm/core';
import chalk from 'chalk';
import inquirer from 'inquirer';
import ora, { type Ora } from 'ora';
import { createUrlLink } from '../ui/index.js';
import { ensureAuthenticated } from '../utils/auth-guard.js';
import { selectBriefFromInput } from '../utils/brief-selection.js';
import { displayError } from '../utils/error-handler.js';
import { getProjectRoot } from '../utils/project-root.js';
/**
* Result type from parse PRD to Hamster operation
*/
export interface ParsePrdToHamsterResult {
success: boolean;
action: 'created' | 'cancelled' | 'error';
brief?: {
id: string;
url: string;
title: string;
};
message?: string;
}
/**
* Options for parsing PRD to Hamster
*/
export interface ParsePrdToHamsterOptions {
/** Path to the PRD file */
prdPath: string;
/** Optional title override */
title?: string;
/** Optional description override */
description?: string;
/** Whether to skip the invite prompt */
skipInvite?: boolean;
}
/**
* Parse a PRD file and create a brief on Hamster
* This is the main entry point called from the legacy CLI
*/
export async function parsePrdToHamster(
options: ParsePrdToHamsterOptions
): Promise<ParsePrdToHamsterResult> {
let spinner: Ora | undefined;
let taskMasterCore: TmCore | undefined;
try {
// 1. Ensure user is authenticated
const authResult = await ensureAuthenticated({
actionName: 'create a brief from your PRD'
});
if (!authResult.authenticated) {
if (authResult.cancelled) {
return {
success: false,
action: 'cancelled',
message: 'Authentication cancelled'
};
}
return {
success: false,
action: 'error',
message: 'Authentication required'
};
}
// 2. Initialize TmCore
const projectRoot = getProjectRoot();
if (!projectRoot) {
return {
success: false,
action: 'error',
message: 'Could not find project root'
};
}
taskMasterCore = await createTmCore({ projectPath: projectRoot });
// 3. Read PRD file content
const prdPath = path.isAbsolute(options.prdPath)
? options.prdPath
: path.join(process.cwd(), options.prdPath);
let prdContent: string;
try {
prdContent = await fs.readFile(prdPath, 'utf-8');
} catch (error) {
return {
success: false,
action: 'error',
message: `Could not read PRD file: ${(error as Error).message}`
};
}
if (!prdContent.trim()) {
return {
success: false,
action: 'error',
message: 'PRD file is empty'
};
}
// 4. Create brief from PRD
spinner = ora('Creating brief from your PRD...').start();
const result = await taskMasterCore.integration.generateBriefFromPrd({
prdContent,
options: {
generateTitle: !options.title,
generateDescription: !options.description,
title: options.title,
description: options.description
}
});
if (!result.success || !result.brief) {
spinner.fail('Failed to create brief');
const errorMsg = result.error?.message || 'Unknown error occurred';
console.error(chalk.red(`\n ${errorMsg}`));
return {
success: false,
action: 'error',
message: errorMsg
};
}
// Brief created, now poll for task generation
spinner.text = 'Generating tasks from your PRD...';
// Poll for completion (max 2 minutes)
const briefId = result.brief.id;
const maxWait = 120000; // 2 minutes
const pollInterval = 2000; // 2 seconds
const startTime = Date.now();
let briefStatus = result.brief.status;
let taskCount = 0;
while (briefStatus === 'generating' && Date.now() - startTime < maxWait) {
await new Promise((resolve) => setTimeout(resolve, pollInterval));
try {
// Poll brief status
const briefInfo = await taskMasterCore.auth.getBrief(briefId);
if (briefInfo) {
const newStatus = (briefInfo as any).plan_generation_status as string;
if (newStatus) briefStatus = newStatus as typeof briefStatus;
taskCount = (briefInfo as any).taskCount || 0;
// Update spinner with progress
if (taskCount > 0) {
spinner.text = `Generating tasks... ${taskCount} tasks created`;
}
if (newStatus === 'ready' || newStatus === 'complete') {
break;
}
if (newStatus === 'failed') {
spinner.fail('Task generation failed');
return {
success: false,
action: 'error',
message: 'Task generation failed on Hamster'
};
}
}
} catch {
// Continue polling on error
}
}
spinner.succeed('Brief created!');
// 5. Display success
console.log('');
console.log(
chalk.green(' ✓ ') + chalk.white.bold(result.brief.title || 'New Brief')
);
if (taskCount > 0) {
console.log(chalk.gray(` ${taskCount} tasks generated`));
}
console.log('');
console.log(` ${createUrlLink(result.brief.url)}`);
console.log('');
// 6. Ask about inviting collaborators (unless skipped)
if (!options.skipInvite) {
const { wantsToInvite } = await inquirer.prompt<{
wantsToInvite: boolean;
}>([
{
type: 'confirm',
name: 'wantsToInvite',
message: 'Want to invite teammates to collaborate?',
default: false
}
]);
if (wantsToInvite) {
await promptAndSendInvites(taskMasterCore, result.brief.id);
}
}
// 7. Show invite URL
showInviteUrl(result.brief.url);
// 8. Set context to the new brief
await setContextToBrief(taskMasterCore, result.brief.url);
console.log(
chalk.green(' ✓ ') +
chalk.white('Context set to new brief. Run ') +
chalk.cyan('tm list') +
chalk.white(' to see your tasks.')
);
console.log('');
return {
success: true,
action: 'created',
brief: {
id: result.brief.id,
url: result.brief.url,
title: result.brief.title || 'New Brief'
}
};
} catch (error: any) {
if (spinner?.isSpinning) spinner.fail('Failed');
displayError(error);
return {
success: false,
action: 'error',
message: error.message || 'Unknown error'
};
}
}
/**
* Prompt for invite emails and send invitations
*/
async function promptAndSendInvites(
_core: TmCore,
_briefId: string
): Promise<void> {
const { emails } = await inquirer.prompt<{ emails: string }>([
{
type: 'input',
name: 'emails',
message: 'Enter email addresses to invite (comma-separated, max 10):',
validate: (input: string) => {
if (!input.trim()) return true;
const emailList = input
.split(',')
.map((e) => e.trim())
.filter(Boolean);
if (emailList.length > 10) {
return 'Maximum 10 email addresses allowed';
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const invalid = emailList.filter((e) => !emailRegex.test(e));
if (invalid.length > 0) {
return `Invalid email format: ${invalid.join(', ')}`;
}
return true;
}
}
]);
if (!emails.trim()) return;
const emailList = emails
.split(',')
.map((e) => e.trim())
.filter(Boolean)
.slice(0, 10);
if (emailList.length > 0) {
const spinner = ora('Sending invitations...').start();
try {
// Note: We'd need to add an invite method to the integration domain
// For now, just show success - invites were sent with the initial request
spinner.succeed(`Invitations sent to ${emailList.length} teammate(s)`);
} catch {
spinner.fail('Could not send invitations');
}
}
}
/**
* Show invite URL for team members
*/
function showInviteUrl(briefUrl: string): void {
const urlMatch = briefUrl.match(
/^(https?:\/\/[^/]+)\/home\/([^/]+)\/briefs\//
);
if (urlMatch) {
const [, baseUrl, orgSlug] = urlMatch;
const membersUrl = `${baseUrl}/home/${orgSlug}/members`;
console.log(chalk.gray(' Invite teammates: ') + createUrlLink(membersUrl));
console.log('');
}
}
/**
* Set context to the newly created brief
*/
async function setContextToBrief(
core: TmCore,
briefUrl: string
): Promise<void> {
try {
const authManager = (core.auth as any).authManager;
if (!authManager) return;
await selectBriefFromInput(authManager, briefUrl, core);
} catch {
// Silently fail - context setting is nice-to-have
}
}
// Default export for easy importing
export default parsePrdToHamster;

View File

@@ -5,39 +5,19 @@
*/
import chalk from 'chalk';
import boxen from 'boxen';
import figlet from 'figlet';
import gradient from 'gradient-string';
import { createLink } from '../formatters/link-formatters.js';
// Create a cool color gradient for the banner
const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']);
/**
* Render ASCII banner text with gradient and creator credit
*/
function renderBannerWithCredit(text: string): void {
try {
const bannerText = figlet.textSync(text, {
font: 'Standard',
horizontalLayout: 'default',
verticalLayout: 'default'
});
console.log(coolGradient(bannerText));
} catch (error) {
// Fallback to simple text if figlet fails
console.log(coolGradient(`=== ${text} ===`));
}
console.log(
chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano')
);
}
export interface AsciiBannerOptions {
/** Version string to display */
version?: string;
/** Project name to display */
/** Project name to display (kept for compatibility but not displayed) */
projectName?: string;
/** Skip the version/project info box */
/** Skip the version/project info box (kept for compatibility) */
skipInfoBox?: boolean;
}
@@ -48,6 +28,13 @@ function isBannerHidden(): boolean {
return process.env.TM_HIDE_BANNER === 'true';
}
/**
* Get terminal width for right-aligning content
*/
function getTerminalWidth(): number {
return process.stdout.columns || 80;
}
/**
* Display the fancy ASCII art banner for the CLI
* Can be hidden by setting TM_HIDE_BANNER=true
@@ -55,32 +42,46 @@ function isBannerHidden(): boolean {
export function displayAsciiBanner(options: AsciiBannerOptions = {}): void {
if (isBannerHidden()) return;
const { version, projectName, skipInfoBox = false } = options;
const { version } = options;
// Display the banner with creator credit
renderBannerWithCredit('Task Master');
// Display version and project info if provided
if (!skipInfoBox && (version || projectName)) {
const infoParts: string[] = [];
if (version) {
infoParts.push(`${chalk.bold('Version:')} ${version}`);
// Render ASCII banner
try {
const bannerText = figlet.textSync('Task Master', {
font: 'Standard',
horizontalLayout: 'default',
verticalLayout: 'default'
});
console.log(coolGradient(bannerText));
} catch {
console.log(coolGradient('=== Task Master ==='));
}
if (projectName) {
infoParts.push(`${chalk.bold('Project:')} ${projectName}`);
// Credits line with version right-aligned
const xLink = createLink('x.com/eyaltoledano', 'https://x.com/eyaltoledano');
const byText = chalk.dim('by ') + chalk.cyan(xLink);
// Version with clickable link to GitHub release
const cleanVersion = version ? version.replace(/^v/, '') : '';
const releaseUrl = `https://github.com/eyaltoledano/claude-task-master/releases/tag/task-master-ai%40${cleanVersion}`;
const versionLink = version
? createLink(`v${cleanVersion}`, releaseUrl, { color: 'gray' })
: '';
if (versionLink) {
// Calculate spacing for right alignment
const byLength = 22; // "by x.com/eyaltoledano" approximate visible length
const versionLength = cleanVersion.length + 1; // "v" + version
const termWidth = getTerminalWidth();
const spacing = Math.max(2, termWidth - byLength - versionLength - 2);
console.log(byText + ' '.repeat(spacing) + versionLink);
} else {
console.log(byText);
}
console.log(
boxen(chalk.white(infoParts.join(' ')), {
padding: 1,
margin: { top: 0, bottom: 1 },
borderStyle: 'round',
borderColor: 'cyan'
})
);
}
// Hamster promo
const hamsterLink = createLink('tryhamster.com', 'https://tryhamster.com');
console.log(chalk.dim('Taskmaster for teams: ') + chalk.magenta(hamsterLink));
console.log('');
}
/**
@@ -90,15 +91,24 @@ export function displayAsciiBanner(options: AsciiBannerOptions = {}): void {
export function displayInitBanner(): void {
if (isBannerHidden()) return;
// Display the banner with creator credit
renderBannerWithCredit('Task Master AI');
// Render ASCII banner
try {
const bannerText = figlet.textSync('Task Master', {
font: 'Standard',
horizontalLayout: 'default',
verticalLayout: 'default'
});
console.log(coolGradient(bannerText));
} catch {
console.log(coolGradient('=== Task Master ==='));
}
console.log(
boxen(chalk.white(`${chalk.bold('Initializing')} your new project`), {
padding: 1,
margin: { top: 0, bottom: 1 },
borderStyle: 'round',
borderColor: 'cyan'
})
);
// Credits line
const xLink = createLink('x.com/eyaltoledano', 'https://x.com/eyaltoledano');
console.log(chalk.dim('by ') + chalk.cyan(xLink));
// Hamster promo
const hamsterLink = createLink('tryhamster.com', 'https://tryhamster.com');
console.log(chalk.dim('Taskmaster for teams: ') + chalk.magenta(hamsterLink));
console.log('');
}

View File

@@ -4,6 +4,7 @@
*/
import chalk from 'chalk';
import { createUrlLink } from '../formatters/link-formatters.js';
/**
* Brief information for API storage
@@ -41,7 +42,9 @@ export function displayHeader(options: HeaderOptions = {}): void {
// Construct and display the brief URL or ID
if (briefInfo.webAppUrl && briefInfo.orgSlug) {
const briefUrl = `${briefInfo.webAppUrl}/home/${briefInfo.orgSlug}/briefs/${briefInfo.briefId}/plan`;
console.log(`Listing tasks from: ${chalk.dim(briefUrl)}`);
console.log(
`Listing tasks from: ${createUrlLink(briefUrl, { color: 'gray' })}`
);
} else if (briefInfo.webAppUrl) {
// Show web app URL and brief ID if org slug is missing
console.log(
@@ -49,7 +52,7 @@ export function displayHeader(options: HeaderOptions = {}): void {
);
console.log(
chalk.yellow(
`💡 Tip: Run ${chalk.cyan('tm context select')} to set your organization and see the full URL`
`Tip: Run ${chalk.cyan('tm context select')} to set your organization and see the full URL`
)
);
} else {

View File

@@ -7,6 +7,7 @@ import type { Task } from '@tm/core';
import boxen from 'boxen';
import chalk from 'chalk';
import { getBoxWidth, getComplexityWithColor } from '../../utils/ui.js';
import { renderContent } from './task-detail.component.js';
/**
* Next task display options
@@ -25,24 +26,29 @@ export interface NextTaskDisplayOptions {
* Display the recommended next task section
*/
export function displayRecommendedNextTask(
task: NextTaskDisplayOptions | undefined
task: NextTaskDisplayOptions | undefined,
hasAnyTasks?: boolean
): void {
if (!task) {
// If no task available, show a message
// Only show warning box if there are literally NO tasks at all
if (!hasAnyTasks) {
console.log(
boxen(
chalk.yellow(
'No tasks available to work on. All tasks are either completed, blocked by dependencies, or in progress.'
),
{
boxen(chalk.yellow('No tasks found in this project.'), {
padding: 1,
borderStyle: 'round',
borderColor: 'yellow',
title: '⚠️ NO TASKS AVAILABLE ⚠️',
titleAlignment: 'center'
}
})
);
} else {
// Tasks exist but none are available to work on - show simple message
console.log(
chalk.yellow(
'✓ All tasks are either completed, blocked by dependencies, or in progress.'
)
);
}
return;
}
@@ -89,10 +95,12 @@ export function displayRecommendedNextTask(
content.push(`Complexity: ${getComplexityWithColor(task.complexity)}`);
}
// Description if available
// Description if available (render HTML from Hamster properly)
if (task.description) {
content.push('');
content.push(`Description: ${chalk.white(task.description)}`);
content.push(
`Description: ${chalk.white(renderContent(task.description))}`
);
}
// Action commands

View File

@@ -9,12 +9,44 @@ import chalk from 'chalk';
import Table from 'cli-table3';
import { MarkedExtension, marked } from 'marked';
import { markedTerminal } from 'marked-terminal';
import TurndownService from 'turndown';
import {
getComplexityWithColor,
getPriorityWithColor,
getStatusWithColor
} from '../../utils/ui.js';
// Initialize turndown for HTML to Markdown conversion
const turndownService = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced',
bulletListMarker: '-'
});
/**
* Convert HTML content to Markdown, then render for terminal
* Handles tiptap HTML from Hamster gracefully
*/
export function renderContent(content: string): string {
if (!content) return '';
// Clean up escape characters first - order matters: handle escaped backslashes first
let cleaned = content
.replace(/\\\\/g, '\\')
.replace(/\\n/g, '\n')
.replace(/\\t/g, '\t')
.replace(/\\"/g, '"');
// Check if content has HTML tags - if so, convert to markdown first
if (/<[^>]+>/.test(cleaned)) {
cleaned = turndownService.turndown(cleaned);
}
// Render markdown to terminal
const result = marked(cleaned);
return typeof result === 'string' ? result.trim() : cleaned;
}
// Configure marked to use terminal renderer with subtle colors
marked.use(
markedTerminal({
@@ -27,7 +59,7 @@ marked.use(
.join('\n');
},
blockquote: chalk.gray.italic,
html: chalk.gray,
html: chalk.gray, // Any remaining HTML will be grayed out (should be rare after turndown)
heading: chalk.white.bold, // White bold for headings
hr: chalk.gray,
listitem: chalk.white, // White for list items
@@ -101,6 +133,9 @@ export function displayTaskProperties(
// Use originalTaskId if provided (for subtasks like "104.1")
const displayId = originalTaskId || String(task.id);
// Render description with markdown/HTML support (handles tiptap HTML from Hamster)
const renderedDescription = renderContent(task.description || '');
// Build the left column (labels) and right column (values)
const labels = [
chalk.cyan('ID:'),
@@ -121,7 +156,7 @@ export function displayTaskProperties(
typeof task.complexity === 'number'
? getComplexityWithColor(task.complexity)
: chalk.gray('N/A'),
task.description || ''
renderedDescription
].join('\n');
table.push([labels, values]);
@@ -133,19 +168,8 @@ export function displayTaskProperties(
* Display implementation details in a box
*/
export function displayImplementationDetails(details: string): void {
// Handle all escaped characters properly
const cleanDetails = details
.replace(/\\n/g, '\n') // Convert \n to actual newlines
.replace(/\\t/g, '\t') // Convert \t to actual tabs
.replace(/\\"/g, '"') // Convert \" to actual quotes
.replace(/\\\\/g, '\\'); // Convert \\ to single backslash
const terminalWidth = process.stdout.columns * 0.95 || 100;
// Parse markdown to terminal-friendly format
const markdownResult = marked(cleanDetails);
const formattedDetails =
typeof markdownResult === 'string' ? markdownResult.trim() : cleanDetails; // Fallback to original if Promise
const formattedDetails = renderContent(details);
console.log(
boxen(
@@ -153,8 +177,8 @@ export function displayImplementationDetails(details: string): void {
{
padding: 1,
borderStyle: 'round',
borderColor: 'cyan', // Changed to cyan to match the original
width: terminalWidth // Fixed width to match the original
borderColor: 'cyan',
width: terminalWidth
}
)
);
@@ -164,25 +188,14 @@ export function displayImplementationDetails(details: string): void {
* Display test strategy in a box
*/
export function displayTestStrategy(testStrategy: string): void {
// Handle all escaped characters properly (same as implementation details)
const cleanStrategy = testStrategy
.replace(/\\n/g, '\n') // Convert \n to actual newlines
.replace(/\\t/g, '\t') // Convert \t to actual tabs
.replace(/\\"/g, '"') // Convert \" to actual quotes
.replace(/\\\\/g, '\\'); // Convert \\ to single backslash
const terminalWidth = process.stdout.columns * 0.95 || 100;
// Parse markdown to terminal-friendly format (same as implementation details)
const markdownResult = marked(cleanStrategy);
const formattedStrategy =
typeof markdownResult === 'string' ? markdownResult.trim() : cleanStrategy; // Fallback to original if Promise
const formattedStrategy = renderContent(testStrategy);
console.log(
boxen(chalk.white.bold('Test Strategy:') + '\n\n' + formattedStrategy, {
padding: 1,
borderStyle: 'round',
borderColor: 'cyan', // Changed to cyan to match implementation details
borderColor: 'cyan',
width: terminalWidth
})
);

View File

@@ -0,0 +1,66 @@
/**
* @fileoverview Terminal link formatters for clickable URLs
* Uses OSC 8 protocol for terminals that support it, with graceful fallback
*/
import chalk from 'chalk';
import terminalLink from 'terminal-link';
/**
* Creates a clickable terminal link if supported, otherwise returns styled text
* @param text - The visible text to display
* @param url - The URL to link to
* @param options - Optional styling options
*/
export function createLink(
text: string,
url: string,
options?: { color?: 'cyan' | 'blue' | 'gray' | 'green' | 'yellow' }
): string {
const colorFn = options?.color ? chalk[options.color] : chalk.cyan;
// terminal-link automatically falls back to plain text if unsupported
const link = terminalLink(text, url, {
fallback: (displayText, linkUrl) => `${displayText} (${linkUrl})`
});
return colorFn(link);
}
/**
* Creates a clickable URL that displays the full URL as the text
* @param url - The URL to display and link to
* @param options - Optional styling options
*/
export function createUrlLink(
url: string,
options?: { color?: 'cyan' | 'blue' | 'gray' | 'green' | 'yellow' }
): string {
return createLink(url, url, options);
}
/**
* Creates a clickable brief ID link
* @param briefId - The brief ID (e.g., "HAM-123")
* @param briefUrl - The full URL to the brief
*/
export function createBriefLink(briefId: string, briefUrl: string): string {
return createLink(briefId, briefUrl, { color: 'cyan' });
}
/**
* Creates a clickable task ID link
* @param taskId - The task ID (e.g., "TAS-67")
* @param taskUrl - The full URL to the task
*/
export function createTaskLink(taskId: string, taskUrl: string): string {
return createLink(taskId, taskUrl, { color: 'cyan' });
}
/**
* Checks if the terminal supports clickable links
* Note: terminal-link handles this internally, but exposed for conditional logic
*/
export function supportsLinks(): boolean {
return terminalLink.isSupported;
}

View File

@@ -27,6 +27,15 @@ export {
// Dependency formatters
export { formatDependenciesWithStatus } from './formatters/dependency-formatters.js';
// Link formatters (clickable terminal links)
export {
createLink,
createUrlLink,
createBriefLink,
createTaskLink,
supportsLinks
} from './formatters/link-formatters.js';
// Layout helpers
export {
getBoxWidth,

View File

@@ -0,0 +1,234 @@
/**
* @fileoverview Auth Guard Utility
* Provides reusable authentication checking and OAuth flow triggering
* for commands that require authentication.
*
* Includes MFA (Multi-Factor Authentication) support.
*/
import {
AUTH_TIMEOUT_MS,
type AuthCredentials,
AuthDomain,
AuthenticationError
} from '@tm/core';
import chalk from 'chalk';
import inquirer from 'inquirer';
import open from 'open';
import {
AuthCountdownTimer,
displayAuthInstructions,
displayWaitingForAuth,
handleMFAFlow
} from './auth-ui.js';
/**
* Options for the auth guard
*/
export interface AuthGuardOptions {
/** Custom message to show when not authenticated */
message?: string;
/** Whether to skip the confirmation prompt and go straight to login */
skipConfirmation?: boolean;
/** Action name for the prompt (e.g., "export tasks", "view briefs") */
actionName?: string;
}
/**
* Result of the auth guard check
*/
export interface AuthGuardResult {
/** Whether authentication succeeded */
authenticated: boolean;
/** The credentials if authenticated */
credentials?: AuthCredentials;
/** Whether the user cancelled the flow */
cancelled?: boolean;
/** Error message if auth failed */
error?: string;
}
/**
* Ensures the user is authenticated before proceeding with an action.
* If not authenticated, prompts the user and triggers OAuth flow.
* Supports MFA if enabled on the user's account.
*
* @param options - Auth guard options
* @returns Promise resolving to auth guard result
*
* @example
* ```typescript
* const result = await ensureAuthenticated({
* actionName: 'export tasks'
* });
*
* if (!result.authenticated) {
* if (result.cancelled) {
* console.log('Export cancelled');
* }
* return;
* }
*
* // Proceed with authenticated action
* await exportTasks();
* ```
*/
export async function ensureAuthenticated(
options: AuthGuardOptions = {}
): Promise<AuthGuardResult> {
const authDomain = new AuthDomain();
// Check if already authenticated
const hasSession = await authDomain.hasValidSession();
if (hasSession) {
return { authenticated: true };
}
// Not authenticated - prompt user
const actionName = options.actionName || 'continue';
const message =
options.message || `You're not logged in. Log in to ${actionName}?`;
console.log('');
console.log(chalk.yellow('🔒 Authentication Required'));
console.log('');
// Skip confirmation if requested
if (!options.skipConfirmation) {
const { shouldLogin } = await inquirer.prompt([
{
type: 'confirm',
name: 'shouldLogin',
message,
default: true
}
]);
if (!shouldLogin) {
return {
authenticated: false,
cancelled: true
};
}
}
// Trigger OAuth flow
try {
const credentials = await authenticateWithBrowser(authDomain);
return {
authenticated: true,
credentials
};
} catch (error) {
return {
authenticated: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Authenticate with browser using OAuth 2.0 with PKCE
* Includes MFA handling if the user has MFA enabled.
*/
async function authenticateWithBrowser(
authDomain: AuthDomain
): Promise<AuthCredentials> {
const countdownTimer = new AuthCountdownTimer(AUTH_TIMEOUT_MS);
try {
const credentials = await authDomain.authenticateWithOAuth({
// Callback to handle browser opening
openBrowser: async (authUrl: string) => {
await open(authUrl);
},
timeout: AUTH_TIMEOUT_MS,
// Callback when auth URL is ready
onAuthUrl: (authUrl: string) => {
displayAuthInstructions(authUrl);
},
// Callback when waiting for authentication
onWaitingForAuth: () => {
displayWaitingForAuth();
countdownTimer.start();
},
// Callback on success
onSuccess: () => {
countdownTimer.stop('success');
},
// Callback on error
onError: () => {
countdownTimer.stop('failure');
}
});
return credentials;
} catch (error: unknown) {
// Check if MFA is required BEFORE showing failure message
if (error instanceof AuthenticationError && error.code === 'MFA_REQUIRED') {
// Stop spinner without showing failure - MFA is required, not a failure
countdownTimer.stop('mfa');
if (!error.mfaChallenge?.factorId) {
throw new AuthenticationError(
'MFA challenge information missing',
'MFA_VERIFICATION_FAILED'
);
}
// Use shared MFA flow handler
return handleMFAFlow(
authDomain.verifyMFAWithRetry.bind(authDomain),
error.mfaChallenge.factorId
);
}
countdownTimer.stop('failure');
throw error;
} finally {
// Ensure cleanup
countdownTimer.cleanup();
}
}
/**
* Higher-order function that wraps a command action with auth checking.
* Use this to easily protect any command that requires authentication.
* Includes MFA support.
*
* @param action - The action to execute after authentication
* @param options - Auth guard options
* @returns Wrapped action function
*
* @example
* ```typescript
* this.action(withAuth(async (options) => {
* // This only runs if authenticated
* await doProtectedAction(options);
* }, { actionName: 'export tasks' }));
* ```
*/
export function withAuth<T extends (...args: any[]) => Promise<void>>(
action: T,
options: AuthGuardOptions = {}
): T {
return (async (...args: Parameters<T>) => {
const result = await ensureAuthenticated(options);
if (!result.authenticated) {
if (result.cancelled) {
console.log(chalk.yellow('\nOperation cancelled.\n'));
} else if (result.error) {
console.log(chalk.red(`\nAuthentication failed: ${result.error}\n`));
}
process.exit(1);
}
// User is now authenticated, proceed with action
return action(...args);
}) as T;
}

View File

@@ -0,0 +1,269 @@
/**
* @fileoverview Shared Auth UI Utilities
* Provides reusable UI components for authentication flows:
* - Countdown timer with spinner
* - MFA code prompting
*
* These are presentation-layer concerns that use ora, inquirer, and chalk.
*/
import {
AUTH_TIMEOUT_MS,
type AuthCredentials,
AuthenticationError,
MFA_MAX_ATTEMPTS
} from '@tm/core';
import chalk from 'chalk';
import inquirer from 'inquirer';
import ora, { type Ora } from 'ora';
import * as ui from './ui.js';
// Re-export constants for convenience
export { AUTH_TIMEOUT_MS, MFA_MAX_ATTEMPTS };
/**
* Countdown timer state
*/
export interface CountdownState {
interval: NodeJS.Timeout | null;
spinner: Ora | null;
}
/**
* Creates and manages an authentication countdown timer
* Displays a spinner with remaining time during OAuth flow
*/
export class AuthCountdownTimer {
private interval: NodeJS.Timeout | null = null;
private spinner: Ora | null = null;
private readonly totalMs: number;
constructor(totalMs: number = AUTH_TIMEOUT_MS) {
this.totalMs = totalMs;
}
/**
* Start the countdown timer
*/
start(): void {
const startTime = Date.now();
const endTime = startTime + this.totalMs;
const updateCountdown = () => {
const remaining = Math.max(0, endTime - Date.now());
const mins = Math.floor(remaining / 60000);
const secs = Math.floor((remaining % 60000) / 1000);
const timeStr = `${mins}:${secs.toString().padStart(2, '0')}`;
if (this.spinner) {
this.spinner.text = `Waiting for authentication... ${chalk.cyan(timeStr)} remaining`;
}
if (remaining <= 0 && this.interval) {
clearInterval(this.interval);
}
};
const initialMins = Math.floor(this.totalMs / 60000);
const initialSecs = Math.floor((this.totalMs % 60000) / 1000);
const initialTimeStr = `${initialMins}:${initialSecs.toString().padStart(2, '0')}`;
this.spinner = ora({
text: `Waiting for authentication... ${chalk.cyan(initialTimeStr)} remaining`,
spinner: 'dots'
}).start();
this.interval = setInterval(updateCountdown, 1000);
}
/**
* Stop the countdown timer
* @param result - 'success', 'failure', or 'mfa' (MFA required, not success/failure)
*/
stop(result: 'success' | 'failure' | 'mfa'): void {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
if (this.spinner) {
if (result === 'mfa') {
this.spinner.stop(); // MFA required, not success/failure
} else if (result === 'success') {
this.spinner.succeed('Authentication successful!');
} else {
this.spinner.fail('Authentication failed');
}
this.spinner = null;
}
}
/**
* Ensure cleanup even if not explicitly stopped
*/
cleanup(): void {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
if (this.spinner) {
this.spinner.stop();
this.spinner = null;
}
}
}
/**
* Display MFA required message
*/
export function displayMFARequired(): void {
console.log(
chalk.yellow('\n⚠ Multi-factor authentication is enabled on your account')
);
console.log(
chalk.white(' Please enter the 6-digit code from your authenticator app\n')
);
}
/**
* Prompt for MFA code with validation
* @returns The entered MFA code or throws if cancelled
*/
export async function promptForMFACode(): Promise<string> {
try {
const response = await inquirer.prompt([
{
type: 'input',
name: 'mfaCode',
message: 'Enter your 6-digit MFA code:',
validate: (input: string) => {
const trimmed = (input || '').trim();
if (trimmed.length === 0) {
return 'MFA code cannot be empty';
}
if (!/^\d{6}$/.test(trimmed)) {
return 'MFA code must be exactly 6 digits (0-9)';
}
return true;
}
}
]);
return response.mfaCode.trim();
} catch (error: any) {
// Handle user cancellation (Ctrl+C)
if (
error.name === 'ExitPromptError' ||
error.message?.includes('force closed')
) {
ui.displayWarning(' MFA verification cancelled by user');
throw new AuthenticationError(
'MFA verification cancelled',
'MFA_VERIFICATION_FAILED'
);
}
throw error;
}
}
/**
* Display MFA verification success
*/
export function displayMFASuccess(): void {
console.log(chalk.green('\n✓ MFA verification successful!'));
}
/**
* Display invalid MFA code message
* @param remaining - Number of attempts remaining
*/
export function displayInvalidMFACode(remaining: number): void {
if (remaining > 0) {
ui.displayError(`Invalid MFA code. Please try again.`);
}
}
/**
* Display authentication URL and instructions
* @param authUrl - The OAuth URL to display
*/
export function displayAuthInstructions(authUrl: string): void {
console.log(chalk.blue.bold('\n[auth] Browser Authentication\n'));
console.log(chalk.white(' Opening your browser to authenticate...'));
console.log(chalk.gray(" If the browser doesn't open, visit:"));
console.log(chalk.cyan.underline(` ${authUrl}\n`));
}
/**
* Display waiting for auth message
*/
export function displayWaitingForAuth(): void {
console.log(
chalk.dim(' If you signed up, check your email to confirm your account.')
);
console.log(
chalk.dim(' The CLI will automatically detect when you log in.\n')
);
}
/**
* MFA verification options for verifyMFAWithRetry
*/
export interface MFAVerificationUIOptions {
maxAttempts?: number;
onInvalidCode?: (attempt: number, remaining: number) => void;
}
/**
* Create standard MFA verification callbacks
* These can be passed to AuthDomain.verifyMFAWithRetry or AuthManager.verifyMFAWithRetry
*/
export function createMFACallbacks(options: MFAVerificationUIOptions = {}) {
return {
promptCallback: promptForMFACode,
options: {
maxAttempts: options.maxAttempts ?? MFA_MAX_ATTEMPTS,
onInvalidCode: options.onInvalidCode ?? displayInvalidMFACode
}
};
}
/**
* Handle complete MFA verification flow
* @param authDomainOrManager - AuthDomain or AuthManager instance
* @param factorId - The MFA factor ID
* @returns AuthCredentials on success
*/
export async function handleMFAFlow(
verifyMFAWithRetry: (
factorId: string,
promptCallback: () => Promise<string>,
options: {
maxAttempts: number;
onInvalidCode: (attempt: number, remaining: number) => void;
}
) => Promise<{
success: boolean;
credentials?: AuthCredentials;
attemptsUsed: number;
}>,
factorId: string
): Promise<AuthCredentials> {
displayMFARequired();
const { promptCallback, options } = createMFACallbacks();
const result = await verifyMFAWithRetry(factorId, promptCallback, options);
if (result.success && result.credentials) {
displayMFASuccess();
return result.credentials;
}
throw new AuthenticationError(
`MFA verification failed after ${result.attemptsUsed} attempts`,
'MFA_VERIFICATION_FAILED'
);
}

View File

@@ -4,9 +4,9 @@
import https from 'https';
import { fetchChangelogHighlights } from './changelog.js';
import type { UpdateInfo } from './types.js';
import { compareVersions, getCurrentVersion } from './version.js';
import { fetchChangelogHighlights } from './changelog.js';
/**
* Check for newer version of task-master-ai

View File

@@ -3,8 +3,8 @@
* CLI presentation layer - uses tm-core for logic, displays with cardBox
*/
import { type LocalOnlyCommand, type TmCore, createTmCore } from '@tm/core';
import chalk from 'chalk';
import { createTmCore, type TmCore, type LocalOnlyCommand } from '@tm/core';
import { displayCardBox } from '../ui/components/cardBox.component.js';
/**

View File

@@ -12,6 +12,14 @@ export {
type CheckAuthOptions
} from './auth-helpers.js';
// Auth guard for commands requiring authentication
export {
ensureAuthenticated,
withAuth,
type AuthGuardOptions,
type AuthGuardResult
} from './auth-guard.js';
// Command guard for local-only commands
export {
checkAndBlockIfAuthenticated,

View File

@@ -10,8 +10,8 @@ import { execSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createSubtask, createTask, createTasksFile } from '@tm/core/testing';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { getCliBinPath } from '../../helpers/test-utils.js';
// Capture initial working directory at module load time

View File

@@ -10,8 +10,8 @@ import { execSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createTask, createTasksFile } from '@tm/core/testing';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { getCliBinPath } from '../../helpers/test-utils.js';
// Capture initial working directory at module load time

View File

@@ -11,8 +11,8 @@ import { execSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createTask, createTasksFile } from '@tm/core/testing';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { getCliBinPath } from '../../helpers/test-utils.js';
// Capture initial working directory at module load time

View File

@@ -10,8 +10,8 @@ import { execSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { createSubtask, createTask, createTasksFile } from '@tm/core/testing';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createTask, createTasksFile, createSubtask } from '@tm/core/testing';
import { getCliBinPath } from '../../helpers/test-utils.js';
// Capture initial working directory at module load time

View File

@@ -2,8 +2,8 @@
* @fileoverview Unit tests for ListTasksCommand
*/
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import type { TmCore } from '@tm/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// Mock dependencies
vi.mock('@tm/core', () => ({

View File

@@ -2,8 +2,8 @@
* @fileoverview Unit tests for ShowCommand
*/
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import type { TmCore } from '@tm/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// Mock dependencies
vi.mock('@tm/core', () => ({

View File

@@ -1,6 +1,6 @@
import { ArrowLeft, RefreshCw, Settings } from 'lucide-react';
import type React from 'react';
import { useEffect, useState, useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { Badge } from './ui/badge';
import { Button } from './ui/button';
import {

View File

@@ -1,21 +1,21 @@
import type React from 'react';
import { useState } from 'react';
import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
import {
Wand2,
Loader2,
PlusCircle,
TrendingDown,
TrendingUp,
TrendingDown
Wand2
} from 'lucide-react';
import type React from 'react';
import { useState } from 'react';
import {
useUpdateTask,
useUpdateSubtask,
useScopeDownTask,
useScopeUpTask,
useScopeDownTask
useUpdateSubtask,
useUpdateTask
} from '../../webview/hooks/useTaskQueries';
import type { TaskMasterTask } from '../../webview/types';

View File

@@ -1,5 +1,5 @@
import type React from 'react';
import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
import type React from 'react';
interface MarkdownRendererProps {
content: string;

View File

@@ -1,11 +1,11 @@
import type React from 'react';
import { useState } from 'react';
import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Badge } from '@/components/ui/badge';
import { CollapsibleSection } from '@/components/ui/CollapsibleSection';
import { Plus, Loader2 } from 'lucide-react';
import { Loader2, Plus } from 'lucide-react';
import type React from 'react';
import { useState } from 'react';
import type { TaskMasterTask } from '../../webview/types';
import { getStatusDotColor } from '../constants';

View File

@@ -1,10 +1,10 @@
import type React from 'react';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Loader2, Play } from 'lucide-react';
import { PriorityBadge } from './PriorityBadge';
import type { TaskMasterTask } from '../../webview/types';
import type React from 'react';
import { useEffect, useState } from 'react';
import { useVSCodeContext } from '../../webview/contexts/VSCodeContext';
import type { TaskMasterTask } from '../../webview/types';
import { PriorityBadge } from './PriorityBadge';
interface TaskMetadataSidebarProps {
currentTask: TaskMasterTask;

View File

@@ -1,8 +1,3 @@
import type React from 'react';
import { useContext, useState, useCallback } from 'react';
import { Button } from '@/components/ui/button';
import { useQueryClient } from '@tanstack/react-query';
import { RefreshCw } from 'lucide-react';
import {
Breadcrumb,
BreadcrumbItem,
@@ -10,14 +5,19 @@ import {
BreadcrumbList,
BreadcrumbSeparator
} from '@/components/ui/breadcrumb';
import { Button } from '@/components/ui/button';
import { useQueryClient } from '@tanstack/react-query';
import { RefreshCw } from 'lucide-react';
import type React from 'react';
import { useCallback, useContext, useState } from 'react';
import { VSCodeContext } from '../webview/contexts/VSCodeContext';
import { taskKeys, useTasks } from '../webview/hooks/useTaskQueries';
import type { TaskMasterTask } from '../webview/types';
import { AIActionsSection } from './TaskDetails/AIActionsSection';
import { DetailsSection } from './TaskDetails/DetailsSection';
import { SubtasksSection } from './TaskDetails/SubtasksSection';
import { TaskMetadataSidebar } from './TaskDetails/TaskMetadataSidebar';
import { DetailsSection } from './TaskDetails/DetailsSection';
import { useTaskDetails } from './TaskDetails/useTaskDetails';
import { useTasks, taskKeys } from '../webview/hooks/useTaskQueries';
import type { TaskMasterTask } from '../webview/types';
interface TaskDetailsViewProps {
taskId: string;

View File

@@ -1,8 +1,8 @@
import { ChevronDown, ChevronRight } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import type React from 'react';
import { useState } from 'react';
import { Button } from './button';
import { ChevronDown, ChevronRight } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
interface CollapsibleSectionProps {
title: string;

View File

@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import { ConfigService } from './services/config-service';
import { PollingService } from './services/polling-service';
import { createPollingStrategy } from './services/polling-strategies';
import { SidebarWebviewManager } from './services/sidebar-webview-manager';
import { TaskRepository } from './services/task-repository';
import { TerminalManager } from './services/terminal-manager';
import { WebviewManager } from './services/webview-manager';
@@ -17,7 +18,6 @@ import {
createMCPConfigFromSettings
} from './utils/mcpClient';
import { TaskMasterApi } from './utils/task-master-api';
import { SidebarWebviewManager } from './services/sidebar-webview-manager';
let logger: ExtensionLogger;
let mcpClient: MCPClientManager;

View File

@@ -3,8 +3,8 @@
* Uses @tm/core for consistent task management with the CLI
*/
import { type TmCore, createTmCore } from '@tm/core';
import * as vscode from 'vscode';
import { createTmCore, type TmCore } from '@tm/core';
import type { ExtensionLogger } from '../utils/logger';
export interface TerminalExecutionOptions {

View File

@@ -1,7 +1,7 @@
import * as path from 'path';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import * as vscode from 'vscode';
import * as path from 'path';
import { logger } from './logger';
export interface MCPConfig {

View File

@@ -3,20 +3,20 @@
*/
import React, { useReducer, useState, useEffect, useRef } from 'react';
import { VSCodeContext } from './contexts/VSCodeContext';
import { QueryProvider } from './providers/QueryProvider';
import { AppContent } from './components/AppContent';
import { ToastContainer } from './components/ToastContainer';
import { ErrorBoundary } from './components/ErrorBoundary';
import { appReducer, initialState } from './reducers/appReducer';
import { useWebviewHeight } from './hooks/useWebviewHeight';
import { ToastContainer } from './components/ToastContainer';
import { VSCodeContext } from './contexts/VSCodeContext';
import { useVSCodeMessages } from './hooks/useVSCodeMessages';
import { useWebviewHeight } from './hooks/useWebviewHeight';
import { QueryProvider } from './providers/QueryProvider';
import { appReducer, initialState } from './reducers/appReducer';
import {
showSuccessToast,
showInfoToast,
showWarningToast,
createToast,
showErrorToast,
createToast
showInfoToast,
showSuccessToast,
showWarningToast
} from './utils/toast';
export const App: React.FC = () => {

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { TaskMasterKanban } from './TaskMasterKanban';
import TaskDetailsView from '@/components/TaskDetailsView';
import { ConfigView } from '@/components/ConfigView';
import TaskDetailsView from '@/components/TaskDetailsView';
import React from 'react';
import { useVSCodeContext } from '../contexts/VSCodeContext';
import { TaskMasterKanban } from './TaskMasterKanban';
export const AppContent: React.FC = () => {
const { state, dispatch, sendMessage } = useVSCodeContext();

View File

@@ -1,5 +1,5 @@
import { ExternalLink, MessageSquare, Plus, Terminal } from 'lucide-react';
import React from 'react';
import { ExternalLink, Terminal, MessageSquare, Plus } from 'lucide-react';
import { TaskMasterLogo } from '../../components/TaskMasterLogo';
interface EmptyStateProps {

View File

@@ -2,8 +2,8 @@
* Priority Badge Component
*/
import React from 'react';
import { Badge } from '@/components/ui/badge';
import React from 'react';
import type { TaskMasterTask } from '../types';
interface PriorityBadgeProps {

View File

@@ -2,10 +2,10 @@
* Task Card Component for Kanban Board
*/
import React from 'react';
import { KanbanCard } from '@/components/ui/shadcn-io/kanban';
import { PriorityBadge } from './PriorityBadge';
import React from 'react';
import type { TaskMasterTask } from '../types';
import { PriorityBadge } from './PriorityBadge';
interface TaskCardProps {
task: TaskMasterTask;

View File

@@ -2,16 +2,16 @@
* Task Edit Modal Component
*/
import React, { useState, useEffect, useRef } from 'react';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import React, { useState, useEffect, useRef } from 'react';
import type { TaskMasterTask, TaskUpdates } from '../types';
interface TaskEditModalProps {

View File

@@ -2,9 +2,6 @@
* Main Kanban Board Component
*/
import React, { useState, useCallback, useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { RefreshCw } from 'lucide-react';
import {
type DragEndEvent,
KanbanBoard,
@@ -12,20 +9,23 @@ import {
KanbanHeader,
KanbanProvider
} from '@/components/ui/shadcn-io/kanban';
import { TaskCard } from './TaskCard';
import { TaskEditModal } from './TaskEditModal';
import { PollingStatus } from './PollingStatus';
import { TagDropdown } from './TagDropdown';
import { EmptyState } from './EmptyState';
import { useQueryClient } from '@tanstack/react-query';
import { RefreshCw } from 'lucide-react';
import React, { useState, useCallback, useEffect } from 'react';
import { HEADER_HEIGHT, kanbanStatuses } from '../constants';
import { useVSCodeContext } from '../contexts/VSCodeContext';
import {
taskKeys,
useTasks,
useUpdateTaskStatus,
useUpdateTask,
taskKeys
useUpdateTaskStatus
} from '../hooks/useTaskQueries';
import { kanbanStatuses, HEADER_HEIGHT } from '../constants';
import type { TaskMasterTask, TaskUpdates } from '../types';
import { EmptyState } from './EmptyState';
import { PollingStatus } from './PollingStatus';
import { TagDropdown } from './TagDropdown';
import { TaskCard } from './TaskCard';
import { TaskEditModal } from './TaskEditModal';
export const TaskMasterKanban: React.FC = () => {
const { state, dispatch, sendMessage, availableHeight } = useVSCodeContext();

View File

@@ -3,8 +3,8 @@
*/
import React from 'react';
import { ToastNotification } from './ToastNotification';
import type { ToastNotification as ToastType } from '../types';
import { ToastNotification } from './ToastNotification';
interface ToastContainerProps {
notifications: ToastType[];

View File

@@ -4,7 +4,7 @@
*/
import React, { createContext, useContext } from 'react';
import type { AppState, AppAction, ToastNotification } from '../types';
import type { AppAction, AppState, ToastNotification } from '../types';
export interface VSCodeContextValue {
vscode?: ReturnType<NonNullable<typeof window.acquireVsCodeApi>>;

View File

@@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useVSCodeContext } from '../contexts/VSCodeContext';
import type { TaskMasterTask, TaskUpdates } from '../types';

View File

@@ -2,10 +2,10 @@
* Hook for handling VS Code messages
*/
import { useEffect, useCallback, useRef } from 'react';
import type { AppState, AppAction } from '../types';
import { createToast } from '../utils/toast';
import { useCallback, useEffect, useRef } from 'react';
import { REQUEST_TIMEOUT } from '../constants';
import type { AppAction, AppState } from '../types';
import { createToast } from '../utils/toast';
interface PendingRequest {
resolve: Function;

View File

@@ -2,7 +2,7 @@
* Hook for managing webview height
*/
import { useState, useEffect, useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
export const useWebviewHeight = () => {
const [availableHeight, setAvailableHeight] = useState<number>(

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
// Create a stable query client
const queryClient = new QueryClient({

View File

@@ -2,7 +2,7 @@
* Main application state reducer
*/
import type { AppState, AppAction } from '../types';
import type { AppAction, AppState } from '../types';
import { logger } from '../utils/logger';
export const appReducer = (state: AppState, action: AppAction): AppState => {

View File

@@ -2,7 +2,7 @@
* Toast notification utilities
*/
import type { ToastNotification, AppAction } from '../types';
import type { AppAction, ToastNotification } from '../types';
let toastIdCounter = 0;

View File

@@ -6,8 +6,8 @@ import fs from 'node:fs';
import path from 'node:path';
import {
LOCAL_ONLY_COMMANDS,
createTmCore,
type LocalOnlyCommand
type LocalOnlyCommand,
createTmCore
} from '@tm/core';
import type { ContentResult, Context } from 'fastmcp';
import packageJson from '../../../../package.json' with { type: 'json' };

View File

@@ -3,11 +3,11 @@
* Resume a previously started TDD workflow from saved state
*/
import { z } from 'zod';
import { handleApiResult, withToolContext } from '../../shared/utils.js';
import type { ToolContext } from '../../shared/types.js';
import { WorkflowService } from '@tm/core';
import type { FastMCP } from 'fastmcp';
import { z } from 'zod';
import type { ToolContext } from '../../shared/types.js';
import { handleApiResult, withToolContext } from '../../shared/utils.js';
const ResumeWorkflowSchema = z.object({
projectRoot: z

View File

@@ -3,11 +3,11 @@
* Initialize and start a new TDD workflow for a task
*/
import { z } from 'zod';
import { handleApiResult, withToolContext } from '../../shared/utils.js';
import type { ToolContext } from '../../shared/types.js';
import { WorkflowService } from '@tm/core';
import type { FastMCP } from 'fastmcp';
import { z } from 'zod';
import type { ToolContext } from '../../shared/types.js';
import { handleApiResult, withToolContext } from '../../shared/utils.js';
const StartWorkflowSchema = z.object({
taskId: z

View File

@@ -3,11 +3,11 @@
* Get comprehensive workflow status and progress information
*/
import { z } from 'zod';
import { handleApiResult, withToolContext } from '../../shared/utils.js';
import type { ToolContext } from '../../shared/types.js';
import { WorkflowService } from '@tm/core';
import type { FastMCP } from 'fastmcp';
import { z } from 'zod';
import type { ToolContext } from '../../shared/types.js';
import { handleApiResult, withToolContext } from '../../shared/utils.js';
const StatusSchema = z.object({
projectRoot: z

View File

@@ -11,8 +11,8 @@ import { execSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createTask, createTasksFile } from '@tm/core/testing';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
describe('get_tasks MCP tool', () => {
let testDir: string;

49
assets/hamster-art.txt Normal file
View File

@@ -0,0 +1,49 @@
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▓▓▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▓▓▓▒▒▓▒▒▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▓▒▓▒▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓████▓▓▓▓▓▓▓▒▒▒▓▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓█ ░░▓▓▒ ▒▓▓▓▓▒▒▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██ ▒▓░▒▒▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▓ ░ ██▒ ▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓██▒ ▓█░ ░ ▒██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓░▓ ▒▒▒▒▒░ ▒█▓▒ ▓██████▓█▓▓▓░▓▓████████▒ ▓▓█▒ ▒▒▒▒ ▓░█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓░░ ░▒▓▓▓▒▒▒ ██▓▓ ▒▓ ░ ░▒▓░▒▒░░ ▒ ▓ ▓█▓█ ▒▓███▓ ▒▒█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█ ▒▒█▓▓▒ ▒▒▒ ▒█▒██ ░▒▒▒░░░░░▒▒▒░▒▒▓▓▒▒▒░▓▓▒▓█ ▒░░▒▓█▒▒ █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓░▒▒▒▒▒▒▒░░░░ █▒▒█▓▒▒▒▒▓▓▓▒▒░░░░▒░▒▒▓▓▒▒▒▒▒█ ░▓▓▒▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒░█▒▒▒▒░░ ▓█ ▒▓░▒░▒▓▓▓▓▓▓██▓▓▒░░░▒▓▒▓███▓▓█░▓░░▒▒▒▒▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
▒▒▒▒▓▓▓▓▓▓▓▓▒▒▒▒▓░░▒▓▒▒░▒ ▓▒▒░▒▓▓▓██████▓▓▓▓▒░▒▓▓▓████████▒▓ ▒▒▒▒ ██▓▓▓▓▓█▒▒▓████▓▓▓▓▓
▒▒▒▓░░ ▒▓ ░░▒▒▒▒▒▓▓ ░▒▒░ ░▒▒▒▒▒▒▓████████▓▓▓█▓▒█▓▒▓█▒ ░▓██▓░░▒ ░██▓▒▓▓▓▓▒░▓░█ ▒▓ █▓▓▓
▓▓▒▓ ▓█ ▒░█▒▒▓▒▒▒▒▓██ ░▒▒▒▒▒▒▒▒▒▓▒▓░ ▒▓▒▒▓▓▓▒█▓▓▓▒▒▒██▒▒█▓▓░▓██▓▓▓███▓█ ▒█ ▒█░▒██▓▓
▓ ▓▓░ ▓█ █▒ ▓▒▒▒▓▓▒██░ ░░▒▒▓▓▒▒▒▓▓██████▓▒▓▓▒▓▓▓▓▒▓▓██████████▒ █▓▓▓▓▒ ▓░▒ ▒█▓▒█ ░▓▒░▓
▓▒ ██░ ████▓ ░▒█▒ ▓▓▓░ ░▒▒▒▒▒▒▓▓▒▒▓█░ ▓▓▓▒▓▒▒▒▓▒▓▒▓▒ ▓▓▓▒▒▒░▓▓▒▓█ ▒█▒░▓█▓████▓ ░█▓
░▒▒ ▓██▒▒▒█▒██░ ▓▓▓ ░░▒▒░▒▓▓▒▓▒▓▓ ░█ ▓▓▒▒▒▒▒▒▒▒▓█ ██ ▒▓▓█▓▒ █▓▓▓█ ▒███▒░ ▒█▒▒█▒░▓
░ ░▓███▓▒▒ ▓██░░▓▓▓░░▒▒░▒▒▒▒░▒▒▒▒▒ ▓ ▓▒▒▒▒▓▒▓▒▒▒▓ ▒▓▒▒▒░▒█▒▓██ ▓██▒████▒ ▓▓
▓▓▓░ ████▓█▒▒░▒██▓░░░░░▓ ░ ░ ▒ ░▒▒▓▓▓▓█████▒ ░ ▒▒▒░▓░ ███▓ ░▓▒▓█▓░ ███▓▒
▒▒▒▓▓ ▓██▓▒▒▒░ ▒▓ ░░░░░░▒▒▒█████▓▓ ▒▓▓▓▓██████████▓▓▓▓▓▓████▒▒░ ▒░▓▒▒░ ▒█▓▓▓▓▓
▒▒▒▒▓█▒ ▒▒▓█▓▓ ░▒▒░▒▒▓▓▒▒▒▒▒▒▒▒█████▓▓▓▒▒▓█▓░▒▓█▓▒███▓▒██▒▒▒▒▒▒▓▒░ ▒▓▒▒▒▓▓▓▒▒█▓▒▒▒▒▒
▒▒▒▒▒█ ░▒▒▒▓▒▓▓███ ░▒▒▒▒▒▓▓▒▒▒▓███▓██▓▒▒████▓▒ ░▒▓▓░ ████▒▓████▓▓██▒ ▓▓▓▓▓▓▓▓ ▓█▒▒▒▒▒▒
▒▒▒▒▒█ ░▒▒░▓▓▓▓▓▓█▓░░▒▒▒▒▒▓▓▓█▓▓▒▓▓▓██▓█████████ ▒ ██████▓█▓▒██▓▓█▓▒░▒▒▒▒▒▒▓▒ ▓▓▒▒▒▒▒
▒▒▒▒▒█ ▒▒░▒▒▒▒▓▒▓▒▓ ░▓▓▓██▒▒▒▒▒▒░░▒▓▒░░█▓███▓█████▒▒███████░ ▒▓▓▒▒▒▓▓▓██▒▒▒▒▒▒▓░░█▓▒▒▒▒▒
▒▒▒▒▒█▒░▒░▒▓▒▒▓▒▒▒▓█ ░▒▒▒▒▓▒▒▒▒▒█▓▓▒ ▓███▓▓▓▒▓▓ ▓███▓▓ ▒▒▓▓▓█▒▓▓▓▒ ░▓█▓▒▒▓ █▓▒▒▒▒▒▒
▒▒▒▒▒█ ░▒░░░▓▒▒▒█▒▒▒░ ▒▒▓▒▒▓███▓▓▒▒▒██▒ ░████░░██▒ ▒▓▓▓▓▓▓▓▓█▓▒▓░▒▒▒▒▓▓▒ █▓▒▒▒▒▒▒
▒▒▒▒▒▓ ░▒▒░░▒▓▒▒▒▒▒▒▒ ░▒▒█▒██▓▓▓██▓▒▓▓██▒ ░██ █▓▒▒▓▓██▓██▓▓██ ▒▓▒▒▒▒▒░░█▒▒▒▒▒▒▒
▒▒▒▒▒▓▓ ░▒░░▒░▒▒▒▒▒▒▒░▒░░▓▓▒▒▒▓▓▓▓▓▒▓▓▓██ ██▒▓▓█▓█▒▓▓▓▓▓▒ ▒█▓▒▒▒▒▒ █▓▒▒▒▒▒▒▒
▒▒▒▒▒▒▓▒░▒▒░░▒▒▒▒▓▒▒▒░ █░▒▒▒▒▓▓▒▒▓▒▓▓▓▒▓█ ▒█▓▒░░▒▒░ ██▓▓█▓▓▒█▒▒▓▒▓▓ ▒▒▒▒▒▒▒░ ▓▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓ ▒▒░░░░░░▒▒▒▒▓▒▒ ░▒▓▓▓▓▓▓▓▓▓▓▒▓▓▓██ ▓█▓▓█▓░░██▓▓▓▒▓▒▓▒▓█▒▒ ░▓▓▒▓▒▒▒░▒█▓▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▓ ░░▒▒▒░▒▒▒▒▒▓▒▒░ ░▒▒▓▓▓▓▓▒▒▒▒▒▒▒▓██▓ ▒█▓▒▒▓▒▓▓▓█▓▓░ ▒▓▓▒▒▒░░ █▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▓░░▓▒░░░░░▒▒░▒▒▒▒▒░ ░▒░▒▓▓▒▓▓▓▒▒▒▓████████▓░▒▓▒▓▒▓░░ ░▒▓▒▒▒▒▓▒▒░░▓▓▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▓ ▒▒▒░░░░░░▒▒▒▒▒▒▒▒▒░░░░░▒▒▒▒▒▒▒▒░ ░▒██▓░ ░▓▒▒▒▒░ ▒▒▒█▓▓▒▒▒▒▒▒▒░ ▒▓▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▓█ ░▒▒░░▒░░▒▒▒▒▒▓▒▒▓▓▓▓▒▒▒░░░░▒▒▒▒▒░ ░▒▒░░░░▒▓▓▓▓▓█▓▓▓▒▒▒▒░ ▓▓▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▒▓ ░ ▒░▒▒▒░▒▒▒▒░▓▓▒▒▒▓▓▒▒▓▓▒▒▓▓▒▒▒░▒▒▒▒▒▒▒▒▓▓▓▓▓▓█▓██▓▓▓▒▓▒▒▒▒▒░ ▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▓█ ░▒░ ░░░░░▒▒▒▒░░▓█▒▒▒▓▓▒▒▓▓▒▒▒▒▒▓▒▓▒▓▒▓▒▒▒▓▒▓███▓▓▓▓▓▒▓▓▒▒░ ░ ▓█▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▒▓ ▒▒▒░░░░░░▒░ ▒▒▒▒░▒▒▓▓▓██▓▓▓▓▓▓█░█▒▒█▒▓██▓███▓▓▓▓▓▓▓▓▓▓▒██▒▒░ ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▓░░▒▒░░░░░▒▒▒▒▒▒▒▒▒▒▓▒▒▓▓▒▒▓█▓▓██▓█████▓▓▓██▓██▓█▓▓▓▓▓▓▓▓▓▒▒▒▓▓░ █▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▒ ▒▒░░░░▒▒░░░▒▒▒▒▒▒▓▒▒▒▒▒▒██▒█▓▒▓▓▓▓▓▓▓█▓▓▓█▓▒▒▓██▒▒▒▒▒█▓▓▒▓▓▒▓▓ ▓▒▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▒▓▓ ▒▒░░░░▒░▒▒▒▒▒▒▓▒▒▒▓▓▓▓▓▒▒░▒▒▒▓▓█▓▓▓▓▒▓▓▓▒▒▓▓▓▒▒▒▓▓▓▓▓▓▓██▒▓▓▓▒░░▓▓▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▓▒ ▒▒░░░░▒▒▒▒▒▒▒▒▓▓▓▓▒▓▓▒▒▒▒▓▒▒▒▒▓▓▒▒▒▓▒▓▒▓▒▒▒▓▒▓▒▓▒█▓▓▓▓▓▓▒▓█▓▒▓▓▓░ ▓▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▓ ░▒░░░░░░░▒▒▒▒▒▓▒▓░▒▓▒▒▓▓▒▓▒██▓▓▒▒▓▒▓▓▒▓▓▒█▒▒▒▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓ ░▓▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▒▓▒ ▒▒░░▒▒▒▒▒▒░▓▓▓▒▒▓▓▓▓▓▓▓▓▓█▒▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▒▓▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▓░ ▒▒░░░░░░░░▒▒▓▒▒▒▒▓▒▒▓▒▒▓▒▓▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓█▓▒▓▓▓▓▓▓▒▓▓░ ▒▒▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▓▒░▒░░░░░░░░▒▒▒▒░▓▒▒▒▒▒▓▒▒▓▒▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▒▒▓▒▓▒█▓▒█▓▒▒▓▓▒▒▒▒▓▓ ▓▓▒▒▒▒▒▒▒▒▒▒
▒▒▒▒▒▓ ▒▒░░▒░░▒░▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▓▓▒▒▓▓▒▓▓▒▓▓▒▓▓▓▓▒▒▒▒▓▒▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▒▒▒▓▒░▓▒▒▒▒▒▒▒▒▒▒
░ ░

View File

@@ -36,7 +36,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `projectVersion`: `Set the initial version for your project, e.g., '0.1.0'.` (CLI: `--version <version>`)
* `authorName`: `Author name.` (CLI: `--author <author>`)
* `skipInstall`: `Skip installing dependencies. Default is false.` (CLI: `--skip-install`)
* `addAliases`: `Add shell aliases tm and taskmaster. Default is false.` (CLI: `--aliases`)
* `addAliases`: `Add shell aliases tm, taskmaster, hamster, and ham. Default is false.` (CLI: `--aliases`)
* `yes`: `Skip prompts and use defaults/provided arguments. Default is false.` (CLI: `-y, --yes`)
* **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Cursor. Operates on the current working directory of the MCP server.
* **Important:** Once complete, you *MUST* parse a prd in order to generate tasks. There will be no tasks files until then. The next step after initializing should be to create a PRD using the example PRD in .taskmaster/templates/example_prd.txt.

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