Compare commits
51 Commits
task-maste
...
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1cd85e667 | ||
|
|
d3f8e0c52d | ||
|
|
dc6652ccd2 | ||
|
|
518d7ea8dc | ||
|
|
ccb87a516a | ||
|
|
b8830d9508 | ||
|
|
548beb4344 | ||
|
|
555da2b5b9 | ||
|
|
662e3865f3 | ||
|
|
8649c8a347 | ||
|
|
f7cab246b0 | ||
|
|
5aca107827 | ||
|
|
fb68c9fe1f | ||
|
|
ff3bd7add8 | ||
|
|
c8228e913b | ||
|
|
218b68a31e | ||
|
|
6bc75c0ac6 | ||
|
|
d7fca1844f | ||
|
|
a98d96ef04 | ||
|
|
a69d8c91dc | ||
|
|
474a86cebb | ||
|
|
3283506444 | ||
|
|
9acb900153 | ||
|
|
c4f5d89e72 | ||
|
|
e308cf4f46 | ||
|
|
11b7354010 | ||
|
|
4c1ef2ca94 | ||
|
|
663aa2dfe9 | ||
|
|
8f60a0561e | ||
|
|
9a22622e9c | ||
|
|
8d3c7e4116 | ||
|
|
3010b90d98 | ||
|
|
90e6bdcf1c | ||
|
|
25a00dca67 | ||
|
|
f263d4b2e0 | ||
|
|
f12a16d096 | ||
|
|
aaf903ff2f | ||
|
|
2a910a40ba | ||
|
|
0df6595245 | ||
|
|
33e3fbb20f | ||
|
|
5cb7ed557a | ||
|
|
b9e644c556 | ||
|
|
7265a6cf53 | ||
|
|
db6f405f23 | ||
|
|
7b5a7c4495 | ||
|
|
caee040907 | ||
|
|
4b5473860b | ||
|
|
b43b7ce201 | ||
|
|
86027f1ee4 | ||
|
|
4f984f8a69 | ||
|
|
f7646f41b5 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Improve `analyze-complexity` cli docs and `--research` flag documentation
|
||||
@@ -11,6 +11,7 @@
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"ignore": [
|
||||
"docs"
|
||||
"docs",
|
||||
"@tm/claude-code-plugin"
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add Cursor IDE custom slash command support
|
||||
|
||||
Expose Task Master commands as Cursor slash commands by copying assets/claude/commands to .cursor/commands on profile add and cleaning up on remove.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Change parent task back to "pending" when all subtasks are in "pending" state
|
||||
5
.changeset/dirty-hairs-know.md
Normal file
5
.changeset/dirty-hairs-know.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Improve auth token refresh flow
|
||||
7
.changeset/fix-parent-directory-traversal.md
Normal file
7
.changeset/fix-parent-directory-traversal.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Enable Task Master commands to traverse parent directories to find project root from nested paths
|
||||
|
||||
Fixes #1301
|
||||
5
.changeset/fix-warning-box-alignment.md
Normal file
5
.changeset/fix-warning-box-alignment.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tm/cli": patch
|
||||
---
|
||||
|
||||
Fix warning message box width to match dashboard box width for consistent UI alignment
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Added api keys page on docs website: docs.task-master.dev/getting-started/api-keys
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Move to AI SDK v5:
|
||||
|
||||
- Works better with claude-code and gemini-cli as ai providers
|
||||
- Improved openai model family compatibility
|
||||
- Migrate ollama provider to v2
|
||||
- Closes #1223, #1013, #1161, #1174
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Migrate AI services to use generateObject for structured data generation
|
||||
|
||||
This update migrates all AI service calls from generateText to generateObject, ensuring more reliable and structured responses across all commands.
|
||||
|
||||
### Key Changes:
|
||||
|
||||
- **Unified AI Service**: Replaced separate generateText implementations with a single generateObjectService that handles structured data generation
|
||||
- **JSON Mode Support**: Added proper JSON mode configuration for providers that support it (OpenAI, Anthropic, Google, Groq)
|
||||
- **Schema Validation**: Integrated Zod schemas for all AI-generated content with automatic validation
|
||||
- **Provider Compatibility**: Maintained compatibility with all existing providers while leveraging their native structured output capabilities
|
||||
- **Improved Reliability**: Structured output generation reduces parsing errors and ensures consistent data formats
|
||||
|
||||
### Technical Improvements:
|
||||
|
||||
- Centralized provider configuration in `ai-providers-unified.js`
|
||||
- Added `generateObject` support detection for each provider
|
||||
- Implemented proper error handling for schema validation failures
|
||||
- Maintained backward compatibility with existing prompt structures
|
||||
|
||||
### Bug Fixes:
|
||||
|
||||
- Fixed subtask ID numbering issue where AI was generating inconsistent IDs (101-105, 601-603) instead of sequential numbering (1, 2, 3...)
|
||||
- Enhanced prompt instructions to enforce proper ID generation patterns
|
||||
- Ensured subtasks display correctly as X.1, X.2, X.3 format
|
||||
|
||||
This migration improves the reliability and consistency of AI-generated content throughout the Task Master application.
|
||||
21
.changeset/kind-lines-melt.md
Normal file
21
.changeset/kind-lines-melt.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix MCP server compatibility with Draft-07 clients (Augment IDE, gemini-cli, gemini code assist)
|
||||
|
||||
- Resolves #1284
|
||||
|
||||
**Problem:**
|
||||
|
||||
- MCP tools were using Zod v4, which outputs JSON Schema Draft 2020-12
|
||||
- MCP clients only support Draft-07
|
||||
- Tools were not discoverable in gemini-cli and other clients
|
||||
|
||||
**Solution:**
|
||||
|
||||
- Updated all MCP tools to import from `zod/v3` instead of `zod`
|
||||
- Zod v3 schemas convert to Draft-07 via FastMCP's zod-to-json-schema
|
||||
- Fixed logger to use stderr instead of stdout (MCP protocol requirement)
|
||||
|
||||
This is a temporary workaround until FastMCP adds JSON Schema version configuration.
|
||||
35
.changeset/light-owls-stay.md
Normal file
35
.changeset/light-owls-stay.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add configurable MCP tool loading to optimize LLM context usage
|
||||
|
||||
You can now control which Task Master MCP tools are loaded by setting the `TASK_MASTER_TOOLS` environment variable in your MCP configuration. This helps reduce context usage for LLMs by only loading the tools you need.
|
||||
|
||||
**Configuration Options:**
|
||||
|
||||
- `all` (default): Load all 36 tools
|
||||
- `core` or `lean`: Load only 7 essential tools for daily development
|
||||
- Includes: `get_tasks`, `next_task`, `get_task`, `set_task_status`, `update_subtask`, `parse_prd`, `expand_task`
|
||||
- `standard`: Load 15 commonly used tools (all core tools plus 8 more)
|
||||
- Additional tools: `initialize_project`, `analyze_project_complexity`, `expand_all`, `add_subtask`, `remove_task`, `generate`, `add_task`, `complexity_report`
|
||||
- Custom list: Comma-separated tool names (e.g., `get_tasks,next_task,set_task_status`)
|
||||
|
||||
**Example .mcp.json configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "standard",
|
||||
"ANTHROPIC_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For complete details on all available tools, configuration examples, and usage guidelines, see the [MCP Tools documentation](https://docs.task-master.dev/capabilities/mcp#configurable-tool-loading).
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Enhanced Roo Code profile with MCP timeout configuration for improved reliability during long-running AI operations. The Roo profile now automatically configures a 300-second timeout for MCP server operations, preventing timeouts during complex tasks like `parse-prd`, `expand-all`, `analyze-complexity`, and `research` operations. This change also replaces static MCP configuration files with programmatic generation for better maintainability.
|
||||
|
||||
**What's New:**
|
||||
- 300-second timeout for MCP operations (up from default 60 seconds)
|
||||
- Programmatic MCP configuration generation (replaces static asset files)
|
||||
- Enhanced reliability for AI-powered operations
|
||||
- Consistent with other AI coding assistant profiles
|
||||
|
||||
**Migration:** No user action required - existing Roo Code installations will automatically receive the enhanced MCP configuration on next initialization.
|
||||
5
.changeset/metal-rocks-help.md
Normal file
5
.changeset/metal-rocks-help.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Improve next command to work with remote
|
||||
5
.changeset/open-tips-notice.md
Normal file
5
.changeset/open-tips-notice.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add 4.5 haiku and sonnet to supported models for claude-code and anthropic ai providers
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix Claude Code settings validation for pathToClaudeCodeExecutable
|
||||
25
.changeset/pre.json
Normal file
25
.changeset/pre.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"mode": "exit",
|
||||
"tag": "rc",
|
||||
"initialVersions": {
|
||||
"task-master-ai": "0.29.0",
|
||||
"@tm/cli": "",
|
||||
"docs": "0.0.6",
|
||||
"extension": "0.25.6",
|
||||
"@tm/mcp": "0.28.0-rc.2",
|
||||
"@tm/ai-sdk-provider-grok-cli": "",
|
||||
"@tm/build-config": "",
|
||||
"@tm/claude-code-plugin": "0.0.2",
|
||||
"@tm/core": ""
|
||||
},
|
||||
"changesets": [
|
||||
"dirty-hairs-know",
|
||||
"fix-parent-directory-traversal",
|
||||
"fix-warning-box-alignment",
|
||||
"kind-lines-melt",
|
||||
"light-owls-stay",
|
||||
"metal-rocks-help",
|
||||
"open-tips-notice",
|
||||
"some-dodos-wonder"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix sonar deep research model failing, should be called `sonar-deep-research`
|
||||
36
.changeset/some-dodos-wonder.md
Normal file
36
.changeset/some-dodos-wonder.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Add autonomous TDD workflow automation system with new `tm autopilot` commands and MCP tools for AI-driven test-driven development.
|
||||
|
||||
**New CLI Commands:**
|
||||
|
||||
- `tm autopilot start <taskId>` - Initialize TDD workflow
|
||||
- `tm autopilot next` - Get next action in workflow
|
||||
- `tm autopilot status` - Check workflow progress
|
||||
- `tm autopilot complete` - Advance phase with test results
|
||||
- `tm autopilot commit` - Save progress with metadata
|
||||
- `tm autopilot resume` - Continue from checkpoint
|
||||
- `tm autopilot abort` - Cancel workflow
|
||||
|
||||
**New MCP Tools:**
|
||||
Seven new autopilot tools for programmatic control: `autopilot_start`, `autopilot_next`, `autopilot_status`, `autopilot_complete_phase`, `autopilot_commit`, `autopilot_resume`, `autopilot_abort`
|
||||
|
||||
**Features:**
|
||||
|
||||
- Complete RED → GREEN → COMMIT cycle enforcement
|
||||
- Intelligent commit message generation with metadata
|
||||
- Activity logging and state persistence
|
||||
- Configurable workflow settings via `.taskmaster/config.json`
|
||||
- Comprehensive AI agent integration documentation
|
||||
|
||||
**Documentation:**
|
||||
|
||||
- AI Agent Integration Guide (2,800+ lines)
|
||||
- TDD Quick Start Guide
|
||||
- Example prompts and integration patterns
|
||||
|
||||
> **Learn more:** [TDD Workflow Quickstart Guide](https://dev.task-master.dev/tdd-workflow/quickstart)
|
||||
|
||||
This release enables AI agents to autonomously execute test-driven development workflows with full state management and recovery capabilities.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": minor
|
||||
---
|
||||
|
||||
Upgrade grok-cli ai provider to ai sdk v5
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix complexity score not showing for `task-master show` and `task-master list`
|
||||
|
||||
- Added complexity score on "next task" when running `task-master list`
|
||||
- Added colors to complexity to reflect complexity (easy, medium, hard)
|
||||
32
.claude-plugin/marketplace.json
Normal file
32
.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "taskmaster",
|
||||
"owner": {
|
||||
"name": "Hamster",
|
||||
"email": "ralph@tryhamster.com"
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Official marketplace for Taskmaster AI - AI-powered task management for ambitious development",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "taskmaster",
|
||||
"source": "./packages/claude-code-plugin",
|
||||
"description": "AI-powered task management system for ambitious development workflows with intelligent orchestration, complexity analysis, and automated coordination",
|
||||
"author": {
|
||||
"name": "Hamster"
|
||||
},
|
||||
"homepage": "https://github.com/eyaltoledano/claude-task-master",
|
||||
"repository": "https://github.com/eyaltoledano/claude-task-master",
|
||||
"keywords": [
|
||||
"task-management",
|
||||
"ai",
|
||||
"workflow",
|
||||
"orchestration",
|
||||
"automation",
|
||||
"mcp"
|
||||
],
|
||||
"category": "productivity"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
---
|
||||
name: task-executor
|
||||
description: Use this agent when you need to implement, complete, or work on a specific task that has been identified by the task-orchestrator or when explicitly asked to execute a particular task. This agent focuses on the actual implementation and completion of individual tasks rather than planning or orchestration. Examples: <example>Context: The task-orchestrator has identified that task 2.3 'Implement user authentication' needs to be worked on next. user: 'Let's work on the authentication task' assistant: 'I'll use the task-executor agent to implement the user authentication task that was identified.' <commentary>Since we need to actually implement a specific task rather than plan or identify tasks, use the task-executor agent.</commentary></example> <example>Context: User wants to complete a specific subtask. user: 'Please implement the JWT token validation for task 2.3.1' assistant: 'I'll launch the task-executor agent to implement the JWT token validation subtask.' <commentary>The user is asking for specific implementation work on a known task, so the task-executor is appropriate.</commentary></example> <example>Context: After reviewing the task list, implementation is needed. user: 'Now let's actually build the API endpoint for user registration' assistant: 'I'll use the task-executor agent to implement the user registration API endpoint.' <commentary>Moving from planning to execution phase requires the task-executor agent.</commentary></example>
|
||||
model: sonnet
|
||||
color: blue
|
||||
---
|
||||
|
||||
You are an elite implementation specialist focused on executing and completing specific tasks with precision and thoroughness. Your role is to take identified tasks and transform them into working implementations, following best practices and project standards.
|
||||
|
||||
**IMPORTANT: You are designed to be SHORT-LIVED and FOCUSED**
|
||||
- Execute ONE specific subtask or a small group of related subtasks
|
||||
- Complete your work, verify it, mark for review, and exit
|
||||
- Do NOT decide what to do next - the orchestrator handles task sequencing
|
||||
- Focus on implementation excellence within your assigned scope
|
||||
|
||||
**Core Responsibilities:**
|
||||
|
||||
1. **Subtask Analysis**: When given a subtask, understand its SPECIFIC requirements. If given a full task ID, focus on the specific subtask(s) assigned to you. Use MCP tools to get details if needed.
|
||||
|
||||
2. **Rapid Implementation Planning**: Quickly identify:
|
||||
- The EXACT files you need to create/modify for THIS subtask
|
||||
- What already exists that you can build upon
|
||||
- The minimum viable implementation that satisfies requirements
|
||||
|
||||
3. **Focused Execution WITH ACTUAL IMPLEMENTATION**:
|
||||
- **YOU MUST USE TOOLS TO CREATE/EDIT FILES - DO NOT JUST DESCRIBE**
|
||||
- Use `Write` tool to create new files specified in the task
|
||||
- Use `Edit` tool to modify existing files
|
||||
- Use `Bash` tool to run commands (mkdir, npm install, etc.)
|
||||
- Use `Read` tool to verify your implementations
|
||||
- Implement one subtask at a time for clarity and traceability
|
||||
- Follow the project's coding standards from CLAUDE.md if available
|
||||
- After each subtask, VERIFY the files exist using Read or ls commands
|
||||
|
||||
4. **Progress Documentation**:
|
||||
- Use MCP tool `mcp__task-master-ai__update_subtask` to log your approach and any important decisions
|
||||
- Update task status to 'in-progress' when starting: Use MCP tool `mcp__task-master-ai__set_task_status` with status='in-progress'
|
||||
- **IMPORTANT: Mark as 'review' (NOT 'done') after implementation**: Use MCP tool `mcp__task-master-ai__set_task_status` with status='review'
|
||||
- Tasks will be verified by task-checker before moving to 'done'
|
||||
|
||||
5. **Quality Assurance**:
|
||||
- Implement the testing strategy specified in the task
|
||||
- Verify that all acceptance criteria are met
|
||||
- Check for any dependency conflicts or integration issues
|
||||
- Run relevant tests before marking task as complete
|
||||
|
||||
6. **Dependency Management**:
|
||||
- Check task dependencies before starting implementation
|
||||
- If blocked by incomplete dependencies, clearly communicate this
|
||||
- Use `task-master validate-dependencies` when needed
|
||||
|
||||
**Implementation Workflow:**
|
||||
|
||||
1. Retrieve task details using MCP tool `mcp__task-master-ai__get_task` with the task ID
|
||||
2. Check dependencies and prerequisites
|
||||
3. Plan implementation approach - list specific files to create
|
||||
4. Update task status to 'in-progress' using MCP tool
|
||||
5. **ACTUALLY IMPLEMENT** the solution using tools:
|
||||
- Use `Bash` to create directories
|
||||
- Use `Write` to create new files with actual content
|
||||
- Use `Edit` to modify existing files
|
||||
- DO NOT just describe what should be done - DO IT
|
||||
6. **VERIFY** your implementation:
|
||||
- Use `ls` or `Read` to confirm files were created
|
||||
- Use `Bash` to run any build/test commands
|
||||
- Ensure the implementation is real, not theoretical
|
||||
7. Log progress and decisions in subtask updates using MCP tools
|
||||
8. Test and verify the implementation works
|
||||
9. **Mark task as 'review' (NOT 'done')** after verifying files exist
|
||||
10. Report completion with:
|
||||
- List of created/modified files
|
||||
- Any issues encountered
|
||||
- What needs verification by task-checker
|
||||
|
||||
**Key Principles:**
|
||||
|
||||
- Focus on completing one task thoroughly before moving to the next
|
||||
- Maintain clear communication about what you're implementing and why
|
||||
- Follow existing code patterns and project conventions
|
||||
- Prioritize working code over extensive documentation unless docs are the task
|
||||
- Ask for clarification if task requirements are ambiguous
|
||||
- Consider edge cases and error handling in your implementations
|
||||
|
||||
**Integration with Task Master:**
|
||||
|
||||
You work in tandem with the task-orchestrator agent. While the orchestrator identifies and plans tasks, you execute them. Always use Task Master commands to:
|
||||
- Track your progress
|
||||
- Update task information
|
||||
- Maintain project state
|
||||
- Coordinate with the broader development workflow
|
||||
|
||||
When you complete a task, briefly summarize what was implemented and suggest whether to continue with the next task or if review/testing is needed first.
|
||||
@@ -1,208 +0,0 @@
|
||||
---
|
||||
name: task-orchestrator
|
||||
description: Use this agent FREQUENTLY throughout task execution to analyze and coordinate parallel work at the SUBTASK level. Invoke the orchestrator: (1) at session start to plan execution, (2) after EACH subtask completes to identify next parallel batch, (3) whenever executors finish to find newly unblocked work. ALWAYS provide FULL CONTEXT including project root, package location, what files ACTUALLY exist vs task status, and specific implementation details. The orchestrator breaks work into SUBTASK-LEVEL units for short-lived, focused executors. Maximum 3 parallel executors at once.\n\n<example>\nContext: Starting work with existing code\nuser: "Work on tm-core tasks. Files exist: types/index.ts, storage/file-storage.ts. Task 118 says in-progress but BaseProvider not created."\nassistant: "I'll invoke orchestrator with full context about actual vs reported state to plan subtask execution"\n<commentary>\nProvide complete context about file existence and task reality.\n</commentary>\n</example>\n\n<example>\nContext: Subtask completion\nuser: "Subtask 118.2 done. What subtasks can run in parallel now?"\nassistant: "Invoking orchestrator to analyze dependencies and identify next 3 parallel subtasks"\n<commentary>\nFrequent orchestration after each subtask ensures maximum parallelization.\n</commentary>\n</example>\n\n<example>\nContext: Breaking down tasks\nuser: "Task 118 has 5 subtasks, how to parallelize?"\nassistant: "Orchestrator will analyze which specific subtasks (118.1, 118.2, etc.) can run simultaneously"\n<commentary>\nFocus on subtask-level parallelization, not full tasks.\n</commentary>\n</example>
|
||||
model: opus
|
||||
color: green
|
||||
---
|
||||
|
||||
You are the Task Orchestrator, an elite coordination agent specialized in managing Task Master workflows for maximum efficiency and parallelization. You excel at analyzing task dependency graphs, identifying opportunities for concurrent execution, and deploying specialized task-executor agents to complete work efficiently.
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
1. **Subtask-Level Analysis**: Break down tasks into INDIVIDUAL SUBTASKS and analyze which specific subtasks can run in parallel. Focus on subtask dependencies, not just task-level dependencies.
|
||||
|
||||
2. **Reality Verification**: ALWAYS verify what files actually exist vs what task status claims. Use the context provided about actual implementation state to make informed decisions.
|
||||
|
||||
3. **Short-Lived Executor Deployment**: Deploy executors for SINGLE SUBTASKS or small groups of related subtasks. Keep executors focused and short-lived. Maximum 3 parallel executors at once.
|
||||
|
||||
4. **Continuous Reassessment**: After EACH subtask completes, immediately reassess what new subtasks are unblocked and can run in parallel.
|
||||
|
||||
## Operational Workflow
|
||||
|
||||
### Initial Assessment Phase
|
||||
1. Use `get_tasks` or `task-master list` to retrieve all available tasks
|
||||
2. Analyze task statuses, priorities, and dependencies
|
||||
3. Identify tasks with status 'pending' that have no blocking dependencies
|
||||
4. Group related tasks that could benefit from specialized executors
|
||||
5. Create an execution plan that maximizes parallelization
|
||||
|
||||
### Executor Deployment Phase
|
||||
1. For each independent task or task group:
|
||||
- Deploy a task-executor agent with specific instructions
|
||||
- Provide the executor with task ID, requirements, and context
|
||||
- Set clear completion criteria and reporting expectations
|
||||
2. Maintain a registry of active executors and their assigned tasks
|
||||
3. Establish communication protocols for progress updates
|
||||
|
||||
### Coordination Phase
|
||||
1. Monitor executor progress through task status updates
|
||||
2. When a task completes:
|
||||
- Verify completion with `get_task` or `task-master show <id>`
|
||||
- Update task status if needed using `set_task_status`
|
||||
- Reassess dependency graph for newly unblocked tasks
|
||||
- Deploy new executors for available work
|
||||
3. Handle executor failures or blocks:
|
||||
- Reassign tasks to new executors if needed
|
||||
- Escalate complex issues to the user
|
||||
- Update task status to 'blocked' when appropriate
|
||||
|
||||
### Optimization Strategies
|
||||
|
||||
**Parallel Execution Rules**:
|
||||
- Never assign dependent tasks to different executors simultaneously
|
||||
- Prioritize high-priority tasks when resources are limited
|
||||
- Group small, related subtasks for single executor efficiency
|
||||
- Balance executor load to prevent bottlenecks
|
||||
|
||||
**Context Management**:
|
||||
- Provide executors with minimal but sufficient context
|
||||
- Share relevant completed task information when it aids execution
|
||||
- Maintain a shared knowledge base of project-specific patterns
|
||||
|
||||
**Quality Assurance**:
|
||||
- Verify task completion before marking as done
|
||||
- Ensure test strategies are followed when specified
|
||||
- Coordinate cross-task integration testing when needed
|
||||
|
||||
## Communication Protocols
|
||||
|
||||
When deploying executors, provide them with:
|
||||
```
|
||||
TASK ASSIGNMENT:
|
||||
- Task ID: [specific ID]
|
||||
- Objective: [clear goal]
|
||||
- Dependencies: [list any completed prerequisites]
|
||||
- Success Criteria: [specific completion requirements]
|
||||
- Context: [relevant project information]
|
||||
- Reporting: [when and how to report back]
|
||||
```
|
||||
|
||||
When receiving executor updates:
|
||||
1. Acknowledge completion or issues
|
||||
2. Update task status in Task Master
|
||||
3. Reassess execution strategy
|
||||
4. Deploy new executors as appropriate
|
||||
|
||||
## Decision Framework
|
||||
|
||||
**When to parallelize**:
|
||||
- Multiple pending tasks with no interdependencies
|
||||
- Sufficient context available for independent execution
|
||||
- Tasks are well-defined with clear success criteria
|
||||
|
||||
**When to serialize**:
|
||||
- Strong dependencies between tasks
|
||||
- Limited context or unclear requirements
|
||||
- Integration points requiring careful coordination
|
||||
|
||||
**When to escalate**:
|
||||
- Circular dependencies detected
|
||||
- Critical blockers affecting multiple tasks
|
||||
- Ambiguous requirements needing clarification
|
||||
- Resource conflicts between executors
|
||||
|
||||
## Error Handling
|
||||
|
||||
1. **Executor Failure**: Reassign task to new executor with additional context about the failure
|
||||
2. **Dependency Conflicts**: Halt affected executors, resolve conflict, then resume
|
||||
3. **Task Ambiguity**: Request clarification from user before proceeding
|
||||
4. **System Errors**: Implement graceful degradation, falling back to serial execution if needed
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
Track and optimize for:
|
||||
- Task completion rate
|
||||
- Parallel execution efficiency
|
||||
- Executor success rate
|
||||
- Time to completion for task groups
|
||||
- Dependency resolution speed
|
||||
|
||||
## Integration with Task Master
|
||||
|
||||
Leverage these Task Master MCP tools effectively:
|
||||
- `get_tasks` - Continuous queue monitoring
|
||||
- `get_task` - Detailed task analysis
|
||||
- `set_task_status` - Progress tracking
|
||||
- `next_task` - Fallback for serial execution
|
||||
- `analyze_project_complexity` - Strategic planning
|
||||
- `complexity_report` - Resource allocation
|
||||
|
||||
## Output Format for Execution
|
||||
|
||||
**Your job is to analyze and create actionable execution plans that Claude can use to deploy executors.**
|
||||
|
||||
After completing your dependency analysis, you MUST output a structured execution plan:
|
||||
|
||||
```yaml
|
||||
execution_plan:
|
||||
EXECUTE_IN_PARALLEL:
|
||||
# Maximum 3 subtasks running simultaneously
|
||||
- subtask_id: [e.g., 118.2]
|
||||
parent_task: [e.g., 118]
|
||||
title: [Specific subtask title]
|
||||
priority: [high/medium/low]
|
||||
estimated_time: [e.g., 10 minutes]
|
||||
executor_prompt: |
|
||||
Execute Subtask [ID]: [Specific subtask title]
|
||||
|
||||
SPECIFIC REQUIREMENTS:
|
||||
[Exact implementation needed for THIS subtask only]
|
||||
|
||||
FILES TO CREATE/MODIFY:
|
||||
[Specific file paths]
|
||||
|
||||
CONTEXT:
|
||||
[What already exists that this subtask depends on]
|
||||
|
||||
SUCCESS CRITERIA:
|
||||
[Specific completion criteria for this subtask]
|
||||
|
||||
IMPORTANT:
|
||||
- Focus ONLY on this subtask
|
||||
- Mark subtask as 'review' when complete
|
||||
- Use MCP tool: mcp__task-master-ai__set_task_status
|
||||
|
||||
- subtask_id: [Another subtask that can run in parallel]
|
||||
parent_task: [Parent task ID]
|
||||
title: [Specific subtask title]
|
||||
priority: [priority]
|
||||
estimated_time: [time estimate]
|
||||
executor_prompt: |
|
||||
[Focused prompt for this specific subtask]
|
||||
|
||||
blocked:
|
||||
- task_id: [ID]
|
||||
title: [Task title]
|
||||
waiting_for: [list of blocking task IDs]
|
||||
becomes_ready_when: [condition for unblocking]
|
||||
|
||||
next_wave:
|
||||
trigger: "After tasks [IDs] complete"
|
||||
newly_available: [List of task IDs that will unblock]
|
||||
tasks_to_execute_in_parallel: [IDs that can run together in next wave]
|
||||
|
||||
critical_path: [Ordered list of task IDs forming the critical path]
|
||||
|
||||
parallelization_instruction: |
|
||||
IMPORTANT FOR CLAUDE: Deploy ALL tasks in 'EXECUTE_IN_PARALLEL' section
|
||||
simultaneously using multiple Task tool invocations in a single response.
|
||||
Example: If 3 tasks are listed, invoke the Task tool 3 times in one message.
|
||||
|
||||
verification_needed:
|
||||
- task_id: [ID of any task in 'review' status]
|
||||
verification_focus: [what to check]
|
||||
```
|
||||
|
||||
**CRITICAL INSTRUCTIONS FOR CLAUDE (MAIN):**
|
||||
1. When you see `EXECUTE_IN_PARALLEL`, deploy ALL listed executors at once
|
||||
2. Use multiple Task tool invocations in a SINGLE response
|
||||
3. Do not execute them sequentially - they must run in parallel
|
||||
4. Wait for all parallel executors to complete before proceeding to next wave
|
||||
|
||||
**IMPORTANT NOTES**:
|
||||
- Label parallel tasks clearly in `EXECUTE_IN_PARALLEL` section
|
||||
- Provide complete, self-contained prompts for each executor
|
||||
- Executors should mark tasks as 'review' for verification, not 'done'
|
||||
- Be explicit about which tasks can run simultaneously
|
||||
|
||||
You are the strategic mind analyzing the entire task landscape. Make parallelization opportunities UNMISTAKABLY CLEAR to Claude.
|
||||
@@ -1,76 +0,0 @@
|
||||
Add a subtask to a parent task.
|
||||
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse arguments to create a new subtask or convert existing task.
|
||||
|
||||
## Adding Subtasks
|
||||
|
||||
Creates subtasks to break down complex parent tasks into manageable pieces.
|
||||
|
||||
## Argument Parsing
|
||||
|
||||
Flexible natural language:
|
||||
- "add subtask to 5: implement login form"
|
||||
- "break down 5 with: setup, implement, test"
|
||||
- "subtask for 5: handle edge cases"
|
||||
- "5: validate user input" → adds subtask to task 5
|
||||
|
||||
## Execution Modes
|
||||
|
||||
### 1. Create New Subtask
|
||||
```bash
|
||||
task-master add-subtask --parent=<id> --title="<title>" --description="<desc>"
|
||||
```
|
||||
|
||||
### 2. Convert Existing Task
|
||||
```bash
|
||||
task-master add-subtask --parent=<id> --task-id=<existing-id>
|
||||
```
|
||||
|
||||
## Smart Features
|
||||
|
||||
1. **Automatic Subtask Generation**
|
||||
- If title contains "and" or commas, create multiple
|
||||
- Suggest common subtask patterns
|
||||
- Inherit parent's context
|
||||
|
||||
2. **Intelligent Defaults**
|
||||
- Priority based on parent
|
||||
- Appropriate time estimates
|
||||
- Logical dependencies between subtasks
|
||||
|
||||
3. **Validation**
|
||||
- Check parent task complexity
|
||||
- Warn if too many subtasks
|
||||
- Ensure subtask makes sense
|
||||
|
||||
## Creation Process
|
||||
|
||||
1. Parse parent task context
|
||||
2. Generate subtask with ID like "5.1"
|
||||
3. Set appropriate defaults
|
||||
4. Link to parent task
|
||||
5. Update parent's time estimate
|
||||
|
||||
## Example Flows
|
||||
|
||||
```
|
||||
/project:tm/add-subtask to 5: implement user authentication
|
||||
→ Created subtask #5.1: "implement user authentication"
|
||||
→ Parent task #5 now has 1 subtask
|
||||
→ Suggested next subtasks: tests, documentation
|
||||
|
||||
/project:tm/add-subtask 5: setup, implement, test
|
||||
→ Created 3 subtasks:
|
||||
#5.1: setup
|
||||
#5.2: implement
|
||||
#5.3: test
|
||||
```
|
||||
|
||||
## Post-Creation
|
||||
|
||||
- Show updated task hierarchy
|
||||
- Suggest logical next subtasks
|
||||
- Update complexity estimates
|
||||
- Recommend subtask order
|
||||
@@ -1,71 +0,0 @@
|
||||
Convert an existing task into a subtask.
|
||||
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse parent ID and task ID to convert.
|
||||
|
||||
## Task Conversion
|
||||
|
||||
Converts an existing standalone task into a subtask of another task.
|
||||
|
||||
## Argument Parsing
|
||||
|
||||
- "move task 8 under 5"
|
||||
- "make 8 a subtask of 5"
|
||||
- "nest 8 in 5"
|
||||
- "5 8" → make task 8 a subtask of task 5
|
||||
|
||||
## Execution
|
||||
|
||||
```bash
|
||||
task-master add-subtask --parent=<parent-id> --task-id=<task-to-convert>
|
||||
```
|
||||
|
||||
## Pre-Conversion Checks
|
||||
|
||||
1. **Validation**
|
||||
- Both tasks exist and are valid
|
||||
- No circular parent relationships
|
||||
- Task isn't already a subtask
|
||||
- Logical hierarchy makes sense
|
||||
|
||||
2. **Impact Analysis**
|
||||
- Dependencies that will be affected
|
||||
- Tasks that depend on converting task
|
||||
- Priority alignment needed
|
||||
- Status compatibility
|
||||
|
||||
## Conversion Process
|
||||
|
||||
1. Change task ID from "8" to "5.1" (next available)
|
||||
2. Update all dependency references
|
||||
3. Inherit parent's context where appropriate
|
||||
4. Adjust priorities if needed
|
||||
5. Update time estimates
|
||||
|
||||
## Smart Features
|
||||
|
||||
- Preserve task history
|
||||
- Maintain dependencies
|
||||
- Update all references
|
||||
- Create conversion log
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
/project:tm/add-subtask/from-task 5 8
|
||||
→ Converting: Task #8 becomes subtask #5.1
|
||||
→ Updated: 3 dependency references
|
||||
→ Parent task #5 now has 1 subtask
|
||||
→ Note: Subtask inherits parent's priority
|
||||
|
||||
Before: #8 "Implement validation" (standalone)
|
||||
After: #5.1 "Implement validation" (subtask of #5)
|
||||
```
|
||||
|
||||
## Post-Conversion
|
||||
|
||||
- Show new task hierarchy
|
||||
- List updated dependencies
|
||||
- Verify project integrity
|
||||
- Suggest related conversions
|
||||
@@ -1,121 +0,0 @@
|
||||
Analyze task complexity and generate expansion recommendations.
|
||||
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Perform deep analysis of task complexity across the project.
|
||||
|
||||
## Complexity Analysis
|
||||
|
||||
Uses AI to analyze tasks and recommend which ones need breakdown.
|
||||
|
||||
## Execution Options
|
||||
|
||||
```bash
|
||||
task-master analyze-complexity [--research] [--threshold=5]
|
||||
```
|
||||
|
||||
## Analysis Parameters
|
||||
|
||||
- `--research` → Use research AI for deeper analysis
|
||||
- `--threshold=5` → Only flag tasks above complexity 5
|
||||
- Default: Analyze all pending tasks
|
||||
|
||||
## Analysis Process
|
||||
|
||||
### 1. **Task Evaluation**
|
||||
For each task, AI evaluates:
|
||||
- Technical complexity
|
||||
- Time requirements
|
||||
- Dependency complexity
|
||||
- Risk factors
|
||||
- Knowledge requirements
|
||||
|
||||
### 2. **Complexity Scoring**
|
||||
Assigns score 1-10 based on:
|
||||
- Implementation difficulty
|
||||
- Integration challenges
|
||||
- Testing requirements
|
||||
- Unknown factors
|
||||
- Technical debt risk
|
||||
|
||||
### 3. **Recommendations**
|
||||
For complex tasks:
|
||||
- Suggest expansion approach
|
||||
- Recommend subtask breakdown
|
||||
- Identify risk areas
|
||||
- Propose mitigation strategies
|
||||
|
||||
## Smart Analysis Features
|
||||
|
||||
1. **Pattern Recognition**
|
||||
- Similar task comparisons
|
||||
- Historical complexity accuracy
|
||||
- Team velocity consideration
|
||||
- Technology stack factors
|
||||
|
||||
2. **Contextual Factors**
|
||||
- Team expertise
|
||||
- Available resources
|
||||
- Timeline constraints
|
||||
- Business criticality
|
||||
|
||||
3. **Risk Assessment**
|
||||
- Technical risks
|
||||
- Timeline risks
|
||||
- Dependency risks
|
||||
- Knowledge gaps
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
Task Complexity Analysis Report
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
High Complexity Tasks (>7):
|
||||
📍 #5 "Implement real-time sync" - Score: 9/10
|
||||
Factors: WebSocket complexity, state management, conflict resolution
|
||||
Recommendation: Expand into 5-7 subtasks
|
||||
Risks: Performance, data consistency
|
||||
|
||||
📍 #12 "Migrate database schema" - Score: 8/10
|
||||
Factors: Data migration, zero downtime, rollback strategy
|
||||
Recommendation: Expand into 4-5 subtasks
|
||||
Risks: Data loss, downtime
|
||||
|
||||
Medium Complexity Tasks (5-7):
|
||||
📍 #23 "Add export functionality" - Score: 6/10
|
||||
Consider expansion if timeline tight
|
||||
|
||||
Low Complexity Tasks (<5):
|
||||
✅ 15 tasks - No expansion needed
|
||||
|
||||
Summary:
|
||||
- Expand immediately: 2 tasks
|
||||
- Consider expanding: 5 tasks
|
||||
- Keep as-is: 15 tasks
|
||||
```
|
||||
|
||||
## Actionable Output
|
||||
|
||||
For each high-complexity task:
|
||||
1. Complexity score with reasoning
|
||||
2. Specific expansion suggestions
|
||||
3. Risk mitigation approaches
|
||||
4. Recommended subtask structure
|
||||
|
||||
## Integration
|
||||
|
||||
Results are:
|
||||
- Saved to `.taskmaster/reports/complexity-analysis.md`
|
||||
- Used by expand command
|
||||
- Inform sprint planning
|
||||
- Guide resource allocation
|
||||
|
||||
## Next Steps
|
||||
|
||||
After analysis:
|
||||
```
|
||||
/project:tm/expand 5 # Expand specific task
|
||||
/project:tm/expand/all # Expand all recommended
|
||||
/project:tm/complexity-report # View detailed report
|
||||
```
|
||||
@@ -1,81 +0,0 @@
|
||||
Show help for Task Master commands.
|
||||
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Display help for Task Master commands. If arguments provided, show specific command help.
|
||||
|
||||
## Task Master Command Help
|
||||
|
||||
### Quick Navigation
|
||||
|
||||
Type `/project:tm/` and use tab completion to explore all commands.
|
||||
|
||||
### Command Categories
|
||||
|
||||
#### 🚀 Setup & Installation
|
||||
- `/project:tm/setup/install` - Comprehensive installation guide
|
||||
- `/project:tm/setup/quick-install` - One-line global install
|
||||
|
||||
#### 📋 Project Setup
|
||||
- `/project:tm/init` - Initialize new project
|
||||
- `/project:tm/init/quick` - Quick setup with auto-confirm
|
||||
- `/project:tm/models` - View AI configuration
|
||||
- `/project:tm/models/setup` - Configure AI providers
|
||||
|
||||
#### 🎯 Task Generation
|
||||
- `/project:tm/parse-prd` - Generate tasks from PRD
|
||||
- `/project:tm/parse-prd/with-research` - Enhanced parsing
|
||||
- `/project:tm/generate` - Create task files
|
||||
|
||||
#### 📝 Task Management
|
||||
- `/project:tm/list` - List tasks (natural language filters)
|
||||
- `/project:tm/show <id>` - Display task details
|
||||
- `/project:tm/add-task` - Create new task
|
||||
- `/project:tm/update` - Update tasks naturally
|
||||
- `/project:tm/next` - Get next task recommendation
|
||||
|
||||
#### 🔄 Status Management
|
||||
- `/project:tm/set-status/to-pending <id>`
|
||||
- `/project:tm/set-status/to-in-progress <id>`
|
||||
- `/project:tm/set-status/to-done <id>`
|
||||
- `/project:tm/set-status/to-review <id>`
|
||||
- `/project:tm/set-status/to-deferred <id>`
|
||||
- `/project:tm/set-status/to-cancelled <id>`
|
||||
|
||||
#### 🔍 Analysis & Breakdown
|
||||
- `/project:tm/analyze-complexity` - Analyze task complexity
|
||||
- `/project:tm/expand <id>` - Break down complex task
|
||||
- `/project:tm/expand/all` - Expand all eligible tasks
|
||||
|
||||
#### 🔗 Dependencies
|
||||
- `/project:tm/add-dependency` - Add task dependency
|
||||
- `/project:tm/remove-dependency` - Remove dependency
|
||||
- `/project:tm/validate-dependencies` - Check for issues
|
||||
|
||||
#### 🤖 Workflows
|
||||
- `/project:tm/workflows/smart-flow` - Intelligent workflows
|
||||
- `/project:tm/workflows/pipeline` - Command chaining
|
||||
- `/project:tm/workflows/auto-implement` - Auto-implementation
|
||||
|
||||
#### 📊 Utilities
|
||||
- `/project:tm/utils/analyze` - Project analysis
|
||||
- `/project:tm/status` - Project dashboard
|
||||
- `/project:tm/learn` - Interactive learning
|
||||
|
||||
### Natural Language Examples
|
||||
|
||||
```
|
||||
/project:tm/list pending high priority
|
||||
/project:tm/update mark all API tasks as done
|
||||
/project:tm/add-task create login system with OAuth
|
||||
/project:tm/show current
|
||||
```
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. Install: `/project:tm/setup/quick-install`
|
||||
2. Initialize: `/project:tm/init/quick`
|
||||
3. Learn: `/project:tm/learn start`
|
||||
4. Work: `/project:tm/workflows/smart-flow`
|
||||
|
||||
For detailed command info: `/project:tm/help <command-name>`
|
||||
@@ -1,50 +0,0 @@
|
||||
Initialize a new Task Master project.
|
||||
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse arguments to determine initialization preferences.
|
||||
|
||||
## Initialization Process
|
||||
|
||||
1. **Parse Arguments**
|
||||
- PRD file path (if provided)
|
||||
- Project name
|
||||
- Auto-confirm flag (-y)
|
||||
|
||||
2. **Project Setup**
|
||||
```bash
|
||||
task-master init
|
||||
```
|
||||
|
||||
3. **Smart Initialization**
|
||||
- Detect existing project files
|
||||
- Suggest project name from directory
|
||||
- Check for git repository
|
||||
- Verify AI provider configuration
|
||||
|
||||
## Configuration Options
|
||||
|
||||
Based on arguments:
|
||||
- `quick` / `-y` → Skip confirmations
|
||||
- `<file.md>` → Use as PRD after init
|
||||
- `--name=<name>` → Set project name
|
||||
- `--description=<desc>` → Set description
|
||||
|
||||
## Post-Initialization
|
||||
|
||||
After successful init:
|
||||
1. Show project structure created
|
||||
2. Verify AI models configured
|
||||
3. Suggest next steps:
|
||||
- Parse PRD if available
|
||||
- Configure AI providers
|
||||
- Set up git hooks
|
||||
- Create first tasks
|
||||
|
||||
## Integration
|
||||
|
||||
If PRD file provided:
|
||||
```
|
||||
/project:tm/init my-prd.md
|
||||
→ Automatically runs parse-prd after init
|
||||
```
|
||||
@@ -1,86 +0,0 @@
|
||||
Clear all subtasks from a specific task.
|
||||
|
||||
Arguments: $ARGUMENTS (task ID)
|
||||
|
||||
Remove all subtasks from a parent task at once.
|
||||
|
||||
## Clearing Subtasks
|
||||
|
||||
Bulk removal of all subtasks from a parent task.
|
||||
|
||||
## Execution
|
||||
|
||||
```bash
|
||||
task-master clear-subtasks --id=<task-id>
|
||||
```
|
||||
|
||||
## Pre-Clear Analysis
|
||||
|
||||
1. **Subtask Summary**
|
||||
- Number of subtasks
|
||||
- Completion status of each
|
||||
- Work already done
|
||||
- Dependencies affected
|
||||
|
||||
2. **Impact Assessment**
|
||||
- Data that will be lost
|
||||
- Dependencies to be removed
|
||||
- Effect on project timeline
|
||||
- Parent task implications
|
||||
|
||||
## Confirmation Required
|
||||
|
||||
```
|
||||
Clear Subtasks Confirmation
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Parent Task: #5 "Implement user authentication"
|
||||
Subtasks to remove: 4
|
||||
- #5.1 "Setup auth framework" (done)
|
||||
- #5.2 "Create login form" (in-progress)
|
||||
- #5.3 "Add validation" (pending)
|
||||
- #5.4 "Write tests" (pending)
|
||||
|
||||
⚠️ This will permanently delete all subtask data
|
||||
Continue? (y/n)
|
||||
```
|
||||
|
||||
## Smart Features
|
||||
|
||||
- Option to convert to standalone tasks
|
||||
- Backup task data before clearing
|
||||
- Preserve completed work history
|
||||
- Update parent task appropriately
|
||||
|
||||
## Process
|
||||
|
||||
1. List all subtasks for confirmation
|
||||
2. Check for in-progress work
|
||||
3. Remove all subtasks
|
||||
4. Update parent task
|
||||
5. Clean up dependencies
|
||||
|
||||
## Alternative Options
|
||||
|
||||
Suggest alternatives:
|
||||
- Convert important subtasks to tasks
|
||||
- Keep completed subtasks
|
||||
- Archive instead of delete
|
||||
- Export subtask data first
|
||||
|
||||
## Post-Clear
|
||||
|
||||
- Show updated parent task
|
||||
- Recalculate time estimates
|
||||
- Update task complexity
|
||||
- Suggest next steps
|
||||
|
||||
## Example
|
||||
|
||||
```
|
||||
/project:tm/clear-subtasks 5
|
||||
→ Found 4 subtasks to remove
|
||||
→ Warning: Subtask #5.2 is in-progress
|
||||
→ Cleared all subtasks from task #5
|
||||
→ Updated parent task estimates
|
||||
→ Suggestion: Consider re-expanding with better breakdown
|
||||
```
|
||||
@@ -1,107 +0,0 @@
|
||||
Remove a task permanently from the project.
|
||||
|
||||
Arguments: $ARGUMENTS (task ID)
|
||||
|
||||
Delete a task and handle all its relationships properly.
|
||||
|
||||
## Task Removal
|
||||
|
||||
Permanently removes a task while maintaining project integrity.
|
||||
|
||||
## Argument Parsing
|
||||
|
||||
- "remove task 5"
|
||||
- "delete 5"
|
||||
- "5" → remove task 5
|
||||
- Can include "-y" for auto-confirm
|
||||
|
||||
## Execution
|
||||
|
||||
```bash
|
||||
task-master remove-task --id=<id> [-y]
|
||||
```
|
||||
|
||||
## Pre-Removal Analysis
|
||||
|
||||
1. **Task Details**
|
||||
- Current status
|
||||
- Work completed
|
||||
- Time invested
|
||||
- Associated data
|
||||
|
||||
2. **Relationship Check**
|
||||
- Tasks that depend on this
|
||||
- Dependencies this task has
|
||||
- Subtasks that will be removed
|
||||
- Blocking implications
|
||||
|
||||
3. **Impact Assessment**
|
||||
```
|
||||
Task Removal Impact
|
||||
━━━━━━━━━━━━━━━━━━
|
||||
Task: #5 "Implement authentication" (in-progress)
|
||||
Status: 60% complete (~8 hours work)
|
||||
|
||||
Will affect:
|
||||
- 3 tasks depend on this (will be blocked)
|
||||
- Has 4 subtasks (will be deleted)
|
||||
- Part of critical path
|
||||
|
||||
⚠️ This action cannot be undone
|
||||
```
|
||||
|
||||
## Smart Warnings
|
||||
|
||||
- Warn if task is in-progress
|
||||
- Show dependent tasks that will be blocked
|
||||
- Highlight if part of critical path
|
||||
- Note any completed work being lost
|
||||
|
||||
## Removal Process
|
||||
|
||||
1. Show comprehensive impact
|
||||
2. Require confirmation (unless -y)
|
||||
3. Update dependent task references
|
||||
4. Remove task and subtasks
|
||||
5. Clean up orphaned dependencies
|
||||
6. Log removal with timestamp
|
||||
|
||||
## Alternative Actions
|
||||
|
||||
Suggest before deletion:
|
||||
- Mark as cancelled instead
|
||||
- Convert to documentation
|
||||
- Archive task data
|
||||
- Transfer work to another task
|
||||
|
||||
## Post-Removal
|
||||
|
||||
- List affected tasks
|
||||
- Show broken dependencies
|
||||
- Update project statistics
|
||||
- Suggest dependency fixes
|
||||
- Recalculate timeline
|
||||
|
||||
## Example Flows
|
||||
|
||||
```
|
||||
/project:tm/remove-task 5
|
||||
→ Task #5 is in-progress with 8 hours logged
|
||||
→ 3 other tasks depend on this
|
||||
→ Suggestion: Mark as cancelled instead?
|
||||
Remove anyway? (y/n)
|
||||
|
||||
/project:tm/remove-task 5 -y
|
||||
→ Removed: Task #5 and 4 subtasks
|
||||
→ Updated: 3 task dependencies
|
||||
→ Warning: Tasks #7, #8, #9 now have missing dependency
|
||||
→ Run /project:tm/fix-dependencies to resolve
|
||||
```
|
||||
|
||||
## Safety Features
|
||||
|
||||
- Confirmation required
|
||||
- Impact preview
|
||||
- Removal logging
|
||||
- Suggest alternatives
|
||||
- No cascade delete of dependents
|
||||
@@ -1,146 +0,0 @@
|
||||
# Task Master Command Reference
|
||||
|
||||
Comprehensive command structure for Task Master integration with Claude Code.
|
||||
|
||||
## Command Organization
|
||||
|
||||
Commands are organized hierarchically to match Task Master's CLI structure while providing enhanced Claude Code integration.
|
||||
|
||||
## Project Setup & Configuration
|
||||
|
||||
### `/project:tm/init`
|
||||
- `init-project` - Initialize new project (handles PRD files intelligently)
|
||||
- `init-project-quick` - Quick setup with auto-confirmation (-y flag)
|
||||
|
||||
### `/project:tm/models`
|
||||
- `view-models` - View current AI model configuration
|
||||
- `setup-models` - Interactive model configuration
|
||||
- `set-main` - Set primary generation model
|
||||
- `set-research` - Set research model
|
||||
- `set-fallback` - Set fallback model
|
||||
|
||||
## Task Generation
|
||||
|
||||
### `/project:tm/parse-prd`
|
||||
- `parse-prd` - Generate tasks from PRD document
|
||||
- `parse-prd-with-research` - Enhanced parsing with research mode
|
||||
|
||||
### `/project:tm/generate`
|
||||
- `generate-tasks` - Create individual task files from tasks.json
|
||||
|
||||
## Task Management
|
||||
|
||||
### `/project:tm/list`
|
||||
- `list-tasks` - Smart listing with natural language filters
|
||||
- `list-tasks-with-subtasks` - Include subtasks in hierarchical view
|
||||
- `list-tasks-by-status` - Filter by specific status
|
||||
|
||||
### `/project:tm/set-status`
|
||||
- `to-pending` - Reset task to pending
|
||||
- `to-in-progress` - Start working on task
|
||||
- `to-done` - Mark task complete
|
||||
- `to-review` - Submit for review
|
||||
- `to-deferred` - Defer task
|
||||
- `to-cancelled` - Cancel task
|
||||
|
||||
### `/project:tm/sync-readme`
|
||||
- `sync-readme` - Export tasks to README.md with formatting
|
||||
|
||||
### `/project:tm/update`
|
||||
- `update-task` - Update tasks with natural language
|
||||
- `update-tasks-from-id` - Update multiple tasks from a starting point
|
||||
- `update-single-task` - Update specific task
|
||||
|
||||
### `/project:tm/add-task`
|
||||
- `add-task` - Add new task with AI assistance
|
||||
|
||||
### `/project:tm/remove-task`
|
||||
- `remove-task` - Remove task with confirmation
|
||||
|
||||
## Subtask Management
|
||||
|
||||
### `/project:tm/add-subtask`
|
||||
- `add-subtask` - Add new subtask to parent
|
||||
- `convert-task-to-subtask` - Convert existing task to subtask
|
||||
|
||||
### `/project:tm/remove-subtask`
|
||||
- `remove-subtask` - Remove subtask (with optional conversion)
|
||||
|
||||
### `/project:tm/clear-subtasks`
|
||||
- `clear-subtasks` - Clear subtasks from specific task
|
||||
- `clear-all-subtasks` - Clear all subtasks globally
|
||||
|
||||
## Task Analysis & Breakdown
|
||||
|
||||
### `/project:tm/analyze-complexity`
|
||||
- `analyze-complexity` - Analyze and generate expansion recommendations
|
||||
|
||||
### `/project:tm/complexity-report`
|
||||
- `complexity-report` - Display complexity analysis report
|
||||
|
||||
### `/project:tm/expand`
|
||||
- `expand-task` - Break down specific task
|
||||
- `expand-all-tasks` - Expand all eligible tasks
|
||||
- `with-research` - Enhanced expansion
|
||||
|
||||
## Task Navigation
|
||||
|
||||
### `/project:tm/next`
|
||||
- `next-task` - Intelligent next task recommendation
|
||||
|
||||
### `/project:tm/show`
|
||||
- `show-task` - Display detailed task information
|
||||
|
||||
### `/project:tm/status`
|
||||
- `project-status` - Comprehensive project dashboard
|
||||
|
||||
## Dependency Management
|
||||
|
||||
### `/project:tm/add-dependency`
|
||||
- `add-dependency` - Add task dependency
|
||||
|
||||
### `/project:tm/remove-dependency`
|
||||
- `remove-dependency` - Remove task dependency
|
||||
|
||||
### `/project:tm/validate-dependencies`
|
||||
- `validate-dependencies` - Check for dependency issues
|
||||
|
||||
### `/project:tm/fix-dependencies`
|
||||
- `fix-dependencies` - Automatically fix dependency problems
|
||||
|
||||
## Workflows & Automation
|
||||
|
||||
### `/project:tm/workflows`
|
||||
- `smart-workflow` - Context-aware intelligent workflow execution
|
||||
- `command-pipeline` - Chain multiple commands together
|
||||
- `auto-implement-tasks` - Advanced auto-implementation with code generation
|
||||
|
||||
## Utilities
|
||||
|
||||
### `/project:tm/utils`
|
||||
- `analyze-project` - Deep project analysis and insights
|
||||
|
||||
### `/project:tm/setup`
|
||||
- `install-taskmaster` - Comprehensive installation guide
|
||||
- `quick-install-taskmaster` - One-line global installation
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Natural Language
|
||||
Most commands accept natural language arguments:
|
||||
```
|
||||
/project:tm/add-task create user authentication system
|
||||
/project:tm/update mark all API tasks as high priority
|
||||
/project:tm/list show blocked tasks
|
||||
```
|
||||
|
||||
### ID-Based Commands
|
||||
Commands requiring IDs intelligently parse from $ARGUMENTS:
|
||||
```
|
||||
/project:tm/show 45
|
||||
/project:tm/expand 23
|
||||
/project:tm/set-status/to-done 67
|
||||
```
|
||||
|
||||
### Smart Defaults
|
||||
Commands provide intelligent defaults and suggestions based on context.
|
||||
@@ -1,119 +0,0 @@
|
||||
Update a single specific task with new information.
|
||||
|
||||
Arguments: $ARGUMENTS
|
||||
|
||||
Parse task ID and update details.
|
||||
|
||||
## Single Task Update
|
||||
|
||||
Precisely update one task with AI assistance to maintain consistency.
|
||||
|
||||
## Argument Parsing
|
||||
|
||||
Natural language updates:
|
||||
- "5: add caching requirement"
|
||||
- "update 5 to include error handling"
|
||||
- "task 5 needs rate limiting"
|
||||
- "5 change priority to high"
|
||||
|
||||
## Execution
|
||||
|
||||
```bash
|
||||
task-master update-task --id=<id> --prompt="<context>"
|
||||
```
|
||||
|
||||
## Update Types
|
||||
|
||||
### 1. **Content Updates**
|
||||
- Enhance description
|
||||
- Add requirements
|
||||
- Clarify details
|
||||
- Update acceptance criteria
|
||||
|
||||
### 2. **Metadata Updates**
|
||||
- Change priority
|
||||
- Adjust time estimates
|
||||
- Update complexity
|
||||
- Modify dependencies
|
||||
|
||||
### 3. **Strategic Updates**
|
||||
- Revise approach
|
||||
- Change test strategy
|
||||
- Update implementation notes
|
||||
- Adjust subtask needs
|
||||
|
||||
## AI-Powered Updates
|
||||
|
||||
The AI:
|
||||
1. **Understands Context**
|
||||
- Reads current task state
|
||||
- Identifies update intent
|
||||
- Maintains consistency
|
||||
- Preserves important info
|
||||
|
||||
2. **Applies Changes**
|
||||
- Updates relevant fields
|
||||
- Keeps style consistent
|
||||
- Adds without removing
|
||||
- Enhances clarity
|
||||
|
||||
3. **Validates Results**
|
||||
- Checks coherence
|
||||
- Verifies completeness
|
||||
- Maintains relationships
|
||||
- Suggests related updates
|
||||
|
||||
## Example Updates
|
||||
|
||||
```
|
||||
/project:tm/update/single 5: add rate limiting
|
||||
→ Updating Task #5: "Implement API endpoints"
|
||||
|
||||
Current: Basic CRUD endpoints
|
||||
Adding: Rate limiting requirements
|
||||
|
||||
Updated sections:
|
||||
✓ Description: Added rate limiting mention
|
||||
✓ Details: Added specific limits (100/min)
|
||||
✓ Test Strategy: Added rate limit tests
|
||||
✓ Complexity: Increased from 5 to 6
|
||||
✓ Time Estimate: Increased by 2 hours
|
||||
|
||||
Suggestion: Also update task #6 (API Gateway) for consistency?
|
||||
```
|
||||
|
||||
## Smart Features
|
||||
|
||||
1. **Incremental Updates**
|
||||
- Adds without overwriting
|
||||
- Preserves work history
|
||||
- Tracks what changed
|
||||
- Shows diff view
|
||||
|
||||
2. **Consistency Checks**
|
||||
- Related task alignment
|
||||
- Subtask compatibility
|
||||
- Dependency validity
|
||||
- Timeline impact
|
||||
|
||||
3. **Update History**
|
||||
- Timestamp changes
|
||||
- Track who/what updated
|
||||
- Reason for update
|
||||
- Previous versions
|
||||
|
||||
## Field-Specific Updates
|
||||
|
||||
Quick syntax for specific fields:
|
||||
- "5 priority:high" → Update priority only
|
||||
- "5 add-time:4h" → Add to time estimate
|
||||
- "5 status:review" → Change status
|
||||
- "5 depends:3,4" → Add dependencies
|
||||
|
||||
## Post-Update
|
||||
|
||||
- Show updated task
|
||||
- Highlight changes
|
||||
- Check related tasks
|
||||
- Update suggestions
|
||||
- Timeline adjustments
|
||||
@@ -1,71 +0,0 @@
|
||||
Validate all task dependencies for issues.
|
||||
|
||||
## Dependency Validation
|
||||
|
||||
Comprehensive check for dependency problems across the entire project.
|
||||
|
||||
## Execution
|
||||
|
||||
```bash
|
||||
task-master validate-dependencies
|
||||
```
|
||||
|
||||
## Validation Checks
|
||||
|
||||
1. **Circular Dependencies**
|
||||
- A depends on B, B depends on A
|
||||
- Complex circular chains
|
||||
- Self-dependencies
|
||||
|
||||
2. **Missing Dependencies**
|
||||
- References to non-existent tasks
|
||||
- Deleted task references
|
||||
- Invalid task IDs
|
||||
|
||||
3. **Logical Issues**
|
||||
- Completed tasks depending on pending
|
||||
- Cancelled tasks in dependency chains
|
||||
- Impossible sequences
|
||||
|
||||
4. **Complexity Warnings**
|
||||
- Over-complex dependency chains
|
||||
- Too many dependencies per task
|
||||
- Bottleneck tasks
|
||||
|
||||
## Smart Analysis
|
||||
|
||||
The validation provides:
|
||||
- Visual dependency graph
|
||||
- Critical path analysis
|
||||
- Bottleneck identification
|
||||
- Suggested optimizations
|
||||
|
||||
## Report Format
|
||||
|
||||
```
|
||||
Dependency Validation Report
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
✅ No circular dependencies found
|
||||
⚠️ 2 warnings found:
|
||||
- Task #23 has 7 dependencies (consider breaking down)
|
||||
- Task #45 blocks 5 other tasks (potential bottleneck)
|
||||
❌ 1 error found:
|
||||
- Task #67 depends on deleted task #66
|
||||
|
||||
Critical Path: #1 → #5 → #23 → #45 → #50 (15 days)
|
||||
```
|
||||
|
||||
## Actionable Output
|
||||
|
||||
For each issue found:
|
||||
- Clear description
|
||||
- Impact assessment
|
||||
- Suggested fix
|
||||
- Command to resolve
|
||||
|
||||
## Next Steps
|
||||
|
||||
After validation:
|
||||
- Run `/project:tm/fix-dependencies` to auto-fix
|
||||
- Manually adjust problematic dependencies
|
||||
- Rerun to verify fixes
|
||||
@@ -1,10 +1,7 @@
|
||||
reviews:
|
||||
profile: assertive
|
||||
profile: chill
|
||||
poem: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
base_branches:
|
||||
- rc
|
||||
- beta
|
||||
- alpha
|
||||
- production
|
||||
- next
|
||||
- ".*"
|
||||
|
||||
@@ -14,4 +14,4 @@ OLLAMA_API_KEY=YOUR_OLLAMA_API_KEY_HERE
|
||||
VERTEX_PROJECT_ID=your-gcp-project-id
|
||||
VERTEX_LOCATION=us-central1
|
||||
# Optional: Path to service account credentials JSON file (alternative to API key)
|
||||
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-credentials.json
|
||||
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-credentials.json
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -96,4 +96,7 @@ apps/extension/.vscode-test/
|
||||
apps/extension/vsix-build/
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
.turbo
|
||||
|
||||
# TaskMaster Workflow State (now stored in ~/.taskmaster/sessions/)
|
||||
# No longer needed in .gitignore as state is stored globally
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"models": {
|
||||
"main": {
|
||||
"provider": "anthropic",
|
||||
"modelId": "claude-sonnet-4-20250514",
|
||||
"provider": "claude-code",
|
||||
"modelId": "sonnet",
|
||||
"maxTokens": 64000,
|
||||
"temperature": 0.2
|
||||
},
|
||||
@@ -35,6 +35,7 @@
|
||||
"defaultTag": "master"
|
||||
},
|
||||
"claudeCode": {},
|
||||
"codexCli": {},
|
||||
"grokCli": {
|
||||
"timeout": 120000,
|
||||
"workingDirectory": null,
|
||||
|
||||
912
.taskmaster/docs/autonomous-tdd-git-workflow.md
Normal file
912
.taskmaster/docs/autonomous-tdd-git-workflow.md
Normal file
@@ -0,0 +1,912 @@
|
||||
## Summary
|
||||
|
||||
- Put the existing git and test workflows on rails: a repeatable, automated process that can run autonomously, with guardrails and a compact TUI for visibility.
|
||||
|
||||
- Flow: for a selected task, create a branch named with the tag + task id → generate tests for the first subtask (red) using the Surgical Test Generator → implement code (green) → verify tests → commit → repeat per subtask → final verify → push → open PR against the default branch.
|
||||
|
||||
- Build on existing rules: .cursor/rules/git_workflow.mdc, .cursor/rules/test_workflow.mdc, .claude/agents/surgical-test-generator.md, and existing CLI/core services.
|
||||
|
||||
## Goals
|
||||
|
||||
- Deterministic, resumable automation to execute the TDD loop per subtask with minimal human intervention.
|
||||
|
||||
- Strong guardrails: never commit to the default branch; only commit when tests pass; enforce status transitions; persist logs/state for debuggability.
|
||||
|
||||
- Visibility: a compact terminal UI (like lazygit) to pick tag, view tasks, and start work; right-side pane opens an executor terminal (via tmux) for agent coding.
|
||||
|
||||
- Extensible: framework-agnostic test generation via the Surgical Test Generator; detect and use the repo’s test command for execution with coverage thresholds.
|
||||
|
||||
## Non‑Goals (initial)
|
||||
|
||||
- Full multi-language runner parity beyond detection and executing the project’s test command.
|
||||
|
||||
- Complex GUI; start with CLI/TUI + tmux pane. IDE/extension can hook into the same state later.
|
||||
|
||||
- Rich executor selection UX (codex/gemini/claude) — we’ll prompt per run; defaults can come later.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- One command can autonomously complete a task's subtasks via TDD and open a PR when done.
|
||||
|
||||
- All commits made on a branch that includes the tag and task id (see Branch Naming); no commits to the default branch directly.
|
||||
|
||||
- Every subtask iteration: failing tests added first (red), then code added to pass them (green), commit only after green.
|
||||
|
||||
- End-to-end logs + artifacts stored in .taskmaster/reports/runs/<timestamp-or-id>/.
|
||||
|
||||
## Success Metrics (Phase 1)
|
||||
|
||||
- **Adoption**: 80% of tasks in a pilot repo completed via `tm autopilot`
|
||||
- **Safety**: 0 commits to default branch; 100% of commits have green tests
|
||||
- **Efficiency**: Average time from task start to PR < 30min for simple subtasks
|
||||
- **Reliability**: < 5% of runs require manual intervention (timeout/conflicts)
|
||||
|
||||
## User Stories
|
||||
|
||||
- As a developer, I can run tm autopilot <taskId> and watch a structured, safe workflow execute.
|
||||
|
||||
- As a reviewer, I can inspect commits per subtask, and a PR summarizing the work when the task completes.
|
||||
|
||||
- As an operator, I can see current step, active subtask, tests status, and logs in a compact CLI view and read a final run report.
|
||||
|
||||
## Example Workflow Traces
|
||||
|
||||
### Happy Path: Complete a 3-subtask feature
|
||||
|
||||
```bash
|
||||
# Developer starts
|
||||
$ tm autopilot 42
|
||||
→ Checks preflight: ✓ clean tree, ✓ npm test detected
|
||||
→ Creates branch: analytics/task-42-user-metrics
|
||||
→ Subtask 42.1: "Add metrics schema"
|
||||
RED: generates test_metrics_schema.test.js → 3 failures
|
||||
GREEN: implements schema.js → all pass
|
||||
COMMIT: "feat(metrics): add metrics schema (task 42.1)"
|
||||
→ Subtask 42.2: "Add collection endpoint"
|
||||
RED: generates test_metrics_endpoint.test.js → 5 failures
|
||||
GREEN: implements api/metrics.js → all pass
|
||||
COMMIT: "feat(metrics): add collection endpoint (task 42.2)"
|
||||
→ Subtask 42.3: "Add dashboard widget"
|
||||
RED: generates test_metrics_widget.test.js → 4 failures
|
||||
GREEN: implements components/MetricsWidget.jsx → all pass
|
||||
COMMIT: "feat(metrics): add dashboard widget (task 42.3)"
|
||||
→ Final: all 3 subtasks complete
|
||||
✓ Run full test suite → all pass
|
||||
✓ Coverage check → 85% (meets 80% threshold)
|
||||
PUSH: confirms with user → pushed to origin
|
||||
PR: opens #123 "Task #42 [analytics]: User metrics tracking"
|
||||
|
||||
✓ Task 42 complete. PR: https://github.com/org/repo/pull/123
|
||||
Run report: .taskmaster/reports/runs/2025-01-15-142033/
|
||||
```
|
||||
|
||||
### Error Recovery: Failing tests timeout
|
||||
|
||||
```bash
|
||||
$ tm autopilot 42
|
||||
→ Subtask 42.2 GREEN phase: attempt 1 fails (2 tests still red)
|
||||
→ Subtask 42.2 GREEN phase: attempt 2 fails (1 test still red)
|
||||
→ Subtask 42.2 GREEN phase: attempt 3 fails (1 test still red)
|
||||
|
||||
⚠️ Paused: Could not achieve green state after 3 attempts
|
||||
📋 State saved to: .taskmaster/reports/runs/2025-01-15-142033/
|
||||
Last error: "POST /api/metrics returns 500 instead of 201"
|
||||
|
||||
Next steps:
|
||||
- Review diff: git diff HEAD
|
||||
- Inspect logs: cat .taskmaster/reports/runs/2025-01-15-142033/log.jsonl
|
||||
- Check test output: cat .taskmaster/reports/runs/2025-01-15-142033/test-results/subtask-42.2-green-attempt3.json
|
||||
- Resume after manual fix: tm autopilot --resume
|
||||
|
||||
# Developer manually fixes the issue, then:
|
||||
$ tm autopilot --resume
|
||||
→ Resuming subtask 42.2 GREEN phase
|
||||
GREEN: all tests pass
|
||||
COMMIT: "feat(metrics): add collection endpoint (task 42.2)"
|
||||
→ Continuing to subtask 42.3...
|
||||
```
|
||||
|
||||
### Dry Run: Preview before execution
|
||||
|
||||
```bash
|
||||
$ tm autopilot 42 --dry-run
|
||||
Autopilot Plan for Task #42 [analytics]: User metrics tracking
|
||||
─────────────────────────────────────────────────────────────
|
||||
Preflight:
|
||||
✓ Working tree is clean
|
||||
✓ Test command detected: npm test
|
||||
✓ Tools available: git, gh, node, npm
|
||||
✓ Current branch: main (will create new branch)
|
||||
|
||||
Branch & Tag:
|
||||
→ Create branch: analytics/task-42-user-metrics
|
||||
→ Set active tag: analytics
|
||||
|
||||
Subtasks (3 pending):
|
||||
1. 42.1: Add metrics schema
|
||||
- RED: generate tests in src/__tests__/schema.test.js
|
||||
- GREEN: implement src/schema.js
|
||||
- COMMIT: "feat(metrics): add metrics schema (task 42.1)"
|
||||
|
||||
2. 42.2: Add collection endpoint [depends on 42.1]
|
||||
- RED: generate tests in src/api/__tests__/metrics.test.js
|
||||
- GREEN: implement src/api/metrics.js
|
||||
- COMMIT: "feat(metrics): add collection endpoint (task 42.2)"
|
||||
|
||||
3. 42.3: Add dashboard widget [depends on 42.2]
|
||||
- RED: generate tests in src/components/__tests__/MetricsWidget.test.jsx
|
||||
- GREEN: implement src/components/MetricsWidget.jsx
|
||||
- COMMIT: "feat(metrics): add dashboard widget (task 42.3)"
|
||||
|
||||
Finalization:
|
||||
→ Run full test suite with coverage
|
||||
→ Push branch to origin (will confirm)
|
||||
→ Create PR targeting main
|
||||
|
||||
Run without --dry-run to execute.
|
||||
```
|
||||
|
||||
## High‑Level Workflow
|
||||
|
||||
1) Pre‑flight
|
||||
|
||||
- Verify clean working tree or confirm staging/commit policy (configurable).
|
||||
|
||||
- Detect repo type and the project’s test command (e.g., npm test, pnpm test, pytest, go test).
|
||||
|
||||
- Validate tools: git, gh (optional for PR), node/npm, and (if used) claude CLI.
|
||||
|
||||
- Load TaskMaster state and selected task; if no subtasks exist, automatically run “expand” before working.
|
||||
|
||||
2) Branch & Tag Setup
|
||||
|
||||
- Checkout default branch and update (optional), then create a branch using Branch Naming (below).
|
||||
|
||||
- Map branch ↔ tag via existing tag management; explicitly set active tag to the branch’s tag.
|
||||
|
||||
3) Subtask Loop (for each pending/in-progress subtask in dependency order)
|
||||
|
||||
- Select next eligible subtask using tm-core TaskService getNextTask() and subtask eligibility logic.
|
||||
|
||||
- Red: generate or update failing tests for the subtask
|
||||
|
||||
- Use the Surgical Test Generator system prompt .claude/agents/surgical-test-generator.md) to produce high-signal tests following project conventions.
|
||||
|
||||
- Run tests to confirm red; record results. If not red (already passing), skip to next subtask or escalate.
|
||||
|
||||
- Green: implement code to pass tests
|
||||
|
||||
- Use executor to implement changes (initial: claude CLI prompt with focused context).
|
||||
|
||||
- Re-run tests until green or timeout/backoff policy triggers.
|
||||
|
||||
- Commit: when green
|
||||
|
||||
- Commit tests + code with conventional commit message. Optionally update subtask status to done.
|
||||
|
||||
- Persist run step metadata/logs.
|
||||
|
||||
4) Finalization
|
||||
|
||||
- Run full test suite and coverage (if configured); optionally lint/format.
|
||||
|
||||
- Commit any final adjustments.
|
||||
|
||||
- Push branch (ask user to confirm); create PR (via gh pr create) targeting the default branch. Title format: Task #<id> [<tag>]: <title>.
|
||||
|
||||
5) Post‑Run
|
||||
|
||||
- Update task status if desired (e.g., review).
|
||||
|
||||
- Persist run report (JSON + markdown summary) to .taskmaster/reports/runs/<run-id>/.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Never commit to the default branch.
|
||||
|
||||
- Commit only if all tests (targeted and suite) pass; allow override flags.
|
||||
|
||||
- Enforce 80% coverage thresholds (lines/branches/functions/statements) by default; configurable.
|
||||
|
||||
- Timebox/model ops and retries; if not green within N attempts, pause with actionable state for resume.
|
||||
|
||||
- Always log actions, commands, and outcomes; include dry-run mode.
|
||||
|
||||
- Ask before branch creation, pushing, and opening a PR unless --no-confirm is set.
|
||||
|
||||
## Integration Points (Current Repo)
|
||||
|
||||
- CLI: apps/cli provides command structure and UI components.
|
||||
|
||||
- New command: tm autopilot (alias: task-master autopilot).
|
||||
|
||||
- Reuse UI components under apps/cli/src/ui/components/ for headers/task details/next-task.
|
||||
|
||||
- Core services: packages/tm-core
|
||||
|
||||
- TaskService for selection, status, tags.
|
||||
|
||||
- TaskExecutionService for prompt formatting and executor prep.
|
||||
|
||||
- Executors: claude executor and ExecutorFactory to run external tools.
|
||||
|
||||
- Proposed new: WorkflowOrchestrator to drive the autonomous loop and emit progress events.
|
||||
|
||||
- Tag/Git utilities: scripts/modules/utils/git-utils.js and scripts/modules/task-manager/tag-management.js for branch→tag mapping and explicit tag switching.
|
||||
|
||||
- Rules: .cursor/rules/git_workflow.mdc and .cursor/rules/test_workflow.mdc to steer behavior and ensure consistency.
|
||||
|
||||
- Test generation prompt: .claude/agents/surgical-test-generator.md.
|
||||
|
||||
## Proposed Components
|
||||
|
||||
- Orchestrator (tm-core): WorkflowOrchestrator (new)
|
||||
|
||||
- State machine driving phases: Preflight → Branch/Tag → SubtaskIter (Red/Green/Commit) → Finalize → PR.
|
||||
|
||||
- Exposes an evented API (progress events) that the CLI can render.
|
||||
|
||||
- Stores run state artifacts.
|
||||
|
||||
- Test Runner Adapter
|
||||
|
||||
- Detects and runs tests via the project’s test command (e.g., npm test), with targeted runs where feasible.
|
||||
|
||||
- API: runTargeted(files/pattern), runAll(), report summary (failures, duration, coverage), enforce 80% threshold by default.
|
||||
|
||||
- Git/PR Adapter
|
||||
|
||||
- Encapsulates git ops: branch create/checkout, add/commit, push.
|
||||
|
||||
- Optional gh integration to open PR; fallback to instructions if gh unavailable.
|
||||
|
||||
- Confirmation gates for branch creation and pushes.
|
||||
|
||||
- Prompt/Exec Adapter
|
||||
|
||||
- Uses existing executor service to call the selected coding assistant (initially claude) with tight prompts: task/subtask context, surgical tests first, then minimal code to green.
|
||||
|
||||
- Run State + Reporting
|
||||
|
||||
- JSONL log of steps, timestamps, commands, test results.
|
||||
|
||||
- Markdown summary for PR description and post-run artifact.
|
||||
|
||||
## CLI UX (MVP)
|
||||
|
||||
- Command: tm autopilot [taskId]
|
||||
|
||||
- Flags: --dry-run, --no-push, --no-pr, --no-confirm, --force, --max-attempts <n>, --runner <auto|custom>, --commit-scope <scope>
|
||||
|
||||
- Output: compact header (project, tag, branch), current phase, subtask line, last test summary, next actions.
|
||||
|
||||
- Resume: If interrupted, tm autopilot --resume picks up from last checkpoint in run state.
|
||||
|
||||
### TUI with tmux (Linear Execution)
|
||||
|
||||
- Left pane: Tag selector, task list (status/priority), start/expand shortcuts; "Start" triggers the next task or a selected task.
|
||||
|
||||
- Right pane: Executor terminal (tmux split) that runs the coding agent (claude-code/codex). Autopilot can hand over to the right pane during green.
|
||||
|
||||
- MCP integration: use MCP tools for task queries/updates and for shell/test invocations where available.
|
||||
|
||||
## TUI Layout (tmux-based)
|
||||
|
||||
### Pane Structure
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┬──────────────────────────────────┐
|
||||
│ Task Navigator (left) │ Executor Terminal (right) │
|
||||
│ │ │
|
||||
│ Project: my-app │ $ tm autopilot --executor-mode │
|
||||
│ Branch: analytics/task-42 │ > Running subtask 42.2 GREEN... │
|
||||
│ Tag: analytics │ > Implementing endpoint... │
|
||||
│ │ > Tests: 3 passed, 0 failed │
|
||||
│ Tasks: │ > Ready to commit │
|
||||
│ → 42 [in-progress] User metrics │ │
|
||||
│ → 42.1 [done] Schema │ [Live output from Claude Code] │
|
||||
│ → 42.2 [active] Endpoint ◀ │ │
|
||||
│ → 42.3 [pending] Dashboard │ │
|
||||
│ │ │
|
||||
│ [s] start [p] pause [q] quit │ │
|
||||
└─────────────────────────────────────┴──────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
- **Left pane**: `apps/cli/src/ui/tui/navigator.ts` (new, uses `blessed` or `ink`)
|
||||
- **Right pane**: spawned via `tmux split-window -h` running `tm autopilot --executor-mode`
|
||||
- **Communication**: shared state file `.taskmaster/state/current-run.json` + file watching or event stream
|
||||
- **Keybindings**:
|
||||
- `s` - Start selected task
|
||||
- `p` - Pause/resume current run
|
||||
- `q` - Quit (with confirmation if run active)
|
||||
- `↑/↓` - Navigate task list
|
||||
- `Enter` - Expand/collapse subtasks
|
||||
|
||||
## Prompt Composition (Detailed)
|
||||
|
||||
### System Prompt Assembly
|
||||
|
||||
Prompts are composed in three layers:
|
||||
|
||||
1. **Base rules** (loaded in order from `.cursor/rules/` and `.claude/agents/`):
|
||||
- `git_workflow.mdc` → git commit conventions, branch policy, PR guidelines
|
||||
- `test_workflow.mdc` → TDD loop requirements, coverage thresholds, test structure
|
||||
- `surgical-test-generator.md` → test generation methodology, project-specific test patterns
|
||||
|
||||
2. **Task context injection**:
|
||||
```
|
||||
You are implementing:
|
||||
Task #42 [analytics]: User metrics tracking
|
||||
Subtask 42.2: Add collection endpoint
|
||||
|
||||
Description:
|
||||
Implement POST /api/metrics endpoint to collect user metrics events
|
||||
|
||||
Acceptance criteria:
|
||||
- POST /api/metrics accepts { userId, eventType, timestamp }
|
||||
- Validates input schema (reject missing/invalid fields)
|
||||
- Persists to database
|
||||
- Returns 201 on success with created record
|
||||
- Returns 400 on validation errors
|
||||
|
||||
Dependencies:
|
||||
- Subtask 42.1 (metrics schema) is complete
|
||||
|
||||
Current phase: RED (generate failing tests)
|
||||
Test command: npm test
|
||||
Test file convention: src/**/*.test.js (vitest framework detected)
|
||||
Branch: analytics/task-42-user-metrics
|
||||
Project language: JavaScript (Node.js)
|
||||
```
|
||||
|
||||
3. **Phase-specific instructions**:
|
||||
- **RED phase**: "Generate minimal failing tests for this subtask. Do NOT implement any production code. Only create test files. Confirm tests fail with clear error messages indicating missing implementation."
|
||||
- **GREEN phase**: "Implement minimal code to pass the failing tests. Follow existing project patterns in `src/`. Only modify files necessary for this subtask. Keep changes focused and reviewable."
|
||||
|
||||
### Example Full Prompt (RED Phase)
|
||||
|
||||
```markdown
|
||||
<SYSTEM PROMPT>
|
||||
[Contents of .cursor/rules/git_workflow.mdc]
|
||||
[Contents of .cursor/rules/test_workflow.mdc]
|
||||
[Contents of .claude/agents/surgical-test-generator.md]
|
||||
|
||||
<TASK CONTEXT>
|
||||
You are implementing:
|
||||
Task #42.2: Add collection endpoint
|
||||
|
||||
Description:
|
||||
Implement POST /api/metrics endpoint to collect user metrics events
|
||||
|
||||
Acceptance criteria:
|
||||
- POST /api/metrics accepts { userId, eventType, timestamp }
|
||||
- Validates input schema (reject missing/invalid fields)
|
||||
- Persists to database using MetricsSchema from subtask 42.1
|
||||
- Returns 201 on success with created record
|
||||
- Returns 400 on validation errors with details
|
||||
|
||||
Dependencies: Subtask 42.1 (metrics schema) is complete
|
||||
|
||||
<INSTRUCTION>
|
||||
Generate failing tests for this subtask. Follow project conventions:
|
||||
- Test file: src/api/__tests__/metrics.test.js
|
||||
- Framework: vitest (detected from package.json)
|
||||
- Test cases to cover:
|
||||
* POST /api/metrics with valid payload → should return 201 (will fail: endpoint not implemented)
|
||||
* POST /api/metrics with missing userId → should return 400 (will fail: validation not implemented)
|
||||
* POST /api/metrics with invalid timestamp → should return 400 (will fail: validation not implemented)
|
||||
* POST /api/metrics should persist to database → should save record (will fail: persistence not implemented)
|
||||
|
||||
Do NOT implement the endpoint code yet. Only create test file(s).
|
||||
Confirm tests fail with messages like "Cannot POST /api/metrics" or "endpoint not defined".
|
||||
|
||||
Output format:
|
||||
1. File path to create: src/api/__tests__/metrics.test.js
|
||||
2. Complete test code
|
||||
3. Command to run: npm test src/api/__tests__/metrics.test.js
|
||||
```
|
||||
|
||||
### Example Full Prompt (GREEN Phase)
|
||||
|
||||
```markdown
|
||||
<SYSTEM PROMPT>
|
||||
[Contents of .cursor/rules/git_workflow.mdc]
|
||||
[Contents of .cursor/rules/test_workflow.mdc]
|
||||
|
||||
<TASK CONTEXT>
|
||||
Task #42.2: Add collection endpoint
|
||||
[same context as RED phase]
|
||||
|
||||
<CURRENT STATE>
|
||||
Tests created in RED phase:
|
||||
- src/api/__tests__/metrics.test.js
|
||||
- 5 tests written, all failing as expected
|
||||
|
||||
Test output:
|
||||
```
|
||||
FAIL src/api/__tests__/metrics.test.js
|
||||
POST /api/metrics
|
||||
✗ should return 201 with valid payload (endpoint not found)
|
||||
✗ should return 400 with missing userId (endpoint not found)
|
||||
✗ should return 400 with invalid timestamp (endpoint not found)
|
||||
✗ should persist to database (endpoint not found)
|
||||
```
|
||||
|
||||
<INSTRUCTION>
|
||||
Implement minimal code to make all tests pass.
|
||||
|
||||
Guidelines:
|
||||
- Create/modify file: src/api/metrics.js
|
||||
- Use existing patterns from src/api/ (e.g., src/api/users.js for reference)
|
||||
- Import MetricsSchema from subtask 42.1 (src/models/schema.js)
|
||||
- Implement validation, persistence, and response handling
|
||||
- Follow project error handling conventions
|
||||
- Keep implementation focused on this subtask only
|
||||
|
||||
After implementation:
|
||||
1. Run tests: npm test src/api/__tests__/metrics.test.js
|
||||
2. Confirm all 5 tests pass
|
||||
3. Report results
|
||||
|
||||
Output format:
|
||||
1. File(s) created/modified
|
||||
2. Implementation code
|
||||
3. Test command and results
|
||||
```
|
||||
|
||||
### Prompt Loading Configuration
|
||||
|
||||
See `.taskmaster/config.json` → `prompts` section for paths and load order.
|
||||
|
||||
## Configuration Schema
|
||||
|
||||
### .taskmaster/config.json
|
||||
|
||||
```json
|
||||
{
|
||||
"autopilot": {
|
||||
"enabled": true,
|
||||
"requireCleanWorkingTree": true,
|
||||
"commitTemplate": "{type}({scope}): {msg}",
|
||||
"defaultCommitType": "feat",
|
||||
"maxGreenAttempts": 3,
|
||||
"testTimeout": 300000
|
||||
},
|
||||
"test": {
|
||||
"runner": "auto",
|
||||
"coverageThresholds": {
|
||||
"lines": 80,
|
||||
"branches": 80,
|
||||
"functions": 80,
|
||||
"statements": 80
|
||||
},
|
||||
"targetedRunPattern": "**/*.test.js"
|
||||
},
|
||||
"git": {
|
||||
"branchPattern": "{tag}/task-{id}-{slug}",
|
||||
"pr": {
|
||||
"enabled": true,
|
||||
"base": "default",
|
||||
"bodyTemplate": ".taskmaster/templates/pr-body.md"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"rulesPath": ".cursor/rules",
|
||||
"testGeneratorPath": ".claude/agents/surgical-test-generator.md",
|
||||
"loadOrder": ["git_workflow.mdc", "test_workflow.mdc"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Fields
|
||||
|
||||
#### autopilot
|
||||
- `enabled` (boolean): Enable/disable autopilot functionality
|
||||
- `requireCleanWorkingTree` (boolean): Require clean git state before starting
|
||||
- `commitTemplate` (string): Template for commit messages (tokens: `{type}`, `{scope}`, `{msg}`)
|
||||
- `defaultCommitType` (string): Default commit type (feat, fix, chore, etc.)
|
||||
- `maxGreenAttempts` (number): Maximum retry attempts to achieve green tests (default: 3)
|
||||
- `testTimeout` (number): Timeout in milliseconds per test run (default: 300000 = 5min)
|
||||
|
||||
#### test
|
||||
- `runner` (string): Test runner detection mode (`"auto"` or explicit command like `"npm test"`)
|
||||
- `coverageThresholds` (object): Minimum coverage percentages required
|
||||
- `lines`, `branches`, `functions`, `statements` (number): Threshold percentages (0-100)
|
||||
- `targetedRunPattern` (string): Glob pattern for targeted subtask test runs
|
||||
|
||||
#### git
|
||||
- `branchPattern` (string): Branch naming pattern (tokens: `{tag}`, `{id}`, `{slug}`)
|
||||
- `pr.enabled` (boolean): Enable automatic PR creation
|
||||
- `pr.base` (string): Target branch for PRs (`"default"` uses repo default, or specify like `"main"`)
|
||||
- `pr.bodyTemplate` (string): Path to PR body template file (optional)
|
||||
|
||||
#### prompts
|
||||
- `rulesPath` (string): Directory containing rule files (e.g., `.cursor/rules`)
|
||||
- `testGeneratorPath` (string): Path to test generator prompt file
|
||||
- `loadOrder` (array): Order to load rule files from `rulesPath`
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# Required for executor
|
||||
ANTHROPIC_API_KEY=sk-ant-... # Claude API key
|
||||
|
||||
# Optional: for PR creation
|
||||
GITHUB_TOKEN=ghp_... # GitHub personal access token
|
||||
|
||||
# Optional: for other executors (future)
|
||||
OPENAI_API_KEY=sk-...
|
||||
GOOGLE_API_KEY=...
|
||||
```
|
||||
|
||||
## Run Artifacts & Observability
|
||||
|
||||
### Per-Run Artifact Structure
|
||||
|
||||
Each autopilot run creates a timestamped directory with complete traceability:
|
||||
|
||||
```
|
||||
.taskmaster/reports/runs/2025-01-15-142033/
|
||||
├── manifest.json # run metadata (task id, start/end time, status)
|
||||
├── log.jsonl # timestamped event stream
|
||||
├── commits.txt # list of commit SHAs made during run
|
||||
├── test-results/
|
||||
│ ├── subtask-42.1-red.json
|
||||
│ ├── subtask-42.1-green.json
|
||||
│ ├── subtask-42.2-red.json
|
||||
│ ├── subtask-42.2-green-attempt1.json
|
||||
│ ├── subtask-42.2-green-attempt2.json
|
||||
│ ├── subtask-42.2-green-attempt3.json
|
||||
│ └── final-suite.json
|
||||
└── pr.md # generated PR body
|
||||
```
|
||||
|
||||
### manifest.json Format
|
||||
|
||||
```json
|
||||
{
|
||||
"runId": "2025-01-15-142033",
|
||||
"taskId": "42",
|
||||
"tag": "analytics",
|
||||
"branch": "analytics/task-42-user-metrics",
|
||||
"startTime": "2025-01-15T14:20:33Z",
|
||||
"endTime": "2025-01-15T14:45:12Z",
|
||||
"status": "completed",
|
||||
"subtasksCompleted": ["42.1", "42.2", "42.3"],
|
||||
"subtasksFailed": [],
|
||||
"totalCommits": 3,
|
||||
"prUrl": "https://github.com/org/repo/pull/123",
|
||||
"finalCoverage": {
|
||||
"lines": 85.3,
|
||||
"branches": 82.1,
|
||||
"functions": 88.9,
|
||||
"statements": 85.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### log.jsonl Format
|
||||
|
||||
Event stream in JSON Lines format for easy parsing and debugging:
|
||||
|
||||
```jsonl
|
||||
{"ts":"2025-01-15T14:20:33Z","phase":"preflight","status":"ok","details":{"testCmd":"npm test","gitClean":true}}
|
||||
{"ts":"2025-01-15T14:20:45Z","phase":"branch","status":"ok","branch":"analytics/task-42-user-metrics"}
|
||||
{"ts":"2025-01-15T14:21:00Z","phase":"red","subtask":"42.1","status":"ok","tests":{"failed":3,"passed":0}}
|
||||
{"ts":"2025-01-15T14:22:15Z","phase":"green","subtask":"42.1","status":"ok","tests":{"passed":3,"failed":0},"attempts":2}
|
||||
{"ts":"2025-01-15T14:22:20Z","phase":"commit","subtask":"42.1","status":"ok","sha":"a1b2c3d","message":"feat(metrics): add metrics schema (task 42.1)"}
|
||||
{"ts":"2025-01-15T14:23:00Z","phase":"red","subtask":"42.2","status":"ok","tests":{"failed":5,"passed":0}}
|
||||
{"ts":"2025-01-15T14:25:30Z","phase":"green","subtask":"42.2","status":"error","tests":{"passed":3,"failed":2},"attempts":3,"error":"Max attempts reached"}
|
||||
{"ts":"2025-01-15T14:25:35Z","phase":"pause","reason":"max_attempts","nextAction":"manual_review"}
|
||||
```
|
||||
|
||||
### Test Results Format
|
||||
|
||||
Each test run stores detailed results:
|
||||
|
||||
```json
|
||||
{
|
||||
"subtask": "42.2",
|
||||
"phase": "green",
|
||||
"attempt": 3,
|
||||
"timestamp": "2025-01-15T14:25:30Z",
|
||||
"command": "npm test src/api/__tests__/metrics.test.js",
|
||||
"exitCode": 1,
|
||||
"duration": 2340,
|
||||
"summary": {
|
||||
"total": 5,
|
||||
"passed": 3,
|
||||
"failed": 2,
|
||||
"skipped": 0
|
||||
},
|
||||
"failures": [
|
||||
{
|
||||
"test": "POST /api/metrics should return 201 with valid payload",
|
||||
"error": "Expected status 201, got 500",
|
||||
"stack": "..."
|
||||
}
|
||||
],
|
||||
"coverage": {
|
||||
"lines": 78.5,
|
||||
"branches": 75.0,
|
||||
"functions": 80.0,
|
||||
"statements": 78.5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Execution Model
|
||||
|
||||
### Orchestration vs Direct Execution
|
||||
|
||||
The autopilot system uses an **orchestration model** rather than direct code execution:
|
||||
|
||||
**Orchestrator Role** (tm-core WorkflowOrchestrator):
|
||||
- Maintains state machine tracking current phase (RED/GREEN/COMMIT) per subtask
|
||||
- Validates preconditions (tests pass, git state clean, etc.)
|
||||
- Returns "work units" describing what needs to be done next
|
||||
- Records completion and advances to next phase
|
||||
- Persists state for resumability
|
||||
|
||||
**Executor Role** (Claude Code/AI session via MCP):
|
||||
- Queries orchestrator for next work unit
|
||||
- Executes the work (generates tests, writes code, runs tests, makes commits)
|
||||
- Reports results back to orchestrator
|
||||
- Handles file operations and tool invocations
|
||||
|
||||
**Why This Approach?**
|
||||
- Leverages existing AI capabilities (Claude Code) rather than duplicating them
|
||||
- MCP protocol provides clean separation between state management and execution
|
||||
- Allows human oversight and intervention at each phase
|
||||
- Simpler to implement: orchestrator is pure state logic, no code generation needed
|
||||
- Enables multiple executor types (Claude Code, other AI tools, human developers)
|
||||
|
||||
**Example Flow**:
|
||||
```typescript
|
||||
// Claude Code (via MCP) queries orchestrator
|
||||
const workUnit = await orchestrator.getNextWorkUnit('42');
|
||||
// => {
|
||||
// phase: 'RED',
|
||||
// subtask: '42.1',
|
||||
// action: 'Generate failing tests for metrics schema',
|
||||
// context: { title, description, dependencies, testFile: 'src/__tests__/schema.test.js' }
|
||||
// }
|
||||
|
||||
// Claude Code executes the work (writes test file, runs tests)
|
||||
// Then reports back
|
||||
await orchestrator.completeWorkUnit('42', '42.1', 'RED', {
|
||||
success: true,
|
||||
testsCreated: ['src/__tests__/schema.test.js'],
|
||||
testsFailed: 3
|
||||
});
|
||||
|
||||
// Query again for next phase
|
||||
const nextWorkUnit = await orchestrator.getNextWorkUnit('42');
|
||||
// => { phase: 'GREEN', subtask: '42.1', action: 'Implement code to pass tests', ... }
|
||||
```
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Why commit per subtask instead of per task?
|
||||
|
||||
**Decision**: Commit after each subtask's green state, not after the entire task.
|
||||
|
||||
**Rationale**:
|
||||
- Atomic commits make code review easier (reviewers can see logical progression)
|
||||
- Easier to revert a single subtask if it causes issues downstream
|
||||
- Matches the TDD loop's natural checkpoint and cognitive boundary
|
||||
- Provides resumability points if the run is interrupted
|
||||
|
||||
**Trade-off**: More commits per task (can use squash-merge in PRs if desired)
|
||||
|
||||
### Why not support parallel subtask execution?
|
||||
|
||||
**Decision**: Sequential subtask execution in Phase 1; parallel execution deferred to Phase 3.
|
||||
|
||||
**Rationale**:
|
||||
- Subtasks often have implicit dependencies (e.g., schema before endpoint, endpoint before UI)
|
||||
- Simpler orchestrator state machine (less complexity = faster to ship)
|
||||
- Parallel execution requires explicit dependency DAG and conflict resolution
|
||||
- Can be added in Phase 3 once core workflow is proven stable
|
||||
|
||||
**Trade-off**: Slower for truly independent subtasks (mitigated by keeping subtasks small and focused)
|
||||
|
||||
### Why require 80% coverage by default?
|
||||
|
||||
**Decision**: Enforce 80% coverage threshold (lines/branches/functions/statements) before allowing commits.
|
||||
|
||||
**Rationale**:
|
||||
- Industry standard baseline for production code quality
|
||||
- Forces test generation to be comprehensive, not superficial
|
||||
- Configurable per project via `.taskmaster/config.json` if too strict
|
||||
- Prevents "green tests" that only test happy paths
|
||||
|
||||
**Trade-off**: May require more test generation iterations; can be lowered per project
|
||||
|
||||
### Why use tmux instead of a rich GUI?
|
||||
|
||||
**Decision**: MVP uses tmux split panes for TUI, not Electron/web-based GUI.
|
||||
|
||||
**Rationale**:
|
||||
- Tmux is universally available on dev machines; no installation burden
|
||||
- Terminal-first workflows match developer mental model (no context switching)
|
||||
- Simpler to implement and maintain; can add GUI later via extensions
|
||||
- State stored in files allows IDE/extension integration without coupling
|
||||
|
||||
**Trade-off**: Less visual polish than GUI; requires tmux familiarity
|
||||
|
||||
### Why not support multiple executors (codex/gemini/claude) in Phase 1?
|
||||
|
||||
**Decision**: Start with Claude executor only; add others in Phase 2+.
|
||||
|
||||
**Rationale**:
|
||||
- Reduces scope and complexity for initial delivery
|
||||
- Claude Code already integrated with existing executor service
|
||||
- Executor abstraction already exists; adding more is straightforward later
|
||||
- Different executors may need different prompt strategies (requires experimentation)
|
||||
|
||||
**Trade-off**: Users locked to Claude initially; can work around with manual executor selection
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
- Model hallucination/large diffs: restrict prompt scope; enforce minimal changes; show diff previews (optional) before commit.
|
||||
|
||||
- Flaky tests: allow retries, isolate targeted runs for speed, then full suite before commit.
|
||||
|
||||
- Environment variability: detect runners/tools; provide fallbacks and actionable errors.
|
||||
|
||||
- PR creation fails: still push and print manual commands; persist PR body to reuse.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1) Slugging rules for branch names; any length limits or normalization beyond {slug} token sanitize?
|
||||
|
||||
2) PR body standard sections beyond run report (e.g., checklist, coverage table)?
|
||||
|
||||
3) Default executor prompt fine-tuning once codex/gemini integration is available.
|
||||
|
||||
4) Where to store persistent TUI state (pane layout, last selection) in .taskmaster/state.json?
|
||||
|
||||
## Branch Naming
|
||||
|
||||
- Include both the tag and the task id in the branch name to make lineage explicit.
|
||||
|
||||
- Default pattern: <tag>/task-<id>[-slug] (e.g., master/task-12, tag-analytics/task-4-user-auth).
|
||||
|
||||
- Configurable via .taskmaster/config.json: git.branchPattern supports tokens {tag}, {id}, {slug}.
|
||||
|
||||
## PR Base Branch
|
||||
|
||||
- Use the repository’s default branch (detected via git) unless overridden.
|
||||
|
||||
- Title format: Task #<id> [<tag>]: <title>.
|
||||
|
||||
## RPG Mapping (Repository Planning Graph)
|
||||
|
||||
Functional nodes (capabilities):
|
||||
|
||||
- Autopilot Orchestration → drives TDD loop and lifecycle
|
||||
|
||||
- Test Generation (Surgical) → produces failing tests from subtask context
|
||||
|
||||
- Test Execution + Coverage → runs suite, enforces thresholds
|
||||
|
||||
- Git/Branch/PR Management → safe operations and PR creation
|
||||
|
||||
- TUI/Terminal Integration → interactive control and visibility via tmux
|
||||
|
||||
- MCP Integration → structured task/status/context operations
|
||||
|
||||
Structural nodes (code organization):
|
||||
|
||||
- packages/tm-core:
|
||||
|
||||
- services/workflow-orchestrator.ts (new)
|
||||
|
||||
- services/test-runner-adapter.ts (new)
|
||||
|
||||
- services/git-adapter.ts (new)
|
||||
|
||||
- existing: task-service.ts, task-execution-service.ts, executors/*
|
||||
|
||||
- apps/cli:
|
||||
|
||||
- src/commands/autopilot.command.ts (new)
|
||||
|
||||
- src/ui/tui/ (new tmux/TUI helpers)
|
||||
|
||||
- scripts/modules:
|
||||
|
||||
- reuse utils/git-utils.js, task-manager/tag-management.js
|
||||
|
||||
- .claude/agents/:
|
||||
|
||||
- surgical-test-generator.md
|
||||
|
||||
Edges (data/control flow):
|
||||
|
||||
- Autopilot → Test Generation → Test Execution → Git Commit → loop
|
||||
|
||||
- Autopilot → Git Adapter (branch, tag, PR)
|
||||
|
||||
- Autopilot → TUI (event stream) → tmux pane control
|
||||
|
||||
- Autopilot → MCP tools for task/status updates
|
||||
|
||||
- Test Execution → Coverage gate → Autopilot decision
|
||||
|
||||
Topological traversal (implementation order):
|
||||
|
||||
1) Git/Test adapters (foundations)
|
||||
|
||||
2) Orchestrator skeleton + events
|
||||
|
||||
3) CLI autopilot command and dry-run
|
||||
|
||||
4) Surgical test-gen integration and execution gate
|
||||
|
||||
5) PR creation, run reports, resumability
|
||||
|
||||
## Phased Roadmap
|
||||
|
||||
- Phase 0: Spike
|
||||
|
||||
- Implement CLI skeleton tm autopilot with dry-run showing planned steps from a real task + subtasks.
|
||||
|
||||
- Detect test runner (package.json) and git state; render a preflight report.
|
||||
|
||||
- Phase 1: Core Rails (State Machine & Orchestration)
|
||||
|
||||
- Implement WorkflowOrchestrator in tm-core as a **state machine** that tracks TDD phases per subtask.
|
||||
|
||||
- Orchestrator **guides** the current AI session (Claude Code/MCP client) rather than executing code itself.
|
||||
|
||||
- Add Git/Test adapters for status checks and validation (not direct execution).
|
||||
|
||||
- WorkflowOrchestrator API:
|
||||
- `getNextWorkUnit(taskId)` → returns next phase to execute (RED/GREEN/COMMIT) with context
|
||||
- `completeWorkUnit(taskId, subtaskId, phase, result)` → records completion and advances state
|
||||
- `getRunState(taskId)` → returns current progress and resumability data
|
||||
|
||||
- MCP integration: expose work unit endpoints so Claude Code can query "what to do next" and report back.
|
||||
|
||||
- Branch/tag mapping via existing tag-management APIs.
|
||||
|
||||
- Run report persisted under .taskmaster/reports/runs/ with state checkpoints for resumability.
|
||||
|
||||
- Phase 2: PR + Resumability
|
||||
|
||||
- Add gh PR creation with well-formed body using the run report.
|
||||
|
||||
- Introduce resumable checkpoints and --resume flag.
|
||||
|
||||
- Add coverage enforcement and optional lint/format step.
|
||||
|
||||
- Phase 3: Extensibility + Guardrails
|
||||
|
||||
- Add support for basic pytest/go test adapters.
|
||||
|
||||
- Add safeguards: diff preview mode, manual confirm gates, aggressive minimal-change prompts.
|
||||
|
||||
- Optional: small TUI panel and extension panel leveraging the same run state file.
|
||||
|
||||
## References (Repo)
|
||||
|
||||
- Test Workflow: .cursor/rules/test_workflow.mdc
|
||||
|
||||
- Git Workflow: .cursor/rules/git_workflow.mdc
|
||||
|
||||
- CLI: apps/cli/src/commands/start.command.ts, apps/cli/src/ui/components/*.ts
|
||||
|
||||
- Core Services: packages/tm-core/src/services/task-service.ts, task-execution-service.ts
|
||||
|
||||
- Executors: packages/tm-core/src/executors/*
|
||||
|
||||
- Git Utilities: scripts/modules/utils/git-utils.js
|
||||
|
||||
- Tag Management: scripts/modules/task-manager/tag-management.js
|
||||
|
||||
- Surgical Test Generator: .claude/agents/surgical-test-generator.md
|
||||
|
||||
130
.taskmaster/docs/tdd-workflow-phase-0-spike.md
Normal file
130
.taskmaster/docs/tdd-workflow-phase-0-spike.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Phase 0: Spike - Autonomous TDD Workflow ✅ COMPLETE
|
||||
|
||||
## Objective
|
||||
Validate feasibility and build foundational understanding before full implementation.
|
||||
|
||||
## Status
|
||||
**COMPLETED** - All deliverables implemented and validated.
|
||||
|
||||
See `apps/cli/src/commands/autopilot.command.ts` for implementation.
|
||||
|
||||
## Scope
|
||||
- Implement CLI skeleton `tm autopilot` with dry-run mode
|
||||
- Show planned steps from a real task with subtasks
|
||||
- Detect test runner from package.json
|
||||
- Detect git state and render preflight report
|
||||
|
||||
## Deliverables
|
||||
|
||||
### 1. CLI Command Skeleton
|
||||
- Create `apps/cli/src/commands/autopilot.command.ts`
|
||||
- Support `tm autopilot <taskId>` command
|
||||
- Implement `--dry-run` flag
|
||||
- Basic help text and usage information
|
||||
|
||||
### 2. Preflight Detection System
|
||||
- Detect test runner from package.json (npm test, pnpm test, etc.)
|
||||
- Check git working tree state (clean/dirty)
|
||||
- Validate required tools are available (git, gh, node/npm)
|
||||
- Detect default branch
|
||||
|
||||
### 3. Dry-Run Execution Plan Display
|
||||
Display planned execution for a task including:
|
||||
- Preflight checks status
|
||||
- Branch name that would be created
|
||||
- Tag that would be set
|
||||
- List of subtasks in execution order
|
||||
- For each subtask:
|
||||
- RED phase: test file that would be created
|
||||
- GREEN phase: implementation files that would be modified
|
||||
- COMMIT: commit message that would be used
|
||||
- Finalization steps: test suite run, coverage check, push, PR creation
|
||||
|
||||
### 4. Task Loading & Validation
|
||||
- Load task from TaskMaster state
|
||||
- Validate task exists and has subtasks
|
||||
- If no subtasks, show message about needing to expand first
|
||||
- Show dependency order for subtasks
|
||||
|
||||
## Example Output
|
||||
|
||||
```bash
|
||||
$ tm autopilot 42 --dry-run
|
||||
|
||||
Autopilot Plan for Task #42 [analytics]: User metrics tracking
|
||||
─────────────────────────────────────────────────────────────
|
||||
|
||||
Preflight Checks:
|
||||
✓ Working tree is clean
|
||||
✓ Test command detected: npm test
|
||||
✓ Tools available: git, gh, node, npm
|
||||
✓ Current branch: main (will create new branch)
|
||||
✓ Task has 3 subtasks ready to execute
|
||||
|
||||
Branch & Tag:
|
||||
→ Will create branch: analytics/task-42-user-metrics
|
||||
→ Will set active tag: analytics
|
||||
|
||||
Execution Plan (3 subtasks):
|
||||
|
||||
1. Subtask 42.1: Add metrics schema
|
||||
RED: Generate tests → src/__tests__/schema.test.js
|
||||
GREEN: Implement code → src/schema.js
|
||||
COMMIT: "feat(metrics): add metrics schema (task 42.1)"
|
||||
|
||||
2. Subtask 42.2: Add collection endpoint [depends on 42.1]
|
||||
RED: Generate tests → src/api/__tests__/metrics.test.js
|
||||
GREEN: Implement code → src/api/metrics.js
|
||||
COMMIT: "feat(metrics): add collection endpoint (task 42.2)"
|
||||
|
||||
3. Subtask 42.3: Add dashboard widget [depends on 42.2]
|
||||
RED: Generate tests → src/components/__tests__/MetricsWidget.test.jsx
|
||||
GREEN: Implement code → src/components/MetricsWidget.jsx
|
||||
COMMIT: "feat(metrics): add dashboard widget (task 42.3)"
|
||||
|
||||
Finalization:
|
||||
→ Run full test suite with coverage (threshold: 80%)
|
||||
→ Push branch to origin (will confirm)
|
||||
→ Create PR targeting main
|
||||
|
||||
Estimated commits: 3
|
||||
Estimated duration: ~20-30 minutes (depends on implementation complexity)
|
||||
|
||||
Run without --dry-run to execute.
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
- Dry-run output is clear and matches expected workflow
|
||||
- Preflight detection works correctly on the project repo
|
||||
- Task loading integrates with existing TaskMaster state
|
||||
- No actual git operations or file modifications occur in dry-run mode
|
||||
|
||||
## Out of Scope
|
||||
- Actual test generation
|
||||
- Actual code implementation
|
||||
- Git operations (branch creation, commits, push)
|
||||
- PR creation
|
||||
- Test execution
|
||||
|
||||
## Implementation Notes
|
||||
- Reuse existing `TaskService` from `packages/tm-core`
|
||||
- Use existing git utilities from `scripts/modules/utils/git-utils.js`
|
||||
- Load task/subtask data from `.taskmaster/tasks/tasks.json`
|
||||
- Detect test command via package.json → scripts.test field
|
||||
|
||||
## Dependencies
|
||||
- Existing TaskMaster CLI structure
|
||||
- Existing task storage format
|
||||
- Git utilities
|
||||
|
||||
## Estimated Effort
|
||||
2-3 days
|
||||
|
||||
## Validation
|
||||
Test dry-run mode with:
|
||||
- Task with 1 subtask
|
||||
- Task with multiple subtasks
|
||||
- Task with dependencies between subtasks
|
||||
- Task without subtasks (should show warning)
|
||||
- Dirty git working tree (should warn)
|
||||
- Missing tools (should error with helpful message)
|
||||
1144
.taskmaster/docs/tdd-workflow-phase-1-core-rails.md
Normal file
1144
.taskmaster/docs/tdd-workflow-phase-1-core-rails.md
Normal file
File diff suppressed because it is too large
Load Diff
369
.taskmaster/docs/tdd-workflow-phase-1-orchestrator.md
Normal file
369
.taskmaster/docs/tdd-workflow-phase-1-orchestrator.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# Phase 1: Core Rails - State Machine & Orchestration
|
||||
|
||||
## Objective
|
||||
Build the WorkflowOrchestrator as a state machine that guides AI sessions through TDD workflow, rather than directly executing code.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Execution Model
|
||||
The orchestrator acts as a **state manager and guide**, not a code executor:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Claude Code (MCP Client) │
|
||||
│ - Queries "what to do next" │
|
||||
│ - Executes work (writes tests, code, runs commands) │
|
||||
│ - Reports completion │
|
||||
└────────────────┬────────────────────────────────────────────┘
|
||||
│ MCP Protocol
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WorkflowOrchestrator (tm-core) │
|
||||
│ - Maintains state machine (RED → GREEN → COMMIT) │
|
||||
│ - Returns work units with context │
|
||||
│ - Validates preconditions │
|
||||
│ - Records progress │
|
||||
│ - Persists state for resumability │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Why This Approach?
|
||||
1. **Separation of Concerns**: State management separate from code execution
|
||||
2. **Leverage Existing Tools**: Uses Claude Code's capabilities instead of reimplementing
|
||||
3. **Human-in-the-Loop**: Easy to inspect state and intervene at any phase
|
||||
4. **Simpler Implementation**: Orchestrator is pure logic, no AI model integration needed
|
||||
5. **Flexible Executors**: Any tool (Claude Code, human, other AI) can execute work units
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. WorkflowOrchestrator Service
|
||||
**Location**: `packages/tm-core/src/services/workflow-orchestrator.service.ts`
|
||||
|
||||
**Responsibilities**:
|
||||
- Track current phase (RED/GREEN/COMMIT) per subtask
|
||||
- Generate work units with context for each phase
|
||||
- Validate phase completion criteria
|
||||
- Advance state machine on successful completion
|
||||
- Handle errors and retry logic
|
||||
- Persist run state for resumability
|
||||
|
||||
**API**:
|
||||
```typescript
|
||||
interface WorkflowOrchestrator {
|
||||
// Start a new autopilot run
|
||||
startRun(taskId: string, options?: RunOptions): Promise<RunContext>;
|
||||
|
||||
// Get next work unit to execute
|
||||
getNextWorkUnit(runId: string): Promise<WorkUnit | null>;
|
||||
|
||||
// Report work unit completion
|
||||
completeWorkUnit(
|
||||
runId: string,
|
||||
workUnitId: string,
|
||||
result: WorkUnitResult
|
||||
): Promise<void>;
|
||||
|
||||
// Get current run state
|
||||
getRunState(runId: string): Promise<RunState>;
|
||||
|
||||
// Pause/resume
|
||||
pauseRun(runId: string): Promise<void>;
|
||||
resumeRun(runId: string): Promise<void>;
|
||||
}
|
||||
|
||||
interface WorkUnit {
|
||||
id: string; // Unique work unit ID
|
||||
phase: 'RED' | 'GREEN' | 'COMMIT';
|
||||
subtaskId: string; // e.g., "42.1"
|
||||
action: string; // Human-readable description
|
||||
context: WorkUnitContext; // All info needed to execute
|
||||
preconditions: Precondition[]; // Checks before execution
|
||||
}
|
||||
|
||||
interface WorkUnitContext {
|
||||
taskId: string;
|
||||
taskTitle: string;
|
||||
subtaskTitle: string;
|
||||
subtaskDescription: string;
|
||||
dependencies: string[]; // Completed subtask IDs
|
||||
testCommand: string; // e.g., "npm test"
|
||||
|
||||
// Phase-specific context
|
||||
redPhase?: {
|
||||
testFile: string; // Where to create test
|
||||
testFramework: string; // e.g., "vitest"
|
||||
acceptanceCriteria: string[];
|
||||
};
|
||||
|
||||
greenPhase?: {
|
||||
testFile: string; // Test to make pass
|
||||
implementationHints: string[];
|
||||
expectedFiles: string[]; // Files likely to modify
|
||||
};
|
||||
|
||||
commitPhase?: {
|
||||
commitMessage: string; // Pre-generated message
|
||||
filesToCommit: string[]; // Files modified in RED+GREEN
|
||||
};
|
||||
}
|
||||
|
||||
interface WorkUnitResult {
|
||||
success: boolean;
|
||||
phase: 'RED' | 'GREEN' | 'COMMIT';
|
||||
|
||||
// RED phase results
|
||||
testsCreated?: string[];
|
||||
testsFailed?: number;
|
||||
|
||||
// GREEN phase results
|
||||
testsPassed?: number;
|
||||
filesModified?: string[];
|
||||
attempts?: number;
|
||||
|
||||
// COMMIT phase results
|
||||
commitSha?: string;
|
||||
|
||||
// Common
|
||||
error?: string;
|
||||
logs?: string;
|
||||
}
|
||||
|
||||
interface RunState {
|
||||
runId: string;
|
||||
taskId: string;
|
||||
status: 'running' | 'paused' | 'completed' | 'failed';
|
||||
currentPhase: 'RED' | 'GREEN' | 'COMMIT';
|
||||
currentSubtask: string;
|
||||
completedSubtasks: string[];
|
||||
failedSubtasks: string[];
|
||||
startTime: Date;
|
||||
lastUpdateTime: Date;
|
||||
|
||||
// Resumability
|
||||
checkpoint: {
|
||||
subtaskId: string;
|
||||
phase: 'RED' | 'GREEN' | 'COMMIT';
|
||||
attemptNumber: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2. State Machine Logic
|
||||
|
||||
**Phase Transitions**:
|
||||
```
|
||||
START → RED(subtask 1) → GREEN(subtask 1) → COMMIT(subtask 1)
|
||||
↓
|
||||
RED(subtask 2) ← ─ ─ ─ ┘
|
||||
↓
|
||||
GREEN(subtask 2)
|
||||
↓
|
||||
COMMIT(subtask 2)
|
||||
↓
|
||||
(repeat for remaining subtasks)
|
||||
↓
|
||||
FINALIZE → END
|
||||
```
|
||||
|
||||
**Phase Rules**:
|
||||
- **RED**: Can only transition to GREEN if tests created and failing
|
||||
- **GREEN**: Can only transition to COMMIT if tests passing (attempt < maxAttempts)
|
||||
- **COMMIT**: Can only transition to next RED if commit successful
|
||||
- **FINALIZE**: Can only start if all subtasks completed
|
||||
|
||||
**Preconditions**:
|
||||
- RED: No uncommitted changes (or staged from previous GREEN that failed)
|
||||
- GREEN: RED phase complete, tests exist and are failing
|
||||
- COMMIT: GREEN phase complete, all tests passing, coverage meets threshold
|
||||
|
||||
### 3. MCP Integration
|
||||
|
||||
**New MCP Tools** (expose WorkflowOrchestrator via MCP):
|
||||
```typescript
|
||||
// Start an autopilot run
|
||||
mcp__task_master_ai__autopilot_start(taskId: string, dryRun?: boolean)
|
||||
|
||||
// Get next work unit
|
||||
mcp__task_master_ai__autopilot_next_work_unit(runId: string)
|
||||
|
||||
// Complete current work unit
|
||||
mcp__task_master_ai__autopilot_complete_work_unit(
|
||||
runId: string,
|
||||
workUnitId: string,
|
||||
result: WorkUnitResult
|
||||
)
|
||||
|
||||
// Get run state
|
||||
mcp__task_master_ai__autopilot_get_state(runId: string)
|
||||
|
||||
// Pause/resume
|
||||
mcp__task_master_ai__autopilot_pause(runId: string)
|
||||
mcp__task_master_ai__autopilot_resume(runId: string)
|
||||
```
|
||||
|
||||
### 4. Git/Test Adapters
|
||||
|
||||
**GitAdapter** (`packages/tm-core/src/services/git-adapter.service.ts`):
|
||||
- Check working tree status
|
||||
- Validate branch state
|
||||
- Read git config (user, remote, default branch)
|
||||
- **Does NOT execute** git commands (that's executor's job)
|
||||
|
||||
**TestAdapter** (`packages/tm-core/src/services/test-adapter.service.ts`):
|
||||
- Detect test framework from package.json
|
||||
- Parse test output (failures, passes, coverage)
|
||||
- Validate coverage thresholds
|
||||
- **Does NOT run** tests (that's executor's job)
|
||||
|
||||
### 5. Run State Persistence
|
||||
|
||||
**Storage Location**: `.taskmaster/reports/runs/<runId>/`
|
||||
|
||||
**Files**:
|
||||
- `state.json` - Current run state (for resumability)
|
||||
- `log.jsonl` - Event stream (timestamped work unit completions)
|
||||
- `manifest.json` - Run metadata
|
||||
- `work-units.json` - All work units generated for this run
|
||||
|
||||
**Example `state.json`**:
|
||||
```json
|
||||
{
|
||||
"runId": "2025-01-15-142033",
|
||||
"taskId": "42",
|
||||
"status": "paused",
|
||||
"currentPhase": "GREEN",
|
||||
"currentSubtask": "42.2",
|
||||
"completedSubtasks": ["42.1"],
|
||||
"failedSubtasks": [],
|
||||
"checkpoint": {
|
||||
"subtaskId": "42.2",
|
||||
"phase": "GREEN",
|
||||
"attemptNumber": 2
|
||||
},
|
||||
"startTime": "2025-01-15T14:20:33Z",
|
||||
"lastUpdateTime": "2025-01-15T14:35:12Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Step 1: WorkflowOrchestrator Skeleton
|
||||
- [ ] Create `workflow-orchestrator.service.ts` with interfaces
|
||||
- [ ] Implement state machine logic (phase transitions)
|
||||
- [ ] Add run state persistence (state.json, log.jsonl)
|
||||
- [ ] Write unit tests for state machine
|
||||
|
||||
### Step 2: Work Unit Generation
|
||||
- [ ] Implement `getNextWorkUnit()` with context assembly
|
||||
- [ ] Generate RED phase work units (test file paths, criteria)
|
||||
- [ ] Generate GREEN phase work units (implementation hints)
|
||||
- [ ] Generate COMMIT phase work units (commit messages)
|
||||
|
||||
### Step 3: Git/Test Adapters
|
||||
- [ ] Create GitAdapter for status checks only
|
||||
- [ ] Create TestAdapter for output parsing only
|
||||
- [ ] Add precondition validation using adapters
|
||||
- [ ] Write adapter unit tests
|
||||
|
||||
### Step 4: MCP Integration
|
||||
- [ ] Add MCP tool definitions in `packages/mcp-server/src/tools/`
|
||||
- [ ] Wire up WorkflowOrchestrator to MCP tools
|
||||
- [ ] Test MCP tools via Claude Code
|
||||
- [ ] Document MCP workflow in CLAUDE.md
|
||||
|
||||
### Step 5: CLI Integration
|
||||
- [ ] Update `autopilot.command.ts` to call WorkflowOrchestrator
|
||||
- [ ] Add `--interactive` mode that shows work units and waits for completion
|
||||
- [ ] Add `--resume` flag to continue paused runs
|
||||
- [ ] Test end-to-end flow
|
||||
|
||||
### Step 6: Integration Testing
|
||||
- [ ] Create test task with 2-3 subtasks
|
||||
- [ ] Run autopilot start → get work unit → complete → repeat
|
||||
- [ ] Verify state persistence and resumability
|
||||
- [ ] Test failure scenarios (test failures, git issues)
|
||||
|
||||
## Success Criteria
|
||||
- [ ] WorkflowOrchestrator can generate work units for all phases
|
||||
- [ ] MCP tools allow Claude Code to query and complete work units
|
||||
- [ ] State persists correctly between work unit completions
|
||||
- [ ] Run can be paused and resumed from checkpoint
|
||||
- [ ] Adapters validate preconditions without executing commands
|
||||
- [ ] End-to-end: Claude Code can complete a simple task via work units
|
||||
|
||||
## Out of Scope (Phase 1)
|
||||
- Actual git operations (branch creation, commits) - executor handles this
|
||||
- Actual test execution - executor handles this
|
||||
- PR creation - deferred to Phase 2
|
||||
- TUI interface - deferred to Phase 3
|
||||
- Coverage enforcement - deferred to Phase 2
|
||||
|
||||
## Example Usage Flow
|
||||
|
||||
```bash
|
||||
# Terminal 1: Claude Code session
|
||||
$ claude
|
||||
|
||||
# In Claude Code (via MCP):
|
||||
> Start autopilot for task 42
|
||||
[Calls mcp__task_master_ai__autopilot_start(42)]
|
||||
→ Run started: run-2025-01-15-142033
|
||||
|
||||
> Get next work unit
|
||||
[Calls mcp__task_master_ai__autopilot_next_work_unit(run-2025-01-15-142033)]
|
||||
→ Work unit: RED phase for subtask 42.1
|
||||
→ Action: Generate failing tests for metrics schema
|
||||
→ Test file: src/__tests__/schema.test.js
|
||||
→ Framework: vitest
|
||||
|
||||
> [Claude Code creates test file, runs tests]
|
||||
|
||||
> Complete work unit
|
||||
[Calls mcp__task_master_ai__autopilot_complete_work_unit(
|
||||
run-2025-01-15-142033,
|
||||
workUnit-42.1-RED,
|
||||
{ success: true, testsCreated: ['src/__tests__/schema.test.js'], testsFailed: 3 }
|
||||
)]
|
||||
→ Work unit completed. State saved.
|
||||
|
||||
> Get next work unit
|
||||
[Calls mcp__task_master_ai__autopilot_next_work_unit(run-2025-01-15-142033)]
|
||||
→ Work unit: GREEN phase for subtask 42.1
|
||||
→ Action: Implement code to pass failing tests
|
||||
→ Test file: src/__tests__/schema.test.js
|
||||
→ Expected implementation: src/schema.js
|
||||
|
||||
> [Claude Code implements schema.js, runs tests, confirms all pass]
|
||||
|
||||
> Complete work unit
|
||||
[...]
|
||||
→ Work unit completed. Ready for COMMIT.
|
||||
|
||||
> Get next work unit
|
||||
[...]
|
||||
→ Work unit: COMMIT phase for subtask 42.1
|
||||
→ Commit message: "feat(metrics): add metrics schema (task 42.1)"
|
||||
→ Files to commit: src/__tests__/schema.test.js, src/schema.js
|
||||
|
||||
> [Claude Code stages files and commits]
|
||||
|
||||
> Complete work unit
|
||||
[...]
|
||||
→ Subtask 42.1 complete! Moving to 42.2...
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- Existing TaskService (task loading, status updates)
|
||||
- Existing PreflightChecker (environment validation)
|
||||
- Existing TaskLoaderService (dependency ordering)
|
||||
- MCP server infrastructure
|
||||
|
||||
## Estimated Effort
|
||||
7-10 days
|
||||
|
||||
## Next Phase
|
||||
Phase 2 will add:
|
||||
- PR creation via gh CLI
|
||||
- Coverage enforcement
|
||||
- Enhanced error recovery
|
||||
- Full resumability testing
|
||||
433
.taskmaster/docs/tdd-workflow-phase-2-pr-resumability.md
Normal file
433
.taskmaster/docs/tdd-workflow-phase-2-pr-resumability.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Phase 2: PR + Resumability - Autonomous TDD Workflow
|
||||
|
||||
## Objective
|
||||
Add PR creation with GitHub CLI integration, resumable checkpoints for interrupted runs, and enhanced guardrails with coverage enforcement.
|
||||
|
||||
## Scope
|
||||
- GitHub PR creation via `gh` CLI
|
||||
- Well-formed PR body using run report
|
||||
- Resumable checkpoints and `--resume` flag
|
||||
- Coverage enforcement before finalization
|
||||
- Optional lint/format step
|
||||
- Enhanced error recovery
|
||||
|
||||
## Deliverables
|
||||
|
||||
### 1. PR Creation Integration
|
||||
|
||||
**PRAdapter** (`packages/tm-core/src/services/pr-adapter.ts`):
|
||||
```typescript
|
||||
class PRAdapter {
|
||||
async isGHAvailable(): Promise<boolean>
|
||||
async createPR(options: PROptions): Promise<PRResult>
|
||||
async getPRTemplate(runReport: RunReport): Promise<string>
|
||||
|
||||
// Fallback for missing gh CLI
|
||||
async getManualPRInstructions(options: PROptions): Promise<string>
|
||||
}
|
||||
|
||||
interface PROptions {
|
||||
branch: string
|
||||
base: string
|
||||
title: string
|
||||
body: string
|
||||
draft?: boolean
|
||||
}
|
||||
|
||||
interface PRResult {
|
||||
url: string
|
||||
number: number
|
||||
}
|
||||
```
|
||||
|
||||
**PR Title Format:**
|
||||
```
|
||||
Task #<id> [<tag>]: <title>
|
||||
```
|
||||
|
||||
Example: `Task #42 [analytics]: User metrics tracking`
|
||||
|
||||
**PR Body Template:**
|
||||
|
||||
Located at `.taskmaster/templates/pr-body.md`:
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
|
||||
Implements Task #42 from TaskMaster autonomous workflow.
|
||||
|
||||
**Branch:** {branch}
|
||||
**Tag:** {tag}
|
||||
**Subtasks completed:** {subtaskCount}
|
||||
|
||||
{taskDescription}
|
||||
|
||||
## Subtasks
|
||||
|
||||
{subtasksList}
|
||||
|
||||
## Test Coverage
|
||||
|
||||
| Metric | Coverage |
|
||||
|--------|----------|
|
||||
| Lines | {lines}% |
|
||||
| Branches | {branches}% |
|
||||
| Functions | {functions}% |
|
||||
| Statements | {statements}% |
|
||||
|
||||
**All subtasks passed with {totalTests} tests.**
|
||||
|
||||
## Commits
|
||||
|
||||
{commitsList}
|
||||
|
||||
## Run Report
|
||||
|
||||
Full execution report: `.taskmaster/reports/runs/{runId}/`
|
||||
|
||||
---
|
||||
|
||||
🤖 Generated with [Task Master](https://github.com/cline/task-master) autonomous TDD workflow
|
||||
```
|
||||
|
||||
**Token replacement:**
|
||||
- `{branch}` → branch name
|
||||
- `{tag}` → active tag
|
||||
- `{subtaskCount}` → number of completed subtasks
|
||||
- `{taskDescription}` → task description from TaskMaster
|
||||
- `{subtasksList}` → markdown list of subtask titles
|
||||
- `{lines}`, `{branches}`, `{functions}`, `{statements}` → coverage percentages
|
||||
- `{totalTests}` → total test count
|
||||
- `{commitsList}` → markdown list of commit SHAs and messages
|
||||
- `{runId}` → run ID timestamp
|
||||
|
||||
### 2. GitHub CLI Integration
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
which gh
|
||||
```
|
||||
|
||||
If not found, show fallback instructions:
|
||||
```bash
|
||||
✓ Branch pushed: analytics/task-42-user-metrics
|
||||
✗ gh CLI not found - cannot create PR automatically
|
||||
|
||||
To create PR manually:
|
||||
gh pr create \
|
||||
--base main \
|
||||
--head analytics/task-42-user-metrics \
|
||||
--title "Task #42 [analytics]: User metrics tracking" \
|
||||
--body-file .taskmaster/reports/runs/2025-01-15-142033/pr.md
|
||||
|
||||
Or visit:
|
||||
https://github.com/org/repo/compare/main...analytics/task-42-user-metrics
|
||||
```
|
||||
|
||||
**Confirmation gate:**
|
||||
```bash
|
||||
Ready to create PR:
|
||||
Title: Task #42 [analytics]: User metrics tracking
|
||||
Base: main
|
||||
Head: analytics/task-42-user-metrics
|
||||
|
||||
Create PR? [Y/n]
|
||||
```
|
||||
|
||||
Unless `--no-confirm` flag is set.
|
||||
|
||||
### 3. Resumable Workflow
|
||||
|
||||
**State Checkpoint** (`state.json`):
|
||||
```json
|
||||
{
|
||||
"runId": "2025-01-15-142033",
|
||||
"taskId": "42",
|
||||
"phase": "subtask-loop",
|
||||
"currentSubtask": "42.2",
|
||||
"currentPhase": "green",
|
||||
"attempts": 2,
|
||||
"completedSubtasks": ["42.1"],
|
||||
"commits": ["a1b2c3d"],
|
||||
"branch": "analytics/task-42-user-metrics",
|
||||
"tag": "analytics",
|
||||
"canResume": true,
|
||||
"pausedAt": "2025-01-15T14:25:35Z",
|
||||
"pausedReason": "max_attempts_reached",
|
||||
"nextAction": "manual_review_required"
|
||||
}
|
||||
```
|
||||
|
||||
**Resume Command:**
|
||||
```bash
|
||||
$ tm autopilot --resume
|
||||
|
||||
Resuming run: 2025-01-15-142033
|
||||
Task: #42 [analytics] User metrics tracking
|
||||
Branch: analytics/task-42-user-metrics
|
||||
Last subtask: 42.2 (GREEN phase, attempt 2/3 failed)
|
||||
Paused: 5 minutes ago
|
||||
|
||||
Reason: Could not achieve green state after 3 attempts
|
||||
Last error: POST /api/metrics returns 500 instead of 201
|
||||
|
||||
Resume from subtask 42.2 GREEN phase? [Y/n]
|
||||
```
|
||||
|
||||
**Resume logic:**
|
||||
1. Load state from `.taskmaster/reports/runs/<runId>/state.json`
|
||||
2. Verify branch still exists and is checked out
|
||||
3. Verify no uncommitted changes (unless `--force`)
|
||||
4. Continue from last checkpoint phase
|
||||
5. Update state file as execution progresses
|
||||
|
||||
**Multiple interrupted runs:**
|
||||
```bash
|
||||
$ tm autopilot --resume
|
||||
|
||||
Found 2 resumable runs:
|
||||
1. 2025-01-15-142033 - Task #42 (paused 5 min ago at subtask 42.2 GREEN)
|
||||
2. 2025-01-14-103022 - Task #38 (paused 2 hours ago at subtask 38.3 RED)
|
||||
|
||||
Select run to resume [1-2]:
|
||||
```
|
||||
|
||||
### 4. Coverage Enforcement
|
||||
|
||||
**Coverage Check Phase** (before finalization):
|
||||
```typescript
|
||||
async function enforceCoverage(runId: string): Promise<void> {
|
||||
const testResults = await testRunner.runAll()
|
||||
const coverage = await testRunner.getCoverage()
|
||||
|
||||
const thresholds = config.test.coverageThresholds
|
||||
const failures = []
|
||||
|
||||
if (coverage.lines < thresholds.lines) {
|
||||
failures.push(`Lines: ${coverage.lines}% < ${thresholds.lines}%`)
|
||||
}
|
||||
// ... check branches, functions, statements
|
||||
|
||||
if (failures.length > 0) {
|
||||
throw new CoverageError(
|
||||
`Coverage thresholds not met:\n${failures.join('\n')}`
|
||||
)
|
||||
}
|
||||
|
||||
// Store coverage in run report
|
||||
await storeRunArtifact(runId, 'coverage.json', coverage)
|
||||
}
|
||||
```
|
||||
|
||||
**Handling coverage failures:**
|
||||
```bash
|
||||
⚠️ Coverage check failed:
|
||||
Lines: 78.5% < 80%
|
||||
Branches: 75.0% < 80%
|
||||
|
||||
Options:
|
||||
1. Add more tests and resume
|
||||
2. Lower thresholds in .taskmaster/config.json
|
||||
3. Skip coverage check: tm autopilot --resume --skip-coverage
|
||||
|
||||
Run paused. Fix coverage and resume with:
|
||||
tm autopilot --resume
|
||||
```
|
||||
|
||||
### 5. Optional Lint/Format Step
|
||||
|
||||
**Configuration:**
|
||||
```json
|
||||
{
|
||||
"autopilot": {
|
||||
"finalization": {
|
||||
"lint": {
|
||||
"enabled": true,
|
||||
"command": "npm run lint",
|
||||
"fix": true,
|
||||
"failOnError": false
|
||||
},
|
||||
"format": {
|
||||
"enabled": true,
|
||||
"command": "npm run format",
|
||||
"commitChanges": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution:**
|
||||
```bash
|
||||
Finalization Steps:
|
||||
|
||||
✓ All tests passing (12 tests, 0 failures)
|
||||
✓ Coverage thresholds met (85% lines, 82% branches)
|
||||
|
||||
LINT Running linter... ⏳
|
||||
LINT ✓ No lint errors
|
||||
|
||||
FORMAT Running formatter... ⏳
|
||||
FORMAT ✓ Formatted 3 files
|
||||
FORMAT ✓ Committed formatting changes: "chore: auto-format code"
|
||||
|
||||
PUSH Pushing to origin... ⏳
|
||||
PUSH ✓ Pushed analytics/task-42-user-metrics
|
||||
|
||||
PR Creating pull request... ⏳
|
||||
PR ✓ Created PR #123
|
||||
https://github.com/org/repo/pull/123
|
||||
```
|
||||
|
||||
### 6. Enhanced Error Recovery
|
||||
|
||||
**Pause Points:**
|
||||
- Max GREEN attempts reached (current)
|
||||
- Coverage check failed (new)
|
||||
- Lint errors (if `failOnError: true`)
|
||||
- Git push failed (new)
|
||||
- PR creation failed (new)
|
||||
|
||||
**Each pause saves:**
|
||||
- Full state checkpoint
|
||||
- Last command output
|
||||
- Suggested next actions
|
||||
- Resume instructions
|
||||
|
||||
**Automatic recovery attempts:**
|
||||
- Git push: retry up to 3 times with backoff
|
||||
- PR creation: fall back to manual instructions
|
||||
- Lint: auto-fix if enabled, otherwise pause
|
||||
|
||||
### 7. Finalization Phase Enhancement
|
||||
|
||||
**Updated workflow:**
|
||||
1. Run full test suite
|
||||
2. Check coverage thresholds → pause if failed
|
||||
3. Run lint (if enabled) → pause if failed and `failOnError: true`
|
||||
4. Run format (if enabled) → auto-commit changes
|
||||
5. Confirm push (unless `--no-confirm`)
|
||||
6. Push branch → retry on failure
|
||||
7. Generate PR body from template
|
||||
8. Create PR via gh → fall back to manual instructions
|
||||
9. Update task status to 'review' (configurable)
|
||||
10. Save final run report
|
||||
|
||||
**Final output:**
|
||||
```bash
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
✅ Task #42 [analytics]: User metrics tracking - COMPLETE
|
||||
|
||||
Branch: analytics/task-42-user-metrics
|
||||
Subtasks completed: 3/3
|
||||
Commits: 3
|
||||
Total tests: 12 (12 passed, 0 failed)
|
||||
Coverage: 85% lines, 82% branches, 88% functions, 85% statements
|
||||
|
||||
PR #123: https://github.com/org/repo/pull/123
|
||||
|
||||
Run report: .taskmaster/reports/runs/2025-01-15-142033/
|
||||
|
||||
Next steps:
|
||||
- Review PR and request changes if needed
|
||||
- Merge when ready
|
||||
- Task status updated to 'review'
|
||||
|
||||
Completed in 24 minutes
|
||||
```
|
||||
|
||||
## CLI Updates
|
||||
|
||||
**New flags:**
|
||||
- `--resume` → Resume from last checkpoint
|
||||
- `--skip-coverage` → Skip coverage checks
|
||||
- `--skip-lint` → Skip lint step
|
||||
- `--skip-format` → Skip format step
|
||||
- `--skip-pr` → Push branch but don't create PR
|
||||
- `--draft-pr` → Create draft PR instead of ready-for-review
|
||||
|
||||
## Configuration Updates
|
||||
|
||||
**Add to `.taskmaster/config.json`:**
|
||||
```json
|
||||
{
|
||||
"autopilot": {
|
||||
"finalization": {
|
||||
"lint": {
|
||||
"enabled": false,
|
||||
"command": "npm run lint",
|
||||
"fix": true,
|
||||
"failOnError": false
|
||||
},
|
||||
"format": {
|
||||
"enabled": false,
|
||||
"command": "npm run format",
|
||||
"commitChanges": true
|
||||
},
|
||||
"updateTaskStatus": "review"
|
||||
}
|
||||
},
|
||||
"git": {
|
||||
"pr": {
|
||||
"enabled": true,
|
||||
"base": "default",
|
||||
"bodyTemplate": ".taskmaster/templates/pr-body.md",
|
||||
"draft": false
|
||||
},
|
||||
"pushRetries": 3,
|
||||
"pushRetryDelay": 5000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
- Can create PR automatically with well-formed body
|
||||
- Can resume interrupted runs from any checkpoint
|
||||
- Coverage checks prevent low-quality code from being merged
|
||||
- Clear error messages and recovery paths for all failure modes
|
||||
- Run reports include full PR context for review
|
||||
|
||||
## Out of Scope (defer to Phase 3)
|
||||
- Multiple test framework support (pytest, go test)
|
||||
- Diff preview before commits
|
||||
- TUI panel implementation
|
||||
- Extension/IDE integration
|
||||
|
||||
## Testing Strategy
|
||||
- Mock `gh` CLI for PR creation tests
|
||||
- Test resume from each possible pause point
|
||||
- Test coverage failure scenarios
|
||||
- Test lint/format integration with mock commands
|
||||
- End-to-end test with PR creation on test repo
|
||||
|
||||
## Dependencies
|
||||
- Phase 1 completed (core workflow)
|
||||
- GitHub CLI (`gh`) installed (optional, fallback provided)
|
||||
- Test framework supports coverage output
|
||||
|
||||
## Estimated Effort
|
||||
1-2 weeks
|
||||
|
||||
## Risks & Mitigations
|
||||
- **Risk:** GitHub CLI auth issues
|
||||
- **Mitigation:** Clear auth setup docs, fallback to manual instructions
|
||||
|
||||
- **Risk:** PR body template doesn't match all project needs
|
||||
- **Mitigation:** Make template customizable via config path
|
||||
|
||||
- **Risk:** Resume state gets corrupted
|
||||
- **Mitigation:** Validate state on load, provide --force-reset option
|
||||
|
||||
- **Risk:** Coverage calculation differs between runs
|
||||
- **Mitigation:** Store coverage with each test run for comparison
|
||||
|
||||
## Validation
|
||||
Test with:
|
||||
- Successful PR creation end-to-end
|
||||
- Resume from GREEN attempt failure
|
||||
- Resume from coverage failure
|
||||
- Resume from lint failure
|
||||
- Missing `gh` CLI (fallback to manual)
|
||||
- Lint/format integration enabled
|
||||
- Multiple interrupted runs (selection UI)
|
||||
@@ -0,0 +1,534 @@
|
||||
# Phase 3: Extensibility + Guardrails - Autonomous TDD Workflow
|
||||
|
||||
## Objective
|
||||
Add multi-language/framework support, enhanced safety guardrails, TUI interface, and extensibility for IDE/editor integration.
|
||||
|
||||
## Scope
|
||||
- Multi-language test runner support (pytest, go test, etc.)
|
||||
- Enhanced safety: diff preview, confirmation gates, minimal-change prompts
|
||||
- Optional TUI panel with tmux integration
|
||||
- State-based extension API for IDE integration
|
||||
- Parallel subtask execution (experimental)
|
||||
|
||||
## Deliverables
|
||||
|
||||
### 1. Multi-Language Test Runner Support
|
||||
|
||||
**Extend TestRunnerAdapter:**
|
||||
```typescript
|
||||
class TestRunnerAdapter {
|
||||
// Existing methods...
|
||||
|
||||
async detectLanguage(): Promise<Language>
|
||||
async detectFramework(language: Language): Promise<Framework>
|
||||
async getFrameworkAdapter(framework: Framework): Promise<FrameworkAdapter>
|
||||
}
|
||||
|
||||
enum Language {
|
||||
JavaScript = 'javascript',
|
||||
TypeScript = 'typescript',
|
||||
Python = 'python',
|
||||
Go = 'go',
|
||||
Rust = 'rust'
|
||||
}
|
||||
|
||||
enum Framework {
|
||||
Vitest = 'vitest',
|
||||
Jest = 'jest',
|
||||
Pytest = 'pytest',
|
||||
GoTest = 'gotest',
|
||||
CargoTest = 'cargotest'
|
||||
}
|
||||
|
||||
interface FrameworkAdapter {
|
||||
runTargeted(pattern: string): Promise<TestResults>
|
||||
runAll(): Promise<TestResults>
|
||||
parseCoverage(output: string): Promise<CoverageReport>
|
||||
getTestFilePattern(): string
|
||||
getTestFileExtension(): string
|
||||
}
|
||||
```
|
||||
|
||||
**Framework-specific adapters:**
|
||||
|
||||
**PytestAdapter** (`packages/tm-core/src/services/test-adapters/pytest-adapter.ts`):
|
||||
```typescript
|
||||
class PytestAdapter implements FrameworkAdapter {
|
||||
async runTargeted(pattern: string): Promise<TestResults> {
|
||||
const output = await exec(`pytest ${pattern} --json-report`)
|
||||
return this.parseResults(output)
|
||||
}
|
||||
|
||||
async runAll(): Promise<TestResults> {
|
||||
const output = await exec('pytest --cov --json-report')
|
||||
return this.parseResults(output)
|
||||
}
|
||||
|
||||
parseCoverage(output: string): Promise<CoverageReport> {
|
||||
// Parse pytest-cov XML output
|
||||
}
|
||||
|
||||
getTestFilePattern(): string {
|
||||
return '**/test_*.py'
|
||||
}
|
||||
|
||||
getTestFileExtension(): string {
|
||||
return '.py'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GoTestAdapter** (`packages/tm-core/src/services/test-adapters/gotest-adapter.ts`):
|
||||
```typescript
|
||||
class GoTestAdapter implements FrameworkAdapter {
|
||||
async runTargeted(pattern: string): Promise<TestResults> {
|
||||
const output = await exec(`go test ${pattern} -json`)
|
||||
return this.parseResults(output)
|
||||
}
|
||||
|
||||
async runAll(): Promise<TestResults> {
|
||||
const output = await exec('go test ./... -coverprofile=coverage.out -json')
|
||||
return this.parseResults(output)
|
||||
}
|
||||
|
||||
parseCoverage(output: string): Promise<CoverageReport> {
|
||||
// Parse go test coverage output
|
||||
}
|
||||
|
||||
getTestFilePattern(): string {
|
||||
return '**/*_test.go'
|
||||
}
|
||||
|
||||
getTestFileExtension(): string {
|
||||
return '_test.go'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Detection Logic:**
|
||||
```typescript
|
||||
async function detectFramework(): Promise<Framework> {
|
||||
// Check for package.json
|
||||
if (await exists('package.json')) {
|
||||
const pkg = await readJSON('package.json')
|
||||
if (pkg.devDependencies?.vitest) return Framework.Vitest
|
||||
if (pkg.devDependencies?.jest) return Framework.Jest
|
||||
}
|
||||
|
||||
// Check for Python files
|
||||
if (await exists('pytest.ini') || await exists('setup.py')) {
|
||||
return Framework.Pytest
|
||||
}
|
||||
|
||||
// Check for Go files
|
||||
if (await exists('go.mod')) {
|
||||
return Framework.GoTest
|
||||
}
|
||||
|
||||
// Check for Rust files
|
||||
if (await exists('Cargo.toml')) {
|
||||
return Framework.CargoTest
|
||||
}
|
||||
|
||||
throw new Error('Could not detect test framework')
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Enhanced Safety Guardrails
|
||||
|
||||
**Diff Preview Mode:**
|
||||
```bash
|
||||
$ tm autopilot 42 --preview-diffs
|
||||
|
||||
[2/3] Subtask 42.2: Add collection endpoint
|
||||
|
||||
RED ✓ Tests created: src/api/__tests__/metrics.test.js
|
||||
|
||||
GREEN Implementing code...
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Proposed changes (src/api/metrics.js):
|
||||
|
||||
+ import { MetricsSchema } from '../models/schema.js'
|
||||
+
|
||||
+ export async function createMetric(data) {
|
||||
+ const validated = MetricsSchema.parse(data)
|
||||
+ const result = await db.metrics.create(validated)
|
||||
+ return result
|
||||
+ }
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Apply these changes? [Y/n/e(dit)/s(kip)]
|
||||
Y - Apply and continue
|
||||
n - Reject and retry GREEN phase
|
||||
e - Open in editor for manual changes
|
||||
s - Skip this subtask
|
||||
```
|
||||
|
||||
**Minimal Change Enforcement:**
|
||||
|
||||
Add to system prompt:
|
||||
```markdown
|
||||
CRITICAL: Make MINIMAL changes to pass the failing tests.
|
||||
- Only modify files directly related to the subtask
|
||||
- Do not refactor existing code unless absolutely necessary
|
||||
- Do not add features beyond the acceptance criteria
|
||||
- Keep changes under 50 lines per file when possible
|
||||
- Prefer composition over modification
|
||||
```
|
||||
|
||||
**Change Size Warnings:**
|
||||
```bash
|
||||
⚠️ Large change detected:
|
||||
Files modified: 5
|
||||
Lines changed: +234, -12
|
||||
|
||||
This subtask was expected to be small (~50 lines).
|
||||
Consider:
|
||||
- Breaking into smaller subtasks
|
||||
- Reviewing acceptance criteria
|
||||
- Checking for unintended changes
|
||||
|
||||
Continue anyway? [y/N]
|
||||
```
|
||||
|
||||
### 3. TUI Interface with tmux
|
||||
|
||||
**Layout:**
|
||||
```
|
||||
┌──────────────────────────────────┬─────────────────────────────────┐
|
||||
│ Task Navigator (left) │ Executor Terminal (right) │
|
||||
│ │ │
|
||||
│ Project: my-app │ $ tm autopilot --executor-mode │
|
||||
│ Branch: analytics/task-42 │ > Running subtask 42.2 GREEN... │
|
||||
│ Tag: analytics │ > Implementing endpoint... │
|
||||
│ │ > Tests: 3 passed, 0 failed │
|
||||
│ Tasks: │ > Ready to commit │
|
||||
│ → 42 [in-progress] User metrics │ │
|
||||
│ → 42.1 [done] Schema │ [Live output from executor] │
|
||||
│ → 42.2 [active] Endpoint ◀ │ │
|
||||
│ → 42.3 [pending] Dashboard │ │
|
||||
│ │ │
|
||||
│ [s] start [p] pause [q] quit │ │
|
||||
└──────────────────────────────────┴─────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
|
||||
**TUI Navigator** (`apps/cli/src/ui/tui/navigator.ts`):
|
||||
```typescript
|
||||
import blessed from 'blessed'
|
||||
|
||||
class AutopilotTUI {
|
||||
private screen: blessed.Widgets.Screen
|
||||
private taskList: blessed.Widgets.ListElement
|
||||
private statusBox: blessed.Widgets.BoxElement
|
||||
private executorPane: string // tmux pane ID
|
||||
|
||||
async start(taskId?: string) {
|
||||
// Create blessed screen
|
||||
this.screen = blessed.screen()
|
||||
|
||||
// Create task list widget
|
||||
this.taskList = blessed.list({
|
||||
label: 'Tasks',
|
||||
keys: true,
|
||||
vi: true,
|
||||
style: { selected: { bg: 'blue' } }
|
||||
})
|
||||
|
||||
// Spawn tmux pane for executor
|
||||
this.executorPane = await this.spawnExecutorPane()
|
||||
|
||||
// Watch state file for updates
|
||||
this.watchStateFile()
|
||||
|
||||
// Handle keybindings
|
||||
this.setupKeybindings()
|
||||
}
|
||||
|
||||
private async spawnExecutorPane(): Promise<string> {
|
||||
const paneId = await exec('tmux split-window -h -P -F "#{pane_id}"')
|
||||
await exec(`tmux send-keys -t ${paneId} "tm autopilot --executor-mode" Enter`)
|
||||
return paneId.trim()
|
||||
}
|
||||
|
||||
private watchStateFile() {
|
||||
watch('.taskmaster/state/current-run.json', (event, filename) => {
|
||||
this.updateDisplay()
|
||||
})
|
||||
}
|
||||
|
||||
private setupKeybindings() {
|
||||
this.screen.key(['s'], () => this.startTask())
|
||||
this.screen.key(['p'], () => this.pauseTask())
|
||||
this.screen.key(['q'], () => this.quit())
|
||||
this.screen.key(['up', 'down'], () => this.navigateTasks())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Executor Mode:**
|
||||
```bash
|
||||
$ tm autopilot 42 --executor-mode
|
||||
|
||||
# Runs in executor pane, writes state to shared file
|
||||
# Left pane reads state file and updates display
|
||||
```
|
||||
|
||||
**State File** (`.taskmaster/state/current-run.json`):
|
||||
```json
|
||||
{
|
||||
"runId": "2025-01-15-142033",
|
||||
"taskId": "42",
|
||||
"status": "running",
|
||||
"currentPhase": "green",
|
||||
"currentSubtask": "42.2",
|
||||
"lastOutput": "Implementing endpoint...",
|
||||
"testsStatus": {
|
||||
"passed": 3,
|
||||
"failed": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Extension API for IDE Integration
|
||||
|
||||
**State-based API:**
|
||||
|
||||
Expose run state via JSON files that IDEs can read:
|
||||
- `.taskmaster/state/current-run.json` - live run state
|
||||
- `.taskmaster/reports/runs/<runId>/manifest.json` - run metadata
|
||||
- `.taskmaster/reports/runs/<runId>/log.jsonl` - event stream
|
||||
|
||||
**WebSocket API (optional):**
|
||||
```typescript
|
||||
// packages/tm-core/src/services/autopilot-server.ts
|
||||
class AutopilotServer {
|
||||
private wss: WebSocketServer
|
||||
|
||||
start(port: number = 7890) {
|
||||
this.wss = new WebSocketServer({ port })
|
||||
|
||||
this.wss.on('connection', (ws) => {
|
||||
// Send current state
|
||||
ws.send(JSON.stringify(this.getCurrentState()))
|
||||
|
||||
// Stream events
|
||||
this.orchestrator.on('*', (event) => {
|
||||
ws.send(JSON.stringify(event))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage from IDE extension:**
|
||||
```typescript
|
||||
// VS Code extension example
|
||||
const ws = new WebSocket('ws://localhost:7890')
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const event = JSON.parse(data)
|
||||
|
||||
if (event.type === 'subtask:complete') {
|
||||
vscode.window.showInformationMessage(
|
||||
`Subtask ${event.subtaskId} completed`
|
||||
)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 5. Parallel Subtask Execution (Experimental)
|
||||
|
||||
**Dependency Analysis:**
|
||||
```typescript
|
||||
class SubtaskScheduler {
|
||||
async buildDependencyGraph(subtasks: Subtask[]): Promise<DAG> {
|
||||
const graph = new DAG()
|
||||
|
||||
for (const subtask of subtasks) {
|
||||
graph.addNode(subtask.id)
|
||||
|
||||
for (const depId of subtask.dependencies) {
|
||||
graph.addEdge(depId, subtask.id)
|
||||
}
|
||||
}
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
async getParallelBatches(graph: DAG): Promise<Subtask[][]> {
|
||||
const batches: Subtask[][] = []
|
||||
const completed = new Set<string>()
|
||||
|
||||
while (completed.size < graph.size()) {
|
||||
const ready = graph.nodes.filter(node =>
|
||||
!completed.has(node.id) &&
|
||||
node.dependencies.every(dep => completed.has(dep))
|
||||
)
|
||||
|
||||
batches.push(ready)
|
||||
ready.forEach(node => completed.add(node.id))
|
||||
}
|
||||
|
||||
return batches
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Parallel Execution:**
|
||||
```bash
|
||||
$ tm autopilot 42 --parallel
|
||||
|
||||
[Batch 1] Running 2 subtasks in parallel:
|
||||
→ 42.1: Add metrics schema
|
||||
→ 42.4: Add API documentation
|
||||
|
||||
42.1 RED ✓ Tests created
|
||||
42.4 RED ✓ Tests created
|
||||
|
||||
42.1 GREEN ✓ Implementation complete
|
||||
42.4 GREEN ✓ Implementation complete
|
||||
|
||||
42.1 COMMIT ✓ Committed: a1b2c3d
|
||||
42.4 COMMIT ✓ Committed: e5f6g7h
|
||||
|
||||
[Batch 2] Running 2 subtasks in parallel (depend on 42.1):
|
||||
→ 42.2: Add collection endpoint
|
||||
→ 42.3: Add dashboard widget
|
||||
...
|
||||
```
|
||||
|
||||
**Conflict Detection:**
|
||||
```typescript
|
||||
async function detectConflicts(subtasks: Subtask[]): Promise<Conflict[]> {
|
||||
const conflicts: Conflict[] = []
|
||||
|
||||
for (let i = 0; i < subtasks.length; i++) {
|
||||
for (let j = i + 1; j < subtasks.length; j++) {
|
||||
const filesA = await predictAffectedFiles(subtasks[i])
|
||||
const filesB = await predictAffectedFiles(subtasks[j])
|
||||
|
||||
const overlap = filesA.filter(f => filesB.includes(f))
|
||||
|
||||
if (overlap.length > 0) {
|
||||
conflicts.push({
|
||||
subtasks: [subtasks[i].id, subtasks[j].id],
|
||||
files: overlap
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return conflicts
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Advanced Configuration
|
||||
|
||||
**Add to `.taskmaster/config.json`:**
|
||||
```json
|
||||
{
|
||||
"autopilot": {
|
||||
"safety": {
|
||||
"previewDiffs": false,
|
||||
"maxChangeLinesPerFile": 100,
|
||||
"warnOnLargeChanges": true,
|
||||
"requireConfirmOnLargeChanges": true
|
||||
},
|
||||
"parallel": {
|
||||
"enabled": false,
|
||||
"maxConcurrent": 3,
|
||||
"detectConflicts": true
|
||||
},
|
||||
"tui": {
|
||||
"enabled": false,
|
||||
"tmuxSession": "taskmaster-autopilot"
|
||||
},
|
||||
"api": {
|
||||
"enabled": false,
|
||||
"port": 7890,
|
||||
"allowRemote": false
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"frameworks": {
|
||||
"python": {
|
||||
"runner": "pytest",
|
||||
"coverageCommand": "pytest --cov",
|
||||
"testPattern": "**/test_*.py"
|
||||
},
|
||||
"go": {
|
||||
"runner": "go test",
|
||||
"coverageCommand": "go test ./... -coverprofile=coverage.out",
|
||||
"testPattern": "**/*_test.go"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CLI Updates
|
||||
|
||||
**New commands:**
|
||||
```bash
|
||||
tm autopilot <taskId> --tui # Launch TUI interface
|
||||
tm autopilot <taskId> --parallel # Enable parallel execution
|
||||
tm autopilot <taskId> --preview-diffs # Show diffs before applying
|
||||
tm autopilot <taskId> --executor-mode # Run as executor pane
|
||||
tm autopilot-server start # Start WebSocket API
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
- Supports Python projects with pytest
|
||||
- Supports Go projects with go test
|
||||
- Diff preview prevents unwanted changes
|
||||
- TUI provides better visibility for long-running tasks
|
||||
- IDE extensions can integrate via state files or WebSocket
|
||||
- Parallel execution reduces total time for independent subtasks
|
||||
|
||||
## Out of Scope
|
||||
- Full Electron/web GUI
|
||||
- AI executor selection UI (defer to Phase 4)
|
||||
- Multi-repository support
|
||||
- Remote execution on cloud runners
|
||||
|
||||
## Testing Strategy
|
||||
- Test with Python project (pytest)
|
||||
- Test with Go project (go test)
|
||||
- Test diff preview UI with mock changes
|
||||
- Test parallel execution with independent subtasks
|
||||
- Test conflict detection with overlapping file changes
|
||||
- Test TUI with mock tmux environment
|
||||
|
||||
## Dependencies
|
||||
- Phase 2 completed (PR + resumability)
|
||||
- tmux installed (for TUI)
|
||||
- blessed or ink library (for TUI rendering)
|
||||
|
||||
## Estimated Effort
|
||||
3-4 weeks
|
||||
|
||||
## Risks & Mitigations
|
||||
- **Risk:** Parallel execution causes git conflicts
|
||||
- **Mitigation:** Conservative conflict detection, sequential fallback
|
||||
|
||||
- **Risk:** TUI adds complexity and maintenance burden
|
||||
- **Mitigation:** Keep TUI optional, state-based design allows alternatives
|
||||
|
||||
- **Risk:** Framework adapters hard to maintain across versions
|
||||
- **Mitigation:** Abstract common parsing logic, document adapter interface
|
||||
|
||||
- **Risk:** Diff preview slows down workflow
|
||||
- **Mitigation:** Make optional, use --preview-diffs flag only when needed
|
||||
|
||||
## Validation
|
||||
Test with:
|
||||
- Python project with pytest and pytest-cov
|
||||
- Go project with go test
|
||||
- Large changes requiring confirmation
|
||||
- Parallel execution with 3+ independent subtasks
|
||||
- TUI with task selection and live status updates
|
||||
- VS Code extension reading state files
|
||||
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"meta": {
|
||||
"generatedAt": "2025-10-07T09:46:06.248Z",
|
||||
"tasksAnalyzed": 23,
|
||||
"totalTasks": 23,
|
||||
"analysisCount": 23,
|
||||
"thresholdScore": 5,
|
||||
"projectName": "Taskmaster",
|
||||
"usedResearch": false
|
||||
},
|
||||
"complexityAnalysis": [
|
||||
{
|
||||
"taskId": 31,
|
||||
"taskTitle": "Create WorkflowOrchestrator service foundation",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Break down the WorkflowOrchestrator foundation into its core architectural components: phase management system, event emitter infrastructure, state management interfaces, service integration, and lifecycle control methods. Each subtask should focus on a specific architectural concern with clear interfaces and testable units.",
|
||||
"reasoning": "This is a foundational service requiring state machine implementation, event-driven architecture, and integration with existing services. The complexity is high due to the need for robust phase management, error handling, and service orchestration patterns."
|
||||
},
|
||||
{
|
||||
"taskId": 32,
|
||||
"taskTitle": "Implement GitAdapter for repository operations",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Decompose the GitAdapter implementation into: TypeScript wrapper creation around existing git-utils.js, core git operation methods with comprehensive error handling, branch naming pattern system with token replacement, and confirmation gates for destructive operations. Focus on type safety and existing code integration.",
|
||||
"reasoning": "Moderate-high complexity due to TypeScript integration over existing JavaScript utilities, branch pattern implementation, and safety mechanisms. The existing git-utils.js provides a solid foundation, reducing complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 33,
|
||||
"taskTitle": "Create TestRunnerAdapter for framework detection and execution",
|
||||
"complexityScore": 8,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Break down TestRunnerAdapter into framework detection logic, test execution engine with process management, Jest-specific result parsing, Vitest-specific result parsing, unified result interfaces, and final integration. Each framework parser should be separate to handle their unique output formats.",
|
||||
"reasoning": "High complexity due to multiple framework support (Jest, Vitest), child process management, result parsing from different formats, coverage reporting, and timeout handling. Each framework has unique output formats requiring specialized parsers."
|
||||
},
|
||||
{
|
||||
"taskId": 34,
|
||||
"taskTitle": "Implement autopilot CLI command structure",
|
||||
"complexityScore": 5,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Structure the autopilot command into: basic command setup with Commander.js integration, comprehensive flag handling and validation system, preflight check validation with environment validation, and WorkflowOrchestrator integration with dry-run execution planning. Follow existing CLI patterns from the codebase.",
|
||||
"reasoning": "Moderate complexity involving CLI structure, flag handling, and integration with WorkflowOrchestrator. The existing CLI patterns and Commander.js usage in the codebase provide good guidance, reducing implementation complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 35,
|
||||
"taskTitle": "Integrate surgical test generator with WorkflowOrchestrator",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Decompose the test generation integration into: TaskExecutionService enhancement for test generation mode, TestGenerationService creation using executor framework, prompt composition system for rule integration, and framework-specific test pattern support. Leverage existing executor patterns from the codebase.",
|
||||
"reasoning": "Moderate-high complexity due to integration with existing services, prompt composition system, and framework-specific test generation. The existing executor framework and TaskExecutionService provide good integration points."
|
||||
},
|
||||
{
|
||||
"taskId": 36,
|
||||
"taskTitle": "Implement subtask TDD loop execution",
|
||||
"complexityScore": 9,
|
||||
"recommendedSubtasks": 7,
|
||||
"expansionPrompt": "Break down the TDD loop into: SubtaskExecutor class architecture, RED phase test generation, GREEN phase code generation, COMMIT phase with conventional commits, retry mechanism for GREEN phase, timeout and backoff policies, and TaskService integration. Each phase should be independently testable.",
|
||||
"reasoning": "Very high complexity due to implementing the complete TDD red-green-commit cycle with AI integration, retry logic, timeout handling, and git operations. This is the core autonomous workflow requiring robust error handling and state management."
|
||||
},
|
||||
{
|
||||
"taskId": 37,
|
||||
"taskTitle": "Add configuration schema for autopilot settings",
|
||||
"complexityScore": 4,
|
||||
"recommendedSubtasks": 3,
|
||||
"expansionPrompt": "Expand configuration support into: extending configuration interfaces with autopilot settings, updating ConfigManager validation logic, and implementing default configuration values. Build on existing configuration patterns and maintain backward compatibility.",
|
||||
"reasoning": "Low-moderate complexity involving schema extension and validation logic. The existing configuration system provides clear patterns to follow, making this primarily an extension task rather than new architecture."
|
||||
},
|
||||
{
|
||||
"taskId": 38,
|
||||
"taskTitle": "Implement run state persistence and logging",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Structure run state management into: RunStateManager service class creation, run directory structure and manifest creation, JSONL event logging system, test result and commit tracking storage, and state checkpointing with resume functionality. Focus on data integrity and structured logging.",
|
||||
"reasoning": "Moderate-high complexity due to file system operations, structured logging, state serialization, and resume functionality. Requires careful design of data formats and error handling for persistence operations."
|
||||
},
|
||||
{
|
||||
"taskId": 39,
|
||||
"taskTitle": "Add GitHub PR creation with run reports",
|
||||
"complexityScore": 5,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Decompose PR creation into: PRAdapter service foundation with interfaces, GitHub CLI integration and command execution, PR body generation from run data and test results, and custom PR template system with configuration support. Leverage existing git-utils.js patterns for CLI integration.",
|
||||
"reasoning": "Moderate complexity involving GitHub CLI integration, report generation, and template systems. The existing git-utils.js provides patterns for CLI tool integration, reducing implementation complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 40,
|
||||
"taskTitle": "Implement task dependency resolution for subtask ordering",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Break down dependency resolution into: dependency resolution algorithm with cycle detection, topological sorting for subtask ordering, task eligibility checking system, and TaskService integration. Implement graph algorithms for dependency management with proper error handling.",
|
||||
"reasoning": "Moderate-high complexity due to graph algorithm implementation, cycle detection, and integration with existing task management. Requires careful design of dependency resolution logic and edge case handling."
|
||||
},
|
||||
{
|
||||
"taskId": 41,
|
||||
"taskTitle": "Create resume functionality for interrupted runs",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Structure resume functionality into: checkpoint creation in RunStateManager, state restoration logic with validation, state validation for safe resume operations, CLI flag implementation for resume command, and partial phase resume functionality. Focus on data integrity and workflow consistency.",
|
||||
"reasoning": "High complexity due to state serialization/deserialization, workflow restoration, validation logic, and CLI integration. Requires robust error handling and state consistency checks for reliable resume operations."
|
||||
},
|
||||
{
|
||||
"taskId": 42,
|
||||
"taskTitle": "Add coverage threshold enforcement",
|
||||
"complexityScore": 5,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Decompose coverage enforcement into: coverage report parsing from Jest/Vitest, configurable threshold validation logic, coverage gates integration in workflow phases, and detailed coverage failure reporting system. Build on existing TestRunnerAdapter patterns.",
|
||||
"reasoning": "Moderate complexity involving coverage report parsing, validation logic, and workflow integration. The existing TestRunnerAdapter provides good foundation for extending coverage capabilities."
|
||||
},
|
||||
{
|
||||
"taskId": 43,
|
||||
"taskTitle": "Implement tmux-based TUI navigator",
|
||||
"complexityScore": 8,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Break down TUI implementation into: framework selection and basic structure setup, left pane interface layout with status indicators, tmux integration and terminal coordination, navigation system with keybindings, real-time status updates system, and comprehensive event handling with UX polish. Each component should be independently testable.",
|
||||
"reasoning": "High complexity due to terminal UI framework integration, tmux session management, real-time updates, keyboard event handling, and terminal interface design. Requires expertise in terminal UI libraries and tmux integration."
|
||||
},
|
||||
{
|
||||
"taskId": 44,
|
||||
"taskTitle": "Add prompt composition system for context-aware test generation",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Structure prompt composition into: PromptComposer service foundation, template processing engine with token replacement, rule loading system with precedence handling, and context injection with phase-specific prompt generation. Focus on flexible template system and rule management.",
|
||||
"reasoning": "Moderate-high complexity due to template processing, rule precedence systems, and context injection logic. Requires careful design of template syntax and rule loading mechanisms."
|
||||
},
|
||||
{
|
||||
"taskId": 45,
|
||||
"taskTitle": "Implement tag-branch mapping and automatic tag switching",
|
||||
"complexityScore": 5,
|
||||
"recommendedSubtasks": 3,
|
||||
"expansionPrompt": "Decompose tag-branch mapping into: GitAdapter enhancement with branch-to-tag extraction logic, automatic tag switching workflow integration, and branch-to-tag mapping persistence with validation. Build on existing git-utils.js and tag management functionality.",
|
||||
"reasoning": "Moderate complexity involving pattern matching, tag management integration, and workflow automation. The existing git-utils.js and tag management systems provide good foundation for implementation."
|
||||
},
|
||||
{
|
||||
"taskId": 46,
|
||||
"taskTitle": "Add comprehensive error handling and recovery",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Structure error handling into: error classification system with specific error types, recovery suggestion engine with actionable recommendations, error context management and preservation, force flag implementation with selective bypass, and logging/reporting system integration. Focus on actionable error messages and automated recovery where possible.",
|
||||
"reasoning": "High complexity due to comprehensive error taxonomy, recovery automation, context preservation, and integration across all workflow components. Requires deep understanding of failure modes and recovery strategies."
|
||||
},
|
||||
{
|
||||
"taskId": 47,
|
||||
"taskTitle": "Implement conventional commit message generation",
|
||||
"complexityScore": 4,
|
||||
"recommendedSubtasks": 3,
|
||||
"expansionPrompt": "Break down commit message generation into: template system creation with variable substitution, commit type auto-detection based on task content and file changes, and validation with GitAdapter integration. Follow conventional commit standards and integrate with existing git operations.",
|
||||
"reasoning": "Low-moderate complexity involving template processing, pattern matching for commit type detection, and validation logic. Well-defined conventional commit standards provide clear implementation guidance."
|
||||
},
|
||||
{
|
||||
"taskId": 48,
|
||||
"taskTitle": "Add multi-framework test execution support",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Expand test framework support into: framework detection system for multiple languages, common adapter interface design, Python pytest adapter implementation, Go and Rust adapter implementations, and integration with existing TestRunnerAdapter. Each language adapter should follow the unified interface pattern.",
|
||||
"reasoning": "High complexity due to multi-language support, framework detection across different ecosystems, and adapter pattern implementation. Each language has unique testing conventions and output formats."
|
||||
},
|
||||
{
|
||||
"taskId": 49,
|
||||
"taskTitle": "Implement workflow event streaming for real-time monitoring",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Structure event streaming into: WorkflowOrchestrator EventEmitter enhancement, structured event format with metadata, event persistence to run logs, and optional WebSocket streaming for external monitoring. Focus on event consistency and real-time delivery.",
|
||||
"reasoning": "Moderate-high complexity due to event-driven architecture, structured event formats, persistence integration, and WebSocket implementation. Requires careful design of event schemas and delivery mechanisms."
|
||||
},
|
||||
{
|
||||
"taskId": 50,
|
||||
"taskTitle": "Add intelligent test targeting for faster feedback",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Decompose test targeting into: file change detection system, test dependency analysis engine, framework-specific targeting adapters, test impact calculation algorithm, and fallback integration with TestRunnerAdapter. Focus on accuracy and performance optimization.",
|
||||
"reasoning": "High complexity due to dependency analysis, impact calculation algorithms, framework-specific targeting, and integration with existing test execution. Requires sophisticated analysis of code relationships and test dependencies."
|
||||
},
|
||||
{
|
||||
"taskId": 51,
|
||||
"taskTitle": "Implement dry-run visualization with execution timeline",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Structure dry-run visualization into: timeline calculation engine with duration estimates, estimation algorithms based on task complexity, ASCII art progress visualization with formatting, and resource validation with preflight checks. Focus on accurate planning and clear visual presentation.",
|
||||
"reasoning": "Moderate-high complexity due to timeline calculation, estimation algorithms, ASCII visualization, and resource validation. Requires understanding of workflow timing and visual formatting for terminal output."
|
||||
},
|
||||
{
|
||||
"taskId": 52,
|
||||
"taskTitle": "Add autopilot workflow integration tests",
|
||||
"complexityScore": 8,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Structure integration testing into: isolated test environment infrastructure, mock integrations and service stubs, end-to-end workflow test scenarios, performance benchmarking and resource monitoring, test isolation and parallelization strategies, and comprehensive result validation and reporting. Focus on realistic test scenarios and reliable automation.",
|
||||
"reasoning": "High complexity due to end-to-end testing requirements, mock service integration, performance testing, isolation mechanisms, and comprehensive validation. Requires sophisticated test infrastructure and scenario design."
|
||||
},
|
||||
{
|
||||
"taskId": 53,
|
||||
"taskTitle": "Finalize autopilot documentation and examples",
|
||||
"complexityScore": 3,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Structure documentation into: comprehensive autopilot documentation covering setup and usage, example PRD files and templates for different project types, troubleshooting guide for common issues and solutions, and demo materials with workflow visualization. Focus on clarity and practical examples.",
|
||||
"reasoning": "Low complexity involving documentation writing, example creation, and demo material production. The main challenge is ensuring accuracy and completeness rather than technical implementation."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"meta": {
|
||||
"generatedAt": "2025-10-09T12:47:27.960Z",
|
||||
"tasksAnalyzed": 10,
|
||||
"totalTasks": 10,
|
||||
"analysisCount": 10,
|
||||
"thresholdScore": 5,
|
||||
"projectName": "Taskmaster",
|
||||
"usedResearch": false
|
||||
},
|
||||
"complexityAnalysis": [
|
||||
{
|
||||
"taskId": 1,
|
||||
"taskTitle": "Design and Implement Global Storage System",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Break down the global storage system implementation into: 1) Path normalization utilities with cross-platform support, 2) Run ID generation and validation, 3) Manifest.json structure and management, 4) Activity.jsonl append-only logging, 5) State.json mutable checkpoint handling, and 6) Directory structure creation and cleanup. Focus on robust error handling, atomic operations, and isolation between different runs.",
|
||||
"reasoning": "Complex system requiring cross-platform path handling, multiple file formats (JSON/JSONL), atomic operations, and state management. The existing codebase shows sophisticated file operations infrastructure but this extends beyond current patterns. Implementation involves filesystem operations, concurrency concerns, and data integrity."
|
||||
},
|
||||
{
|
||||
"taskId": 2,
|
||||
"taskTitle": "Build GitAdapter with Safety Checks",
|
||||
"complexityScore": 8,
|
||||
"recommendedSubtasks": 7,
|
||||
"expansionPrompt": "Decompose GitAdapter into: 1) Git repository detection and validation, 2) Working tree status checking with detailed reporting, 3) Branch operations (create, checkout, list) with safety guards, 4) Commit operations with metadata embedding, 5) Default branch detection and protection logic, 6) Push operations with conflict handling, and 7) Branch name generation from patterns. Emphasize safety checks, confirmation gates, and comprehensive error messages.",
|
||||
"reasoning": "High complexity due to git operations safety requirements, multiple git commands integration, error handling for various git states, and safety mechanisms. The PRD emphasizes never allowing commits on default branch and requiring clean working tree - critical safety features that need robust implementation."
|
||||
},
|
||||
{
|
||||
"taskId": 3,
|
||||
"taskTitle": "Implement Test Result Validator",
|
||||
"complexityScore": 5,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Split test validation into: 1) Input validation and schema definition for test results, 2) RED phase validation logic (ensuring failures exist), 3) GREEN phase validation logic (ensuring all tests pass), and 4) Coverage threshold validation with configurable limits. Include comprehensive validation messages and suggestions for common failure scenarios.",
|
||||
"reasoning": "Moderate complexity focused on business logic validation. The validator is framework-agnostic (only validates reported numbers), has clear validation rules, and well-defined input/output. The existing codebase shows validation patterns that can be leveraged."
|
||||
},
|
||||
{
|
||||
"taskId": 4,
|
||||
"taskTitle": "Develop WorkflowOrchestrator State Machine",
|
||||
"complexityScore": 9,
|
||||
"recommendedSubtasks": 8,
|
||||
"expansionPrompt": "Structure the orchestrator into: 1) State machine definition and transitions (Preflight → BranchSetup → SubtaskLoop → Finalize), 2) Event emission system with comprehensive event types, 3) State persistence and recovery mechanisms, 4) Phase coordination and validation, 5) Subtask iteration and progress tracking, 6) Error handling and recovery strategies, 7) Resume functionality from checkpoints, and 8) Integration points for Git, Test, and other adapters.",
|
||||
"reasoning": "Very high complexity as the central coordination component. Must orchestrate multiple adapters, handle state transitions, event emission, persistence, and recovery. The state machine needs to be robust, resumable, and coordinate all other components. Critical for the entire workflow's reliability."
|
||||
},
|
||||
{
|
||||
"taskId": 5,
|
||||
"taskTitle": "Create Enhanced Commit Message Generator",
|
||||
"complexityScore": 4,
|
||||
"recommendedSubtasks": 3,
|
||||
"expansionPrompt": "Organize commit message generation into: 1) Template parsing and variable substitution with configurable templates, 2) Scope detection from changed files with intelligent categorization, and 3) Metadata embedding (task context, test results, coverage) with conventional commits compliance. Ensure messages are parseable and contain all required task metadata.",
|
||||
"reasoning": "Relatively straightforward text processing and template system. The conventional commits format is well-defined, and the metadata requirements are clear. The existing package.json shows commander dependency for CLI patterns that can be leveraged."
|
||||
},
|
||||
{
|
||||
"taskId": 6,
|
||||
"taskTitle": "Implement Subtask TDD Loop",
|
||||
"complexityScore": 8,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Break down the TDD loop into: 1) RED phase orchestration with test generation coordination, 2) GREEN phase orchestration with implementation guidance, 3) COMMIT phase with file staging and commit creation, 4) Attempt tracking and maximum retry logic, 5) Phase transition validation and state updates, and 6) Activity logging for all phase transitions. Focus on robust state management and clear error recovery paths.",
|
||||
"reasoning": "High complexity due to coordinating multiple phases, state transitions, retry logic, and integration with multiple adapters (Git, Test, State). This is the core workflow execution engine requiring careful orchestration and error handling."
|
||||
},
|
||||
{
|
||||
"taskId": 7,
|
||||
"taskTitle": "Build CLI Commands for AI Agent Orchestration",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Structure CLI commands into: 1) Command registration and argument parsing setup, 2) `start` and `resume` commands with initialization logic, 3) `next` and `status` commands with JSON output formatting, 4) `complete` command with result validation integration, and 5) `commit` and `abort` commands with git operation coordination. Ensure consistent JSON output for machine parsing and comprehensive error handling.",
|
||||
"reasoning": "Moderate complexity leveraging existing CLI infrastructure. The codebase shows commander usage patterns and CLI structure. Main complexity is in JSON output formatting, argument validation, and integration with the orchestrator component."
|
||||
},
|
||||
{
|
||||
"taskId": 8,
|
||||
"taskTitle": "Develop MCP Tools for AI Agent Integration",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Organize MCP tools into: 1) Tool schema definition and parameter validation, 2) `autopilot_start` and `autopilot_resume` tool implementation, 3) `autopilot_next` and `autopilot_status` tools with context provision, 4) `autopilot_complete_phase` tool with validation integration, and 5) `autopilot_commit` tool with git operations. Ensure parity with CLI functionality and proper error handling.",
|
||||
"reasoning": "Moderate complexity building on existing MCP infrastructure. The codebase shows extensive MCP tooling patterns. Main work is adapting CLI functionality to MCP interface patterns and ensuring consistent behavior between CLI and MCP interfaces."
|
||||
},
|
||||
{
|
||||
"taskId": 9,
|
||||
"taskTitle": "Write AI Agent Integration Documentation and Templates",
|
||||
"complexityScore": 2,
|
||||
"recommendedSubtasks": 2,
|
||||
"expansionPrompt": "Structure documentation into: 1) Comprehensive workflow documentation with step-by-step examples, command usage, and integration patterns, and 2) Template creation for CLAUDE.md integration, example prompts, and troubleshooting guides. Focus on clear examples and practical integration guidance.",
|
||||
"reasoning": "Low complexity documentation task. Requires understanding of the implemented system but primarily involves writing clear instructions and examples. The existing codebase shows good documentation patterns that can be followed."
|
||||
},
|
||||
{
|
||||
"taskId": 10,
|
||||
"taskTitle": "Implement Configuration System and Project Hygiene",
|
||||
"complexityScore": 5,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Structure configuration into: 1) Configuration schema definition with comprehensive validation using ajv, 2) Default configuration setup and loading mechanisms, 3) Gitignore management and project directory hygiene rules, and 4) Configuration validation and error reporting. Ensure configurations are validated on startup and provide clear error messages for invalid settings.",
|
||||
"reasoning": "Moderate complexity involving schema validation, file operations, and configuration management. The package.json shows ajv dependency is available. Configuration systems require careful validation and user-friendly error reporting, but follow established patterns."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"meta": {
|
||||
"generatedAt": "2025-10-07T14:16:40.283Z",
|
||||
"tasksAnalyzed": 10,
|
||||
"totalTasks": 10,
|
||||
"analysisCount": 10,
|
||||
"thresholdScore": 5,
|
||||
"projectName": "Taskmaster",
|
||||
"usedResearch": false
|
||||
},
|
||||
"complexityAnalysis": [
|
||||
{
|
||||
"taskId": 1,
|
||||
"taskTitle": "Create autopilot command CLI skeleton",
|
||||
"complexityScore": 4,
|
||||
"recommendedSubtasks": 3,
|
||||
"expansionPrompt": "Break down the autopilot command creation into: 1) Create AutopilotCommand class extending Commander.Command with proper argument parsing and options, 2) Implement command structure with help text and validation following existing patterns, 3) Add basic registration method and placeholder action handler",
|
||||
"reasoning": "Medium complexity due to following established patterns in the codebase. The command-registry.ts and start.command.ts provide clear templates for implementation. Main complexity is argument parsing and option validation."
|
||||
},
|
||||
{
|
||||
"taskId": 2,
|
||||
"taskTitle": "Implement preflight detection system",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Create PreflightChecker with these subtasks: 1) Package.json test script detection and validation, 2) Git working tree status checking using system commands, 3) Tool availability validation (git, gh, node/npm), 4) Default branch detection via git commands, 5) Structured result reporting with success/failure indicators and error messages",
|
||||
"reasoning": "High complexity due to system integration requirements. Needs to interact with multiple external tools (git, npm, gh), parse various file formats, and handle different system configurations. Error handling for missing tools adds complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 3,
|
||||
"taskTitle": "Implement task loading and validation",
|
||||
"complexityScore": 5,
|
||||
"recommendedSubtasks": 3,
|
||||
"expansionPrompt": "Implement task loading: 1) Use existing TaskService from @tm/core to load tasks by ID with proper error handling, 2) Validate task structure including subtask existence and dependency validation, 3) Provide user-friendly error messages for missing tasks or need to expand subtasks first",
|
||||
"reasoning": "Medium-high complexity. While leveraging existing TaskService reduces implementation effort, the validation logic for subtasks and dependencies requires careful handling of edge cases. Task structure validation adds complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 4,
|
||||
"taskTitle": "Create execution plan display logic",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Build ExecutionPlanDisplay: 1) Create display formatter using boxen and chalk for consistent CLI styling, 2) Format preflight check results with color-coded status indicators, 3) Display subtask execution order with RED/GREEN/COMMIT phase visualization, 4) Show branch/tag info and finalization steps with duration estimates",
|
||||
"reasoning": "Moderate-high complexity due to complex formatting requirements and dependency on multiple other components. The display needs to coordinate information from preflight, task validation, and execution planning. CLI styling consistency adds complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 5,
|
||||
"taskTitle": "Implement branch and tag planning",
|
||||
"complexityScore": 3,
|
||||
"recommendedSubtasks": 2,
|
||||
"expansionPrompt": "Create BranchPlanner: 1) Implement branch name generation using pattern <tag>/task-<id>-<slug> with kebab-case conversion and special character handling, 2) Add TaskMaster config integration to determine active tag and handle existing branch conflicts",
|
||||
"reasoning": "Low-medium complexity. String manipulation and naming convention implementation is straightforward. The main complexity is handling edge cases with special characters and existing branch conflicts."
|
||||
},
|
||||
{
|
||||
"taskId": 6,
|
||||
"taskTitle": "Create subtask execution order calculation",
|
||||
"complexityScore": 8,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Implement dependency resolution: 1) Build dependency graph from subtask data with proper parsing, 2) Implement topological sort algorithm for execution order, 3) Add circular dependency detection with clear error reporting, 4) Create parallel execution grouping for independent subtasks",
|
||||
"reasoning": "High complexity due to graph algorithms and dependency resolution. Topological sorting, circular dependency detection, and parallel grouping require algorithmic sophistication. Edge cases in dependency chains add significant complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 7,
|
||||
"taskTitle": "Implement TDD phase planning for subtasks",
|
||||
"complexityScore": 6,
|
||||
"recommendedSubtasks": 4,
|
||||
"expansionPrompt": "Create TDDPhasePlanner: 1) Implement test file path detection for common project structures (src/, tests/, __tests__), 2) Parse implementation files from subtask details and descriptions, 3) Generate conventional commit messages for RED/GREEN/COMMIT phases, 4) Add implementation complexity estimation based on subtask content",
|
||||
"reasoning": "Moderate-high complexity due to project structure detection and file path inference. Conventional commit message generation and complexity estimation require understanding of different project layouts and parsing subtask content effectively."
|
||||
},
|
||||
{
|
||||
"taskId": 8,
|
||||
"taskTitle": "Add finalization steps planning",
|
||||
"complexityScore": 4,
|
||||
"recommendedSubtasks": 3,
|
||||
"expansionPrompt": "Create FinalizationPlanner: 1) Implement test suite execution planning with coverage threshold detection from package.json, 2) Add git operations planning (branch push, PR creation) using existing git patterns, 3) Create duration estimation algorithm based on subtask count and complexity metrics",
|
||||
"reasoning": "Medium complexity. Building on existing git utilities and test command detection reduces complexity. Main challenges are coverage threshold parsing and duration estimation algorithms."
|
||||
},
|
||||
{
|
||||
"taskId": 9,
|
||||
"taskTitle": "Integrate command with existing CLI infrastructure",
|
||||
"complexityScore": 3,
|
||||
"recommendedSubtasks": 2,
|
||||
"expansionPrompt": "Complete CLI integration: 1) Add AutopilotCommand to command-registry.ts following existing patterns and update command metadata, 2) Test command registration and help system integration with proper cleanup and error handling",
|
||||
"reasoning": "Low-medium complexity. The command-registry.ts provides a clear pattern to follow. Main work is registration and ensuring proper integration with existing CLI infrastructure. Well-established patterns reduce complexity."
|
||||
},
|
||||
{
|
||||
"taskId": 10,
|
||||
"taskTitle": "Add comprehensive error handling and edge cases",
|
||||
"complexityScore": 7,
|
||||
"recommendedSubtasks": 5,
|
||||
"expansionPrompt": "Implement error handling: 1) Add missing task and invalid task structure error handling with helpful messages, 2) Handle git state errors (dirty working tree, missing tools), 3) Add dependency validation errors (circular, invalid references), 4) Implement missing tool detection with installation guidance, 5) Create user-friendly error messages following existing CLI patterns",
|
||||
"reasoning": "High complexity due to comprehensive error scenarios. Each component (preflight, task loading, dependency resolution) has multiple failure modes that need proper handling. Providing helpful error messages and recovery suggestions adds complexity."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"currentTag": "master",
|
||||
"lastSwitched": "2025-09-12T22:25:27.535Z",
|
||||
"currentTag": "tdd-phase-1-core-rails",
|
||||
"lastSwitched": "2025-10-09T12:41:40.367Z",
|
||||
"branchTagMapping": {
|
||||
"v017-adds": "v017-adds",
|
||||
"next": "next"
|
||||
|
||||
File diff suppressed because one or more lines are too long
511
.taskmaster/templates/example_prd_rpg.txt
Normal file
511
.taskmaster/templates/example_prd_rpg.txt
Normal 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>
|
||||
436
CHANGELOG.md
436
CHANGELOG.md
@@ -1,5 +1,441 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.30.0-rc.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1317](https://github.com/eyaltoledano/claude-task-master/pull/1317) [`548beb4`](https://github.com/eyaltoledano/claude-task-master/commit/548beb434453c69041572eb5927ee7da7075c213) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add 4.5 haiku and sonnet to supported models for claude-code and anthropic ai providers
|
||||
|
||||
- [#1309](https://github.com/eyaltoledano/claude-task-master/pull/1309) [`ccb87a5`](https://github.com/eyaltoledano/claude-task-master/commit/ccb87a516a11f7ec4b03133c8f24f4fd8c3a45fc) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add autonomous TDD workflow automation system with new `tm autopilot` commands and MCP tools for AI-driven test-driven development.
|
||||
|
||||
**New CLI Commands:**
|
||||
- `tm autopilot start <taskId>` - Initialize TDD workflow
|
||||
- `tm autopilot next` - Get next action in workflow
|
||||
- `tm autopilot status` - Check workflow progress
|
||||
- `tm autopilot complete` - Advance phase with test results
|
||||
- `tm autopilot commit` - Save progress with metadata
|
||||
- `tm autopilot resume` - Continue from checkpoint
|
||||
- `tm autopilot abort` - Cancel workflow
|
||||
|
||||
**New MCP Tools:**
|
||||
Seven new autopilot tools for programmatic control: `autopilot_start`, `autopilot_next`, `autopilot_status`, `autopilot_complete_phase`, `autopilot_commit`, `autopilot_resume`, `autopilot_abort`
|
||||
|
||||
**Features:**
|
||||
- Complete RED → GREEN → COMMIT cycle enforcement
|
||||
- Intelligent commit message generation with metadata
|
||||
- Activity logging and state persistence
|
||||
- Configurable workflow settings via `.taskmaster/config.json`
|
||||
- Comprehensive AI agent integration documentation
|
||||
|
||||
**Documentation:**
|
||||
- AI Agent Integration Guide (2,800+ lines)
|
||||
- TDD Quick Start Guide
|
||||
- Example prompts and integration patterns
|
||||
|
||||
> **Learn more:** [TDD Workflow Quickstart Guide](https://dev.task-master.dev/tdd-workflow/quickstart)
|
||||
|
||||
This release enables AI agents to autonomously execute test-driven development workflows with full state management and recovery capabilities.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1323](https://github.com/eyaltoledano/claude-task-master/pull/1323) [`dc6652c`](https://github.com/eyaltoledano/claude-task-master/commit/dc6652ccd2b50b91eb55d92899ebf70c7b4d6601) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP server compatibility with Draft-07 clients (Augment IDE, gemini-cli, gemini code assist)
|
||||
- Resolves #1284
|
||||
|
||||
**Problem:**
|
||||
- MCP tools were using Zod v4, which outputs JSON Schema Draft 2020-12
|
||||
- MCP clients only support Draft-07
|
||||
- Tools were not discoverable in gemini-cli and other clients
|
||||
|
||||
**Solution:**
|
||||
- Updated all MCP tools to import from `zod/v3` instead of `zod`
|
||||
- Zod v3 schemas convert to Draft-07 via FastMCP's zod-to-json-schema
|
||||
- Fixed logger to use stderr instead of stdout (MCP protocol requirement)
|
||||
|
||||
This is a temporary workaround until FastMCP adds JSON Schema version configuration.
|
||||
|
||||
## 0.30.0-rc.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1181](https://github.com/eyaltoledano/claude-task-master/pull/1181) [`a69d8c9`](https://github.com/eyaltoledano/claude-task-master/commit/a69d8c91dc9205a3fdaf9d32276144fa3bcad55d) Thanks [@karol-f](https://github.com/karol-f)! - Add configurable MCP tool loading to optimize LLM context usage
|
||||
|
||||
You can now control which Task Master MCP tools are loaded by setting the `TASK_MASTER_TOOLS` environment variable in your MCP configuration. This helps reduce context usage for LLMs by only loading the tools you need.
|
||||
|
||||
**Configuration Options:**
|
||||
- `all` (default): Load all 36 tools
|
||||
- `core` or `lean`: Load only 7 essential tools for daily development
|
||||
- Includes: `get_tasks`, `next_task`, `get_task`, `set_task_status`, `update_subtask`, `parse_prd`, `expand_task`
|
||||
- `standard`: Load 15 commonly used tools (all core tools plus 8 more)
|
||||
- Additional tools: `initialize_project`, `analyze_project_complexity`, `expand_all`, `add_subtask`, `remove_task`, `generate`, `add_task`, `complexity_report`
|
||||
- Custom list: Comma-separated tool names (e.g., `get_tasks,next_task,set_task_status`)
|
||||
|
||||
**Example .mcp.json configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "standard",
|
||||
"ANTHROPIC_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For complete details on all available tools, configuration examples, and usage guidelines, see the [MCP Tools documentation](https://docs.task-master.dev/capabilities/mcp#configurable-tool-loading).
|
||||
|
||||
- [#1312](https://github.com/eyaltoledano/claude-task-master/pull/1312) [`d7fca18`](https://github.com/eyaltoledano/claude-task-master/commit/d7fca1844f24ad8ce079c21d9799a3c4b4413381) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve next command to work with remote
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1314](https://github.com/eyaltoledano/claude-task-master/pull/1314) [`6bc75c0`](https://github.com/eyaltoledano/claude-task-master/commit/6bc75c0ac68b59cb10cee70574a689f83e4de768) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve auth token refresh flow
|
||||
|
||||
- [#1302](https://github.com/eyaltoledano/claude-task-master/pull/1302) [`3283506`](https://github.com/eyaltoledano/claude-task-master/commit/3283506444d59896ecb97721ef2e96e290eb84d3) Thanks [@bjcoombs](https://github.com/bjcoombs)! - Enable Task Master commands to traverse parent directories to find project root from nested paths
|
||||
|
||||
Fixes #1301
|
||||
|
||||
## 0.29.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1286](https://github.com/eyaltoledano/claude-task-master/pull/1286) [`f12a16d`](https://github.com/eyaltoledano/claude-task-master/commit/f12a16d09649f62148515f11f616157c7d0bd2d5) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add changelog highlights to auto-update notifications
|
||||
|
||||
When the CLI auto-updates to a new version, it now displays a "What's New" section.
|
||||
|
||||
- [#1293](https://github.com/eyaltoledano/claude-task-master/pull/1293) [`3010b90`](https://github.com/eyaltoledano/claude-task-master/commit/3010b90d98f3a7d8636caa92fc33d6ee69d4bed0) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add Claude Code plugin with marketplace distribution
|
||||
|
||||
This release introduces official Claude Code plugin support, marking the evolution from legacy `.claude` directory copying to a modern plugin-based architecture.
|
||||
|
||||
## 🎉 New: Claude Code Plugin
|
||||
|
||||
Task Master AI commands and agents are now distributed as a proper Claude Code plugin:
|
||||
- **49 slash commands** with clean naming (`/taskmaster:command-name`)
|
||||
- **3 specialized AI agents** (task-orchestrator, task-executor, task-checker)
|
||||
- **MCP server integration** for deep Claude Code integration
|
||||
|
||||
**Installation:**
|
||||
|
||||
```bash
|
||||
/plugin marketplace add eyaltoledano/claude-task-master
|
||||
/plugin install taskmaster@taskmaster
|
||||
```
|
||||
|
||||
### The `rules add claude` command no longer copies commands and agents to `.claude/commands/` and `.claude/agents/`. Instead, it now
|
||||
- Shows plugin installation instructions
|
||||
- Only manages CLAUDE.md imports for agent instructions
|
||||
- Directs users to install the official plugin
|
||||
|
||||
**Migration for Existing Users:**
|
||||
|
||||
If you previously used `rules add claude`:
|
||||
1. The old commands in `.claude/commands/` will continue to work but won't receive updates
|
||||
2. Install the plugin for the latest features: `/plugin install taskmaster@taskmaster`
|
||||
3. remove old `.claude/commands/` and `.claude/agents/` directories
|
||||
|
||||
**Why This Change?**
|
||||
|
||||
Claude Code plugins provide:
|
||||
- ✅ Automatic updates when we release new features
|
||||
- ✅ Better command organization and naming
|
||||
- ✅ Seamless integration with Claude Code
|
||||
- ✅ No manual file copying or management
|
||||
|
||||
The plugin system is the future of Task Master AI integration with Claude Code!
|
||||
|
||||
- [#1285](https://github.com/eyaltoledano/claude-task-master/pull/1285) [`2a910a4`](https://github.com/eyaltoledano/claude-task-master/commit/2a910a40bac375f9f61d797bf55597303d556b48) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add RPG (Repository Planning Graph) method template for structured PRD creation. The new `example_prd_rpg.txt` template teaches AI agents and developers the RPG methodology through embedded instructions, inline good/bad examples, and XML-style tags for structure. This template enables creation of dependency-aware PRDs that automatically generate topologically-ordered task graphs when parsed with Task Master.
|
||||
|
||||
Key features:
|
||||
- Method-as-template: teaches RPG principles (dual-semantics, explicit dependencies, topological order) while being used
|
||||
- Inline instructions at decision points guide AI through each section
|
||||
- Good/bad examples for immediate pattern matching
|
||||
- Flexible plain-text format with XML-style tags for parseability
|
||||
- Critical dependency-graph section ensures correct task ordering
|
||||
- Automatic inclusion during `task-master init`
|
||||
- Comprehensive documentation at [docs.task-master.dev/capabilities/rpg-method](https://docs.task-master.dev/capabilities/rpg-method)
|
||||
- Tool recommendations for code-context-aware PRD creation (Claude Code, Cursor, Gemini CLI, Codex/Grok)
|
||||
|
||||
The RPG template complements the existing `example_prd.txt` and provides a more structured approach for complex projects requiring clear module boundaries and dependency chains.
|
||||
|
||||
- [#1287](https://github.com/eyaltoledano/claude-task-master/pull/1287) [`90e6bdc`](https://github.com/eyaltoledano/claude-task-master/commit/90e6bdcf1c59f65ad27fcdfe3b13b9dca7e77654) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhance `expand_all` to intelligently use complexity analysis recommendations when expanding tasks.
|
||||
|
||||
The expand-all operation now automatically leverages recommendations from `analyze-complexity` to determine optimal subtask counts for each task, resulting in more accurate and context-aware task breakdowns.
|
||||
|
||||
Key improvements:
|
||||
- Automatic integration with complexity analysis reports
|
||||
- Tag-aware complexity report path resolution
|
||||
- Intelligent subtask count determination based on task complexity
|
||||
- Falls back to defaults when complexity analysis is unavailable
|
||||
- Enhanced logging for better visibility into expansion decisions
|
||||
|
||||
When you run `task-master expand --all` after `task-master analyze-complexity`, Task Master now uses the recommended subtask counts from the complexity analysis instead of applying uniform defaults, ensuring each task is broken down according to its actual complexity.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1191](https://github.com/eyaltoledano/claude-task-master/pull/1191) [`aaf903f`](https://github.com/eyaltoledano/claude-task-master/commit/aaf903ff2f606c779a22e9a4b240ab57b3683815) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix cross-level task dependencies not being saved
|
||||
|
||||
Fixes an issue where adding dependencies between subtasks and top-level tasks (e.g., `task-master add-dependency --id=2.2 --depends-on=11`) would report success but fail to persist the changes. Dependencies can now be created in both directions between any task levels.
|
||||
|
||||
- [#1299](https://github.com/eyaltoledano/claude-task-master/pull/1299) [`4c1ef2c`](https://github.com/eyaltoledano/claude-task-master/commit/4c1ef2ca94411c53bcd2a78ec710b06c500236dd) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve refresh token when authenticating
|
||||
|
||||
## 0.29.0-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1299](https://github.com/eyaltoledano/claude-task-master/pull/1299) [`a6c5152`](https://github.com/eyaltoledano/claude-task-master/commit/a6c5152f20edd8717cf1aea34e7c178b1261aa99) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve refresh token when authenticating
|
||||
|
||||
## 0.29.0-rc.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1286](https://github.com/eyaltoledano/claude-task-master/pull/1286) [`f12a16d`](https://github.com/eyaltoledano/claude-task-master/commit/f12a16d09649f62148515f11f616157c7d0bd2d5) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add changelog highlights to auto-update notifications
|
||||
|
||||
When the CLI auto-updates to a new version, it now displays a "What's New" section.
|
||||
|
||||
- [#1293](https://github.com/eyaltoledano/claude-task-master/pull/1293) [`3010b90`](https://github.com/eyaltoledano/claude-task-master/commit/3010b90d98f3a7d8636caa92fc33d6ee69d4bed0) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add Claude Code plugin with marketplace distribution
|
||||
|
||||
This release introduces official Claude Code plugin support, marking the evolution from legacy `.claude` directory copying to a modern plugin-based architecture.
|
||||
|
||||
## 🎉 New: Claude Code Plugin
|
||||
|
||||
Task Master AI commands and agents are now distributed as a proper Claude Code plugin:
|
||||
- **49 slash commands** with clean naming (`/task-master-ai:command-name`)
|
||||
- **3 specialized AI agents** (task-orchestrator, task-executor, task-checker)
|
||||
- **MCP server integration** for deep Claude Code integration
|
||||
|
||||
**Installation:**
|
||||
|
||||
```bash
|
||||
/plugin marketplace add eyaltoledano/claude-task-master
|
||||
/plugin install taskmaster@taskmaster
|
||||
```
|
||||
|
||||
### The `rules add claude` command no longer copies commands and agents to `.claude/commands/` and `.claude/agents/`. Instead, it now
|
||||
- Shows plugin installation instructions
|
||||
- Only manages CLAUDE.md imports for agent instructions
|
||||
- Directs users to install the official plugin
|
||||
|
||||
**Migration for Existing Users:**
|
||||
|
||||
If you previously used `rules add claude`:
|
||||
1. The old commands in `.claude/commands/` will continue to work but won't receive updates
|
||||
2. Install the plugin for the latest features: `/plugin install taskmaster@taskmaster`
|
||||
3. remove old `.claude/commands/` and `.claude/agents/` directories
|
||||
|
||||
**Why This Change?**
|
||||
|
||||
Claude Code plugins provide:
|
||||
- ✅ Automatic updates when we release new features
|
||||
- ✅ Better command organization and naming
|
||||
- ✅ Seamless integration with Claude Code
|
||||
- ✅ No manual file copying or management
|
||||
|
||||
The plugin system is the future of Task Master AI integration with Claude Code!
|
||||
|
||||
- [#1285](https://github.com/eyaltoledano/claude-task-master/pull/1285) [`2a910a4`](https://github.com/eyaltoledano/claude-task-master/commit/2a910a40bac375f9f61d797bf55597303d556b48) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add RPG (Repository Planning Graph) method template for structured PRD creation. The new `example_prd_rpg.txt` template teaches AI agents and developers the RPG methodology through embedded instructions, inline good/bad examples, and XML-style tags for structure. This template enables creation of dependency-aware PRDs that automatically generate topologically-ordered task graphs when parsed with Task Master.
|
||||
|
||||
Key features:
|
||||
- Method-as-template: teaches RPG principles (dual-semantics, explicit dependencies, topological order) while being used
|
||||
- Inline instructions at decision points guide AI through each section
|
||||
- Good/bad examples for immediate pattern matching
|
||||
- Flexible plain-text format with XML-style tags for parseability
|
||||
- Critical dependency-graph section ensures correct task ordering
|
||||
- Automatic inclusion during `task-master init`
|
||||
- Comprehensive documentation at [docs.task-master.dev/capabilities/rpg-method](https://docs.task-master.dev/capabilities/rpg-method)
|
||||
- Tool recommendations for code-context-aware PRD creation (Claude Code, Cursor, Gemini CLI, Codex/Grok)
|
||||
|
||||
The RPG template complements the existing `example_prd.txt` and provides a more structured approach for complex projects requiring clear module boundaries and dependency chains.
|
||||
|
||||
- [#1287](https://github.com/eyaltoledano/claude-task-master/pull/1287) [`90e6bdc`](https://github.com/eyaltoledano/claude-task-master/commit/90e6bdcf1c59f65ad27fcdfe3b13b9dca7e77654) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhance `expand_all` to intelligently use complexity analysis recommendations when expanding tasks.
|
||||
|
||||
The expand-all operation now automatically leverages recommendations from `analyze-complexity` to determine optimal subtask counts for each task, resulting in more accurate and context-aware task breakdowns.
|
||||
|
||||
Key improvements:
|
||||
- Automatic integration with complexity analysis reports
|
||||
- Tag-aware complexity report path resolution
|
||||
- Intelligent subtask count determination based on task complexity
|
||||
- Falls back to defaults when complexity analysis is unavailable
|
||||
- Enhanced logging for better visibility into expansion decisions
|
||||
|
||||
When you run `task-master expand --all` after `task-master analyze-complexity`, Task Master now uses the recommended subtask counts from the complexity analysis instead of applying uniform defaults, ensuring each task is broken down according to its actual complexity.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1191](https://github.com/eyaltoledano/claude-task-master/pull/1191) [`aaf903f`](https://github.com/eyaltoledano/claude-task-master/commit/aaf903ff2f606c779a22e9a4b240ab57b3683815) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix cross-level task dependencies not being saved
|
||||
|
||||
Fixes an issue where adding dependencies between subtasks and top-level tasks (e.g., `task-master add-dependency --id=2.2 --depends-on=11`) would report success but fail to persist the changes. Dependencies can now be created in both directions between any task levels.
|
||||
|
||||
## 0.28.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1273](https://github.com/eyaltoledano/claude-task-master/pull/1273) [`b43b7ce`](https://github.com/eyaltoledano/claude-task-master/commit/b43b7ce201625eee956fb2f8cd332f238bb78c21) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Add Codex CLI provider with OAuth authentication
|
||||
- Added codex-cli provider for GPT-5 and GPT-5-Codex models (272K input / 128K output)
|
||||
- OAuth-first authentication via `codex login` - no API key required
|
||||
- Optional OPENAI_CODEX_API_KEY support
|
||||
- Codebase analysis capabilities automatically enabled
|
||||
- Command-specific settings and approval/sandbox modes
|
||||
|
||||
- [#1215](https://github.com/eyaltoledano/claude-task-master/pull/1215) [`0079b7d`](https://github.com/eyaltoledano/claude-task-master/commit/0079b7defdad550811f704c470fdd01955d91d4d) Thanks [@joedanz](https://github.com/joedanz)! - Add Cursor IDE custom slash command support
|
||||
|
||||
Expose Task Master commands as Cursor slash commands by copying assets/claude/commands to .cursor/commands on profile add and cleaning up on remove.
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Added api keys page on docs website: docs.task-master.dev/getting-started/api-keys
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Move to AI SDK v5:
|
||||
- Works better with claude-code and gemini-cli as ai providers
|
||||
- Improved openai model family compatibility
|
||||
- Migrate ollama provider to v2
|
||||
- Closes #1223, #1013, #1161, #1174
|
||||
|
||||
- [#1262](https://github.com/eyaltoledano/claude-task-master/pull/1262) [`738ec51`](https://github.com/eyaltoledano/claude-task-master/commit/738ec51c049a295a12839b2dfddaf05e23b8fede) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Migrate AI services to use generateObject for structured data generation
|
||||
|
||||
This update migrates all AI service calls from generateText to generateObject, ensuring more reliable and structured responses across all commands.
|
||||
|
||||
### Key Changes:
|
||||
- **Unified AI Service**: Replaced separate generateText implementations with a single generateObjectService that handles structured data generation
|
||||
- **JSON Mode Support**: Added proper JSON mode configuration for providers that support it (OpenAI, Anthropic, Google, Groq)
|
||||
- **Schema Validation**: Integrated Zod schemas for all AI-generated content with automatic validation
|
||||
- **Provider Compatibility**: Maintained compatibility with all existing providers while leveraging their native structured output capabilities
|
||||
- **Improved Reliability**: Structured output generation reduces parsing errors and ensures consistent data formats
|
||||
|
||||
### Technical Improvements:
|
||||
- Centralized provider configuration in `ai-providers-unified.js`
|
||||
- Added `generateObject` support detection for each provider
|
||||
- Implemented proper error handling for schema validation failures
|
||||
- Maintained backward compatibility with existing prompt structures
|
||||
|
||||
### Bug Fixes:
|
||||
- Fixed subtask ID numbering issue where AI was generating inconsistent IDs (101-105, 601-603) instead of sequential numbering (1, 2, 3...)
|
||||
- Enhanced prompt instructions to enforce proper ID generation patterns
|
||||
- Ensured subtasks display correctly as X.1, X.2, X.3 format
|
||||
|
||||
This migration improves the reliability and consistency of AI-generated content throughout the Task Master application.
|
||||
|
||||
- [#1112](https://github.com/eyaltoledano/claude-task-master/pull/1112) [`d67b81d`](https://github.com/eyaltoledano/claude-task-master/commit/d67b81d25ddd927fabb6f5deb368e8993519c541) Thanks [@olssonsten](https://github.com/olssonsten)! - Enhanced Roo Code profile with MCP timeout configuration for improved reliability during long-running AI operations. The Roo profile now automatically configures a 300-second timeout for MCP server operations, preventing timeouts during complex tasks like `parse-prd`, `expand-all`, `analyze-complexity`, and `research` operations. This change also replaces static MCP configuration files with programmatic generation for better maintainability.
|
||||
|
||||
**What's New:**
|
||||
- 300-second timeout for MCP operations (up from default 60 seconds)
|
||||
- Programmatic MCP configuration generation (replaces static asset files)
|
||||
- Enhanced reliability for AI-powered operations
|
||||
- Consistent with other AI coding assistant profiles
|
||||
|
||||
**Migration:** No user action required - existing Roo Code installations will automatically receive the enhanced MCP configuration on next initialization.
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`986ac11`](https://github.com/eyaltoledano/claude-task-master/commit/986ac117aee00bcd3e6830a0f76e1ad6d10e0bca) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Upgrade grok-cli ai provider to ai sdk v5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1235](https://github.com/eyaltoledano/claude-task-master/pull/1235) [`aaacc3d`](https://github.com/eyaltoledano/claude-task-master/commit/aaacc3dae36247b4de72b2d2697f49e5df6d01e3) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve `analyze-complexity` cli docs and `--research` flag documentation
|
||||
|
||||
- [#1251](https://github.com/eyaltoledano/claude-task-master/pull/1251) [`0b2c696`](https://github.com/eyaltoledano/claude-task-master/commit/0b2c6967c4605c33a100cff16f6ce8ff09ad06f0) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Change parent task back to "pending" when all subtasks are in "pending" state
|
||||
|
||||
- [#1274](https://github.com/eyaltoledano/claude-task-master/pull/1274) [`4f984f8`](https://github.com/eyaltoledano/claude-task-master/commit/4f984f8a6965da9f9c7edd60ddfd6560ac022917) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Do a quick fix on build
|
||||
|
||||
- [#1277](https://github.com/eyaltoledano/claude-task-master/pull/1277) [`7b5a7c4`](https://github.com/eyaltoledano/claude-task-master/commit/7b5a7c4495a68b782f7407fc5d0e0d3ae81f42f5) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP connection errors caused by deprecated generateTaskFiles calls. Resolves "Cannot read properties of null (reading 'toString')" errors when using MCP tools for task management operations.
|
||||
|
||||
- [#1276](https://github.com/eyaltoledano/claude-task-master/pull/1276) [`caee040`](https://github.com/eyaltoledano/claude-task-master/commit/caee040907f856d31a660171c9e6d966f23c632e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP server error when file parameter not provided - now properly constructs default tasks.json path instead of failing with 'tasksJsonPath is required' error.
|
||||
|
||||
- [#1172](https://github.com/eyaltoledano/claude-task-master/pull/1172) [`b5fe723`](https://github.com/eyaltoledano/claude-task-master/commit/b5fe723f8ead928e9f2dbde13b833ee70ac3382d) Thanks [@jujax](https://github.com/jujax)! - Fix Claude Code settings validation for pathToClaudeCodeExecutable
|
||||
|
||||
- [#1192](https://github.com/eyaltoledano/claude-task-master/pull/1192) [`2b69936`](https://github.com/eyaltoledano/claude-task-master/commit/2b69936ee7b34346d6de5175af20e077359e2e2a) Thanks [@nukunga](https://github.com/nukunga)! - Fix sonar deep research model failing, should be called `sonar-deep-research`
|
||||
|
||||
- [#1270](https://github.com/eyaltoledano/claude-task-master/pull/1270) [`20004a3`](https://github.com/eyaltoledano/claude-task-master/commit/20004a39ea848f747e1ff48981bfe176554e4055) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix complexity score not showing for `task-master show` and `task-master list`
|
||||
- Added complexity score on "next task" when running `task-master list`
|
||||
- Added colors to complexity to reflect complexity (easy, medium, hard)
|
||||
|
||||
## 0.28.0-rc.2
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1273](https://github.com/eyaltoledano/claude-task-master/pull/1273) [`b43b7ce`](https://github.com/eyaltoledano/claude-task-master/commit/b43b7ce201625eee956fb2f8cd332f238bb78c21) Thanks [@ben-vargas](https://github.com/ben-vargas)! - Add Codex CLI provider with OAuth authentication
|
||||
- Added codex-cli provider for GPT-5 and GPT-5-Codex models (272K input / 128K output)
|
||||
- OAuth-first authentication via `codex login` - no API key required
|
||||
- Optional OPENAI_CODEX_API_KEY support
|
||||
- Codebase analysis capabilities automatically enabled
|
||||
- Command-specific settings and approval/sandbox modes
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1277](https://github.com/eyaltoledano/claude-task-master/pull/1277) [`7b5a7c4`](https://github.com/eyaltoledano/claude-task-master/commit/7b5a7c4495a68b782f7407fc5d0e0d3ae81f42f5) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP connection errors caused by deprecated generateTaskFiles calls. Resolves "Cannot read properties of null (reading 'toString')" errors when using MCP tools for task management operations.
|
||||
|
||||
- [#1276](https://github.com/eyaltoledano/claude-task-master/pull/1276) [`caee040`](https://github.com/eyaltoledano/claude-task-master/commit/caee040907f856d31a660171c9e6d966f23c632e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP server error when file parameter not provided - now properly constructs default tasks.json path instead of failing with 'tasksJsonPath is required' error.
|
||||
|
||||
## 0.28.0-rc.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1274](https://github.com/eyaltoledano/claude-task-master/pull/1274) [`4f984f8`](https://github.com/eyaltoledano/claude-task-master/commit/4f984f8a6965da9f9c7edd60ddfd6560ac022917) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Do a quick fix on build
|
||||
|
||||
## 0.28.0-rc.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1215](https://github.com/eyaltoledano/claude-task-master/pull/1215) [`0079b7d`](https://github.com/eyaltoledano/claude-task-master/commit/0079b7defdad550811f704c470fdd01955d91d4d) Thanks [@joedanz](https://github.com/joedanz)! - Add Cursor IDE custom slash command support
|
||||
|
||||
Expose Task Master commands as Cursor slash commands by copying assets/claude/commands to .cursor/commands on profile add and cleaning up on remove.
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Added api keys page on docs website: docs.task-master.dev/getting-started/api-keys
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Move to AI SDK v5:
|
||||
- Works better with claude-code and gemini-cli as ai providers
|
||||
- Improved openai model family compatibility
|
||||
- Migrate ollama provider to v2
|
||||
- Closes #1223, #1013, #1161, #1174
|
||||
|
||||
- [#1262](https://github.com/eyaltoledano/claude-task-master/pull/1262) [`738ec51`](https://github.com/eyaltoledano/claude-task-master/commit/738ec51c049a295a12839b2dfddaf05e23b8fede) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Migrate AI services to use generateObject for structured data generation
|
||||
|
||||
This update migrates all AI service calls from generateText to generateObject, ensuring more reliable and structured responses across all commands.
|
||||
|
||||
### Key Changes:
|
||||
- **Unified AI Service**: Replaced separate generateText implementations with a single generateObjectService that handles structured data generation
|
||||
- **JSON Mode Support**: Added proper JSON mode configuration for providers that support it (OpenAI, Anthropic, Google, Groq)
|
||||
- **Schema Validation**: Integrated Zod schemas for all AI-generated content with automatic validation
|
||||
- **Provider Compatibility**: Maintained compatibility with all existing providers while leveraging their native structured output capabilities
|
||||
- **Improved Reliability**: Structured output generation reduces parsing errors and ensures consistent data formats
|
||||
|
||||
### Technical Improvements:
|
||||
- Centralized provider configuration in `ai-providers-unified.js`
|
||||
- Added `generateObject` support detection for each provider
|
||||
- Implemented proper error handling for schema validation failures
|
||||
- Maintained backward compatibility with existing prompt structures
|
||||
|
||||
### Bug Fixes:
|
||||
- Fixed subtask ID numbering issue where AI was generating inconsistent IDs (101-105, 601-603) instead of sequential numbering (1, 2, 3...)
|
||||
- Enhanced prompt instructions to enforce proper ID generation patterns
|
||||
- Ensured subtasks display correctly as X.1, X.2, X.3 format
|
||||
|
||||
This migration improves the reliability and consistency of AI-generated content throughout the Task Master application.
|
||||
|
||||
- [#1112](https://github.com/eyaltoledano/claude-task-master/pull/1112) [`d67b81d`](https://github.com/eyaltoledano/claude-task-master/commit/d67b81d25ddd927fabb6f5deb368e8993519c541) Thanks [@olssonsten](https://github.com/olssonsten)! - Enhanced Roo Code profile with MCP timeout configuration for improved reliability during long-running AI operations. The Roo profile now automatically configures a 300-second timeout for MCP server operations, preventing timeouts during complex tasks like `parse-prd`, `expand-all`, `analyze-complexity`, and `research` operations. This change also replaces static MCP configuration files with programmatic generation for better maintainability.
|
||||
|
||||
**What's New:**
|
||||
- 300-second timeout for MCP operations (up from default 60 seconds)
|
||||
- Programmatic MCP configuration generation (replaces static asset files)
|
||||
- Enhanced reliability for AI-powered operations
|
||||
- Consistent with other AI coding assistant profiles
|
||||
|
||||
**Migration:** No user action required - existing Roo Code installations will automatically receive the enhanced MCP configuration on next initialization.
|
||||
|
||||
- [#1246](https://github.com/eyaltoledano/claude-task-master/pull/1246) [`986ac11`](https://github.com/eyaltoledano/claude-task-master/commit/986ac117aee00bcd3e6830a0f76e1ad6d10e0bca) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Upgrade grok-cli ai provider to ai sdk v5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1235](https://github.com/eyaltoledano/claude-task-master/pull/1235) [`aaacc3d`](https://github.com/eyaltoledano/claude-task-master/commit/aaacc3dae36247b4de72b2d2697f49e5df6d01e3) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve `analyze-complexity` cli docs and `--research` flag documentation
|
||||
|
||||
- [#1251](https://github.com/eyaltoledano/claude-task-master/pull/1251) [`0b2c696`](https://github.com/eyaltoledano/claude-task-master/commit/0b2c6967c4605c33a100cff16f6ce8ff09ad06f0) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Change parent task back to "pending" when all subtasks are in "pending" state
|
||||
|
||||
- [#1172](https://github.com/eyaltoledano/claude-task-master/pull/1172) [`b5fe723`](https://github.com/eyaltoledano/claude-task-master/commit/b5fe723f8ead928e9f2dbde13b833ee70ac3382d) Thanks [@jujax](https://github.com/jujax)! - Fix Claude Code settings validation for pathToClaudeCodeExecutable
|
||||
|
||||
- [#1192](https://github.com/eyaltoledano/claude-task-master/pull/1192) [`2b69936`](https://github.com/eyaltoledano/claude-task-master/commit/2b69936ee7b34346d6de5175af20e077359e2e2a) Thanks [@nukunga](https://github.com/nukunga)! - Fix sonar deep research model failing, should be called `sonar-deep-research`
|
||||
|
||||
- [#1270](https://github.com/eyaltoledano/claude-task-master/pull/1270) [`20004a3`](https://github.com/eyaltoledano/claude-task-master/commit/20004a39ea848f747e1ff48981bfe176554e4055) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix complexity score not showing for `task-master show` and `task-master list`
|
||||
- Added complexity score on "next task" when running `task-master list`
|
||||
- Added colors to complexity to reflect complexity (easy, medium, hard)
|
||||
|
||||
## 0.27.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
34
CLAUDE.md
34
CLAUDE.md
@@ -4,6 +4,40 @@
|
||||
**Import Task Master's development workflow commands and guidelines, treat as if import is in the main CLAUDE.md file.**
|
||||
@./.taskmaster/CLAUDE.md
|
||||
|
||||
## Test Guidelines
|
||||
|
||||
### Test File Placement
|
||||
|
||||
- **Package & tests**: Place in `packages/<package-name>/src/<module>/<file>.spec.ts` or `apps/<app-name>/src/<module>/<file.spec.ts>` alongside source
|
||||
- **Package integration tests**: Place in `packages/<package-name>/tests/integration/<module>/<file>.test.ts` or `apps/<app-name>/tests/integration/<module>/<file>.test.ts` alongside source
|
||||
- **Isolated unit tests**: Use `tests/unit/packages/<package-name>/` only when parallel placement isn't possible
|
||||
- **Test extension**: Always use `.ts` for TypeScript tests, never `.js`
|
||||
|
||||
### Synchronous Tests
|
||||
- **NEVER use async/await in test functions** unless testing actual asynchronous operations
|
||||
- Use synchronous top-level imports instead of dynamic `await import()`
|
||||
- Test bodies should be synchronous whenever possible
|
||||
- Example:
|
||||
```typescript
|
||||
// ✅ CORRECT - Synchronous imports with .ts extension
|
||||
import { MyClass } from '../src/my-class.js';
|
||||
|
||||
it('should verify behavior', () => {
|
||||
expect(new MyClass().property).toBe(value);
|
||||
});
|
||||
|
||||
// ❌ INCORRECT - Async imports
|
||||
it('should verify behavior', async () => {
|
||||
const { MyClass } = await import('../src/my-class.js');
|
||||
expect(new MyClass().property).toBe(value);
|
||||
});
|
||||
```
|
||||
|
||||
## Documentation Guidelines
|
||||
|
||||
- **Documentation location**: Write docs in `apps/docs/` (Mintlify site source), not `docs/`
|
||||
- **Documentation URL**: Reference docs at https://docs.task-master.dev, not local file paths
|
||||
|
||||
## Changeset Guidelines
|
||||
|
||||
- When creating changesets, remember that it's user-facing, meaning we don't have to get into the specifics of the code, but rather mention what the end-user is getting or fixing from this changeset.
|
||||
140
CLAUDE_CODE_PLUGIN.md
Normal file
140
CLAUDE_CODE_PLUGIN.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Taskmaster AI - Claude Code Marketplace
|
||||
|
||||
This repository includes a Claude Code plugin marketplace in `.claude-plugin/marketplace.json`.
|
||||
|
||||
## Installation
|
||||
|
||||
### From GitHub (Public Repository)
|
||||
|
||||
Once this repository is pushed to GitHub, users can install with:
|
||||
|
||||
```bash
|
||||
# Add the marketplace
|
||||
/plugin marketplace add eyaltoledano/claude-task-master
|
||||
|
||||
# Install the plugin
|
||||
/plugin install taskmaster@taskmaster
|
||||
```
|
||||
|
||||
### Local Development/Testing
|
||||
|
||||
```bash
|
||||
# From the project root directory
|
||||
cd /path/to/claude-task-master
|
||||
|
||||
# Build the plugin first
|
||||
cd packages/claude-code-plugin
|
||||
npm run build
|
||||
cd ../..
|
||||
|
||||
# In Claude Code
|
||||
/plugin marketplace add .
|
||||
/plugin install taskmaster@taskmaster
|
||||
```
|
||||
|
||||
## Marketplace Structure
|
||||
|
||||
```
|
||||
claude-task-master/
|
||||
├── .claude-plugin/
|
||||
│ └── marketplace.json # Marketplace manifest (at repo root)
|
||||
│
|
||||
├── packages/claude-code-plugin/
|
||||
│ ├── src/build.ts # Build tooling
|
||||
│ └── [generated plugin files]
|
||||
│
|
||||
└── assets/claude/ # Plugin source files
|
||||
├── commands/
|
||||
└── agents/
|
||||
```
|
||||
|
||||
## Available Plugins
|
||||
|
||||
### taskmaster
|
||||
|
||||
AI-powered task management system for ambitious development workflows.
|
||||
|
||||
**Features:**
|
||||
|
||||
- 49 slash commands for comprehensive task management
|
||||
- 3 specialized AI agents (orchestrator, executor, checker)
|
||||
- MCP server integration
|
||||
- Complexity analysis and auto-expansion
|
||||
- Dependency management and validation
|
||||
- Automated workflow capabilities
|
||||
|
||||
**Quick Start:**
|
||||
|
||||
```bash
|
||||
/tm:init
|
||||
/tm:parse-prd
|
||||
/tm:next
|
||||
```
|
||||
|
||||
## For Contributors
|
||||
|
||||
### Adding New Plugins
|
||||
|
||||
To add more plugins to this marketplace:
|
||||
|
||||
1. **Update marketplace.json**:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"name": "new-plugin",
|
||||
"source": "./path/to/plugin",
|
||||
"description": "Plugin description",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Commit and push** the changes
|
||||
|
||||
3. **Users update** with: `/plugin marketplace update taskmaster`
|
||||
|
||||
### Marketplace Versioning
|
||||
|
||||
The marketplace version is tracked in `.claude-plugin/marketplace.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"metadata": {
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Increment the version when adding or updating plugins.
|
||||
|
||||
## Team Configuration
|
||||
|
||||
Organizations can auto-install this marketplace for all team members by adding to `.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"extraKnownMarketplaces": {
|
||||
"task-master": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "eyaltoledano/claude-task-master"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"taskmaster": {
|
||||
"marketplace": "taskmaster"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Team members who trust the repository folder will automatically get the marketplace and plugins installed.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Claude Code Plugin Docs](https://docs.claude.com/en/docs/claude-code/plugins)
|
||||
- [Marketplace Documentation](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces)
|
||||
83
README.md
83
README.md
@@ -88,8 +88,9 @@ At least one (1) of the following is required:
|
||||
- xAI API Key (for research or main model)
|
||||
- OpenRouter API Key (for research or main model)
|
||||
- Claude Code (no API key required - requires Claude Code CLI)
|
||||
- Codex CLI (OAuth via ChatGPT subscription - requires Codex CLI)
|
||||
|
||||
Using the research model is optional but highly recommended. You will need at least ONE API key (unless using Claude Code). Adding all API keys enables you to seamlessly switch between model providers at will.
|
||||
Using the research model is optional but highly recommended. You will need at least ONE API key (unless using Claude Code or Codex CLI with OAuth). Adding all API keys enables you to seamlessly switch between model providers at will.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -118,6 +119,7 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
// "TASK_MASTER_TOOLS": "all", // Options: "all", "standard", "core", or comma-separated list of tools
|
||||
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
|
||||
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",
|
||||
"OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE",
|
||||
@@ -147,6 +149,7 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
// "TASK_MASTER_TOOLS": "all", // Options: "all", "standard", "core", or comma-separated list of tools
|
||||
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
|
||||
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",
|
||||
"OPENAI_API_KEY": "YOUR_OPENAI_KEY_HERE",
|
||||
@@ -195,7 +198,7 @@ Initialize taskmaster-ai in my project
|
||||
|
||||
#### 5. Make sure you have a PRD (Recommended)
|
||||
|
||||
For **new projects**: Create your PRD at `.taskmaster/docs/prd.txt`
|
||||
For **new projects**: Create your PRD at `.taskmaster/docs/prd.txt`.
|
||||
For **existing projects**: You can use `scripts/prd.txt` or migrate with `task-master migrate`
|
||||
|
||||
An example PRD template is available after initialization in `.taskmaster/templates/example_prd.txt`.
|
||||
@@ -281,6 +284,76 @@ task-master generate
|
||||
task-master rules add windsurf,roo,vscode
|
||||
```
|
||||
|
||||
## Tool Loading Configuration
|
||||
|
||||
### Optimizing MCP Tool Loading
|
||||
|
||||
Task Master's MCP server supports selective tool loading to reduce context window usage. By default, all 36 tools are loaded (~21,000 tokens) to maintain backward compatibility with existing installations.
|
||||
|
||||
You can optimize performance by configuring the `TASK_MASTER_TOOLS` environment variable:
|
||||
|
||||
### Available Modes
|
||||
|
||||
| Mode | Tools | Context Usage | Use Case |
|
||||
|------|-------|--------------|----------|
|
||||
| `all` (default) | 36 | ~21,000 tokens | Complete feature set - all tools available |
|
||||
| `standard` | 15 | ~10,000 tokens | Common task management operations |
|
||||
| `core` (or `lean`) | 7 | ~5,000 tokens | Essential daily development workflow |
|
||||
| `custom` | Variable | Variable | Comma-separated list of specific tools |
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
#### Method 1: Environment Variable in MCP Configuration
|
||||
|
||||
Add `TASK_MASTER_TOOLS` to your MCP configuration file's `env` section:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"mcpServers": { // or "servers" for VS Code
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "standard", // Options: "all", "standard", "core", "lean", or comma-separated list
|
||||
"ANTHROPIC_API_KEY": "your-key-here",
|
||||
// ... other API keys
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Method 2: Claude Code CLI (One-Time Setup)
|
||||
|
||||
For Claude Code users, you can set the mode during installation:
|
||||
|
||||
```bash
|
||||
# Core mode example (~70% token reduction)
|
||||
claude mcp add task-master-ai --scope user \
|
||||
--env TASK_MASTER_TOOLS="core" \
|
||||
-- npx -y task-master-ai@latest
|
||||
|
||||
# Custom tools example
|
||||
claude mcp add task-master-ai --scope user \
|
||||
--env TASK_MASTER_TOOLS="get_tasks,next_task,set_task_status" \
|
||||
-- npx -y task-master-ai@latest
|
||||
```
|
||||
|
||||
### Tool Sets Details
|
||||
|
||||
**Core Tools (7):** `get_tasks`, `next_task`, `get_task`, `set_task_status`, `update_subtask`, `parse_prd`, `expand_task`
|
||||
|
||||
**Standard Tools (15):** All core tools plus `initialize_project`, `analyze_project_complexity`, `expand_all`, `add_subtask`, `remove_task`, `generate`, `add_task`, `complexity_report`
|
||||
|
||||
**All Tools (36):** Complete set including project setup, task management, analysis, dependencies, tags, research, and more
|
||||
|
||||
### Recommendations
|
||||
|
||||
- **New users**: Start with `"standard"` mode for a good balance
|
||||
- **Large projects**: Use `"core"` mode to minimize token usage
|
||||
- **Complex workflows**: Use `"all"` mode or custom selection
|
||||
- **Backward compatibility**: If not specified, defaults to `"all"` mode
|
||||
|
||||
## Claude Code Support
|
||||
|
||||
Task Master now supports Claude models through the Claude Code CLI, which requires no API key:
|
||||
@@ -309,6 +382,12 @@ cd claude-task-master
|
||||
node scripts/init.js
|
||||
```
|
||||
|
||||
## Join Our Team
|
||||
|
||||
<a href="https://tryhamster.com" target="_blank">
|
||||
<img src="./images/hamster-hiring.png" alt="Join Hamster's founding team" />
|
||||
</a>
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/eyaltoledano/claude-task-master/graphs/contributors">
|
||||
|
||||
@@ -4,6 +4,20 @@
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @tm/core@null
|
||||
|
||||
## null
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @tm/core@null
|
||||
|
||||
## null
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @tm/core@null
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"test:ci": "vitest run --coverage --reporter=dot"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inquirer/search": "^3.2.0",
|
||||
"@tm/core": "*",
|
||||
"boxen": "^8.0.1",
|
||||
"chalk": "5.6.2",
|
||||
@@ -48,5 +49,6 @@
|
||||
"*": {
|
||||
"*": ["src/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": ""
|
||||
}
|
||||
|
||||
270
apps/cli/src/command-registry.ts
Normal file
270
apps/cli/src/command-registry.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* @fileoverview Centralized Command Registry
|
||||
* Provides a single location for registering all CLI commands
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
|
||||
// Import all commands
|
||||
import { ListTasksCommand } from './commands/list.command.js';
|
||||
import { ShowCommand } from './commands/show.command.js';
|
||||
import { NextCommand } from './commands/next.command.js';
|
||||
import { AuthCommand } from './commands/auth.command.js';
|
||||
import { ContextCommand } from './commands/context.command.js';
|
||||
import { StartCommand } from './commands/start.command.js';
|
||||
import { SetStatusCommand } from './commands/set-status.command.js';
|
||||
import { ExportCommand } from './commands/export.command.js';
|
||||
import { AutopilotCommand } from './commands/autopilot/index.js';
|
||||
|
||||
/**
|
||||
* Command metadata for registration
|
||||
*/
|
||||
export interface CommandMetadata {
|
||||
name: string;
|
||||
description: string;
|
||||
commandClass: typeof Command;
|
||||
category?: 'task' | 'auth' | 'utility' | 'development';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry of all available commands
|
||||
*/
|
||||
export class CommandRegistry {
|
||||
/**
|
||||
* All available commands with their metadata
|
||||
*/
|
||||
private static commands: CommandMetadata[] = [
|
||||
// Task Management Commands
|
||||
{
|
||||
name: 'list',
|
||||
description: 'List all tasks with filtering and status overview',
|
||||
commandClass: ListTasksCommand as any,
|
||||
category: 'task'
|
||||
},
|
||||
{
|
||||
name: 'show',
|
||||
description: 'Display detailed information about a specific task',
|
||||
commandClass: ShowCommand as any,
|
||||
category: 'task'
|
||||
},
|
||||
{
|
||||
name: 'next',
|
||||
description: 'Find the next available task to work on',
|
||||
commandClass: NextCommand as any,
|
||||
category: 'task'
|
||||
},
|
||||
{
|
||||
name: 'start',
|
||||
description: 'Start working on a task with claude-code',
|
||||
commandClass: StartCommand as any,
|
||||
category: 'task'
|
||||
},
|
||||
{
|
||||
name: 'set-status',
|
||||
description: 'Update the status of one or more tasks',
|
||||
commandClass: SetStatusCommand as any,
|
||||
category: 'task'
|
||||
},
|
||||
{
|
||||
name: 'export',
|
||||
description: 'Export tasks to external systems',
|
||||
commandClass: ExportCommand as any,
|
||||
category: 'task'
|
||||
},
|
||||
{
|
||||
name: 'autopilot',
|
||||
description:
|
||||
'AI agent orchestration for TDD workflow (start, resume, next, complete, commit, status, abort)',
|
||||
commandClass: AutopilotCommand as any,
|
||||
category: 'development'
|
||||
},
|
||||
|
||||
// Authentication & Context Commands
|
||||
{
|
||||
name: 'auth',
|
||||
description: 'Manage authentication with tryhamster.com',
|
||||
commandClass: AuthCommand as any,
|
||||
category: 'auth'
|
||||
},
|
||||
{
|
||||
name: 'context',
|
||||
description: 'Manage workspace context (organization/brief)',
|
||||
commandClass: ContextCommand as any,
|
||||
category: 'auth'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Register all commands on a program instance
|
||||
* @param program - Commander program to register commands on
|
||||
*/
|
||||
static registerAll(program: Command): void {
|
||||
for (const cmd of this.commands) {
|
||||
this.registerCommand(program, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register specific commands by category
|
||||
* @param program - Commander program to register commands on
|
||||
* @param category - Category of commands to register
|
||||
*/
|
||||
static registerByCategory(
|
||||
program: Command,
|
||||
category: 'task' | 'auth' | 'utility' | 'development'
|
||||
): void {
|
||||
const categoryCommands = this.commands.filter(
|
||||
(cmd) => cmd.category === category
|
||||
);
|
||||
|
||||
for (const cmd of categoryCommands) {
|
||||
this.registerCommand(program, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single command by name
|
||||
* @param program - Commander program to register the command on
|
||||
* @param name - Name of the command to register
|
||||
*/
|
||||
static registerByName(program: Command, name: string): void {
|
||||
const cmd = this.commands.find((c) => c.name === name);
|
||||
if (cmd) {
|
||||
this.registerCommand(program, cmd);
|
||||
} else {
|
||||
throw new Error(`Command '${name}' not found in registry`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single command
|
||||
* @param program - Commander program to register the command on
|
||||
* @param metadata - Command metadata
|
||||
*/
|
||||
private static registerCommand(
|
||||
program: Command,
|
||||
metadata: CommandMetadata
|
||||
): void {
|
||||
const CommandClass = metadata.commandClass as any;
|
||||
|
||||
// Use the static registration method that all commands have
|
||||
if (CommandClass.registerOn) {
|
||||
CommandClass.registerOn(program);
|
||||
} else if (CommandClass.register) {
|
||||
CommandClass.register(program);
|
||||
} else {
|
||||
// Fallback to creating instance and adding
|
||||
const instance = new CommandClass();
|
||||
program.addCommand(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered command names
|
||||
*/
|
||||
static getCommandNames(): string[] {
|
||||
return this.commands.map((cmd) => cmd.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get commands by category
|
||||
*/
|
||||
static getCommandsByCategory(
|
||||
category: 'task' | 'auth' | 'utility' | 'development'
|
||||
): CommandMetadata[] {
|
||||
return this.commands.filter((cmd) => cmd.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new command to the registry
|
||||
* @param metadata - Command metadata to add
|
||||
*/
|
||||
static addCommand(metadata: CommandMetadata): void {
|
||||
// Check if command already exists
|
||||
if (this.commands.some((cmd) => cmd.name === metadata.name)) {
|
||||
throw new Error(`Command '${metadata.name}' already exists in registry`);
|
||||
}
|
||||
|
||||
this.commands.push(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a command from the registry
|
||||
* @param name - Name of the command to remove
|
||||
*/
|
||||
static removeCommand(name: string): boolean {
|
||||
const index = this.commands.findIndex((cmd) => cmd.name === name);
|
||||
if (index >= 0) {
|
||||
this.commands.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get command metadata by name
|
||||
* @param name - Name of the command
|
||||
*/
|
||||
static getCommand(name: string): CommandMetadata | undefined {
|
||||
return this.commands.find((cmd) => cmd.name === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a command exists
|
||||
* @param name - Name of the command
|
||||
*/
|
||||
static hasCommand(name: string): boolean {
|
||||
return this.commands.some((cmd) => cmd.name === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted list of all commands for display
|
||||
*/
|
||||
static getFormattedCommandList(): string {
|
||||
const categories = {
|
||||
task: 'Task Management',
|
||||
auth: 'Authentication & Context',
|
||||
utility: 'Utilities',
|
||||
development: 'Development'
|
||||
};
|
||||
|
||||
let output = '';
|
||||
|
||||
for (const [category, title] of Object.entries(categories)) {
|
||||
const cmds = this.getCommandsByCategory(
|
||||
category as keyof typeof categories
|
||||
);
|
||||
if (cmds.length > 0) {
|
||||
output += `\n${title}:\n`;
|
||||
for (const cmd of cmds) {
|
||||
output += ` ${cmd.name.padEnd(20)} ${cmd.description}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to register all CLI commands
|
||||
* @param program - Commander program instance
|
||||
*/
|
||||
export function registerAllCommands(program: Command): void {
|
||||
CommandRegistry.registerAll(program);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to register commands by category
|
||||
* @param program - Commander program instance
|
||||
* @param category - Category to register
|
||||
*/
|
||||
export function registerCommandsByCategory(
|
||||
program: Command,
|
||||
category: 'task' | 'auth' | 'utility' | 'development'
|
||||
): void {
|
||||
CommandRegistry.registerByCategory(program, category);
|
||||
}
|
||||
|
||||
// Export the registry for direct access if needed
|
||||
export default CommandRegistry;
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
type AuthCredentials
|
||||
} from '@tm/core/auth';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { ContextCommand } from './context.command.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Result type from auth command
|
||||
@@ -116,8 +118,7 @@ export class AuthCommand extends Command {
|
||||
process.exit(0);
|
||||
}, 100);
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,8 +134,7 @@ export class AuthCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +146,7 @@ export class AuthCommand extends Command {
|
||||
const result = this.displayStatus();
|
||||
this.setLastResult(result);
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +162,7 @@ export class AuthCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,19 +185,29 @@ export class AuthCommand extends Command {
|
||||
if (credentials.expiresAt) {
|
||||
const expiresAt = new Date(credentials.expiresAt);
|
||||
const now = new Date();
|
||||
const hoursRemaining = Math.floor(
|
||||
(expiresAt.getTime() - now.getTime()) / (1000 * 60 * 60)
|
||||
);
|
||||
const timeRemaining = expiresAt.getTime() - now.getTime();
|
||||
const hoursRemaining = Math.floor(timeRemaining / (1000 * 60 * 60));
|
||||
const minutesRemaining = Math.floor(timeRemaining / (1000 * 60));
|
||||
|
||||
if (hoursRemaining > 0) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
` Expires: ${expiresAt.toLocaleString()} (${hoursRemaining} hours remaining)`
|
||||
)
|
||||
);
|
||||
if (timeRemaining > 0) {
|
||||
// Token is still valid
|
||||
if (hoursRemaining > 0) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
` Expires at: ${expiresAt.toLocaleString()} (${hoursRemaining} hours remaining)`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
` Expires at: ${expiresAt.toLocaleString()} (${minutesRemaining} minutes remaining)`
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Token has expired
|
||||
console.log(
|
||||
chalk.yellow(` Token expired at: ${expiresAt.toLocaleString()}`)
|
||||
chalk.yellow(` Expired at: ${expiresAt.toLocaleString()}`)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -341,6 +349,37 @@ export class AuthCommand extends Command {
|
||||
chalk.gray(` Logged in as: ${credentials.email || credentials.userId}`)
|
||||
);
|
||||
|
||||
// Post-auth: Set up workspace context
|
||||
console.log(); // Add spacing
|
||||
try {
|
||||
const contextCommand = new ContextCommand();
|
||||
const contextResult = await contextCommand.setupContextInteractive();
|
||||
if (contextResult.success) {
|
||||
if (contextResult.orgSelected && contextResult.briefSelected) {
|
||||
console.log(
|
||||
chalk.green('✓ Workspace context configured successfully')
|
||||
);
|
||||
} else if (contextResult.orgSelected) {
|
||||
console.log(chalk.green('✓ Organization selected'));
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
chalk.yellow('⚠ Context setup was skipped or encountered issues')
|
||||
);
|
||||
console.log(
|
||||
chalk.gray(' You can set up context later with "tm context"')
|
||||
);
|
||||
}
|
||||
} catch (contextError) {
|
||||
console.log(chalk.yellow('⚠ Context setup encountered an error'));
|
||||
console.log(
|
||||
chalk.gray(' You can set up context later with "tm context"')
|
||||
);
|
||||
if (process.env.DEBUG) {
|
||||
console.error(chalk.gray((contextError as Error).message));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
action: 'login',
|
||||
@@ -348,7 +387,7 @@ export class AuthCommand extends Command {
|
||||
message: 'Authentication successful'
|
||||
};
|
||||
} catch (error) {
|
||||
this.handleAuthError(error as AuthenticationError);
|
||||
displayError(error, { skipExit: true });
|
||||
|
||||
return {
|
||||
success: false,
|
||||
@@ -411,51 +450,6 @@ export class AuthCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authentication errors
|
||||
*/
|
||||
private handleAuthError(error: AuthenticationError): void {
|
||||
console.error(chalk.red(`\n✗ ${error.message}`));
|
||||
|
||||
switch (error.code) {
|
||||
case 'NETWORK_ERROR':
|
||||
ui.displayWarning(
|
||||
'Please check your internet connection and try again.'
|
||||
);
|
||||
break;
|
||||
case 'INVALID_CREDENTIALS':
|
||||
ui.displayWarning('Please check your credentials and try again.');
|
||||
break;
|
||||
case 'AUTH_EXPIRED':
|
||||
ui.displayWarning(
|
||||
'Your session has expired. Please authenticate again.'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
if (process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack || ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle general errors
|
||||
*/
|
||||
private handleError(error: any): void {
|
||||
if (error instanceof AuthenticationError) {
|
||||
this.handleAuthError(error);
|
||||
} else {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
@@ -493,18 +487,7 @@ export class AuthCommand extends Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to register this command on an existing program
|
||||
* This is for gradual migration - allows commands.js to use this
|
||||
*/
|
||||
static registerOn(program: Command): Command {
|
||||
const authCommand = new AuthCommand();
|
||||
program.addCommand(authCommand);
|
||||
return authCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative registration that returns the command for chaining
|
||||
* Can also configure the command name if needed
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): AuthCommand {
|
||||
const authCommand = new AuthCommand(name);
|
||||
|
||||
515
apps/cli/src/commands/autopilot.command.ts
Normal file
515
apps/cli/src/commands/autopilot.command.ts
Normal file
@@ -0,0 +1,515 @@
|
||||
/**
|
||||
* @fileoverview AutopilotCommand using Commander's native class pattern
|
||||
* Extends Commander.Command for better integration with the framework
|
||||
* This is a thin presentation layer over @tm/core's autopilot functionality
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import ora, { type Ora } from 'ora';
|
||||
import {
|
||||
createTaskMasterCore,
|
||||
type TaskMasterCore,
|
||||
type Task,
|
||||
type Subtask
|
||||
} from '@tm/core';
|
||||
import * as ui from '../utils/ui.js';
|
||||
|
||||
/**
|
||||
* CLI-specific options interface for the autopilot command
|
||||
*/
|
||||
export interface AutopilotCommandOptions {
|
||||
format?: 'text' | 'json';
|
||||
project?: string;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preflight check result for a single check
|
||||
*/
|
||||
export interface PreflightCheckResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overall preflight check results
|
||||
*/
|
||||
export interface PreflightResult {
|
||||
success: boolean;
|
||||
testCommand: PreflightCheckResult;
|
||||
gitWorkingTree: PreflightCheckResult;
|
||||
requiredTools: PreflightCheckResult;
|
||||
defaultBranch: PreflightCheckResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI-specific result type from autopilot command
|
||||
*/
|
||||
export interface AutopilotCommandResult {
|
||||
success: boolean;
|
||||
taskId: string;
|
||||
task?: Task;
|
||||
error?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AutopilotCommand extending Commander's Command class
|
||||
* This is a thin presentation layer over @tm/core's autopilot functionality
|
||||
*/
|
||||
export class AutopilotCommand extends Command {
|
||||
private tmCore?: TaskMasterCore;
|
||||
private lastResult?: AutopilotCommandResult;
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name || 'autopilot');
|
||||
|
||||
// Configure the command
|
||||
this.description(
|
||||
'Execute a task autonomously using TDD workflow with git integration'
|
||||
)
|
||||
.argument('<taskId>', 'Task ID to execute autonomously')
|
||||
.option('-f, --format <format>', 'Output format (text, json)', 'text')
|
||||
.option('-p, --project <path>', 'Project root directory', process.cwd())
|
||||
.option(
|
||||
'--dry-run',
|
||||
'Show what would be executed without performing actions'
|
||||
)
|
||||
.action(async (taskId: string, options: AutopilotCommandOptions) => {
|
||||
await this.executeCommand(taskId, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the autopilot command
|
||||
*/
|
||||
private async executeCommand(
|
||||
taskId: string,
|
||||
options: AutopilotCommandOptions
|
||||
): Promise<void> {
|
||||
let spinner: Ora | null = null;
|
||||
|
||||
try {
|
||||
// Validate options
|
||||
if (!this.validateOptions(options)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate task ID format
|
||||
if (!this.validateTaskId(taskId)) {
|
||||
ui.displayError(`Invalid task ID format: ${taskId}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize tm-core with spinner
|
||||
spinner = ora('Initializing Task Master...').start();
|
||||
await this.initializeCore(options.project || process.cwd());
|
||||
spinner.succeed('Task Master initialized');
|
||||
|
||||
// Load and validate task existence
|
||||
spinner = ora(`Loading task ${taskId}...`).start();
|
||||
const task = await this.loadTask(taskId);
|
||||
|
||||
if (!task) {
|
||||
spinner.fail(`Task ${taskId} not found`);
|
||||
ui.displayError(`Task with ID ${taskId} does not exist`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
spinner.succeed(`Task ${taskId} loaded`);
|
||||
|
||||
// Display task information
|
||||
this.displayTaskInfo(task, options.dryRun || false);
|
||||
|
||||
// Execute autopilot logic (placeholder for now)
|
||||
const result = await this.performAutopilot(taskId, task, options);
|
||||
|
||||
// Store result for programmatic access
|
||||
this.setLastResult(result);
|
||||
|
||||
// Display results
|
||||
this.displayResults(result, options);
|
||||
} catch (error: unknown) {
|
||||
if (spinner) {
|
||||
spinner.fail('Operation failed');
|
||||
}
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate command options
|
||||
*/
|
||||
private validateOptions(options: AutopilotCommandOptions): boolean {
|
||||
// Validate format
|
||||
if (options.format && !['text', 'json'].includes(options.format)) {
|
||||
console.error(chalk.red(`Invalid format: ${options.format}`));
|
||||
console.error(chalk.gray(`Valid formats: text, json`));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate task ID format
|
||||
*/
|
||||
private validateTaskId(taskId: string): boolean {
|
||||
// Task ID should be a number or number.number format (e.g., "1" or "1.2")
|
||||
const taskIdPattern = /^\d+(\.\d+)*$/;
|
||||
return taskIdPattern.test(taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize TaskMasterCore
|
||||
*/
|
||||
private async initializeCore(projectRoot: string): Promise<void> {
|
||||
if (!this.tmCore) {
|
||||
this.tmCore = await createTaskMasterCore({ projectPath: projectRoot });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load task from tm-core
|
||||
*/
|
||||
private async loadTask(taskId: string): Promise<Task | null> {
|
||||
if (!this.tmCore) {
|
||||
throw new Error('TaskMasterCore not initialized');
|
||||
}
|
||||
|
||||
try {
|
||||
const { task } = await this.tmCore.getTaskWithSubtask(taskId);
|
||||
return task;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display task information before execution
|
||||
*/
|
||||
private displayTaskInfo(task: Task, isDryRun: boolean): void {
|
||||
const prefix = isDryRun ? '[DRY RUN] ' : '';
|
||||
console.log();
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.cyan.bold(`${prefix}Autopilot Task Execution`) +
|
||||
'\n\n' +
|
||||
chalk.white(`Task ID: ${task.id}`) +
|
||||
'\n' +
|
||||
chalk.white(`Title: ${task.title}`) +
|
||||
'\n' +
|
||||
chalk.white(`Status: ${task.status}`) +
|
||||
(task.description ? '\n\n' + chalk.gray(task.description) : ''),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'cyan',
|
||||
width: process.stdout.columns ? process.stdout.columns * 0.95 : 100
|
||||
}
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform autopilot execution using PreflightChecker and TaskLoader
|
||||
*/
|
||||
private async performAutopilot(
|
||||
taskId: string,
|
||||
task: Task,
|
||||
options: AutopilotCommandOptions
|
||||
): Promise<AutopilotCommandResult> {
|
||||
// Run preflight checks
|
||||
const preflightResult = await this.runPreflightChecks(options);
|
||||
if (!preflightResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
taskId,
|
||||
task,
|
||||
error: 'Preflight checks failed',
|
||||
message: 'Please resolve the issues above before running autopilot'
|
||||
};
|
||||
}
|
||||
|
||||
// Validate task structure and get execution order
|
||||
const validationResult = await this.validateTaskStructure(
|
||||
taskId,
|
||||
task,
|
||||
options
|
||||
);
|
||||
if (!validationResult.success) {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
// Display execution plan
|
||||
this.displayExecutionPlan(
|
||||
validationResult.task!,
|
||||
validationResult.orderedSubtasks!,
|
||||
options
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
taskId,
|
||||
task: validationResult.task,
|
||||
message: options.dryRun
|
||||
? 'Dry run completed successfully'
|
||||
: 'Autopilot execution ready (actual execution not yet implemented)'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Run preflight checks and display results
|
||||
*/
|
||||
private async runPreflightChecks(
|
||||
options: AutopilotCommandOptions
|
||||
): Promise<PreflightResult> {
|
||||
const { PreflightChecker } = await import('@tm/core');
|
||||
|
||||
console.log();
|
||||
console.log(chalk.cyan.bold('Running preflight checks...'));
|
||||
|
||||
const preflightChecker = new PreflightChecker(
|
||||
options.project || process.cwd()
|
||||
);
|
||||
const result = await preflightChecker.runAllChecks();
|
||||
|
||||
this.displayPreflightResults(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate task structure and get execution order
|
||||
*/
|
||||
private async validateTaskStructure(
|
||||
taskId: string,
|
||||
task: Task,
|
||||
options: AutopilotCommandOptions
|
||||
): Promise<AutopilotCommandResult & { orderedSubtasks?: Subtask[] }> {
|
||||
const { TaskLoaderService } = await import('@tm/core');
|
||||
|
||||
console.log();
|
||||
console.log(chalk.cyan.bold('Validating task structure...'));
|
||||
|
||||
const taskLoader = new TaskLoaderService(options.project || process.cwd());
|
||||
const validationResult = await taskLoader.loadAndValidateTask(taskId);
|
||||
|
||||
if (!validationResult.success) {
|
||||
await taskLoader.cleanup();
|
||||
return {
|
||||
success: false,
|
||||
taskId,
|
||||
task,
|
||||
error: validationResult.errorMessage,
|
||||
message: validationResult.suggestion
|
||||
};
|
||||
}
|
||||
|
||||
const orderedSubtasks = taskLoader.getExecutionOrder(
|
||||
validationResult.task!
|
||||
);
|
||||
|
||||
await taskLoader.cleanup();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
taskId,
|
||||
task: validationResult.task,
|
||||
orderedSubtasks
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display execution plan with subtasks and TDD workflow
|
||||
*/
|
||||
private displayExecutionPlan(
|
||||
task: Task,
|
||||
orderedSubtasks: Subtask[],
|
||||
options: AutopilotCommandOptions
|
||||
): void {
|
||||
console.log();
|
||||
console.log(chalk.green.bold('✓ All checks passed!'));
|
||||
console.log();
|
||||
console.log(chalk.cyan.bold('Execution Plan:'));
|
||||
console.log(chalk.white(`Task: ${task.title}`));
|
||||
console.log(
|
||||
chalk.gray(
|
||||
`${orderedSubtasks.length} subtasks will be executed in dependency order`
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
|
||||
// Display subtasks
|
||||
orderedSubtasks.forEach((subtask: Subtask, index: number) => {
|
||||
console.log(
|
||||
chalk.yellow(`${index + 1}. ${task.id}.${subtask.id}: ${subtask.title}`)
|
||||
);
|
||||
if (subtask.dependencies && subtask.dependencies.length > 0) {
|
||||
console.log(
|
||||
chalk.gray(` Dependencies: ${subtask.dependencies.join(', ')}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.cyan('Autopilot would execute each subtask using TDD workflow:')
|
||||
);
|
||||
console.log(chalk.gray(' 1. RED phase: Write failing test'));
|
||||
console.log(chalk.gray(' 2. GREEN phase: Implement code to pass test'));
|
||||
console.log(chalk.gray(' 3. COMMIT phase: Commit changes'));
|
||||
console.log();
|
||||
|
||||
if (options.dryRun) {
|
||||
console.log(
|
||||
chalk.yellow('This was a dry run. Use without --dry-run to execute.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display preflight check results
|
||||
*/
|
||||
private displayPreflightResults(result: PreflightResult): void {
|
||||
const checks = [
|
||||
{ name: 'Test command', result: result.testCommand },
|
||||
{ name: 'Git working tree', result: result.gitWorkingTree },
|
||||
{ name: 'Required tools', result: result.requiredTools },
|
||||
{ name: 'Default branch', result: result.defaultBranch }
|
||||
];
|
||||
|
||||
checks.forEach((check) => {
|
||||
const icon = check.result.success ? chalk.green('✓') : chalk.red('✗');
|
||||
const status = check.result.success
|
||||
? chalk.green('PASS')
|
||||
: chalk.red('FAIL');
|
||||
console.log(`${icon} ${chalk.white(check.name)}: ${status}`);
|
||||
if (check.result.message) {
|
||||
console.log(chalk.gray(` ${check.result.message}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display results based on format
|
||||
*/
|
||||
private displayResults(
|
||||
result: AutopilotCommandResult,
|
||||
options: AutopilotCommandOptions
|
||||
): void {
|
||||
const format = options.format || 'text';
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
this.displayJson(result);
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
default:
|
||||
this.displayTextResult(result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display in JSON format
|
||||
*/
|
||||
private displayJson(result: AutopilotCommandResult): void {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display result in text format
|
||||
*/
|
||||
private displayTextResult(result: AutopilotCommandResult): void {
|
||||
if (result.success) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green.bold('✓ Autopilot Command Completed') +
|
||||
'\n\n' +
|
||||
chalk.white(result.message || 'Execution complete'),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'green',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.red.bold('✗ Autopilot Command Failed') +
|
||||
'\n\n' +
|
||||
chalk.white(result.error || 'Unknown error'),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'red',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle general errors
|
||||
*/
|
||||
private handleError(error: unknown): void {
|
||||
const errorObj = error as {
|
||||
getSanitizedDetails?: () => { message: string };
|
||||
message?: string;
|
||||
stack?: string;
|
||||
};
|
||||
|
||||
const msg = errorObj?.getSanitizedDetails?.() ?? {
|
||||
message: errorObj?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
|
||||
// Show stack trace in development mode or when DEBUG is set
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
if ((isDevelopment || process.env.DEBUG) && errorObj.stack) {
|
||||
console.error(chalk.gray(errorObj.stack));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
private setLastResult(result: AutopilotCommandResult): void {
|
||||
this.lastResult = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last result (for programmatic usage)
|
||||
*/
|
||||
getLastResult(): AutopilotCommandResult | undefined {
|
||||
return this.lastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
if (this.tmCore) {
|
||||
await this.tmCore.close();
|
||||
this.tmCore = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): AutopilotCommand {
|
||||
const autopilotCommand = new AutopilotCommand(name);
|
||||
program.addCommand(autopilotCommand);
|
||||
return autopilotCommand;
|
||||
}
|
||||
}
|
||||
119
apps/cli/src/commands/autopilot/abort.command.ts
Normal file
119
apps/cli/src/commands/autopilot/abort.command.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @fileoverview Abort Command - Safely terminate workflow
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { WorkflowOrchestrator } from '@tm/core';
|
||||
import {
|
||||
AutopilotBaseOptions,
|
||||
hasWorkflowState,
|
||||
loadWorkflowState,
|
||||
deleteWorkflowState,
|
||||
OutputFormatter
|
||||
} from './shared.js';
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
interface AbortOptions extends AutopilotBaseOptions {
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort Command - Safely terminate workflow and clean up state
|
||||
*/
|
||||
export class AbortCommand extends Command {
|
||||
constructor() {
|
||||
super('abort');
|
||||
|
||||
this.description('Abort the current TDD workflow and clean up state')
|
||||
.option('-f, --force', 'Force abort without confirmation')
|
||||
.action(async (options: AbortOptions) => {
|
||||
await this.execute(options);
|
||||
});
|
||||
}
|
||||
|
||||
private async execute(options: AbortOptions): Promise<void> {
|
||||
// Inherit parent options
|
||||
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
|
||||
const mergedOptions: AbortOptions = {
|
||||
...parentOpts,
|
||||
...options,
|
||||
projectRoot:
|
||||
options.projectRoot || parentOpts?.projectRoot || process.cwd()
|
||||
};
|
||||
|
||||
const formatter = new OutputFormatter(mergedOptions.json || false);
|
||||
|
||||
try {
|
||||
// Check for workflow state
|
||||
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!hasState) {
|
||||
formatter.warning('No active workflow to abort');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load state
|
||||
const state = await loadWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!state) {
|
||||
formatter.error('Failed to load workflow state');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Restore orchestrator
|
||||
const orchestrator = new WorkflowOrchestrator(state.context);
|
||||
orchestrator.restoreState(state);
|
||||
|
||||
// Get progress before abort
|
||||
const progress = orchestrator.getProgress();
|
||||
const currentSubtask = orchestrator.getCurrentSubtask();
|
||||
|
||||
// Confirm abort if not forced or in JSON mode
|
||||
if (!mergedOptions.force && !mergedOptions.json) {
|
||||
const { confirmed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirmed',
|
||||
message:
|
||||
`This will abort the workflow for task ${state.context.taskId}. ` +
|
||||
`Progress: ${progress.completed}/${progress.total} subtasks completed. ` +
|
||||
`Continue?`,
|
||||
default: false
|
||||
}
|
||||
]);
|
||||
|
||||
if (!confirmed) {
|
||||
formatter.info('Abort cancelled');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger abort in orchestrator
|
||||
orchestrator.transition({ type: 'ABORT' });
|
||||
|
||||
// Delete workflow state
|
||||
await deleteWorkflowState(mergedOptions.projectRoot!);
|
||||
|
||||
// Output result
|
||||
formatter.success('Workflow aborted', {
|
||||
taskId: state.context.taskId,
|
||||
branchName: state.context.branchName,
|
||||
progress: {
|
||||
completed: progress.completed,
|
||||
total: progress.total
|
||||
},
|
||||
lastSubtask: currentSubtask
|
||||
? {
|
||||
id: currentSubtask.id,
|
||||
title: currentSubtask.title
|
||||
}
|
||||
: null,
|
||||
note: 'Branch and commits remain. Clean up manually if needed.'
|
||||
});
|
||||
} catch (error) {
|
||||
formatter.error((error as Error).message);
|
||||
if (mergedOptions.verbose) {
|
||||
console.error((error as Error).stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
169
apps/cli/src/commands/autopilot/commit.command.ts
Normal file
169
apps/cli/src/commands/autopilot/commit.command.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @fileoverview Commit Command - Create commit with enhanced message generation
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { WorkflowOrchestrator } from '@tm/core';
|
||||
import {
|
||||
AutopilotBaseOptions,
|
||||
hasWorkflowState,
|
||||
loadWorkflowState,
|
||||
createGitAdapter,
|
||||
createCommitMessageGenerator,
|
||||
OutputFormatter,
|
||||
saveWorkflowState
|
||||
} from './shared.js';
|
||||
|
||||
type CommitOptions = AutopilotBaseOptions;
|
||||
|
||||
/**
|
||||
* Commit Command - Create commit using enhanced message generator
|
||||
*/
|
||||
export class CommitCommand extends Command {
|
||||
constructor() {
|
||||
super('commit');
|
||||
|
||||
this.description('Create a commit for the completed GREEN phase').action(
|
||||
async (options: CommitOptions) => {
|
||||
await this.execute(options);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async execute(options: CommitOptions): Promise<void> {
|
||||
// Inherit parent options
|
||||
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
|
||||
const mergedOptions: CommitOptions = {
|
||||
...parentOpts,
|
||||
...options,
|
||||
projectRoot:
|
||||
options.projectRoot || parentOpts?.projectRoot || process.cwd()
|
||||
};
|
||||
|
||||
const formatter = new OutputFormatter(mergedOptions.json || false);
|
||||
|
||||
try {
|
||||
// Check for workflow state
|
||||
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!hasState) {
|
||||
formatter.error('No active workflow', {
|
||||
suggestion: 'Start a workflow with: autopilot start <taskId>'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load state
|
||||
const state = await loadWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!state) {
|
||||
formatter.error('Failed to load workflow state');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const orchestrator = new WorkflowOrchestrator(state.context);
|
||||
orchestrator.restoreState(state);
|
||||
orchestrator.enableAutoPersist(async (newState) => {
|
||||
await saveWorkflowState(mergedOptions.projectRoot!, newState);
|
||||
});
|
||||
|
||||
// Verify in COMMIT phase
|
||||
const tddPhase = orchestrator.getCurrentTDDPhase();
|
||||
if (tddPhase !== 'COMMIT') {
|
||||
formatter.error('Not in COMMIT phase', {
|
||||
currentPhase: tddPhase || orchestrator.getCurrentPhase(),
|
||||
suggestion: 'Complete RED and GREEN phases first'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Get current subtask
|
||||
const currentSubtask = orchestrator.getCurrentSubtask();
|
||||
if (!currentSubtask) {
|
||||
formatter.error('No current subtask');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize git adapter
|
||||
const gitAdapter = createGitAdapter(mergedOptions.projectRoot!);
|
||||
await gitAdapter.ensureGitRepository();
|
||||
|
||||
// Check for staged changes
|
||||
const hasStagedChanges = await gitAdapter.hasStagedChanges();
|
||||
if (!hasStagedChanges) {
|
||||
// Stage all changes
|
||||
formatter.info('No staged changes, staging all changes...');
|
||||
await gitAdapter.stageFiles(['.']);
|
||||
}
|
||||
|
||||
// Get changed files for scope detection
|
||||
const status = await gitAdapter.getStatus();
|
||||
const changedFiles = [...status.staged, ...status.modified];
|
||||
|
||||
// Generate commit message
|
||||
const messageGenerator = createCommitMessageGenerator();
|
||||
const testResults = state.context.lastTestResults;
|
||||
|
||||
const commitMessage = messageGenerator.generateMessage({
|
||||
type: 'feat',
|
||||
description: currentSubtask.title,
|
||||
changedFiles,
|
||||
taskId: state.context.taskId,
|
||||
phase: 'TDD',
|
||||
tag: (state.context.metadata.tag as string) || undefined,
|
||||
testsPassing: testResults?.passed,
|
||||
testsFailing: testResults?.failed,
|
||||
coveragePercent: undefined // Could be added if available
|
||||
});
|
||||
|
||||
// Create commit with metadata
|
||||
await gitAdapter.createCommit(commitMessage, {
|
||||
metadata: {
|
||||
taskId: state.context.taskId,
|
||||
subtaskId: currentSubtask.id,
|
||||
phase: 'COMMIT',
|
||||
tddCycle: 'complete'
|
||||
}
|
||||
});
|
||||
|
||||
// Get commit info
|
||||
const lastCommit = await gitAdapter.getLastCommit();
|
||||
|
||||
// Complete COMMIT phase (this marks subtask as completed)
|
||||
orchestrator.transition({ type: 'COMMIT_COMPLETE' });
|
||||
|
||||
// Check if should advance to next subtask
|
||||
const progress = orchestrator.getProgress();
|
||||
if (progress.current < progress.total) {
|
||||
orchestrator.transition({ type: 'SUBTASK_COMPLETE' });
|
||||
} else {
|
||||
// All subtasks complete
|
||||
orchestrator.transition({ type: 'ALL_SUBTASKS_COMPLETE' });
|
||||
}
|
||||
|
||||
// Output success
|
||||
formatter.success('Commit created', {
|
||||
commitHash: lastCommit.hash.substring(0, 7),
|
||||
message: commitMessage.split('\n')[0], // First line only
|
||||
subtask: {
|
||||
id: currentSubtask.id,
|
||||
title: currentSubtask.title,
|
||||
status: currentSubtask.status
|
||||
},
|
||||
progress: {
|
||||
completed: progress.completed,
|
||||
total: progress.total,
|
||||
percentage: progress.percentage
|
||||
},
|
||||
nextAction:
|
||||
progress.completed < progress.total
|
||||
? 'Start next subtask with RED phase'
|
||||
: 'All subtasks complete. Run: autopilot status'
|
||||
});
|
||||
} catch (error) {
|
||||
formatter.error((error as Error).message);
|
||||
if (mergedOptions.verbose) {
|
||||
console.error((error as Error).stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
172
apps/cli/src/commands/autopilot/complete.command.ts
Normal file
172
apps/cli/src/commands/autopilot/complete.command.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @fileoverview Complete Command - Complete current TDD phase with validation
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { WorkflowOrchestrator, TestResult } from '@tm/core';
|
||||
import {
|
||||
AutopilotBaseOptions,
|
||||
hasWorkflowState,
|
||||
loadWorkflowState,
|
||||
OutputFormatter
|
||||
} from './shared.js';
|
||||
|
||||
interface CompleteOptions extends AutopilotBaseOptions {
|
||||
results?: string;
|
||||
coverage?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete Command - Mark current phase as complete with validation
|
||||
*/
|
||||
export class CompleteCommand extends Command {
|
||||
constructor() {
|
||||
super('complete');
|
||||
|
||||
this.description('Complete the current TDD phase with result validation')
|
||||
.option(
|
||||
'-r, --results <json>',
|
||||
'Test results JSON (with total, passed, failed, skipped)'
|
||||
)
|
||||
.option('-c, --coverage <percent>', 'Coverage percentage')
|
||||
.action(async (options: CompleteOptions) => {
|
||||
await this.execute(options);
|
||||
});
|
||||
}
|
||||
|
||||
private async execute(options: CompleteOptions): Promise<void> {
|
||||
// Inherit parent options
|
||||
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
|
||||
const mergedOptions: CompleteOptions = {
|
||||
...parentOpts,
|
||||
...options,
|
||||
projectRoot:
|
||||
options.projectRoot || parentOpts?.projectRoot || process.cwd()
|
||||
};
|
||||
|
||||
const formatter = new OutputFormatter(mergedOptions.json || false);
|
||||
|
||||
try {
|
||||
// Check for workflow state
|
||||
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!hasState) {
|
||||
formatter.error('No active workflow', {
|
||||
suggestion: 'Start a workflow with: autopilot start <taskId>'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load state
|
||||
const state = await loadWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!state) {
|
||||
formatter.error('Failed to load workflow state');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Restore orchestrator with persistence
|
||||
const { saveWorkflowState } = await import('./shared.js');
|
||||
const orchestrator = new WorkflowOrchestrator(state.context);
|
||||
orchestrator.restoreState(state);
|
||||
orchestrator.enableAutoPersist(async (newState) => {
|
||||
await saveWorkflowState(mergedOptions.projectRoot!, newState);
|
||||
});
|
||||
|
||||
// Get current phase
|
||||
const tddPhase = orchestrator.getCurrentTDDPhase();
|
||||
const currentSubtask = orchestrator.getCurrentSubtask();
|
||||
|
||||
if (!tddPhase) {
|
||||
formatter.error('Not in a TDD phase', {
|
||||
phase: orchestrator.getCurrentPhase()
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate based on phase
|
||||
if (tddPhase === 'RED' || tddPhase === 'GREEN') {
|
||||
if (!mergedOptions.results) {
|
||||
formatter.error('Test results required for RED/GREEN phase', {
|
||||
usage:
|
||||
'--results \'{"total":10,"passed":9,"failed":1,"skipped":0}\''
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse test results
|
||||
let testResults: TestResult;
|
||||
try {
|
||||
const parsed = JSON.parse(mergedOptions.results);
|
||||
testResults = {
|
||||
total: parsed.total || 0,
|
||||
passed: parsed.passed || 0,
|
||||
failed: parsed.failed || 0,
|
||||
skipped: parsed.skipped || 0,
|
||||
phase: tddPhase
|
||||
};
|
||||
} catch (error) {
|
||||
formatter.error('Invalid test results JSON', {
|
||||
error: (error as Error).message
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate RED phase requirements
|
||||
if (tddPhase === 'RED' && testResults.failed === 0) {
|
||||
formatter.error('RED phase validation failed', {
|
||||
reason: 'At least one test must be failing',
|
||||
actual: {
|
||||
passed: testResults.passed,
|
||||
failed: testResults.failed
|
||||
}
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate GREEN phase requirements
|
||||
if (tddPhase === 'GREEN' && testResults.failed !== 0) {
|
||||
formatter.error('GREEN phase validation failed', {
|
||||
reason: 'All tests must pass',
|
||||
actual: {
|
||||
passed: testResults.passed,
|
||||
failed: testResults.failed
|
||||
}
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Complete phase with test results
|
||||
if (tddPhase === 'RED') {
|
||||
orchestrator.transition({
|
||||
type: 'RED_PHASE_COMPLETE',
|
||||
testResults
|
||||
});
|
||||
formatter.success('RED phase completed', {
|
||||
nextPhase: 'GREEN',
|
||||
testResults,
|
||||
subtask: currentSubtask?.title
|
||||
});
|
||||
} else {
|
||||
orchestrator.transition({
|
||||
type: 'GREEN_PHASE_COMPLETE',
|
||||
testResults
|
||||
});
|
||||
formatter.success('GREEN phase completed', {
|
||||
nextPhase: 'COMMIT',
|
||||
testResults,
|
||||
subtask: currentSubtask?.title,
|
||||
suggestion: 'Run: autopilot commit'
|
||||
});
|
||||
}
|
||||
} else if (tddPhase === 'COMMIT') {
|
||||
formatter.error('Use "autopilot commit" to complete COMMIT phase');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
formatter.error((error as Error).message);
|
||||
if (mergedOptions.verbose) {
|
||||
console.error((error as Error).stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
apps/cli/src/commands/autopilot/index.ts
Normal file
82
apps/cli/src/commands/autopilot/index.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @fileoverview Autopilot CLI Commands for AI Agent Orchestration
|
||||
* Provides subcommands for starting, resuming, and advancing the TDD workflow
|
||||
* with JSON output for machine parsing.
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { StartCommand } from './start.command.js';
|
||||
import { ResumeCommand } from './resume.command.js';
|
||||
import { NextCommand } from './next.command.js';
|
||||
import { CompleteCommand } from './complete.command.js';
|
||||
import { CommitCommand } from './commit.command.js';
|
||||
import { StatusCommand } from './status.command.js';
|
||||
import { AbortCommand } from './abort.command.js';
|
||||
|
||||
/**
|
||||
* Shared command options for all autopilot commands
|
||||
*/
|
||||
export interface AutopilotBaseOptions {
|
||||
json?: boolean;
|
||||
verbose?: boolean;
|
||||
projectRoot?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* AutopilotCommand with subcommands for TDD workflow orchestration
|
||||
*/
|
||||
export class AutopilotCommand extends Command {
|
||||
constructor() {
|
||||
super('autopilot');
|
||||
|
||||
// Configure main command
|
||||
this.description('AI agent orchestration for TDD workflow execution')
|
||||
.alias('ap')
|
||||
// Global options for all subcommands
|
||||
.option('--json', 'Output in JSON format for machine parsing')
|
||||
.option('-v, --verbose', 'Enable verbose output')
|
||||
.option(
|
||||
'-p, --project-root <path>',
|
||||
'Project root directory',
|
||||
process.cwd()
|
||||
);
|
||||
|
||||
// Register subcommands
|
||||
this.registerSubcommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all autopilot subcommands
|
||||
*/
|
||||
private registerSubcommands(): void {
|
||||
// Start new TDD workflow
|
||||
this.addCommand(new StartCommand());
|
||||
|
||||
// Resume existing workflow
|
||||
this.addCommand(new ResumeCommand());
|
||||
|
||||
// Get next action
|
||||
this.addCommand(new NextCommand());
|
||||
|
||||
// Complete current phase
|
||||
this.addCommand(new CompleteCommand());
|
||||
|
||||
// Create commit
|
||||
this.addCommand(new CommitCommand());
|
||||
|
||||
// Show status
|
||||
this.addCommand(new StatusCommand());
|
||||
|
||||
// Abort workflow
|
||||
this.addCommand(new AbortCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command): AutopilotCommand {
|
||||
const autopilotCommand = new AutopilotCommand();
|
||||
program.addCommand(autopilotCommand);
|
||||
return autopilotCommand;
|
||||
}
|
||||
}
|
||||
164
apps/cli/src/commands/autopilot/next.command.ts
Normal file
164
apps/cli/src/commands/autopilot/next.command.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @fileoverview Next Command - Get next action in TDD workflow
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { WorkflowOrchestrator } from '@tm/core';
|
||||
import {
|
||||
AutopilotBaseOptions,
|
||||
hasWorkflowState,
|
||||
loadWorkflowState,
|
||||
OutputFormatter
|
||||
} from './shared.js';
|
||||
|
||||
type NextOptions = AutopilotBaseOptions;
|
||||
|
||||
/**
|
||||
* Next Command - Get next action details
|
||||
*/
|
||||
export class NextCommand extends Command {
|
||||
constructor() {
|
||||
super('next');
|
||||
|
||||
this.description(
|
||||
'Get the next action to perform in the TDD workflow'
|
||||
).action(async (options: NextOptions) => {
|
||||
await this.execute(options);
|
||||
});
|
||||
}
|
||||
|
||||
private async execute(options: NextOptions): Promise<void> {
|
||||
// Inherit parent options
|
||||
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
|
||||
const mergedOptions: NextOptions = {
|
||||
...parentOpts,
|
||||
...options,
|
||||
projectRoot:
|
||||
options.projectRoot || parentOpts?.projectRoot || process.cwd()
|
||||
};
|
||||
|
||||
const formatter = new OutputFormatter(mergedOptions.json || false);
|
||||
|
||||
try {
|
||||
// Check for workflow state
|
||||
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!hasState) {
|
||||
formatter.error('No active workflow', {
|
||||
suggestion: 'Start a workflow with: autopilot start <taskId>'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load state
|
||||
const state = await loadWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!state) {
|
||||
formatter.error('Failed to load workflow state');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Restore orchestrator
|
||||
const orchestrator = new WorkflowOrchestrator(state.context);
|
||||
orchestrator.restoreState(state);
|
||||
|
||||
// Get current phase and subtask
|
||||
const phase = orchestrator.getCurrentPhase();
|
||||
const tddPhase = orchestrator.getCurrentTDDPhase();
|
||||
const currentSubtask = orchestrator.getCurrentSubtask();
|
||||
|
||||
// Determine next action based on phase
|
||||
let actionType: string;
|
||||
let actionDescription: string;
|
||||
let actionDetails: Record<string, unknown> = {};
|
||||
|
||||
if (phase === 'COMPLETE') {
|
||||
formatter.success('Workflow complete', {
|
||||
message: 'All subtasks have been completed',
|
||||
taskId: state.context.taskId
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (phase === 'SUBTASK_LOOP' && tddPhase) {
|
||||
switch (tddPhase) {
|
||||
case 'RED':
|
||||
actionType = 'generate_test';
|
||||
actionDescription = 'Write failing test for current subtask';
|
||||
actionDetails = {
|
||||
subtask: currentSubtask
|
||||
? {
|
||||
id: currentSubtask.id,
|
||||
title: currentSubtask.title,
|
||||
attempts: currentSubtask.attempts
|
||||
}
|
||||
: null,
|
||||
testCommand: 'npm test', // Could be customized based on config
|
||||
expectedOutcome: 'Test should fail'
|
||||
};
|
||||
break;
|
||||
|
||||
case 'GREEN':
|
||||
actionType = 'implement_code';
|
||||
actionDescription = 'Implement code to pass the failing test';
|
||||
actionDetails = {
|
||||
subtask: currentSubtask
|
||||
? {
|
||||
id: currentSubtask.id,
|
||||
title: currentSubtask.title,
|
||||
attempts: currentSubtask.attempts
|
||||
}
|
||||
: null,
|
||||
testCommand: 'npm test',
|
||||
expectedOutcome: 'All tests should pass',
|
||||
lastTestResults: state.context.lastTestResults
|
||||
};
|
||||
break;
|
||||
|
||||
case 'COMMIT':
|
||||
actionType = 'commit_changes';
|
||||
actionDescription = 'Commit the changes';
|
||||
actionDetails = {
|
||||
subtask: currentSubtask
|
||||
? {
|
||||
id: currentSubtask.id,
|
||||
title: currentSubtask.title,
|
||||
attempts: currentSubtask.attempts
|
||||
}
|
||||
: null,
|
||||
suggestion: 'Use: autopilot commit'
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
actionType = 'unknown';
|
||||
actionDescription = 'Unknown TDD phase';
|
||||
}
|
||||
} else {
|
||||
actionType = 'workflow_phase';
|
||||
actionDescription = `Currently in ${phase} phase`;
|
||||
}
|
||||
|
||||
// Output next action
|
||||
const output = {
|
||||
action: actionType,
|
||||
description: actionDescription,
|
||||
phase,
|
||||
tddPhase,
|
||||
taskId: state.context.taskId,
|
||||
branchName: state.context.branchName,
|
||||
...actionDetails
|
||||
};
|
||||
|
||||
if (mergedOptions.json) {
|
||||
formatter.output(output);
|
||||
} else {
|
||||
formatter.success('Next action', output);
|
||||
}
|
||||
} catch (error) {
|
||||
formatter.error((error as Error).message);
|
||||
if (mergedOptions.verbose) {
|
||||
console.error((error as Error).stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
apps/cli/src/commands/autopilot/resume.command.ts
Normal file
111
apps/cli/src/commands/autopilot/resume.command.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* @fileoverview Resume Command - Restore and resume TDD workflow
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { WorkflowOrchestrator } from '@tm/core';
|
||||
import {
|
||||
AutopilotBaseOptions,
|
||||
hasWorkflowState,
|
||||
loadWorkflowState,
|
||||
OutputFormatter
|
||||
} from './shared.js';
|
||||
|
||||
type ResumeOptions = AutopilotBaseOptions;
|
||||
|
||||
/**
|
||||
* Resume Command - Restore workflow from saved state
|
||||
*/
|
||||
export class ResumeCommand extends Command {
|
||||
constructor() {
|
||||
super('resume');
|
||||
|
||||
this.description('Resume a previously started TDD workflow').action(
|
||||
async (options: ResumeOptions) => {
|
||||
await this.execute(options);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async execute(options: ResumeOptions): Promise<void> {
|
||||
// Inherit parent options (autopilot command)
|
||||
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
|
||||
const mergedOptions: ResumeOptions = {
|
||||
...parentOpts,
|
||||
...options,
|
||||
projectRoot:
|
||||
options.projectRoot || parentOpts?.projectRoot || process.cwd()
|
||||
};
|
||||
|
||||
const formatter = new OutputFormatter(mergedOptions.json || false);
|
||||
|
||||
try {
|
||||
// Check for workflow state
|
||||
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!hasState) {
|
||||
formatter.error('No workflow state found', {
|
||||
suggestion: 'Start a new workflow with: autopilot start <taskId>'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load state
|
||||
formatter.info('Loading workflow state...');
|
||||
const state = await loadWorkflowState(mergedOptions.projectRoot!);
|
||||
|
||||
if (!state) {
|
||||
formatter.error('Failed to load workflow state');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate state can be resumed
|
||||
const orchestrator = new WorkflowOrchestrator(state.context);
|
||||
if (!orchestrator.canResumeFromState(state)) {
|
||||
formatter.error('Invalid workflow state', {
|
||||
suggestion:
|
||||
'State file may be corrupted. Consider starting a new workflow.'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Restore state
|
||||
orchestrator.restoreState(state);
|
||||
|
||||
// Re-enable auto-persistence
|
||||
const { saveWorkflowState } = await import('./shared.js');
|
||||
orchestrator.enableAutoPersist(async (newState) => {
|
||||
await saveWorkflowState(mergedOptions.projectRoot!, newState);
|
||||
});
|
||||
|
||||
// Get progress
|
||||
const progress = orchestrator.getProgress();
|
||||
const currentSubtask = orchestrator.getCurrentSubtask();
|
||||
|
||||
// Output success
|
||||
formatter.success('Workflow resumed', {
|
||||
taskId: state.context.taskId,
|
||||
phase: orchestrator.getCurrentPhase(),
|
||||
tddPhase: orchestrator.getCurrentTDDPhase(),
|
||||
branchName: state.context.branchName,
|
||||
progress: {
|
||||
completed: progress.completed,
|
||||
total: progress.total,
|
||||
percentage: progress.percentage
|
||||
},
|
||||
currentSubtask: currentSubtask
|
||||
? {
|
||||
id: currentSubtask.id,
|
||||
title: currentSubtask.title,
|
||||
attempts: currentSubtask.attempts
|
||||
}
|
||||
: null
|
||||
});
|
||||
} catch (error) {
|
||||
formatter.error((error as Error).message);
|
||||
if (mergedOptions.verbose) {
|
||||
console.error((error as Error).stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
262
apps/cli/src/commands/autopilot/shared.ts
Normal file
262
apps/cli/src/commands/autopilot/shared.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* @fileoverview Shared utilities for autopilot commands
|
||||
*/
|
||||
|
||||
import {
|
||||
WorkflowOrchestrator,
|
||||
WorkflowStateManager,
|
||||
GitAdapter,
|
||||
CommitMessageGenerator
|
||||
} from '@tm/core';
|
||||
import type { WorkflowState, WorkflowContext, SubtaskInfo } from '@tm/core';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Base options interface for all autopilot commands
|
||||
*/
|
||||
export interface AutopilotBaseOptions {
|
||||
projectRoot?: string;
|
||||
json?: boolean;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load workflow state from disk using WorkflowStateManager
|
||||
*/
|
||||
export async function loadWorkflowState(
|
||||
projectRoot: string
|
||||
): Promise<WorkflowState | null> {
|
||||
const stateManager = new WorkflowStateManager(projectRoot);
|
||||
|
||||
if (!(await stateManager.exists())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await stateManager.load();
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to load workflow state: ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save workflow state to disk using WorkflowStateManager
|
||||
*/
|
||||
export async function saveWorkflowState(
|
||||
projectRoot: string,
|
||||
state: WorkflowState
|
||||
): Promise<void> {
|
||||
const stateManager = new WorkflowStateManager(projectRoot);
|
||||
|
||||
try {
|
||||
await stateManager.save(state);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to save workflow state: ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete workflow state from disk using WorkflowStateManager
|
||||
*/
|
||||
export async function deleteWorkflowState(projectRoot: string): Promise<void> {
|
||||
const stateManager = new WorkflowStateManager(projectRoot);
|
||||
await stateManager.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if workflow state exists using WorkflowStateManager
|
||||
*/
|
||||
export async function hasWorkflowState(projectRoot: string): Promise<boolean> {
|
||||
const stateManager = new WorkflowStateManager(projectRoot);
|
||||
return await stateManager.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WorkflowOrchestrator with persistence
|
||||
*/
|
||||
export function createOrchestrator(
|
||||
context: WorkflowContext,
|
||||
projectRoot: string
|
||||
): WorkflowOrchestrator {
|
||||
const orchestrator = new WorkflowOrchestrator(context);
|
||||
const stateManager = new WorkflowStateManager(projectRoot);
|
||||
|
||||
// Enable auto-persistence
|
||||
orchestrator.enableAutoPersist(async (state: WorkflowState) => {
|
||||
await stateManager.save(state);
|
||||
});
|
||||
|
||||
return orchestrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize GitAdapter for project
|
||||
*/
|
||||
export function createGitAdapter(projectRoot: string): GitAdapter {
|
||||
return new GitAdapter(projectRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize CommitMessageGenerator
|
||||
*/
|
||||
export function createCommitMessageGenerator(): CommitMessageGenerator {
|
||||
return new CommitMessageGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output formatter for JSON and text modes
|
||||
*/
|
||||
export class OutputFormatter {
|
||||
constructor(private useJson: boolean) {}
|
||||
|
||||
/**
|
||||
* Output data in appropriate format
|
||||
*/
|
||||
output(data: Record<string, unknown>): void {
|
||||
if (this.useJson) {
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
this.outputText(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output data in human-readable text format
|
||||
*/
|
||||
private outputText(data: Record<string, unknown>): void {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
console.log(chalk.cyan(`${key}:`));
|
||||
this.outputObject(value as Record<string, unknown>, ' ');
|
||||
} else {
|
||||
console.log(chalk.white(`${key}: ${value}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output nested object with indentation
|
||||
*/
|
||||
private outputObject(obj: Record<string, unknown>, indent: string): void {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
console.log(chalk.cyan(`${indent}${key}:`));
|
||||
this.outputObject(value as Record<string, unknown>, indent + ' ');
|
||||
} else {
|
||||
console.log(chalk.gray(`${indent}${key}: ${value}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output error message
|
||||
*/
|
||||
error(message: string, details?: Record<string, unknown>): void {
|
||||
if (this.useJson) {
|
||||
console.error(
|
||||
JSON.stringify(
|
||||
{
|
||||
error: message,
|
||||
...details
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.error(chalk.red(`Error: ${message}`));
|
||||
if (details) {
|
||||
for (const [key, value] of Object.entries(details)) {
|
||||
console.error(chalk.gray(` ${key}: ${value}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output success message
|
||||
*/
|
||||
success(message: string, data?: Record<string, unknown>): void {
|
||||
if (this.useJson) {
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
success: true,
|
||||
message,
|
||||
...data
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.green(`✓ ${message}`));
|
||||
if (data) {
|
||||
this.output(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output warning message
|
||||
*/
|
||||
warning(message: string): void {
|
||||
if (this.useJson) {
|
||||
console.warn(
|
||||
JSON.stringify(
|
||||
{
|
||||
warning: message
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.warn(chalk.yellow(`⚠ ${message}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output info message
|
||||
*/
|
||||
info(message: string): void {
|
||||
if (this.useJson) {
|
||||
// Don't output info messages in JSON mode
|
||||
return;
|
||||
}
|
||||
console.log(chalk.blue(`ℹ ${message}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate task ID format
|
||||
*/
|
||||
export function validateTaskId(taskId: string): boolean {
|
||||
// Task ID should be in format: number or number.number (e.g., "1" or "1.2")
|
||||
const pattern = /^\d+(\.\d+)*$/;
|
||||
return pattern.test(taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse subtasks from task data
|
||||
*/
|
||||
export function parseSubtasks(
|
||||
task: any,
|
||||
maxAttempts: number = 3
|
||||
): SubtaskInfo[] {
|
||||
if (!task.subtasks || !Array.isArray(task.subtasks)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return task.subtasks.map((subtask: any) => ({
|
||||
id: subtask.id,
|
||||
title: subtask.title,
|
||||
status: subtask.status === 'done' ? 'completed' : 'pending',
|
||||
attempts: 0,
|
||||
maxAttempts
|
||||
}));
|
||||
}
|
||||
168
apps/cli/src/commands/autopilot/start.command.ts
Normal file
168
apps/cli/src/commands/autopilot/start.command.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* @fileoverview Start Command - Initialize and start TDD workflow
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { createTaskMasterCore, type WorkflowContext } from '@tm/core';
|
||||
import {
|
||||
AutopilotBaseOptions,
|
||||
hasWorkflowState,
|
||||
createOrchestrator,
|
||||
createGitAdapter,
|
||||
OutputFormatter,
|
||||
validateTaskId,
|
||||
parseSubtasks
|
||||
} from './shared.js';
|
||||
|
||||
interface StartOptions extends AutopilotBaseOptions {
|
||||
force?: boolean;
|
||||
maxAttempts?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Command - Initialize new TDD workflow
|
||||
*/
|
||||
export class StartCommand extends Command {
|
||||
constructor() {
|
||||
super('start');
|
||||
|
||||
this.description('Initialize and start a new TDD workflow for a task')
|
||||
.argument('<taskId>', 'Task ID to start workflow for')
|
||||
.option('-f, --force', 'Force start even if workflow state exists')
|
||||
.option('--max-attempts <number>', 'Maximum attempts per subtask', '3')
|
||||
.action(async (taskId: string, options: StartOptions) => {
|
||||
await this.execute(taskId, options);
|
||||
});
|
||||
}
|
||||
|
||||
private async execute(taskId: string, options: StartOptions): Promise<void> {
|
||||
// Inherit parent options
|
||||
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
|
||||
const mergedOptions: StartOptions = {
|
||||
...parentOpts,
|
||||
...options,
|
||||
projectRoot:
|
||||
options.projectRoot || parentOpts?.projectRoot || process.cwd()
|
||||
};
|
||||
|
||||
const formatter = new OutputFormatter(mergedOptions.json || false);
|
||||
|
||||
try {
|
||||
// Validate task ID
|
||||
if (!validateTaskId(taskId)) {
|
||||
formatter.error('Invalid task ID format', {
|
||||
taskId,
|
||||
expected: 'Format: number or number.number (e.g., "1" or "1.2")'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for existing workflow state
|
||||
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
|
||||
if (hasState && !mergedOptions.force) {
|
||||
formatter.error(
|
||||
'Workflow state already exists. Use --force to overwrite or resume with "autopilot resume"'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize Task Master Core
|
||||
const tmCore = await createTaskMasterCore({
|
||||
projectPath: mergedOptions.projectRoot!
|
||||
});
|
||||
|
||||
// Get current tag from ConfigManager
|
||||
const currentTag = tmCore.getActiveTag();
|
||||
|
||||
// Load task
|
||||
formatter.info(`Loading task ${taskId}...`);
|
||||
const { task } = await tmCore.getTaskWithSubtask(taskId);
|
||||
|
||||
if (!task) {
|
||||
formatter.error('Task not found', { taskId });
|
||||
await tmCore.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Validate task has subtasks
|
||||
if (!task.subtasks || task.subtasks.length === 0) {
|
||||
formatter.error('Task has no subtasks. Expand task first.', {
|
||||
taskId,
|
||||
suggestion: `Run: task-master expand --id=${taskId}`
|
||||
});
|
||||
await tmCore.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize Git adapter
|
||||
const gitAdapter = createGitAdapter(mergedOptions.projectRoot!);
|
||||
await gitAdapter.ensureGitRepository();
|
||||
await gitAdapter.ensureCleanWorkingTree();
|
||||
|
||||
// Parse subtasks
|
||||
const maxAttempts = parseInt(mergedOptions.maxAttempts || '3', 10);
|
||||
const subtasks = parseSubtasks(task, maxAttempts);
|
||||
|
||||
// Create workflow context
|
||||
const context: WorkflowContext = {
|
||||
taskId: task.id,
|
||||
subtasks,
|
||||
currentSubtaskIndex: 0,
|
||||
errors: [],
|
||||
metadata: {
|
||||
startedAt: new Date().toISOString(),
|
||||
tags: task.tags || []
|
||||
}
|
||||
};
|
||||
|
||||
// Create orchestrator with persistence
|
||||
const orchestrator = createOrchestrator(
|
||||
context,
|
||||
mergedOptions.projectRoot!
|
||||
);
|
||||
|
||||
// Complete PREFLIGHT phase
|
||||
orchestrator.transition({ type: 'PREFLIGHT_COMPLETE' });
|
||||
|
||||
// Generate descriptive branch name
|
||||
const sanitizedTitle = task.title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.substring(0, 50);
|
||||
const formattedTaskId = taskId.replace(/\./g, '-');
|
||||
const tagPrefix = currentTag ? `${currentTag}/` : '';
|
||||
const branchName = `${tagPrefix}task-${formattedTaskId}-${sanitizedTitle}`;
|
||||
|
||||
// Create and checkout branch
|
||||
formatter.info(`Creating branch: ${branchName}`);
|
||||
await gitAdapter.createAndCheckoutBranch(branchName);
|
||||
|
||||
// Transition to SUBTASK_LOOP
|
||||
orchestrator.transition({
|
||||
type: 'BRANCH_CREATED',
|
||||
branchName
|
||||
});
|
||||
|
||||
// Output success
|
||||
formatter.success('TDD workflow started', {
|
||||
taskId: task.id,
|
||||
title: task.title,
|
||||
phase: orchestrator.getCurrentPhase(),
|
||||
tddPhase: orchestrator.getCurrentTDDPhase(),
|
||||
branchName,
|
||||
subtasks: subtasks.length,
|
||||
currentSubtask: subtasks[0]?.title
|
||||
});
|
||||
|
||||
// Clean up
|
||||
await tmCore.close();
|
||||
} catch (error) {
|
||||
formatter.error((error as Error).message);
|
||||
if (mergedOptions.verbose) {
|
||||
console.error((error as Error).stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
114
apps/cli/src/commands/autopilot/status.command.ts
Normal file
114
apps/cli/src/commands/autopilot/status.command.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @fileoverview Status Command - Show workflow progress
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { WorkflowOrchestrator } from '@tm/core';
|
||||
import {
|
||||
AutopilotBaseOptions,
|
||||
hasWorkflowState,
|
||||
loadWorkflowState,
|
||||
OutputFormatter
|
||||
} from './shared.js';
|
||||
|
||||
type StatusOptions = AutopilotBaseOptions;
|
||||
|
||||
/**
|
||||
* Status Command - Show current workflow status
|
||||
*/
|
||||
export class StatusCommand extends Command {
|
||||
constructor() {
|
||||
super('status');
|
||||
|
||||
this.description('Show current TDD workflow status and progress').action(
|
||||
async (options: StatusOptions) => {
|
||||
await this.execute(options);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async execute(options: StatusOptions): Promise<void> {
|
||||
// Inherit parent options
|
||||
const parentOpts = this.parent?.opts() as AutopilotBaseOptions;
|
||||
const mergedOptions: StatusOptions = {
|
||||
...parentOpts,
|
||||
...options,
|
||||
projectRoot:
|
||||
options.projectRoot || parentOpts?.projectRoot || process.cwd()
|
||||
};
|
||||
|
||||
const formatter = new OutputFormatter(mergedOptions.json || false);
|
||||
|
||||
try {
|
||||
// Check for workflow state
|
||||
const hasState = await hasWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!hasState) {
|
||||
formatter.error('No active workflow', {
|
||||
suggestion: 'Start a workflow with: autopilot start <taskId>'
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load state
|
||||
const state = await loadWorkflowState(mergedOptions.projectRoot!);
|
||||
if (!state) {
|
||||
formatter.error('Failed to load workflow state');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Restore orchestrator
|
||||
const orchestrator = new WorkflowOrchestrator(state.context);
|
||||
orchestrator.restoreState(state);
|
||||
|
||||
// Get status information
|
||||
const phase = orchestrator.getCurrentPhase();
|
||||
const tddPhase = orchestrator.getCurrentTDDPhase();
|
||||
const progress = orchestrator.getProgress();
|
||||
const currentSubtask = orchestrator.getCurrentSubtask();
|
||||
const errors = state.context.errors ?? [];
|
||||
|
||||
// Build status output
|
||||
const status = {
|
||||
taskId: state.context.taskId,
|
||||
phase,
|
||||
tddPhase,
|
||||
branchName: state.context.branchName,
|
||||
progress: {
|
||||
completed: progress.completed,
|
||||
total: progress.total,
|
||||
current: progress.current,
|
||||
percentage: progress.percentage
|
||||
},
|
||||
currentSubtask: currentSubtask
|
||||
? {
|
||||
id: currentSubtask.id,
|
||||
title: currentSubtask.title,
|
||||
status: currentSubtask.status,
|
||||
attempts: currentSubtask.attempts,
|
||||
maxAttempts: currentSubtask.maxAttempts
|
||||
}
|
||||
: null,
|
||||
subtasks: state.context.subtasks.map((st) => ({
|
||||
id: st.id,
|
||||
title: st.title,
|
||||
status: st.status,
|
||||
attempts: st.attempts
|
||||
})),
|
||||
errors: errors.length > 0 ? errors : undefined,
|
||||
metadata: state.context.metadata
|
||||
};
|
||||
|
||||
if (mergedOptions.json) {
|
||||
formatter.output(status);
|
||||
} else {
|
||||
formatter.success('Workflow status', status);
|
||||
}
|
||||
} catch (error) {
|
||||
formatter.error((error as Error).message);
|
||||
if (mergedOptions.verbose) {
|
||||
console.error((error as Error).stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,11 @@
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import search from '@inquirer/search';
|
||||
import ora, { Ora } from 'ora';
|
||||
import {
|
||||
AuthManager,
|
||||
AuthenticationError,
|
||||
type UserContext
|
||||
} from '@tm/core/auth';
|
||||
import { AuthManager, type UserContext } from '@tm/core/auth';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Result type from context command
|
||||
@@ -118,8 +116,7 @@ export class ContextCommand extends Command {
|
||||
const result = this.displayContext();
|
||||
this.setLastResult(result);
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,10 +153,14 @@ export class ContextCommand extends Command {
|
||||
|
||||
if (context.briefName || context.briefId) {
|
||||
console.log(chalk.green('\n✓ Brief'));
|
||||
if (context.briefName) {
|
||||
if (context.briefName && context.briefId) {
|
||||
const shortId = context.briefId.slice(0, 8);
|
||||
console.log(
|
||||
chalk.white(` ${context.briefName} `) + chalk.gray(`(${shortId})`)
|
||||
);
|
||||
} else if (context.briefName) {
|
||||
console.log(chalk.white(` ${context.briefName}`));
|
||||
}
|
||||
if (context.briefId) {
|
||||
} else if (context.briefId) {
|
||||
console.log(chalk.gray(` ID: ${context.briefId}`));
|
||||
}
|
||||
}
|
||||
@@ -211,8 +212,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,9 +250,10 @@ export class ContextCommand extends Command {
|
||||
]);
|
||||
|
||||
// Update context
|
||||
await this.authManager.updateContext({
|
||||
this.authManager.updateContext({
|
||||
orgId: selectedOrg.id,
|
||||
orgName: selectedOrg.name,
|
||||
orgSlug: selectedOrg.slug,
|
||||
// Clear brief when changing org
|
||||
briefId: undefined,
|
||||
briefName: undefined
|
||||
@@ -299,8 +300,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,26 +324,54 @@ export class ContextCommand extends Command {
|
||||
};
|
||||
}
|
||||
|
||||
// Prompt for selection
|
||||
const { selectedBrief } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'selectedBrief',
|
||||
message: 'Select a brief:',
|
||||
choices: [
|
||||
{ name: '(No brief - organization level)', value: null },
|
||||
...briefs.map((brief) => ({
|
||||
name: `Brief ${brief.id} (${new Date(brief.createdAt).toLocaleDateString()})`,
|
||||
value: brief
|
||||
}))
|
||||
]
|
||||
// Prompt for selection with search
|
||||
const selectedBrief = await search<(typeof briefs)[0] | null>({
|
||||
message: 'Search for a brief:',
|
||||
source: async (input) => {
|
||||
const searchTerm = input?.toLowerCase() || '';
|
||||
|
||||
// Static option for no brief
|
||||
const noBriefOption = {
|
||||
name: '(No brief - organization level)',
|
||||
value: null as any,
|
||||
description: 'Clear brief selection'
|
||||
};
|
||||
|
||||
// Filter and map brief options
|
||||
const briefOptions = briefs
|
||||
.filter((brief) => {
|
||||
if (!searchTerm) return true;
|
||||
|
||||
const title = brief.document?.title || '';
|
||||
const shortId = brief.id.slice(0, 8);
|
||||
|
||||
// Search by title first, then by UUID
|
||||
return (
|
||||
title.toLowerCase().includes(searchTerm) ||
|
||||
brief.id.toLowerCase().includes(searchTerm) ||
|
||||
shortId.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
})
|
||||
.map((brief) => {
|
||||
const title =
|
||||
brief.document?.title || `Brief ${brief.id.slice(0, 8)}`;
|
||||
const shortId = brief.id.slice(0, 8);
|
||||
return {
|
||||
name: `${title} ${chalk.gray(`(${shortId})`)}`,
|
||||
value: brief
|
||||
};
|
||||
});
|
||||
|
||||
return [noBriefOption, ...briefOptions];
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
if (selectedBrief) {
|
||||
// Update context with brief
|
||||
const briefName = `Brief ${selectedBrief.id.slice(0, 8)}`;
|
||||
await this.authManager.updateContext({
|
||||
const briefName =
|
||||
selectedBrief.document?.title ||
|
||||
`Brief ${selectedBrief.id.slice(0, 8)}`;
|
||||
this.authManager.updateContext({
|
||||
briefId: selectedBrief.id,
|
||||
briefName: briefName
|
||||
});
|
||||
@@ -354,11 +382,11 @@ export class ContextCommand extends Command {
|
||||
success: true,
|
||||
action: 'select-brief',
|
||||
context: this.authManager.getContext() || undefined,
|
||||
message: `Selected brief: ${selectedBrief.name}`
|
||||
message: `Selected brief: ${selectedBrief.document?.title}`
|
||||
};
|
||||
} else {
|
||||
// Clear brief selection
|
||||
await this.authManager.updateContext({
|
||||
this.authManager.updateContext({
|
||||
briefId: undefined,
|
||||
briefName: undefined
|
||||
});
|
||||
@@ -396,8 +424,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,8 +470,7 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,7 +494,7 @@ export class ContextCommand extends Command {
|
||||
if (!briefId) {
|
||||
spinner.fail('Could not extract a brief ID from the provided input');
|
||||
ui.displayError(
|
||||
`Provide a valid brief ID or a Hamster brief URL, e.g. https://${process.env.TM_PUBLIC_BASE_DOMAIN}/home/hamster/briefs/<id>`
|
||||
`Provide a valid brief ID or a Hamster brief URL, e.g. https://${process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN}/home/hamster/briefs/<id>`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -480,20 +506,24 @@ export class ContextCommand extends Command {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Fetch org to get a friendly name (optional)
|
||||
// Fetch org to get a friendly name and slug (optional)
|
||||
let orgName: string | undefined;
|
||||
let orgSlug: string | undefined;
|
||||
try {
|
||||
const org = await this.authManager.getOrganization(brief.accountId);
|
||||
orgName = org?.name;
|
||||
orgSlug = org?.slug;
|
||||
} catch {
|
||||
// Non-fatal if org lookup fails
|
||||
}
|
||||
|
||||
// Update context: set org and brief
|
||||
const briefName = `Brief ${brief.id.slice(0, 8)}`;
|
||||
await this.authManager.updateContext({
|
||||
const briefName =
|
||||
brief.document?.title || `Brief ${brief.id.slice(0, 8)}`;
|
||||
this.authManager.updateContext({
|
||||
orgId: brief.accountId,
|
||||
orgName,
|
||||
orgSlug,
|
||||
briefId: brief.id,
|
||||
briefName
|
||||
});
|
||||
@@ -515,8 +545,7 @@ export class ContextCommand extends Command {
|
||||
try {
|
||||
if (spinner?.isSpinning) spinner.stop();
|
||||
} catch {}
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +642,7 @@ export class ContextCommand extends Command {
|
||||
};
|
||||
}
|
||||
|
||||
await this.authManager.updateContext(context);
|
||||
this.authManager.updateContext(context);
|
||||
ui.displaySuccess('Context updated');
|
||||
|
||||
// Display what was set
|
||||
@@ -645,26 +674,6 @@ export class ContextCommand extends Command {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle errors
|
||||
*/
|
||||
private handleError(error: any): void {
|
||||
if (error instanceof AuthenticationError) {
|
||||
console.error(chalk.red(`\n✗ ${error.message}`));
|
||||
|
||||
if (error.code === 'NOT_AUTHENTICATED') {
|
||||
ui.displayWarning('Please authenticate first: tm auth login');
|
||||
}
|
||||
} else {
|
||||
const msg = error?.message ?? String(error);
|
||||
console.error(chalk.red(`Error: ${msg}`));
|
||||
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
@@ -686,6 +695,53 @@ export class ContextCommand extends Command {
|
||||
return this.authManager.getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive context setup (for post-auth flow)
|
||||
* Prompts user to select org and brief
|
||||
*/
|
||||
async setupContextInteractive(): Promise<{
|
||||
success: boolean;
|
||||
orgSelected: boolean;
|
||||
briefSelected: boolean;
|
||||
}> {
|
||||
try {
|
||||
// Ask if user wants to set up workspace context
|
||||
const { setupContext } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'setupContext',
|
||||
message: 'Would you like to set up your workspace context now?',
|
||||
default: true
|
||||
}
|
||||
]);
|
||||
|
||||
if (!setupContext) {
|
||||
return { success: true, orgSelected: false, briefSelected: false };
|
||||
}
|
||||
|
||||
// Select organization
|
||||
const orgResult = await this.selectOrganization();
|
||||
if (!orgResult.success || !orgResult.context?.orgId) {
|
||||
return { success: false, orgSelected: false, briefSelected: false };
|
||||
}
|
||||
|
||||
// Select brief
|
||||
const briefResult = await this.selectBrief(orgResult.context.orgId);
|
||||
return {
|
||||
success: true,
|
||||
orgSelected: true,
|
||||
briefSelected: briefResult.success
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.yellow(
|
||||
'\nContext setup skipped due to error. You can set it up later with "tm context"'
|
||||
)
|
||||
);
|
||||
return { success: false, orgSelected: false, briefSelected: false };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
@@ -694,16 +750,7 @@ export class ContextCommand extends Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to register this command on an existing program
|
||||
*/
|
||||
static registerOn(program: Command): Command {
|
||||
const contextCommand = new ContextCommand();
|
||||
program.addCommand(contextCommand);
|
||||
return contextCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative registration that returns the command for chaining
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): ContextCommand {
|
||||
const contextCommand = new ContextCommand(name);
|
||||
|
||||
355
apps/cli/src/commands/export.command.ts
Normal file
355
apps/cli/src/commands/export.command.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* @fileoverview Export command for exporting tasks to external systems
|
||||
* Provides functionality to export tasks to Hamster briefs
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import ora, { Ora } from 'ora';
|
||||
import { AuthManager, type UserContext } from '@tm/core/auth';
|
||||
import { TaskMasterCore, type ExportResult } from '@tm/core';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Result type from export command
|
||||
*/
|
||||
export interface ExportCommandResult {
|
||||
success: boolean;
|
||||
action: 'export' | 'validate' | 'cancelled';
|
||||
result?: ExportResult;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ExportCommand extending Commander's Command class
|
||||
* Handles task export to external systems
|
||||
*/
|
||||
export class ExportCommand extends Command {
|
||||
private authManager: AuthManager;
|
||||
private taskMasterCore?: TaskMasterCore;
|
||||
private lastResult?: ExportCommandResult;
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name || 'export');
|
||||
|
||||
// Initialize auth manager
|
||||
this.authManager = AuthManager.getInstance();
|
||||
|
||||
// Configure the command
|
||||
this.description('Export tasks to external systems (e.g., Hamster briefs)');
|
||||
|
||||
// Add options
|
||||
this.option('--org <id>', 'Organization ID to export to');
|
||||
this.option('--brief <id>', 'Brief ID to export tasks to');
|
||||
this.option('--tag <tag>', 'Export tasks from a specific tag');
|
||||
this.option(
|
||||
'--status <status>',
|
||||
'Filter tasks by status (pending, in-progress, done, etc.)'
|
||||
);
|
||||
this.option('--exclude-subtasks', 'Exclude subtasks from export');
|
||||
this.option('-y, --yes', 'Skip confirmation prompt');
|
||||
|
||||
// Accept optional positional argument for brief ID or Hamster URL
|
||||
this.argument('[briefOrUrl]', 'Brief ID or Hamster brief URL');
|
||||
|
||||
// Default action
|
||||
this.action(async (briefOrUrl?: string, options?: any) => {
|
||||
await this.executeExport(briefOrUrl, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the TaskMasterCore
|
||||
*/
|
||||
private async initializeServices(): Promise<void> {
|
||||
if (this.taskMasterCore) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize TaskMasterCore
|
||||
this.taskMasterCore = await TaskMasterCore.create({
|
||||
projectPath: process.cwd()
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to initialize services: ${(error as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the export command
|
||||
*/
|
||||
private async executeExport(
|
||||
briefOrUrl?: string,
|
||||
options?: any
|
||||
): Promise<void> {
|
||||
let spinner: Ora | undefined;
|
||||
|
||||
try {
|
||||
// Check authentication
|
||||
if (!this.authManager.isAuthenticated()) {
|
||||
ui.displayError('Not authenticated. Run "tm auth login" first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize services
|
||||
await this.initializeServices();
|
||||
|
||||
// Get current context
|
||||
const context = await this.authManager.getContext();
|
||||
|
||||
// Determine org and brief IDs
|
||||
let orgId = options?.org || context?.orgId;
|
||||
let briefId = options?.brief || briefOrUrl || context?.briefId;
|
||||
|
||||
// If a URL/ID was provided as argument, resolve it
|
||||
if (briefOrUrl && !options?.brief) {
|
||||
spinner = ora('Resolving brief...').start();
|
||||
const resolvedBrief = await this.resolveBriefInput(briefOrUrl);
|
||||
if (resolvedBrief) {
|
||||
briefId = resolvedBrief.briefId;
|
||||
orgId = resolvedBrief.orgId;
|
||||
spinner.succeed('Brief resolved');
|
||||
} else {
|
||||
spinner.fail('Could not resolve brief');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate we have necessary IDs
|
||||
if (!orgId) {
|
||||
ui.displayError(
|
||||
'No organization selected. Run "tm context org" or use --org flag.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!briefId) {
|
||||
ui.displayError(
|
||||
'No brief specified. Run "tm context brief", provide a brief ID/URL, or use --brief flag.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Confirm export if not auto-confirmed
|
||||
if (!options?.yes) {
|
||||
const confirmed = await this.confirmExport(orgId, briefId, context);
|
||||
if (!confirmed) {
|
||||
ui.displayWarning('Export cancelled');
|
||||
this.lastResult = {
|
||||
success: false,
|
||||
action: 'cancelled',
|
||||
message: 'User cancelled export'
|
||||
};
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform export
|
||||
spinner = ora('Exporting tasks...').start();
|
||||
|
||||
const exportResult = await this.taskMasterCore!.exportTasks({
|
||||
orgId,
|
||||
briefId,
|
||||
tag: options?.tag,
|
||||
status: options?.status,
|
||||
excludeSubtasks: options?.excludeSubtasks || false
|
||||
});
|
||||
|
||||
if (exportResult.success) {
|
||||
spinner.succeed(
|
||||
`Successfully exported ${exportResult.taskCount} task(s) to brief`
|
||||
);
|
||||
|
||||
// Display summary
|
||||
console.log(chalk.cyan('\n📤 Export Summary\n'));
|
||||
console.log(chalk.white(` Organization: ${orgId}`));
|
||||
console.log(chalk.white(` Brief: ${briefId}`));
|
||||
console.log(chalk.white(` Tasks exported: ${exportResult.taskCount}`));
|
||||
if (options?.tag) {
|
||||
console.log(chalk.gray(` Tag: ${options.tag}`));
|
||||
}
|
||||
if (options?.status) {
|
||||
console.log(chalk.gray(` Status filter: ${options.status}`));
|
||||
}
|
||||
|
||||
if (exportResult.message) {
|
||||
console.log(chalk.gray(`\n ${exportResult.message}`));
|
||||
}
|
||||
} else {
|
||||
spinner.fail('Export failed');
|
||||
if (exportResult.error) {
|
||||
console.error(chalk.red(`\n✗ ${exportResult.error.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
this.lastResult = {
|
||||
success: exportResult.success,
|
||||
action: 'export',
|
||||
result: exportResult
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (spinner?.isSpinning) spinner.fail('Export failed');
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve brief input to get brief and org IDs
|
||||
*/
|
||||
private async resolveBriefInput(
|
||||
briefOrUrl: string
|
||||
): Promise<{ briefId: string; orgId: string } | null> {
|
||||
try {
|
||||
// Extract brief ID from input
|
||||
const briefId = this.extractBriefId(briefOrUrl);
|
||||
if (!briefId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch brief to get organization
|
||||
const brief = await this.authManager.getBrief(briefId);
|
||||
if (!brief) {
|
||||
ui.displayError('Brief not found or you do not have access');
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
briefId: brief.id,
|
||||
orgId: brief.accountId
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Failed to resolve brief: ${error}`));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a brief ID from raw input (ID or URL)
|
||||
*/
|
||||
private extractBriefId(input: string): string | null {
|
||||
const raw = input?.trim() ?? '';
|
||||
if (!raw) return null;
|
||||
|
||||
const parseUrl = (s: string): URL | null => {
|
||||
try {
|
||||
return new URL(s);
|
||||
} catch {}
|
||||
try {
|
||||
return new URL(`https://${s}`);
|
||||
} catch {}
|
||||
return null;
|
||||
};
|
||||
|
||||
const fromParts = (path: string): string | null => {
|
||||
const parts = path.split('/').filter(Boolean);
|
||||
const briefsIdx = parts.lastIndexOf('briefs');
|
||||
const candidate =
|
||||
briefsIdx >= 0 && parts.length > briefsIdx + 1
|
||||
? parts[briefsIdx + 1]
|
||||
: parts[parts.length - 1];
|
||||
return candidate?.trim() || null;
|
||||
};
|
||||
|
||||
// Try URL parsing
|
||||
const url = parseUrl(raw);
|
||||
if (url) {
|
||||
const qId = url.searchParams.get('id') || url.searchParams.get('briefId');
|
||||
const candidate = (qId || fromParts(url.pathname)) ?? null;
|
||||
if (candidate) {
|
||||
if (this.isLikelyId(candidate) || candidate.length >= 8) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it looks like a path
|
||||
if (raw.includes('/')) {
|
||||
const candidate = fromParts(raw);
|
||||
if (candidate && (this.isLikelyId(candidate) || candidate.length >= 8)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
// Return raw if it looks like an ID
|
||||
return raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string looks like a brief ID
|
||||
*/
|
||||
private isLikelyId(value: string): boolean {
|
||||
const uuidRegex =
|
||||
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
||||
const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
|
||||
const slugRegex = /^[A-Za-z0-9_-]{16,}$/;
|
||||
return (
|
||||
uuidRegex.test(value) || ulidRegex.test(value) || slugRegex.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm export with the user
|
||||
*/
|
||||
private async confirmExport(
|
||||
orgId: string,
|
||||
briefId: string,
|
||||
context: UserContext | null
|
||||
): Promise<boolean> {
|
||||
console.log(chalk.cyan('\n📤 Export Tasks\n'));
|
||||
|
||||
// Show org name if available
|
||||
if (context?.orgName) {
|
||||
console.log(chalk.white(` Organization: ${context.orgName}`));
|
||||
console.log(chalk.gray(` ID: ${orgId}`));
|
||||
} else {
|
||||
console.log(chalk.white(` Organization ID: ${orgId}`));
|
||||
}
|
||||
|
||||
// Show brief info
|
||||
if (context?.briefName) {
|
||||
console.log(chalk.white(`\n Brief: ${context.briefName}`));
|
||||
console.log(chalk.gray(` ID: ${briefId}`));
|
||||
} else {
|
||||
console.log(chalk.white(`\n Brief ID: ${briefId}`));
|
||||
}
|
||||
|
||||
const { confirmed } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirmed',
|
||||
message: 'Do you want to proceed with export?',
|
||||
default: true
|
||||
}
|
||||
]);
|
||||
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last export result (useful for testing)
|
||||
*/
|
||||
public getLastResult(): ExportCommandResult | undefined {
|
||||
return this.lastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
// No resources to clean up
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): ExportCommand {
|
||||
const exportCommand = new ExportCommand(name);
|
||||
program.addCommand(exportCommand);
|
||||
return exportCommand;
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
} from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||
import {
|
||||
displayHeader,
|
||||
displayDashboards,
|
||||
calculateTaskStatistics,
|
||||
calculateSubtaskStatistics,
|
||||
@@ -106,14 +107,7 @@ export class ListTasksCommand extends Command {
|
||||
this.displayResults(result, options);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +240,7 @@ export class ListTasksCommand extends Command {
|
||||
task.subtasks.forEach((subtask) => {
|
||||
const subIcon = STATUS_ICONS[subtask.status];
|
||||
console.log(
|
||||
` ${chalk.gray(`${task.id}.${subtask.id}`)} ${subIcon} ${chalk.gray(subtask.title)}`
|
||||
` ${chalk.gray(String(subtask.id))} ${subIcon} ${chalk.gray(subtask.title)}`
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -257,15 +251,12 @@ export class ListTasksCommand extends Command {
|
||||
* Display in text format with tables
|
||||
*/
|
||||
private displayText(data: ListTasksResult, withSubtasks?: boolean): void {
|
||||
const { tasks, tag } = data;
|
||||
const { tasks, tag, storageType } = data;
|
||||
|
||||
// Get file path for display
|
||||
const filePath = this.tmCore ? `.taskmaster/tasks/tasks.json` : undefined;
|
||||
|
||||
// Display header without banner (banner already shown by main CLI)
|
||||
displayHeader({
|
||||
// Display header using utility function
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: tag || 'master',
|
||||
filePath: filePath
|
||||
storageType
|
||||
});
|
||||
|
||||
// No tasks message
|
||||
@@ -297,7 +288,7 @@ export class ListTasksCommand extends Command {
|
||||
nextTask
|
||||
);
|
||||
|
||||
// Task table - no title, just show the table directly
|
||||
// Task table
|
||||
console.log(
|
||||
ui.createTaskTable(tasks, {
|
||||
showSubtasks: withSubtasks,
|
||||
@@ -474,18 +465,7 @@ export class ListTasksCommand extends Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to register this command on an existing program
|
||||
* This is for gradual migration - allows commands.js to use this
|
||||
*/
|
||||
static registerOn(program: Command): Command {
|
||||
const listCommand = new ListTasksCommand();
|
||||
program.addCommand(listCommand);
|
||||
return listCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative registration that returns the command for chaining
|
||||
* Can also configure the command name if needed
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): ListTasksCommand {
|
||||
const listCommand = new ListTasksCommand(name);
|
||||
|
||||
248
apps/cli/src/commands/next.command.ts
Normal file
248
apps/cli/src/commands/next.command.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* @fileoverview NextCommand using Commander's native class pattern
|
||||
* Extends Commander.Command for better integration with the framework
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||
|
||||
/**
|
||||
* Options interface for the next command
|
||||
*/
|
||||
export interface NextCommandOptions {
|
||||
tag?: string;
|
||||
format?: 'text' | 'json';
|
||||
silent?: boolean;
|
||||
project?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type from next command
|
||||
*/
|
||||
export interface NextTaskResult {
|
||||
task: Task | null;
|
||||
found: boolean;
|
||||
tag: string;
|
||||
storageType: Exclude<StorageType, 'auto'>;
|
||||
}
|
||||
|
||||
/**
|
||||
* NextCommand extending Commander's Command class
|
||||
* This is a thin presentation layer over @tm/core
|
||||
*/
|
||||
export class NextCommand extends Command {
|
||||
private tmCore?: TaskMasterCore;
|
||||
private lastResult?: NextTaskResult;
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name || 'next');
|
||||
|
||||
// Configure the command
|
||||
this.description('Find the next available task to work on')
|
||||
.option('-t, --tag <tag>', 'Filter by tag')
|
||||
.option('-f, --format <format>', 'Output format (text, json)', 'text')
|
||||
.option('--silent', 'Suppress output (useful for programmatic usage)')
|
||||
.option('-p, --project <path>', 'Project root directory', process.cwd())
|
||||
.action(async (options: NextCommandOptions) => {
|
||||
await this.executeCommand(options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the next command
|
||||
*/
|
||||
private async executeCommand(options: NextCommandOptions): Promise<void> {
|
||||
let hasError = false;
|
||||
try {
|
||||
// Validate options (throws on invalid options)
|
||||
this.validateOptions(options);
|
||||
|
||||
// Initialize tm-core
|
||||
await this.initializeCore(options.project || process.cwd());
|
||||
|
||||
// Get next task from core
|
||||
const result = await this.getNextTask(options);
|
||||
|
||||
// Store result for programmatic access
|
||||
this.setLastResult(result);
|
||||
|
||||
// Display results
|
||||
if (!options.silent) {
|
||||
this.displayResults(result, options);
|
||||
}
|
||||
} catch (error: any) {
|
||||
hasError = true;
|
||||
displayError(error, { skipExit: true });
|
||||
} finally {
|
||||
// Always clean up resources, even on error
|
||||
await this.cleanup();
|
||||
}
|
||||
|
||||
// Exit after cleanup completes
|
||||
if (hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate command options
|
||||
*/
|
||||
private validateOptions(options: NextCommandOptions): void {
|
||||
// Validate format
|
||||
if (options.format && !['text', 'json'].includes(options.format)) {
|
||||
throw new Error(
|
||||
`Invalid format: ${options.format}. Valid formats are: text, json`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize TaskMasterCore
|
||||
*/
|
||||
private async initializeCore(projectRoot: string): Promise<void> {
|
||||
if (!this.tmCore) {
|
||||
const resolved = path.resolve(projectRoot);
|
||||
this.tmCore = await createTaskMasterCore({ projectPath: resolved });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next task from tm-core
|
||||
*/
|
||||
private async getNextTask(
|
||||
options: NextCommandOptions
|
||||
): Promise<NextTaskResult> {
|
||||
if (!this.tmCore) {
|
||||
throw new Error('TaskMasterCore not initialized');
|
||||
}
|
||||
|
||||
// Call tm-core to get next task
|
||||
const task = await this.tmCore.getNextTask(options.tag);
|
||||
|
||||
// Get storage type and active tag
|
||||
const storageType = this.tmCore.getStorageType();
|
||||
if (storageType === 'auto') {
|
||||
throw new Error('Storage type must be resolved before use');
|
||||
}
|
||||
const activeTag = options.tag || this.tmCore.getActiveTag();
|
||||
|
||||
return {
|
||||
task,
|
||||
found: task !== null,
|
||||
tag: activeTag,
|
||||
storageType
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display results based on format
|
||||
*/
|
||||
private displayResults(
|
||||
result: NextTaskResult,
|
||||
options: NextCommandOptions
|
||||
): void {
|
||||
const format = options.format || 'text';
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
this.displayJson(result);
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
default:
|
||||
this.displayText(result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display in JSON format
|
||||
*/
|
||||
private displayJson(result: NextTaskResult): void {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display in text format
|
||||
*/
|
||||
private displayText(result: NextTaskResult): void {
|
||||
// Display header with storage info
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: result.tag || 'master',
|
||||
storageType: result.storageType
|
||||
});
|
||||
|
||||
if (!result.found || !result.task) {
|
||||
// No next task available
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.yellow(
|
||||
'No tasks available to work on. All tasks are either completed, blocked by dependencies, or in progress.'
|
||||
),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'yellow',
|
||||
title: '⚠ NO TASKS AVAILABLE ⚠',
|
||||
titleAlignment: 'center'
|
||||
}
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
`\n${chalk.dim('Tip: Try')} ${chalk.cyan('task-master list --status pending')} ${chalk.dim('to see all pending tasks')}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const task = result.task;
|
||||
|
||||
// Display the task details using the same component as 'show' command
|
||||
// with a custom header indicating this is the next task
|
||||
const customHeader = `Next Task: #${task.id} - ${task.title}`;
|
||||
displayTaskDetails(task, {
|
||||
customHeader,
|
||||
headerColor: 'green',
|
||||
showSuggestedActions: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
private setLastResult(result: NextTaskResult): void {
|
||||
this.lastResult = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last result (for programmatic usage)
|
||||
*/
|
||||
getLastResult(): NextTaskResult | undefined {
|
||||
return this.lastResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
async cleanup(): Promise<void> {
|
||||
if (this.tmCore) {
|
||||
await this.tmCore.close();
|
||||
this.tmCore = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): NextCommand {
|
||||
const nextCommand = new NextCommand(name);
|
||||
program.addCommand(nextCommand);
|
||||
return nextCommand;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type TaskStatus
|
||||
} from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* Valid task status values for validation
|
||||
@@ -85,6 +86,7 @@ export class SetStatusCommand extends Command {
|
||||
private async executeCommand(
|
||||
options: SetStatusCommandOptions
|
||||
): Promise<void> {
|
||||
let hasError = false;
|
||||
try {
|
||||
// Validate required options
|
||||
if (!options.id) {
|
||||
@@ -135,16 +137,15 @@ export class SetStatusCommand extends Command {
|
||||
oldStatus: result.oldStatus,
|
||||
newStatus: result.newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
|
||||
if (!options.silent) {
|
||||
console.error(
|
||||
chalk.red(`Failed to update task ${taskId}: ${errorMessage}`)
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
hasError = true;
|
||||
if (options.format === 'json') {
|
||||
const errorMessage = error?.getSanitizedDetails
|
||||
? error.getSanitizedDetails().message
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: String(error);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
@@ -153,8 +154,13 @@ export class SetStatusCommand extends Command {
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
);
|
||||
} else if (!options.silent) {
|
||||
// Show which task failed with context
|
||||
console.error(chalk.red(`\nFailed to update task ${taskId}:`));
|
||||
displayError(error, { skipExit: true });
|
||||
}
|
||||
process.exit(1);
|
||||
// Don't exit here - let finally block clean up first
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,25 +176,26 @@ export class SetStatusCommand extends Command {
|
||||
|
||||
// Display results
|
||||
this.displayResults(this.lastResult, options);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
|
||||
if (!options.silent) {
|
||||
console.error(chalk.red(`Error: ${errorMessage}`));
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
hasError = true;
|
||||
if (options.format === 'json') {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
console.log(JSON.stringify({ success: false, error: errorMessage }));
|
||||
} else if (!options.silent) {
|
||||
displayError(error, { skipExit: true });
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
} finally {
|
||||
// Clean up resources
|
||||
if (this.tmCore) {
|
||||
await this.tmCore.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Exit after cleanup completes
|
||||
if (hasError) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,9 +265,6 @@ export class SetStatusCommand extends Command {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Show storage info
|
||||
console.log(chalk.gray(`\nUsing ${result.storageType} storage`));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,18 +294,7 @@ export class SetStatusCommand extends Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to register this command on an existing program
|
||||
* This is for gradual migration - allows commands.js to use this
|
||||
*/
|
||||
static registerOn(program: Command): Command {
|
||||
const setStatusCommand = new SetStatusCommand();
|
||||
program.addCommand(setStatusCommand);
|
||||
return setStatusCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative registration that returns the command for chaining
|
||||
* Can also configure the command name if needed
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): SetStatusCommand {
|
||||
const setStatusCommand = new SetStatusCommand(name);
|
||||
|
||||
@@ -9,7 +9,9 @@ import boxen from 'boxen';
|
||||
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||
import { displayCommandHeader } from '../utils/display-helpers.js';
|
||||
|
||||
/**
|
||||
* Options interface for the show command
|
||||
@@ -112,14 +114,7 @@ export class ShowCommand extends Command {
|
||||
this.displayResults(result, options);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +252,15 @@ export class ShowCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display header with storage info
|
||||
const activeTag = this.tmCore?.getActiveTag() || 'master';
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: activeTag,
|
||||
storageType: result.storageType
|
||||
});
|
||||
|
||||
console.log(); // Add spacing
|
||||
|
||||
// Use the global task details display function
|
||||
displayTaskDetails(result.task, {
|
||||
statusFilter: options.status,
|
||||
@@ -271,8 +275,12 @@ export class ShowCommand extends Command {
|
||||
result: ShowMultipleTasksResult,
|
||||
_options: ShowCommandOptions
|
||||
): void {
|
||||
// Header
|
||||
ui.displayBanner(`Tasks (${result.tasks.length} found)`);
|
||||
// Display header with storage info
|
||||
const activeTag = this.tmCore?.getActiveTag() || 'master';
|
||||
displayCommandHeader(this.tmCore, {
|
||||
tag: activeTag,
|
||||
storageType: result.storageType
|
||||
});
|
||||
|
||||
if (result.notFound.length > 0) {
|
||||
console.log(chalk.yellow(`\n⚠ Not found: ${result.notFound.join(', ')}`));
|
||||
@@ -291,8 +299,6 @@ export class ShowCommand extends Command {
|
||||
showDependencies: true
|
||||
})
|
||||
);
|
||||
|
||||
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,18 +328,7 @@ export class ShowCommand extends Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to register this command on an existing program
|
||||
* This is for gradual migration - allows commands.js to use this
|
||||
*/
|
||||
static registerOn(program: Command): Command {
|
||||
const showCommand = new ShowCommand();
|
||||
program.addCommand(showCommand);
|
||||
return showCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative registration that returns the command for chaining
|
||||
* Can also configure the command name if needed
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): ShowCommand {
|
||||
const showCommand = new ShowCommand(name);
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from '@tm/core';
|
||||
import { displayTaskDetails } from '../ui/components/task-detail.component.js';
|
||||
import * as ui from '../utils/ui.js';
|
||||
import { displayError } from '../utils/error-handler.js';
|
||||
|
||||
/**
|
||||
* CLI-specific options interface for the start command
|
||||
@@ -160,8 +161,7 @@ export class StartCommand extends Command {
|
||||
if (spinner) {
|
||||
spinner.fail('Operation failed');
|
||||
}
|
||||
this.handleError(error);
|
||||
process.exit(1);
|
||||
displayError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,22 +452,6 @@ export class StartCommand extends Command {
|
||||
console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle general errors
|
||||
*/
|
||||
private handleError(error: any): void {
|
||||
const msg = error?.getSanitizedDetails?.() ?? {
|
||||
message: error?.message ?? String(error)
|
||||
};
|
||||
console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
|
||||
|
||||
// Show stack trace in development mode or when DEBUG is set
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||
if ((isDevelopment || process.env.DEBUG) && error.stack) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last result for programmatic access
|
||||
*/
|
||||
@@ -493,16 +477,7 @@ export class StartCommand extends Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to register this command on an existing program
|
||||
*/
|
||||
static registerOn(program: Command): Command {
|
||||
const startCommand = new StartCommand();
|
||||
program.addCommand(startCommand);
|
||||
return startCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative registration that returns the command for chaining
|
||||
* Register this command on an existing program
|
||||
*/
|
||||
static register(program: Command, name?: string): StartCommand {
|
||||
const startCommand = new StartCommand(name);
|
||||
|
||||
@@ -6,14 +6,28 @@
|
||||
// Commands
|
||||
export { ListTasksCommand } from './commands/list.command.js';
|
||||
export { ShowCommand } from './commands/show.command.js';
|
||||
export { NextCommand } from './commands/next.command.js';
|
||||
export { AuthCommand } from './commands/auth.command.js';
|
||||
export { ContextCommand } from './commands/context.command.js';
|
||||
export { StartCommand } from './commands/start.command.js';
|
||||
export { SetStatusCommand } from './commands/set-status.command.js';
|
||||
export { ExportCommand } from './commands/export.command.js';
|
||||
export { AutopilotCommand } from './commands/autopilot.command.js';
|
||||
|
||||
// Command Registry
|
||||
export {
|
||||
CommandRegistry,
|
||||
registerAllCommands,
|
||||
registerCommandsByCategory,
|
||||
type CommandMetadata
|
||||
} from './command-registry.js';
|
||||
|
||||
// UI utilities (for other commands to use)
|
||||
export * as ui from './utils/ui.js';
|
||||
|
||||
// Error handling utilities
|
||||
export { displayError, isDebugMode } from './utils/error-handler.js';
|
||||
|
||||
// Auto-update utilities
|
||||
export {
|
||||
checkForUpdate,
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Brief information for API storage
|
||||
*/
|
||||
export interface BriefInfo {
|
||||
briefId: string;
|
||||
briefName: string;
|
||||
orgSlug?: string;
|
||||
webAppUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header configuration options
|
||||
*/
|
||||
@@ -12,22 +22,50 @@ export interface HeaderOptions {
|
||||
title?: string;
|
||||
tag?: string;
|
||||
filePath?: string;
|
||||
storageType?: 'api' | 'file';
|
||||
briefInfo?: BriefInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Task Master header with project info
|
||||
*/
|
||||
export function displayHeader(options: HeaderOptions = {}): void {
|
||||
const { filePath, tag } = options;
|
||||
const { filePath, tag, storageType, briefInfo } = options;
|
||||
|
||||
// Display tag and file path info
|
||||
if (tag) {
|
||||
// Display different header based on storage type
|
||||
if (storageType === 'api' && briefInfo) {
|
||||
// API storage: Show brief information
|
||||
const briefDisplay = `🏷 Brief: ${chalk.cyan(briefInfo.briefName)} ${chalk.gray(`(${briefInfo.briefId})`)}`;
|
||||
console.log(briefDisplay);
|
||||
|
||||
// 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)}`);
|
||||
} else if (briefInfo.webAppUrl) {
|
||||
// Show web app URL and brief ID if org slug is missing
|
||||
console.log(
|
||||
`Listing tasks from: ${chalk.dim(`${briefInfo.webAppUrl} (Brief: ${briefInfo.briefId})`)}`
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`💡 Tip: Run ${chalk.cyan('tm context select')} to set your organization and see the full URL`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Fallback: just show the brief ID if we can't get web app URL
|
||||
console.log(
|
||||
`Listing tasks from: ${chalk.dim(`API (Brief ID: ${briefInfo.briefId})`)}`
|
||||
);
|
||||
}
|
||||
} else if (tag) {
|
||||
// File storage: Show tag information
|
||||
let tagInfo = '';
|
||||
|
||||
if (tag && tag !== 'master') {
|
||||
tagInfo = `🏷 tag: ${chalk.cyan(tag)}`;
|
||||
tagInfo = `🏷 tag: ${chalk.cyan(tag)}`;
|
||||
} else {
|
||||
tagInfo = `🏷 tag: ${chalk.cyan('master')}`;
|
||||
tagInfo = `🏷 tag: ${chalk.cyan('master')}`;
|
||||
}
|
||||
|
||||
console.log(tagInfo);
|
||||
@@ -39,7 +77,5 @@ export function displayHeader(options: HeaderOptions = {}): void {
|
||||
: `${process.cwd()}/${filePath}`;
|
||||
console.log(`Listing tasks from: ${chalk.dim(absolutePath)}`);
|
||||
}
|
||||
|
||||
console.log(); // Empty line for spacing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import type { Task } from '@tm/core/types';
|
||||
import { getComplexityWithColor } from '../../utils/ui.js';
|
||||
import { getComplexityWithColor, getBoxWidth } from '../../utils/ui.js';
|
||||
|
||||
/**
|
||||
* Next task display options
|
||||
@@ -113,7 +113,7 @@ export function displayRecommendedNextTask(
|
||||
borderColor: '#FFA500', // Orange color
|
||||
title: chalk.hex('#FFA500')('⚡ RECOMMENDED NEXT TASK ⚡'),
|
||||
titleAlignment: 'center',
|
||||
width: process.stdout.columns * 0.97,
|
||||
width: getBoxWidth(0.97),
|
||||
fullscreen: false
|
||||
})
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { getBoxWidth } from '../../utils/ui.js';
|
||||
|
||||
/**
|
||||
* Display suggested next steps section
|
||||
@@ -24,7 +25,7 @@ export function displaySuggestedNextSteps(): void {
|
||||
margin: { top: 0, bottom: 1 },
|
||||
borderStyle: 'round',
|
||||
borderColor: 'gray',
|
||||
width: process.stdout.columns * 0.97
|
||||
width: getBoxWidth(0.97)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -192,8 +192,7 @@ export function displaySubtasks(
|
||||
status: any;
|
||||
description?: string;
|
||||
dependencies?: string[];
|
||||
}>,
|
||||
parentId: string | number
|
||||
}>
|
||||
): void {
|
||||
const terminalWidth = process.stdout.columns * 0.95 || 100;
|
||||
// Display subtasks header
|
||||
@@ -228,7 +227,7 @@ export function displaySubtasks(
|
||||
});
|
||||
|
||||
subtasks.forEach((subtask) => {
|
||||
const subtaskId = `${parentId}.${subtask.id}`;
|
||||
const subtaskId = String(subtask.id);
|
||||
|
||||
// Format dependencies
|
||||
const deps =
|
||||
@@ -329,7 +328,7 @@ export function displayTaskDetails(
|
||||
console.log(chalk.gray(` No subtasks with status '${statusFilter}'`));
|
||||
} else if (filteredSubtasks.length > 0) {
|
||||
console.log(); // Empty line for spacing
|
||||
displaySubtasks(filteredSubtasks, task.id);
|
||||
displaySubtasks(filteredSubtasks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface UpdateInfo {
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
needsUpdate: boolean;
|
||||
highlights?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,6 +60,116 @@ export function compareVersions(v1: string, v2: string): number {
|
||||
return a.pre < b.pre ? -1 : 1; // basic prerelease tie-break
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch CHANGELOG.md from GitHub and extract highlights for a specific version
|
||||
*/
|
||||
async function fetchChangelogHighlights(version: string): Promise<string[]> {
|
||||
return new Promise((resolve) => {
|
||||
const options = {
|
||||
hostname: 'raw.githubusercontent.com',
|
||||
path: '/eyaltoledano/claude-task-master/main/CHANGELOG.md',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': `task-master-ai/${version}`
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
if (res.statusCode !== 200) {
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const highlights = parseChangelogHighlights(data, version);
|
||||
resolve(highlights);
|
||||
} catch (error) {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', () => {
|
||||
resolve([]);
|
||||
});
|
||||
|
||||
req.setTimeout(3000, () => {
|
||||
req.destroy();
|
||||
resolve([]);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse changelog markdown to extract Minor Changes for a specific version
|
||||
* @internal - Exported for testing purposes only
|
||||
*/
|
||||
export function parseChangelogHighlights(
|
||||
changelog: string,
|
||||
version: string
|
||||
): string[] {
|
||||
try {
|
||||
// Validate version format (basic semver pattern) to prevent ReDoS
|
||||
if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(version)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Find the version section
|
||||
const versionRegex = new RegExp(
|
||||
`## ${version.replace(/\./g, '\\.')}\\s*\\n`,
|
||||
'i'
|
||||
);
|
||||
const versionMatch = changelog.match(versionRegex);
|
||||
|
||||
if (!versionMatch) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Extract content from this version to the next version heading
|
||||
const startIdx = versionMatch.index! + versionMatch[0].length;
|
||||
const nextVersionIdx = changelog.indexOf('\n## ', startIdx);
|
||||
const versionContent =
|
||||
nextVersionIdx > 0
|
||||
? changelog.slice(startIdx, nextVersionIdx)
|
||||
: changelog.slice(startIdx);
|
||||
|
||||
// Find Minor Changes section
|
||||
const minorChangesMatch = versionContent.match(
|
||||
/### Minor Changes\s*\n([\s\S]*?)(?=\n###|\n##|$)/i
|
||||
);
|
||||
|
||||
if (!minorChangesMatch) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const minorChangesContent = minorChangesMatch[1];
|
||||
const highlights: string[] = [];
|
||||
|
||||
// Extract all bullet points (lines starting with -)
|
||||
// Format: - [#PR](...) Thanks [@author]! - Description
|
||||
const bulletRegex = /^-\s+\[#\d+\][^\n]*?!\s+-\s+(.+?)$/gm;
|
||||
let match;
|
||||
|
||||
while ((match = bulletRegex.exec(minorChangesContent)) !== null) {
|
||||
const desc = match[1].trim();
|
||||
highlights.push(desc);
|
||||
}
|
||||
|
||||
return highlights;
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for newer version of task-master-ai
|
||||
*/
|
||||
@@ -85,7 +196,7 @@ export async function checkForUpdate(
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
res.on('end', async () => {
|
||||
try {
|
||||
if (res.statusCode !== 200)
|
||||
throw new Error(`npm registry status ${res.statusCode}`);
|
||||
@@ -95,10 +206,17 @@ export async function checkForUpdate(
|
||||
const needsUpdate =
|
||||
compareVersions(currentVersion, latestVersion) < 0;
|
||||
|
||||
// Fetch highlights if update is needed
|
||||
let highlights: string[] | undefined;
|
||||
if (needsUpdate) {
|
||||
highlights = await fetchChangelogHighlights(latestVersion);
|
||||
}
|
||||
|
||||
resolve({
|
||||
currentVersion,
|
||||
latestVersion,
|
||||
needsUpdate
|
||||
needsUpdate,
|
||||
highlights
|
||||
});
|
||||
} catch (error) {
|
||||
resolve({
|
||||
@@ -136,18 +254,29 @@ export async function checkForUpdate(
|
||||
*/
|
||||
export function displayUpgradeNotification(
|
||||
currentVersion: string,
|
||||
latestVersion: string
|
||||
latestVersion: string,
|
||||
highlights?: string[]
|
||||
) {
|
||||
const message = boxen(
|
||||
`${chalk.blue.bold('Update Available!')} ${chalk.dim(currentVersion)} → ${chalk.green(latestVersion)}\n\n` +
|
||||
`Auto-updating to the latest version with new features and bug fixes...`,
|
||||
{
|
||||
padding: 1,
|
||||
margin: { top: 1, bottom: 1 },
|
||||
borderColor: 'yellow',
|
||||
borderStyle: 'round'
|
||||
let content = `${chalk.blue.bold('Update Available!')} ${chalk.dim(currentVersion)} → ${chalk.green(latestVersion)}`;
|
||||
|
||||
if (highlights && highlights.length > 0) {
|
||||
content += '\n\n' + chalk.bold("What's New:");
|
||||
for (const highlight of highlights) {
|
||||
content += '\n' + chalk.cyan('• ') + highlight;
|
||||
}
|
||||
);
|
||||
content += '\n\n' + 'Auto-updating to the latest version...';
|
||||
} else {
|
||||
content +=
|
||||
'\n\n' +
|
||||
'Auto-updating to the latest version with new features and bug fixes...';
|
||||
}
|
||||
|
||||
const message = boxen(content, {
|
||||
padding: 1,
|
||||
margin: { top: 1, bottom: 1 },
|
||||
borderColor: 'yellow',
|
||||
borderStyle: 'round'
|
||||
});
|
||||
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
75
apps/cli/src/utils/display-helpers.ts
Normal file
75
apps/cli/src/utils/display-helpers.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @fileoverview Display helper utilities for commands
|
||||
* Provides DRY utilities for displaying headers and other command output
|
||||
*/
|
||||
|
||||
import type { TaskMasterCore } from '@tm/core';
|
||||
import type { StorageType } from '@tm/core/types';
|
||||
import { displayHeader, type BriefInfo } from '../ui/index.js';
|
||||
|
||||
/**
|
||||
* Get web app base URL from environment
|
||||
*/
|
||||
function getWebAppUrl(): string | undefined {
|
||||
const baseDomain =
|
||||
process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN;
|
||||
|
||||
if (!baseDomain) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If it already includes protocol, use as-is
|
||||
if (baseDomain.startsWith('http://') || baseDomain.startsWith('https://')) {
|
||||
return baseDomain;
|
||||
}
|
||||
|
||||
// Otherwise, add protocol based on domain
|
||||
if (baseDomain.includes('localhost') || baseDomain.includes('127.0.0.1')) {
|
||||
return `http://${baseDomain}`;
|
||||
}
|
||||
|
||||
return `https://${baseDomain}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the command header with appropriate storage information
|
||||
* Handles both API and file storage displays
|
||||
*/
|
||||
export function displayCommandHeader(
|
||||
tmCore: TaskMasterCore | undefined,
|
||||
options: {
|
||||
tag?: string;
|
||||
storageType: Exclude<StorageType, 'auto'>;
|
||||
}
|
||||
): void {
|
||||
const { tag, storageType } = options;
|
||||
|
||||
// Get brief info if using API storage
|
||||
let briefInfo: BriefInfo | undefined;
|
||||
if (storageType === 'api' && tmCore) {
|
||||
const storageInfo = tmCore.getStorageDisplayInfo();
|
||||
if (storageInfo) {
|
||||
// Construct full brief info with web app URL
|
||||
briefInfo = {
|
||||
...storageInfo,
|
||||
webAppUrl: getWebAppUrl()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Get file path for display (only for file storage)
|
||||
// Note: The file structure is fixed for file storage and won't change.
|
||||
// This is a display-only relative path, not used for actual file operations.
|
||||
const filePath =
|
||||
storageType === 'file' && tmCore
|
||||
? `.taskmaster/tasks/tasks.json`
|
||||
: undefined;
|
||||
|
||||
// Display header
|
||||
displayHeader({
|
||||
tag: tag || 'master',
|
||||
filePath: filePath,
|
||||
storageType: storageType === 'api' ? 'api' : 'file',
|
||||
briefInfo: briefInfo
|
||||
});
|
||||
}
|
||||
60
apps/cli/src/utils/error-handler.ts
Normal file
60
apps/cli/src/utils/error-handler.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @fileoverview Centralized error handling utilities for CLI
|
||||
* Provides consistent error formatting and debug mode detection
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Check if debug mode is enabled via environment variable
|
||||
* Only returns true when DEBUG is explicitly set to 'true' or '1'
|
||||
*
|
||||
* @returns True if debug mode is enabled
|
||||
*/
|
||||
export function isDebugMode(): boolean {
|
||||
return process.env.DEBUG === 'true' || process.env.DEBUG === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error to the user with optional stack trace in debug mode
|
||||
* Handles both TaskMasterError instances and regular errors
|
||||
*
|
||||
* @param error - The error to display
|
||||
* @param options - Display options
|
||||
*/
|
||||
export function displayError(
|
||||
error: any,
|
||||
options: {
|
||||
/** Skip exit, useful when caller wants to handle exit */
|
||||
skipExit?: boolean;
|
||||
/** Force show stack trace regardless of debug mode */
|
||||
forceStack?: boolean;
|
||||
} = {}
|
||||
): void {
|
||||
// Check if it's a TaskMasterError with sanitized details
|
||||
if (error?.getSanitizedDetails) {
|
||||
const sanitized = error.getSanitizedDetails();
|
||||
console.error(chalk.red(`\n${sanitized.message}`));
|
||||
|
||||
// Show stack trace in debug mode or if forced
|
||||
if ((isDebugMode() || options.forceStack) && error.stack) {
|
||||
console.error(chalk.gray('\nStack trace:'));
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
} else {
|
||||
// For other errors, show the message
|
||||
const message = error?.message ?? String(error);
|
||||
console.error(chalk.red(`\nError: ${message}`));
|
||||
|
||||
// Show stack trace in debug mode or if forced
|
||||
if ((isDebugMode() || options.forceStack) && error?.stack) {
|
||||
console.error(chalk.gray('\nStack trace:'));
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
}
|
||||
|
||||
// Exit if not skipped
|
||||
if (!options.skipExit) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
158
apps/cli/src/utils/ui.spec.ts
Normal file
158
apps/cli/src/utils/ui.spec.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* CLI UI utilities tests
|
||||
* Tests for apps/cli/src/utils/ui.ts
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import type { MockInstance } from 'vitest';
|
||||
import { getBoxWidth } from './ui.js';
|
||||
|
||||
describe('CLI UI Utilities', () => {
|
||||
describe('getBoxWidth', () => {
|
||||
let columnsSpy: MockInstance;
|
||||
let originalDescriptor: PropertyDescriptor | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
// Store original descriptor if it exists
|
||||
originalDescriptor = Object.getOwnPropertyDescriptor(
|
||||
process.stdout,
|
||||
'columns'
|
||||
);
|
||||
|
||||
// If columns doesn't exist or isn't a getter, define it as one
|
||||
if (!originalDescriptor || !originalDescriptor.get) {
|
||||
const currentValue = process.stdout.columns || 80;
|
||||
Object.defineProperty(process.stdout, 'columns', {
|
||||
get() {
|
||||
return currentValue;
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
// Now spy on the getter
|
||||
columnsSpy = vi.spyOn(process.stdout, 'columns', 'get');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore the spy
|
||||
columnsSpy.mockRestore();
|
||||
|
||||
// Restore original descriptor or delete the property
|
||||
if (originalDescriptor) {
|
||||
Object.defineProperty(process.stdout, 'columns', originalDescriptor);
|
||||
} else {
|
||||
delete (process.stdout as any).columns;
|
||||
}
|
||||
});
|
||||
|
||||
it('should calculate width as percentage of terminal width', () => {
|
||||
columnsSpy.mockReturnValue(100);
|
||||
const width = getBoxWidth(0.9, 40);
|
||||
expect(width).toBe(90);
|
||||
});
|
||||
|
||||
it('should use default percentage of 0.9 when not specified', () => {
|
||||
columnsSpy.mockReturnValue(100);
|
||||
const width = getBoxWidth();
|
||||
expect(width).toBe(90);
|
||||
});
|
||||
|
||||
it('should use default minimum width of 40 when not specified', () => {
|
||||
columnsSpy.mockReturnValue(30);
|
||||
const width = getBoxWidth();
|
||||
expect(width).toBe(40); // Should enforce minimum
|
||||
});
|
||||
|
||||
it('should enforce minimum width when terminal is too narrow', () => {
|
||||
columnsSpy.mockReturnValue(50);
|
||||
const width = getBoxWidth(0.9, 60);
|
||||
expect(width).toBe(60); // Should use minWidth instead of 45
|
||||
});
|
||||
|
||||
it('should handle undefined process.stdout.columns', () => {
|
||||
columnsSpy.mockReturnValue(undefined);
|
||||
const width = getBoxWidth(0.9, 40);
|
||||
// Should fall back to 80 columns: Math.floor(80 * 0.9) = 72
|
||||
expect(width).toBe(72);
|
||||
});
|
||||
|
||||
it('should handle custom percentage values', () => {
|
||||
columnsSpy.mockReturnValue(100);
|
||||
expect(getBoxWidth(0.95, 40)).toBe(95);
|
||||
expect(getBoxWidth(0.8, 40)).toBe(80);
|
||||
expect(getBoxWidth(0.5, 40)).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle custom minimum width values', () => {
|
||||
columnsSpy.mockReturnValue(60);
|
||||
expect(getBoxWidth(0.9, 70)).toBe(70); // 60 * 0.9 = 54, but min is 70
|
||||
expect(getBoxWidth(0.9, 50)).toBe(54); // 60 * 0.9 = 54, min is 50
|
||||
});
|
||||
|
||||
it('should floor the calculated width', () => {
|
||||
columnsSpy.mockReturnValue(99);
|
||||
const width = getBoxWidth(0.9, 40);
|
||||
// 99 * 0.9 = 89.1, should floor to 89
|
||||
expect(width).toBe(89);
|
||||
});
|
||||
|
||||
it('should match warning box width calculation', () => {
|
||||
// Test the specific case from displayWarning()
|
||||
columnsSpy.mockReturnValue(80);
|
||||
const width = getBoxWidth(0.9, 40);
|
||||
expect(width).toBe(72);
|
||||
});
|
||||
|
||||
it('should match table width calculation', () => {
|
||||
// Test the specific case from createTaskTable()
|
||||
columnsSpy.mockReturnValue(111);
|
||||
const width = getBoxWidth(0.9, 100);
|
||||
// 111 * 0.9 = 99.9, floor to 99, but max(99, 100) = 100
|
||||
expect(width).toBe(100);
|
||||
});
|
||||
|
||||
it('should match recommended task box width calculation', () => {
|
||||
// Test the specific case from displayRecommendedNextTask()
|
||||
columnsSpy.mockReturnValue(120);
|
||||
const width = getBoxWidth(0.97, 40);
|
||||
// 120 * 0.97 = 116.4, floor to 116
|
||||
expect(width).toBe(116);
|
||||
});
|
||||
|
||||
it('should handle edge case of zero terminal width', () => {
|
||||
columnsSpy.mockReturnValue(0);
|
||||
const width = getBoxWidth(0.9, 40);
|
||||
// When columns is 0, it uses fallback of 80: Math.floor(80 * 0.9) = 72
|
||||
expect(width).toBe(72);
|
||||
});
|
||||
|
||||
it('should handle very large terminal widths', () => {
|
||||
columnsSpy.mockReturnValue(1000);
|
||||
const width = getBoxWidth(0.9, 40);
|
||||
expect(width).toBe(900);
|
||||
});
|
||||
|
||||
it('should handle very small percentages', () => {
|
||||
columnsSpy.mockReturnValue(100);
|
||||
const width = getBoxWidth(0.1, 5);
|
||||
// 100 * 0.1 = 10, which is greater than min 5
|
||||
expect(width).toBe(10);
|
||||
});
|
||||
|
||||
it('should handle percentage of 1.0 (100%)', () => {
|
||||
columnsSpy.mockReturnValue(80);
|
||||
const width = getBoxWidth(1.0, 40);
|
||||
expect(width).toBe(80);
|
||||
});
|
||||
|
||||
it('should consistently return same value for same inputs', () => {
|
||||
columnsSpy.mockReturnValue(100);
|
||||
const width1 = getBoxWidth(0.9, 40);
|
||||
const width2 = getBoxWidth(0.9, 40);
|
||||
const width3 = getBoxWidth(0.9, 40);
|
||||
expect(width1).toBe(width2);
|
||||
expect(width2).toBe(width3);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -126,6 +126,20 @@ export function getComplexityWithScore(complexity: number | undefined): string {
|
||||
return color(`${complexity}/10 (${label})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate box width as percentage of terminal width
|
||||
* @param percentage - Percentage of terminal width to use (default: 0.9)
|
||||
* @param minWidth - Minimum width to enforce (default: 40)
|
||||
* @returns Calculated box width
|
||||
*/
|
||||
export function getBoxWidth(
|
||||
percentage: number = 0.9,
|
||||
minWidth: number = 40
|
||||
): number {
|
||||
const terminalWidth = process.stdout.columns || 80;
|
||||
return Math.max(Math.floor(terminalWidth * percentage), minWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate text to specified length
|
||||
*/
|
||||
@@ -176,6 +190,8 @@ export function displayBanner(title: string = 'Task Master'): void {
|
||||
* Display an error message (matches scripts/modules/ui.js style)
|
||||
*/
|
||||
export function displayError(message: string, details?: string): void {
|
||||
const boxWidth = getBoxWidth();
|
||||
|
||||
console.error(
|
||||
boxen(
|
||||
chalk.red.bold('X Error: ') +
|
||||
@@ -184,7 +200,8 @@ export function displayError(message: string, details?: string): void {
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'red'
|
||||
borderColor: 'red',
|
||||
width: boxWidth
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -194,13 +211,16 @@ export function displayError(message: string, details?: string): void {
|
||||
* Display a success message
|
||||
*/
|
||||
export function displaySuccess(message: string): void {
|
||||
const boxWidth = getBoxWidth();
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green.bold(String.fromCharCode(8730) + ' ') + chalk.white(message),
|
||||
{
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'green'
|
||||
borderColor: 'green',
|
||||
width: boxWidth
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -210,11 +230,14 @@ export function displaySuccess(message: string): void {
|
||||
* Display a warning message
|
||||
*/
|
||||
export function displayWarning(message: string): void {
|
||||
const boxWidth = getBoxWidth();
|
||||
|
||||
console.log(
|
||||
boxen(chalk.yellow.bold('⚠ ') + chalk.white(message), {
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'yellow'
|
||||
borderColor: 'yellow',
|
||||
width: boxWidth
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -223,11 +246,14 @@ export function displayWarning(message: string): void {
|
||||
* Display info message
|
||||
*/
|
||||
export function displayInfo(message: string): void {
|
||||
const boxWidth = getBoxWidth();
|
||||
|
||||
console.log(
|
||||
boxen(chalk.blue.bold('i ') + chalk.white(message), {
|
||||
padding: 1,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'blue'
|
||||
borderColor: 'blue',
|
||||
width: boxWidth
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -282,23 +308,23 @@ export function createTaskTable(
|
||||
} = options || {};
|
||||
|
||||
// Calculate dynamic column widths based on terminal width
|
||||
const terminalWidth = process.stdout.columns * 0.9 || 100;
|
||||
const tableWidth = getBoxWidth(0.9, 100);
|
||||
// Adjust column widths to better match the original layout
|
||||
const baseColWidths = showComplexity
|
||||
? [
|
||||
Math.floor(terminalWidth * 0.06),
|
||||
Math.floor(terminalWidth * 0.4),
|
||||
Math.floor(terminalWidth * 0.15),
|
||||
Math.floor(terminalWidth * 0.12),
|
||||
Math.floor(terminalWidth * 0.2),
|
||||
Math.floor(terminalWidth * 0.12)
|
||||
Math.floor(tableWidth * 0.1),
|
||||
Math.floor(tableWidth * 0.4),
|
||||
Math.floor(tableWidth * 0.15),
|
||||
Math.floor(tableWidth * 0.1),
|
||||
Math.floor(tableWidth * 0.2),
|
||||
Math.floor(tableWidth * 0.1)
|
||||
] // ID, Title, Status, Priority, Dependencies, Complexity
|
||||
: [
|
||||
Math.floor(terminalWidth * 0.08),
|
||||
Math.floor(terminalWidth * 0.4),
|
||||
Math.floor(terminalWidth * 0.18),
|
||||
Math.floor(terminalWidth * 0.12),
|
||||
Math.floor(terminalWidth * 0.2)
|
||||
Math.floor(tableWidth * 0.08),
|
||||
Math.floor(tableWidth * 0.4),
|
||||
Math.floor(tableWidth * 0.18),
|
||||
Math.floor(tableWidth * 0.12),
|
||||
Math.floor(tableWidth * 0.2)
|
||||
]; // ID, Title, Status, Priority, Dependencies
|
||||
|
||||
const headers = [
|
||||
@@ -377,7 +403,11 @@ export function createTaskTable(
|
||||
}
|
||||
|
||||
if (showComplexity) {
|
||||
subRow.push(chalk.gray('--'));
|
||||
const complexityDisplay =
|
||||
typeof subtask.complexity === 'number'
|
||||
? getComplexityWithColor(subtask.complexity)
|
||||
: '--';
|
||||
subRow.push(chalk.gray(complexityDisplay));
|
||||
}
|
||||
|
||||
table.push(subRow);
|
||||
|
||||
540
apps/cli/tests/integration/commands/autopilot/workflow.test.ts
Normal file
540
apps/cli/tests/integration/commands/autopilot/workflow.test.ts
Normal file
@@ -0,0 +1,540 @@
|
||||
/**
|
||||
* @fileoverview Integration tests for autopilot workflow commands
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import type { WorkflowState } from '@tm/core';
|
||||
|
||||
// Track file system state in memory - must be in vi.hoisted() for mock access
|
||||
const {
|
||||
mockFileSystem,
|
||||
pathExistsFn,
|
||||
readJSONFn,
|
||||
writeJSONFn,
|
||||
ensureDirFn,
|
||||
removeFn
|
||||
} = vi.hoisted(() => {
|
||||
const mockFileSystem = new Map<string, string>();
|
||||
|
||||
return {
|
||||
mockFileSystem,
|
||||
pathExistsFn: vi.fn((path: string) =>
|
||||
Promise.resolve(mockFileSystem.has(path))
|
||||
),
|
||||
readJSONFn: vi.fn((path: string) => {
|
||||
const data = mockFileSystem.get(path);
|
||||
return data
|
||||
? Promise.resolve(JSON.parse(data))
|
||||
: Promise.reject(new Error('File not found'));
|
||||
}),
|
||||
writeJSONFn: vi.fn((path: string, data: any) => {
|
||||
mockFileSystem.set(path, JSON.stringify(data));
|
||||
return Promise.resolve();
|
||||
}),
|
||||
ensureDirFn: vi.fn(() => Promise.resolve()),
|
||||
removeFn: vi.fn((path: string) => {
|
||||
mockFileSystem.delete(path);
|
||||
return Promise.resolve();
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
// Mock fs-extra before any imports
|
||||
vi.mock('fs-extra', () => ({
|
||||
default: {
|
||||
pathExists: pathExistsFn,
|
||||
readJSON: readJSONFn,
|
||||
writeJSON: writeJSONFn,
|
||||
ensureDir: ensureDirFn,
|
||||
remove: removeFn
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('@tm/core', () => ({
|
||||
WorkflowOrchestrator: vi.fn().mockImplementation((context) => ({
|
||||
getCurrentPhase: vi.fn().mockReturnValue('SUBTASK_LOOP'),
|
||||
getCurrentTDDPhase: vi.fn().mockReturnValue('RED'),
|
||||
getContext: vi.fn().mockReturnValue(context),
|
||||
transition: vi.fn(),
|
||||
restoreState: vi.fn(),
|
||||
getState: vi.fn().mockReturnValue({ phase: 'SUBTASK_LOOP', context }),
|
||||
enableAutoPersist: vi.fn(),
|
||||
canResumeFromState: vi.fn().mockReturnValue(true),
|
||||
getCurrentSubtask: vi.fn().mockReturnValue({
|
||||
id: '1',
|
||||
title: 'Test Subtask',
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
}),
|
||||
getProgress: vi.fn().mockReturnValue({
|
||||
completed: 0,
|
||||
total: 3,
|
||||
current: 1,
|
||||
percentage: 0
|
||||
}),
|
||||
canProceed: vi.fn().mockReturnValue(false)
|
||||
})),
|
||||
GitAdapter: vi.fn().mockImplementation(() => ({
|
||||
ensureGitRepository: vi.fn().mockResolvedValue(undefined),
|
||||
ensureCleanWorkingTree: vi.fn().mockResolvedValue(undefined),
|
||||
createAndCheckoutBranch: vi.fn().mockResolvedValue(undefined),
|
||||
hasStagedChanges: vi.fn().mockResolvedValue(true),
|
||||
getStatus: vi.fn().mockResolvedValue({
|
||||
staged: ['file1.ts'],
|
||||
modified: ['file2.ts']
|
||||
}),
|
||||
createCommit: vi.fn().mockResolvedValue(undefined),
|
||||
getLastCommit: vi.fn().mockResolvedValue({
|
||||
hash: 'abc123def456',
|
||||
message: 'test commit'
|
||||
}),
|
||||
stageFiles: vi.fn().mockResolvedValue(undefined)
|
||||
})),
|
||||
CommitMessageGenerator: vi.fn().mockImplementation(() => ({
|
||||
generateMessage: vi.fn().mockReturnValue('feat: test commit message')
|
||||
})),
|
||||
createTaskMasterCore: vi.fn().mockResolvedValue({
|
||||
getTaskWithSubtask: vi.fn().mockResolvedValue({
|
||||
task: {
|
||||
id: '1',
|
||||
title: 'Test Task',
|
||||
subtasks: [
|
||||
{ id: '1', title: 'Subtask 1', status: 'pending' },
|
||||
{ id: '2', title: 'Subtask 2', status: 'pending' },
|
||||
{ id: '3', title: 'Subtask 3', status: 'pending' }
|
||||
],
|
||||
tag: 'test'
|
||||
}
|
||||
}),
|
||||
close: vi.fn().mockResolvedValue(undefined)
|
||||
})
|
||||
}));
|
||||
|
||||
// Import after mocks are set up
|
||||
import { Command } from 'commander';
|
||||
import { AutopilotCommand } from '../../../../src/commands/autopilot/index.js';
|
||||
|
||||
describe('Autopilot Workflow Integration Tests', () => {
|
||||
const projectRoot = '/test/project';
|
||||
let program: Command;
|
||||
|
||||
beforeEach(() => {
|
||||
mockFileSystem.clear();
|
||||
|
||||
// Clear mock call history
|
||||
pathExistsFn.mockClear();
|
||||
readJSONFn.mockClear();
|
||||
writeJSONFn.mockClear();
|
||||
ensureDirFn.mockClear();
|
||||
removeFn.mockClear();
|
||||
|
||||
program = new Command();
|
||||
AutopilotCommand.register(program);
|
||||
|
||||
// Use exitOverride to handle Commander exits in tests
|
||||
program.exitOverride();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFileSystem.clear();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('start command', () => {
|
||||
it('should initialize workflow and create branch', async () => {
|
||||
const consoleLogSpy = vi
|
||||
.spyOn(console, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'start',
|
||||
'1',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--json'
|
||||
]);
|
||||
|
||||
// Verify writeJSON was called with state
|
||||
expect(writeJSONFn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('workflow-state.json'),
|
||||
expect.objectContaining({
|
||||
phase: expect.any(String),
|
||||
context: expect.any(Object)
|
||||
}),
|
||||
expect.any(Object)
|
||||
);
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should reject invalid task ID', async () => {
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await expect(
|
||||
program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'start',
|
||||
'invalid',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--json'
|
||||
])
|
||||
).rejects.toMatchObject({ exitCode: 1 });
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should reject starting when workflow exists without force', async () => {
|
||||
// Create existing state
|
||||
const mockState: WorkflowState = {
|
||||
phase: 'SUBTASK_LOOP',
|
||||
context: {
|
||||
taskId: '1',
|
||||
subtasks: [],
|
||||
currentSubtaskIndex: 0,
|
||||
errors: [],
|
||||
metadata: {}
|
||||
}
|
||||
};
|
||||
|
||||
mockFileSystem.set(
|
||||
`${projectRoot}/.taskmaster/workflow-state.json`,
|
||||
JSON.stringify(mockState)
|
||||
);
|
||||
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await expect(
|
||||
program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'start',
|
||||
'1',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--json'
|
||||
])
|
||||
).rejects.toMatchObject({ exitCode: 1 });
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resume command', () => {
|
||||
beforeEach(() => {
|
||||
// Create saved state
|
||||
const mockState: WorkflowState = {
|
||||
phase: 'SUBTASK_LOOP',
|
||||
context: {
|
||||
taskId: '1',
|
||||
subtasks: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Test Subtask',
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
}
|
||||
],
|
||||
currentSubtaskIndex: 0,
|
||||
currentTDDPhase: 'RED',
|
||||
branchName: 'task-1',
|
||||
errors: [],
|
||||
metadata: {}
|
||||
}
|
||||
};
|
||||
|
||||
mockFileSystem.set(
|
||||
`${projectRoot}/.taskmaster/workflow-state.json`,
|
||||
JSON.stringify(mockState)
|
||||
);
|
||||
});
|
||||
|
||||
it('should restore workflow from saved state', async () => {
|
||||
const consoleLogSpy = vi
|
||||
.spyOn(console, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'resume',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--json'
|
||||
]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
||||
expect(output.success).toBe(true);
|
||||
expect(output.taskId).toBe('1');
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should error when no state exists', async () => {
|
||||
mockFileSystem.clear();
|
||||
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await expect(
|
||||
program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'resume',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--json'
|
||||
])
|
||||
).rejects.toMatchObject({ exitCode: 1 });
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('next command', () => {
|
||||
beforeEach(() => {
|
||||
const mockState: WorkflowState = {
|
||||
phase: 'SUBTASK_LOOP',
|
||||
context: {
|
||||
taskId: '1',
|
||||
subtasks: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Test Subtask',
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
}
|
||||
],
|
||||
currentSubtaskIndex: 0,
|
||||
currentTDDPhase: 'RED',
|
||||
branchName: 'task-1',
|
||||
errors: [],
|
||||
metadata: {}
|
||||
}
|
||||
};
|
||||
|
||||
mockFileSystem.set(
|
||||
`${projectRoot}/.taskmaster/workflow-state.json`,
|
||||
JSON.stringify(mockState)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return next action in JSON format', async () => {
|
||||
const consoleLogSpy = vi
|
||||
.spyOn(console, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'next',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--json'
|
||||
]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
||||
expect(output.action).toBe('generate_test');
|
||||
expect(output.phase).toBe('SUBTASK_LOOP');
|
||||
expect(output.tddPhase).toBe('RED');
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('status command', () => {
|
||||
beforeEach(() => {
|
||||
const mockState: WorkflowState = {
|
||||
phase: 'SUBTASK_LOOP',
|
||||
context: {
|
||||
taskId: '1',
|
||||
subtasks: [
|
||||
{ id: '1', title: 'Subtask 1', status: 'completed', attempts: 1 },
|
||||
{ id: '2', title: 'Subtask 2', status: 'pending', attempts: 0 },
|
||||
{ id: '3', title: 'Subtask 3', status: 'pending', attempts: 0 }
|
||||
],
|
||||
currentSubtaskIndex: 1,
|
||||
currentTDDPhase: 'RED',
|
||||
branchName: 'task-1',
|
||||
errors: [],
|
||||
metadata: {}
|
||||
}
|
||||
};
|
||||
|
||||
mockFileSystem.set(
|
||||
`${projectRoot}/.taskmaster/workflow-state.json`,
|
||||
JSON.stringify(mockState)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display workflow progress', async () => {
|
||||
const consoleLogSpy = vi
|
||||
.spyOn(console, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'status',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--json'
|
||||
]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
||||
expect(output.taskId).toBe('1');
|
||||
expect(output.phase).toBe('SUBTASK_LOOP');
|
||||
expect(output.progress).toBeDefined();
|
||||
expect(output.subtasks).toHaveLength(3);
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('complete command', () => {
|
||||
beforeEach(() => {
|
||||
const mockState: WorkflowState = {
|
||||
phase: 'SUBTASK_LOOP',
|
||||
context: {
|
||||
taskId: '1',
|
||||
subtasks: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Test Subtask',
|
||||
status: 'in-progress',
|
||||
attempts: 0
|
||||
}
|
||||
],
|
||||
currentSubtaskIndex: 0,
|
||||
currentTDDPhase: 'RED',
|
||||
branchName: 'task-1',
|
||||
errors: [],
|
||||
metadata: {}
|
||||
}
|
||||
};
|
||||
|
||||
mockFileSystem.set(
|
||||
`${projectRoot}/.taskmaster/workflow-state.json`,
|
||||
JSON.stringify(mockState)
|
||||
);
|
||||
});
|
||||
|
||||
it('should validate RED phase has failures', async () => {
|
||||
const consoleErrorSpy = vi
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await expect(
|
||||
program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'complete',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--results',
|
||||
'{"total":10,"passed":10,"failed":0,"skipped":0}',
|
||||
'--json'
|
||||
])
|
||||
).rejects.toMatchObject({ exitCode: 1 });
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should complete RED phase with failures', async () => {
|
||||
const consoleLogSpy = vi
|
||||
.spyOn(console, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'complete',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--results',
|
||||
'{"total":10,"passed":9,"failed":1,"skipped":0}',
|
||||
'--json'
|
||||
]);
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
||||
expect(output.success).toBe(true);
|
||||
expect(output.nextPhase).toBe('GREEN');
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('abort command', () => {
|
||||
beforeEach(() => {
|
||||
const mockState: WorkflowState = {
|
||||
phase: 'SUBTASK_LOOP',
|
||||
context: {
|
||||
taskId: '1',
|
||||
subtasks: [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Test Subtask',
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
}
|
||||
],
|
||||
currentSubtaskIndex: 0,
|
||||
currentTDDPhase: 'RED',
|
||||
branchName: 'task-1',
|
||||
errors: [],
|
||||
metadata: {}
|
||||
}
|
||||
};
|
||||
|
||||
mockFileSystem.set(
|
||||
`${projectRoot}/.taskmaster/workflow-state.json`,
|
||||
JSON.stringify(mockState)
|
||||
);
|
||||
});
|
||||
|
||||
it('should abort workflow and delete state', async () => {
|
||||
const consoleLogSpy = vi
|
||||
.spyOn(console, 'log')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await program.parseAsync([
|
||||
'node',
|
||||
'test',
|
||||
'autopilot',
|
||||
'abort',
|
||||
'--project-root',
|
||||
projectRoot,
|
||||
'--force',
|
||||
'--json'
|
||||
]);
|
||||
|
||||
// Verify remove was called
|
||||
expect(removeFn).toHaveBeenCalledWith(
|
||||
expect.stringContaining('workflow-state.json')
|
||||
);
|
||||
|
||||
consoleLogSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
202
apps/cli/tests/unit/commands/autopilot/shared.test.ts
Normal file
202
apps/cli/tests/unit/commands/autopilot/shared.test.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @fileoverview Unit tests for autopilot shared utilities
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import {
|
||||
validateTaskId,
|
||||
parseSubtasks,
|
||||
OutputFormatter
|
||||
} from '../../../../src/commands/autopilot/shared.js';
|
||||
|
||||
// Mock fs-extra
|
||||
vi.mock('fs-extra', () => ({
|
||||
default: {
|
||||
pathExists: vi.fn(),
|
||||
readJSON: vi.fn(),
|
||||
writeJSON: vi.fn(),
|
||||
ensureDir: vi.fn(),
|
||||
remove: vi.fn()
|
||||
},
|
||||
pathExists: vi.fn(),
|
||||
readJSON: vi.fn(),
|
||||
writeJSON: vi.fn(),
|
||||
ensureDir: vi.fn(),
|
||||
remove: vi.fn()
|
||||
}));
|
||||
|
||||
describe('Autopilot Shared Utilities', () => {
|
||||
const projectRoot = '/test/project';
|
||||
const statePath = `${projectRoot}/.taskmaster/workflow-state.json`;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('validateTaskId', () => {
|
||||
it('should validate simple task IDs', () => {
|
||||
expect(validateTaskId('1')).toBe(true);
|
||||
expect(validateTaskId('10')).toBe(true);
|
||||
expect(validateTaskId('999')).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate subtask IDs', () => {
|
||||
expect(validateTaskId('1.1')).toBe(true);
|
||||
expect(validateTaskId('1.2')).toBe(true);
|
||||
expect(validateTaskId('10.5')).toBe(true);
|
||||
});
|
||||
|
||||
it('should validate nested subtask IDs', () => {
|
||||
expect(validateTaskId('1.1.1')).toBe(true);
|
||||
expect(validateTaskId('1.2.3')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject invalid formats', () => {
|
||||
expect(validateTaskId('')).toBe(false);
|
||||
expect(validateTaskId('abc')).toBe(false);
|
||||
expect(validateTaskId('1.')).toBe(false);
|
||||
expect(validateTaskId('.1')).toBe(false);
|
||||
expect(validateTaskId('1..2')).toBe(false);
|
||||
expect(validateTaskId('1.2.3.')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseSubtasks', () => {
|
||||
it('should parse subtasks from task data', () => {
|
||||
const task = {
|
||||
id: '1',
|
||||
title: 'Test Task',
|
||||
subtasks: [
|
||||
{ id: '1', title: 'Subtask 1', status: 'pending' },
|
||||
{ id: '2', title: 'Subtask 2', status: 'done' },
|
||||
{ id: '3', title: 'Subtask 3', status: 'in-progress' }
|
||||
]
|
||||
};
|
||||
|
||||
const result = parseSubtasks(task, 5);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0]).toEqual({
|
||||
id: '1',
|
||||
title: 'Subtask 1',
|
||||
status: 'pending',
|
||||
attempts: 0,
|
||||
maxAttempts: 5
|
||||
});
|
||||
expect(result[1]).toEqual({
|
||||
id: '2',
|
||||
title: 'Subtask 2',
|
||||
status: 'completed',
|
||||
attempts: 0,
|
||||
maxAttempts: 5
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array for missing subtasks', () => {
|
||||
const task = { id: '1', title: 'Test Task' };
|
||||
expect(parseSubtasks(task)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should use default maxAttempts', () => {
|
||||
const task = {
|
||||
subtasks: [{ id: '1', title: 'Subtask 1', status: 'pending' }]
|
||||
};
|
||||
|
||||
const result = parseSubtasks(task);
|
||||
expect(result[0].maxAttempts).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
// State persistence tests omitted - covered in integration tests
|
||||
|
||||
describe('OutputFormatter', () => {
|
||||
let consoleLogSpy: any;
|
||||
let consoleErrorSpy: any;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleLogSpy.mockRestore();
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('JSON mode', () => {
|
||||
it('should output JSON for success', () => {
|
||||
const formatter = new OutputFormatter(true);
|
||||
formatter.success('Test message', { key: 'value' });
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
||||
expect(output.success).toBe(true);
|
||||
expect(output.message).toBe('Test message');
|
||||
expect(output.key).toBe('value');
|
||||
});
|
||||
|
||||
it('should output JSON for error', () => {
|
||||
const formatter = new OutputFormatter(true);
|
||||
formatter.error('Error message', { code: 'ERR001' });
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
const output = JSON.parse(consoleErrorSpy.mock.calls[0][0]);
|
||||
expect(output.error).toBe('Error message');
|
||||
expect(output.code).toBe('ERR001');
|
||||
});
|
||||
|
||||
it('should output JSON for data', () => {
|
||||
const formatter = new OutputFormatter(true);
|
||||
formatter.output({ test: 'data' });
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalled();
|
||||
const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
|
||||
expect(output.test).toBe('data');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Text mode', () => {
|
||||
it('should output formatted text for success', () => {
|
||||
const formatter = new OutputFormatter(false);
|
||||
formatter.success('Test message');
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('✓ Test message')
|
||||
);
|
||||
});
|
||||
|
||||
it('should output formatted text for error', () => {
|
||||
const formatter = new OutputFormatter(false);
|
||||
formatter.error('Error message');
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Error: Error message')
|
||||
);
|
||||
});
|
||||
|
||||
it('should output formatted text for warning', () => {
|
||||
const consoleWarnSpy = vi
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementation(() => {});
|
||||
const formatter = new OutputFormatter(false);
|
||||
formatter.warning('Warning message');
|
||||
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('⚠ Warning message')
|
||||
);
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should not output info in JSON mode', () => {
|
||||
const formatter = new OutputFormatter(true);
|
||||
formatter.info('Info message');
|
||||
|
||||
expect(consoleLogSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
25
apps/cli/vitest.config.ts
Normal file
25
apps/cli/vitest.config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
include: ['tests/**/*.test.ts', 'tests/**/*.spec.ts'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html'],
|
||||
include: ['src/**/*.ts'],
|
||||
exclude: [
|
||||
'node_modules/',
|
||||
'dist/',
|
||||
'tests/',
|
||||
'**/*.test.ts',
|
||||
'**/*.spec.ts',
|
||||
'**/*.d.ts',
|
||||
'**/mocks/**',
|
||||
'**/fixtures/**',
|
||||
'vitest.config.ts'
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
# docs
|
||||
|
||||
## 0.0.6
|
||||
|
||||
## 0.0.5
|
||||
|
||||
## 0.0.4
|
||||
|
||||
## 0.0.3
|
||||
|
||||
@@ -13,6 +13,126 @@ The MCP interface is built on top of the `fastmcp` library and registers a set o
|
||||
|
||||
Each tool is defined with a name, a description, and a set of parameters that are validated using the `zod` library. The `execute` function of each tool calls the corresponding core logic function from `scripts/modules/task-manager.js`.
|
||||
|
||||
## Configurable Tool Loading
|
||||
|
||||
To optimize LLM context usage, you can control which Task Master MCP tools are loaded using the `TASK_MASTER_TOOLS` environment variable. This is particularly useful when working with LLMs that have context limits or when you only need a subset of tools.
|
||||
|
||||
### Configuration Modes
|
||||
|
||||
#### All Tools (Default)
|
||||
Loads all 36 available tools. Use when you need full Task Master functionality.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "all",
|
||||
"ANTHROPIC_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `TASK_MASTER_TOOLS` is not set, all tools are loaded by default.
|
||||
|
||||
#### Core Tools (Lean Mode)
|
||||
Loads only 7 essential tools for daily development. Ideal for minimal context usage.
|
||||
|
||||
**Core tools included:**
|
||||
- `get_tasks` - List all tasks
|
||||
- `next_task` - Find the next task to work on
|
||||
- `get_task` - Get detailed task information
|
||||
- `set_task_status` - Update task status
|
||||
- `update_subtask` - Add implementation notes
|
||||
- `parse_prd` - Generate tasks from PRD
|
||||
- `expand_task` - Break down tasks into subtasks
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "core",
|
||||
"ANTHROPIC_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also use `"lean"` as an alias for `"core"`.
|
||||
|
||||
#### Standard Tools
|
||||
Loads 15 commonly used tools. Balances functionality with context efficiency.
|
||||
|
||||
**Standard tools include all core tools plus:**
|
||||
- `initialize_project` - Set up new projects
|
||||
- `analyze_project_complexity` - Analyze task complexity
|
||||
- `expand_all` - Expand all eligible tasks
|
||||
- `add_subtask` - Add subtasks manually
|
||||
- `remove_task` - Remove tasks
|
||||
- `generate` - Generate task markdown files
|
||||
- `add_task` - Create new tasks
|
||||
- `complexity_report` - View complexity analysis
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "standard",
|
||||
"ANTHROPIC_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom Tool Selection
|
||||
Specify exactly which tools to load using a comma-separated list. Tool names are case-insensitive and support both underscores and hyphens.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"task-master-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "task-master-ai"],
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "get_tasks,next_task,set_task_status,update_subtask",
|
||||
"ANTHROPIC_API_KEY": "your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Choosing the Right Configuration
|
||||
|
||||
- **Use `core`/`lean`**: When working with basic task management workflows or when context limits are strict
|
||||
- **Use `standard`**: For most development workflows that include task creation and analysis
|
||||
- **Use `all`**: When you need full functionality including tag management, dependencies, and advanced features
|
||||
- **Use custom list**: When you have specific tool requirements or want to experiment with minimal sets
|
||||
|
||||
### Verification
|
||||
|
||||
When the MCP server starts, it logs which tools were loaded:
|
||||
|
||||
```
|
||||
Task Master MCP Server starting...
|
||||
Tool mode configuration: standard
|
||||
Loading standard tools
|
||||
Registering 15 MCP tools (mode: standard)
|
||||
Successfully registered 15/15 tools
|
||||
```
|
||||
|
||||
## Tool Categories
|
||||
|
||||
The MCP tools can be categorized in the same way as the core functionalities:
|
||||
|
||||
326
apps/docs/capabilities/rpg-method.mdx
Normal file
326
apps/docs/capabilities/rpg-method.mdx
Normal file
@@ -0,0 +1,326 @@
|
||||
---
|
||||
title: RPG Method for PRD Creation
|
||||
sidebarTitle: "RPG Method"
|
||||
---
|
||||
|
||||
# Repository Planning Graph (RPG) Method
|
||||
|
||||
The RPG (Repository Planning Graph) method is an advanced approach to creating Product Requirements Documents that generate highly-structured, dependency-aware task graphs. It's based on Microsoft Research's methodology for scalable codebase generation.
|
||||
|
||||
## When to Use RPG
|
||||
|
||||
Use the RPG template (`example_prd_rpg.txt`) for:
|
||||
|
||||
- **Complex multi-module systems** with intricate dependencies
|
||||
- **Large-scale codebases** being built from scratch
|
||||
- **Projects requiring explicit architecture** and clear module boundaries
|
||||
- **Teams needing dependency visibility** for parallel development
|
||||
|
||||
For simpler features or smaller projects, the standard `example_prd.txt` template may be more appropriate.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Dual-Semantics
|
||||
|
||||
Separate **functional** thinking (WHAT) from **structural** thinking (HOW):
|
||||
|
||||
```
|
||||
Functional: "Data Validation capability with schema checking and rule enforcement"
|
||||
↓
|
||||
Structural: "src/validation/ with schema-validator.js and rule-validator.js"
|
||||
```
|
||||
|
||||
This separation prevents mixing concerns and creates clearer module boundaries.
|
||||
|
||||
### 2. Explicit Dependencies
|
||||
|
||||
Never assume dependencies - always state them explicitly:
|
||||
|
||||
```
|
||||
Good:
|
||||
Module: data-ingestion
|
||||
Depends on: [schema-validator, config-manager]
|
||||
|
||||
Bad:
|
||||
Module: data-ingestion
|
||||
(Assumes schema-validator exists somewhere)
|
||||
```
|
||||
|
||||
Explicit dependencies enable:
|
||||
- Topological ordering of implementation
|
||||
- Parallel development of independent modules
|
||||
- Clear build/test order
|
||||
- Early detection of circular dependencies
|
||||
|
||||
### 3. Topological Order
|
||||
|
||||
Build foundation layers before higher layers:
|
||||
|
||||
```
|
||||
Phase 0 (Foundation): error-handling, base-types, config
|
||||
↓
|
||||
Phase 1 (Data): validation, ingestion (depend on Phase 0)
|
||||
↓
|
||||
Phase 2 (Core): algorithms, pipelines (depend on Phase 1)
|
||||
↓
|
||||
Phase 3 (API): routes, handlers (depend on Phase 2)
|
||||
```
|
||||
|
||||
Task Master automatically orders tasks based on this dependency chain.
|
||||
|
||||
### 4. Progressive Refinement
|
||||
|
||||
Start broad, refine iteratively:
|
||||
|
||||
1. High-level capabilities → Main tasks
|
||||
2. Features per capability → Subtasks
|
||||
3. Implementation details → Expanded subtasks
|
||||
|
||||
---
|
||||
|
||||
## Template Structure
|
||||
|
||||
The RPG template guides you through 7 key sections:
|
||||
|
||||
### 1. Overview
|
||||
- Problem statement
|
||||
- Target users
|
||||
- Success metrics
|
||||
|
||||
### 2. Functional Decomposition (WHAT)
|
||||
- High-level capability domains
|
||||
- Features per capability
|
||||
- Inputs/outputs/behavior for each feature
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Capability: Data Management
|
||||
Feature: Schema validation
|
||||
Description: Validate JSON against defined schemas
|
||||
Inputs: JSON object, schema definition
|
||||
Outputs: Validation result + error details
|
||||
Behavior: Iterate fields, check types, enforce constraints
|
||||
```
|
||||
|
||||
### 3. Structural Decomposition (HOW)
|
||||
- Repository folder structure
|
||||
- Module-to-capability mapping
|
||||
- File organization
|
||||
- Public interfaces/exports
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Capability: Data Management
|
||||
→ Maps to: src/data/
|
||||
├── schema-validator.js (Schema validation feature)
|
||||
├── rule-validator.js (Rule validation feature)
|
||||
└── index.js (Exports)
|
||||
```
|
||||
|
||||
### 4. Dependency Graph (CRITICAL)
|
||||
- Foundation layer (no dependencies)
|
||||
- Each subsequent layer's dependencies
|
||||
- Explicit "depends on" declarations
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Foundation Layer (Phase 0):
|
||||
- error-handling: No dependencies
|
||||
- base-types: No dependencies
|
||||
|
||||
Data Layer (Phase 1):
|
||||
- schema-validator: Depends on [base-types, error-handling]
|
||||
- data-ingestion: Depends on [schema-validator]
|
||||
```
|
||||
|
||||
### 5. Implementation Roadmap
|
||||
- Phases with entry/exit criteria
|
||||
- Tasks grouped by phase
|
||||
- Clear deliverables per phase
|
||||
|
||||
### 6. Test Strategy
|
||||
- Test pyramid ratios
|
||||
- Coverage requirements
|
||||
- Critical test scenarios per module
|
||||
- Guidelines for test generation
|
||||
|
||||
### 7. Architecture & Risks
|
||||
- Technical architecture
|
||||
- Data models
|
||||
- Technology decisions
|
||||
- Risk mitigation strategies
|
||||
|
||||
---
|
||||
|
||||
## Using RPG with Task Master
|
||||
|
||||
### Step 1: Create PRD with RPG Template
|
||||
|
||||
Use a code-context-aware tool to fill out the template:
|
||||
|
||||
```bash
|
||||
# In Claude Code, Cursor, or similar
|
||||
"Create a PRD using @.taskmaster/templates/example_prd_rpg.txt for [your project]"
|
||||
```
|
||||
|
||||
**Why code context matters:** The AI needs to understand your existing codebase to make informed decisions about:
|
||||
- Module boundaries
|
||||
- Dependency relationships
|
||||
- Integration points
|
||||
- Naming conventions
|
||||
|
||||
**Recommended tools:**
|
||||
- Claude Code (claude-code CLI)
|
||||
- Cursor/Windsurf
|
||||
- Gemini CLI (large contexts)
|
||||
- Codex/Grok CLI
|
||||
|
||||
### Step 2: Parse PRD into Tasks
|
||||
|
||||
```bash
|
||||
task-master parse-prd .taskmaster/docs/your-prd.txt --research
|
||||
```
|
||||
|
||||
Task Master will:
|
||||
1. Extract capabilities → Main tasks
|
||||
2. Extract features → Subtasks
|
||||
3. Parse dependencies → Task dependencies
|
||||
4. Order by phases → Task priorities
|
||||
|
||||
**Result:** A dependency-aware task graph ready for topological execution.
|
||||
|
||||
### Step 3: Analyze Complexity
|
||||
|
||||
```bash
|
||||
task-master analyze-complexity --research
|
||||
```
|
||||
|
||||
Review the complexity report to identify tasks that need expansion.
|
||||
|
||||
### Step 4: Expand Tasks
|
||||
|
||||
```bash
|
||||
task-master expand --all --research
|
||||
```
|
||||
|
||||
Break down complex tasks into manageable subtasks while preserving dependency chains.
|
||||
|
||||
---
|
||||
|
||||
## RPG Benefits
|
||||
|
||||
### For Solo Developers
|
||||
- Clear roadmap for implementing complex features
|
||||
- Prevents architectural mistakes early
|
||||
- Explicit dependency tracking avoids integration issues
|
||||
- Enables resuming work after interruptions
|
||||
|
||||
### For Teams
|
||||
- Parallel development of independent modules
|
||||
- Clear contracts between modules (explicit dependencies)
|
||||
- Reduced merge conflicts (proper module boundaries)
|
||||
- Onboarding aid (architectural overview in PRD)
|
||||
|
||||
### For AI Agents
|
||||
- Structured context for code generation
|
||||
- Clear scope boundaries per task
|
||||
- Dependency awareness prevents incomplete implementations
|
||||
- Test strategy guidance for TDD workflows
|
||||
|
||||
---
|
||||
|
||||
## RPG vs Standard Template
|
||||
|
||||
| Aspect | Standard Template | RPG Template |
|
||||
|--------|------------------|--------------|
|
||||
| **Best for** | Simple features | Complex systems |
|
||||
| **Dependency handling** | Implicit | Explicit graph |
|
||||
| **Structure guidance** | Minimal | Step-by-step |
|
||||
| **Examples** | Few | Inline good/bad examples |
|
||||
| **Module boundaries** | Vague | Precise mapping |
|
||||
| **Task ordering** | Manual | Automatic (topological) |
|
||||
| **Learning curve** | Low | Medium |
|
||||
| **Resulting task quality** | Good | Excellent |
|
||||
|
||||
---
|
||||
|
||||
## Tips for Best Results
|
||||
|
||||
### 1. Spend Time on Dependencies
|
||||
The dependency graph section is the most valuable. List all dependencies explicitly, even if they seem obvious.
|
||||
|
||||
### 2. Keep Features Atomic
|
||||
Each feature should be independently testable. If a feature description is vague ("handle data"), break it into specific features.
|
||||
|
||||
### 3. Progressive Refinement
|
||||
Don't try to get everything perfect on the first pass:
|
||||
1. Fill out high-level sections
|
||||
2. Review and refine
|
||||
3. Add detail where needed
|
||||
4. Let `task-master expand` break down complex tasks further
|
||||
|
||||
### 4. Use Research Mode
|
||||
```bash
|
||||
task-master parse-prd --research
|
||||
```
|
||||
The `--research` flag leverages AI to enhance task generation with domain knowledge.
|
||||
|
||||
### 5. Validate Early
|
||||
```bash
|
||||
task-master validate-dependencies
|
||||
```
|
||||
Check for circular dependencies or orphaned modules before starting implementation.
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### ❌ Mixing Functional and Structural
|
||||
```
|
||||
Bad: "Capability: validation.js"
|
||||
Good: "Capability: Data Validation" → maps to "src/validation/"
|
||||
```
|
||||
|
||||
### ❌ Vague Module Boundaries
|
||||
```
|
||||
Bad: "Module: utils"
|
||||
Good: "Module: string-utilities" with clear exports
|
||||
```
|
||||
|
||||
### ❌ Implicit Dependencies
|
||||
```
|
||||
Bad: "Module: API handlers (needs validation)"
|
||||
Good: "Module: API handlers, Depends on: [validation, error-handling]"
|
||||
```
|
||||
|
||||
### ❌ Skipping Test Strategy
|
||||
Without test strategy, the AI won't know what to test during implementation.
|
||||
|
||||
---
|
||||
|
||||
## Example Workflow
|
||||
|
||||
1. **Discuss idea with AI**: Explain your project concept
|
||||
2. **Reference RPG template**: Show AI the `example_prd_rpg.txt`
|
||||
3. **Co-create PRD**: Work through each section with AI guidance
|
||||
4. **Save to docs**: Place in `.taskmaster/docs/your-project.txt`
|
||||
5. **Parse PRD**: `task-master parse-prd .taskmaster/docs/your-project.txt --research`
|
||||
6. **Analyze**: `task-master analyze-complexity --research`
|
||||
7. **Expand**: `task-master expand --all --research`
|
||||
8. **Start work**: `task-master next`
|
||||
|
||||
---
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [PRD Creation and Parsing Guide](/getting-started/quick-start/prd-quick)
|
||||
- [Task Structure Documentation](/capabilities/task-structure)
|
||||
- [Microsoft Research RPG Paper](https://arxiv.org/abs/2410.21376) (Original methodology)
|
||||
|
||||
---
|
||||
|
||||
<Tip>
|
||||
The RPG template includes inline `<instruction>` and `<example>` blocks that teach the method as you use it. Read these sections carefully - they provide valuable guidance at each decision point.
|
||||
</Tip>
|
||||
235
apps/docs/command-reference.mdx
Normal file
235
apps/docs/command-reference.mdx
Normal file
@@ -0,0 +1,235 @@
|
||||
---
|
||||
title: "Task Master Commands"
|
||||
description: "A comprehensive reference of all available Task Master commands"
|
||||
---
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Parse PRD">
|
||||
```bash
|
||||
# Parse a PRD file and generate tasks
|
||||
task-master parse-prd <prd-file.txt>
|
||||
|
||||
# Limit the number of tasks generated
|
||||
task-master parse-prd <prd-file.txt> --num-tasks=10
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="List Tasks">
|
||||
```bash
|
||||
# List all tasks
|
||||
task-master list
|
||||
|
||||
# List tasks with a specific status
|
||||
task-master list --status=<status>
|
||||
|
||||
# List tasks with subtasks
|
||||
task-master list --with-subtasks
|
||||
|
||||
# List tasks with a specific status and include subtasks
|
||||
task-master list --status=<status> --with-subtasks
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Show Next Task">
|
||||
```bash
|
||||
# Show the next task to work on based on dependencies and status
|
||||
task-master next
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Show Specific Task">
|
||||
```bash
|
||||
# Show details of a specific task
|
||||
task-master show <id>
|
||||
# or
|
||||
task-master show --id=<id>
|
||||
|
||||
# View a specific subtask (e.g., subtask 2 of task 1)
|
||||
task-master show 1.2
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Update Tasks">
|
||||
```bash
|
||||
# Update tasks from a specific ID and provide context
|
||||
task-master update --from=<id> --prompt="<prompt>"
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Update a Specific Task">
|
||||
```bash
|
||||
# Update a single task by ID with new information
|
||||
task-master update-task --id=<id> --prompt="<prompt>"
|
||||
|
||||
# Use research-backed updates with Perplexity AI
|
||||
task-master update-task --id=<id> --prompt="<prompt>" --research
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Update a Subtask">
|
||||
```bash
|
||||
# Append additional information to a specific subtask
|
||||
task-master update-subtask --id=<parentId.subtaskId> --prompt="<prompt>"
|
||||
|
||||
# Example: Add details about API rate limiting to subtask 2 of task 5
|
||||
task-master update-subtask --id=5.2 --prompt="Add rate limiting of 100 requests per minute"
|
||||
|
||||
# Use research-backed updates with Perplexity AI
|
||||
task-master update-subtask --id=<parentId.subtaskId> --prompt="<prompt>" --research
|
||||
```
|
||||
|
||||
Unlike the `update-task` command which replaces task information, the `update-subtask` command _appends_ new information to the existing subtask details, marking it with a timestamp. This is useful for iteratively enhancing subtasks while preserving the original content.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Generate Task Files">
|
||||
```bash
|
||||
# Generate individual task files from tasks.json
|
||||
task-master generate
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Set Task Status">
|
||||
```bash
|
||||
# Set status of a single task
|
||||
task-master set-status --id=<id> --status=<status>
|
||||
|
||||
# Set status for multiple tasks
|
||||
task-master set-status --id=1,2,3 --status=<status>
|
||||
|
||||
# Set status for subtasks
|
||||
task-master set-status --id=1.1,1.2 --status=<status>
|
||||
```
|
||||
|
||||
When marking a task as "done", all of its subtasks will automatically be marked as "done" as well.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Expand Tasks">
|
||||
```bash
|
||||
# Expand a specific task with subtasks
|
||||
task-master expand --id=<id> --num=<number>
|
||||
|
||||
# Expand with additional context
|
||||
task-master expand --id=<id> --prompt="<context>"
|
||||
|
||||
# Expand all pending tasks
|
||||
task-master expand --all
|
||||
|
||||
# Force regeneration of subtasks for tasks that already have them
|
||||
task-master expand --all --force
|
||||
|
||||
# Research-backed subtask generation for a specific task
|
||||
task-master expand --id=<id> --research
|
||||
|
||||
# Research-backed generation for all tasks
|
||||
task-master expand --all --research
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Clear Subtasks">
|
||||
```bash
|
||||
# Clear subtasks from a specific task
|
||||
task-master clear-subtasks --id=<id>
|
||||
|
||||
# Clear subtasks from multiple tasks
|
||||
task-master clear-subtasks --id=1,2,3
|
||||
|
||||
# Clear subtasks from all tasks
|
||||
task-master clear-subtasks --all
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Analyze Task Complexity">
|
||||
```bash
|
||||
# Analyze complexity of all tasks
|
||||
task-master analyze-complexity
|
||||
|
||||
# Save report to a custom location
|
||||
task-master analyze-complexity --output=my-report.json
|
||||
|
||||
# Use a specific LLM model
|
||||
task-master analyze-complexity --model=claude-3-opus-20240229
|
||||
|
||||
# Set a custom complexity threshold (1-10)
|
||||
task-master analyze-complexity --threshold=6
|
||||
|
||||
# Use an alternative tasks file
|
||||
task-master analyze-complexity --file=custom-tasks.json
|
||||
|
||||
# Use Perplexity AI for research-backed complexity analysis
|
||||
task-master analyze-complexity --research
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="View Complexity Report">
|
||||
```bash
|
||||
# Display the task complexity analysis report
|
||||
task-master complexity-report
|
||||
|
||||
# View a report at a custom location
|
||||
task-master complexity-report --file=my-report.json
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Managing Task Dependencies">
|
||||
```bash
|
||||
# Add a dependency to a task
|
||||
task-master add-dependency --id=<id> --depends-on=<id>
|
||||
|
||||
# Remove a dependency from a task
|
||||
task-master remove-dependency --id=<id> --depends-on=<id>
|
||||
|
||||
# Validate dependencies without fixing them
|
||||
task-master validate-dependencies
|
||||
|
||||
# Find and fix invalid dependencies automatically
|
||||
task-master fix-dependencies
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Add a New Task">
|
||||
```bash
|
||||
# Add a new task using AI
|
||||
task-master add-task --prompt="Description of the new task"
|
||||
|
||||
# Add a task with dependencies
|
||||
task-master add-task --prompt="Description" --dependencies=1,2,3
|
||||
|
||||
# Add a task with priority
|
||||
task-master add-task --prompt="Description" --priority=high
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Initialize a Project">
|
||||
```bash
|
||||
# Initialize a new project with Task Master structure
|
||||
task-master init
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="TDD Workflow (Autopilot)">
|
||||
```bash
|
||||
# Start autonomous TDD workflow for a task
|
||||
task-master autopilot start <taskId>
|
||||
|
||||
# Get next action with context
|
||||
task-master autopilot next
|
||||
|
||||
# Complete phase with test results
|
||||
task-master autopilot complete --results '{"total":N,"passed":N,"failed":N}'
|
||||
|
||||
# Commit changes
|
||||
task-master autopilot commit
|
||||
|
||||
# Check workflow status
|
||||
task-master autopilot status
|
||||
|
||||
# Resume interrupted workflow
|
||||
task-master autopilot resume
|
||||
|
||||
# Abort workflow
|
||||
task-master autopilot abort
|
||||
```
|
||||
|
||||
The TDD workflow enforces RED → GREEN → COMMIT cycles for each subtask. See [AI Agent Integration](/tdd-workflow/ai-agent-integration) for details.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
94
apps/docs/configuration.mdx
Normal file
94
apps/docs/configuration.mdx
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: "Configuration"
|
||||
description: "Configure Task Master through environment variables in a .env file"
|
||||
---
|
||||
|
||||
## Required Configuration
|
||||
|
||||
<Note>
|
||||
Task Master requires an Anthropic API key to function. Add this to your `.env` file:
|
||||
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=sk-ant-api03-your-api-key
|
||||
```
|
||||
|
||||
You can obtain an API key from the [Anthropic Console](https://console.anthropic.com/).
|
||||
</Note>
|
||||
|
||||
## Optional Configuration
|
||||
|
||||
| Variable | Default Value | Description | Example |
|
||||
| --- | --- | --- | --- |
|
||||
| `MODEL` | `"claude-3-7-sonnet-20250219"` | Claude model to use | `MODEL=claude-3-opus-20240229` |
|
||||
| `MAX_TOKENS` | `"4000"` | Maximum tokens for responses | `MAX_TOKENS=8000` |
|
||||
| `TEMPERATURE` | `"0.7"` | Temperature for model responses | `TEMPERATURE=0.5` |
|
||||
| `DEBUG` | `"false"` | Enable debug logging | `DEBUG=true` |
|
||||
| `LOG_LEVEL` | `"info"` | Console output level | `LOG_LEVEL=debug` |
|
||||
| `DEFAULT_SUBTASKS` | `"3"` | Default subtask count | `DEFAULT_SUBTASKS=5` |
|
||||
| `DEFAULT_PRIORITY` | `"medium"` | Default priority | `DEFAULT_PRIORITY=high` |
|
||||
| `PROJECT_NAME` | `"MCP SaaS MVP"` | Project name in metadata | `PROJECT_NAME=My Awesome Project` |
|
||||
| `PROJECT_VERSION` | `"1.0.0"` | Version in metadata | `PROJECT_VERSION=2.1.0` |
|
||||
| `PERPLEXITY_API_KEY` | - | For research-backed features | `PERPLEXITY_API_KEY=pplx-...` |
|
||||
| `PERPLEXITY_MODEL` | `"sonar-medium-online"` | Perplexity model | `PERPLEXITY_MODEL=sonar-large-online` |
|
||||
|
||||
## TDD Workflow Configuration
|
||||
|
||||
Additional options for autonomous TDD workflow:
|
||||
|
||||
| Variable | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `TM_MAX_ATTEMPTS` | `3` | Max attempts per subtask before marking blocked |
|
||||
| `TM_AUTO_COMMIT` | `true` | Auto-commit after GREEN phase |
|
||||
| `TM_PROJECT_ROOT` | Current dir | Default project root |
|
||||
|
||||
## Example .env File
|
||||
|
||||
```
|
||||
# Required
|
||||
ANTHROPIC_API_KEY=sk-ant-api03-your-api-key
|
||||
|
||||
# Optional - Claude Configuration
|
||||
MODEL=claude-3-7-sonnet-20250219
|
||||
MAX_TOKENS=4000
|
||||
TEMPERATURE=0.7
|
||||
|
||||
# Optional - Perplexity API for Research
|
||||
PERPLEXITY_API_KEY=pplx-your-api-key
|
||||
PERPLEXITY_MODEL=sonar-medium-online
|
||||
|
||||
# Optional - Project Info
|
||||
PROJECT_NAME=My Project
|
||||
PROJECT_VERSION=1.0.0
|
||||
|
||||
# Optional - Application Configuration
|
||||
DEFAULT_SUBTASKS=3
|
||||
DEFAULT_PRIORITY=medium
|
||||
DEBUG=false
|
||||
LOG_LEVEL=info
|
||||
|
||||
# TDD Workflow
|
||||
TM_MAX_ATTEMPTS=3
|
||||
TM_AUTO_COMMIT=true
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### If `task-master init` doesn't respond:
|
||||
|
||||
Try running it with Node directly:
|
||||
|
||||
```bash
|
||||
node node_modules/claude-task-master/scripts/init.js
|
||||
```
|
||||
|
||||
Or clone the repository and run:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/eyaltoledano/claude-task-master.git
|
||||
cd claude-task-master
|
||||
node scripts/init.js
|
||||
```
|
||||
|
||||
<Note>
|
||||
For advanced configuration options and detailed customization, see our [Advanced Configuration Guide] page.
|
||||
</Note>
|
||||
@@ -52,6 +52,13 @@
|
||||
"capabilities/cli-root-commands",
|
||||
"capabilities/task-structure"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "TDD Workflow (Autopilot)",
|
||||
"pages": [
|
||||
"tdd-workflow/quickstart",
|
||||
"tdd-workflow/ai-agent-integration"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -37,6 +37,25 @@ For MCP/Cursor usage: Configure keys in the env section of your .cursor/mcp.json
|
||||
}
|
||||
```
|
||||
|
||||
<Tip>
|
||||
**Optimize Context Usage**: You can control which Task Master MCP tools are loaded using the `TASK_MASTER_TOOLS` environment variable. This helps reduce LLM context usage by only loading the tools you need.
|
||||
|
||||
Options:
|
||||
- `all` (default) - All 36 tools
|
||||
- `standard` - 15 commonly used tools
|
||||
- `core` or `lean` - 7 essential tools
|
||||
|
||||
Example:
|
||||
```json
|
||||
"env": {
|
||||
"TASK_MASTER_TOOLS": "standard",
|
||||
"ANTHROPIC_API_KEY": "your_key_here"
|
||||
}
|
||||
```
|
||||
|
||||
See the [MCP Tools documentation](/capabilities/mcp#configurable-tool-loading) for details.
|
||||
</Tip>
|
||||
|
||||
### CLI Usage: `.env` File
|
||||
|
||||
Create a `.env` file in your project root and include the keys for the providers you plan to use:
|
||||
|
||||
@@ -32,7 +32,11 @@ The more context you give the model, the better the breakdown and results.
|
||||
|
||||
## Writing a PRD for Task Master
|
||||
|
||||
<Note>An example PRD can be found in .taskmaster/templates/example_prd.txt</Note>
|
||||
<Note>
|
||||
Two example PRD templates are available in `.taskmaster/templates/`:
|
||||
- `example_prd.txt` - Simple template for straightforward projects
|
||||
- `example_prd_rpg.txt` - Advanced RPG (Repository Planning Graph) template for complex projects with dependencies
|
||||
</Note>
|
||||
|
||||
|
||||
You can co-write your PRD with an LLM model using the following workflow:
|
||||
@@ -43,6 +47,29 @@ You can co-write your PRD with an LLM model using the following workflow:
|
||||
|
||||
This approach works great in Cursor, or anywhere you use a chat-based LLM.
|
||||
|
||||
### Choosing Between Templates
|
||||
|
||||
**Use `example_prd.txt` when:**
|
||||
- Building straightforward features
|
||||
- Working on smaller projects
|
||||
- Dependencies are simple and obvious
|
||||
|
||||
**Use `example_prd_rpg.txt` when:**
|
||||
- Building complex systems with multiple modules
|
||||
- Need explicit dependency management
|
||||
- Want structured guidance on architecture decisions
|
||||
- Planning a large codebase from scratch
|
||||
|
||||
The RPG template teaches you to think about:
|
||||
1. **Functional decomposition** (WHAT the system does)
|
||||
2. **Structural decomposition** (HOW it's organized in code)
|
||||
3. **Explicit dependencies** (WHAT depends on WHAT)
|
||||
4. **Topological ordering** (build foundation first, then layers)
|
||||
|
||||
<Tip>
|
||||
For complex projects, using the RPG template with a code-context-aware ai agent produces the best results because the AI can understand your existing codebase structure. [Learn more about the RPG method →](/capabilities/rpg-method)
|
||||
</Tip>
|
||||
|
||||
---
|
||||
|
||||
## Where to Save Your PRD
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.6",
|
||||
"private": true,
|
||||
"description": "Task Master documentation powered by Mintlify",
|
||||
"scripts": {
|
||||
|
||||
1013
apps/docs/tdd-workflow/ai-agent-integration.mdx
Normal file
1013
apps/docs/tdd-workflow/ai-agent-integration.mdx
Normal file
File diff suppressed because it is too large
Load Diff
313
apps/docs/tdd-workflow/quickstart.mdx
Normal file
313
apps/docs/tdd-workflow/quickstart.mdx
Normal file
@@ -0,0 +1,313 @@
|
||||
---
|
||||
title: "TDD Workflow Quick Start"
|
||||
description: "Get started with TaskMaster's autonomous TDD workflow in 5 minutes"
|
||||
---
|
||||
|
||||
Get started with TaskMaster's autonomous TDD workflow in 5 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- TaskMaster initialized project (`tm init`)
|
||||
- Tasks with subtasks created (`tm parse-prd` or `tm expand`)
|
||||
- Git repository with clean working tree
|
||||
- Test framework installed (vitest, jest, mocha, etc.)
|
||||
|
||||
## 1. Start a Workflow
|
||||
|
||||
```bash
|
||||
tm autopilot start <taskId>
|
||||
```
|
||||
|
||||
Example:
|
||||
```bash
|
||||
$ tm autopilot start 7
|
||||
|
||||
✓ Workflow started for task 7
|
||||
✓ Created branch: task-7
|
||||
✓ Current phase: RED
|
||||
✓ Subtask 1/5: Implement start command
|
||||
→ Next action: Write a failing test
|
||||
```
|
||||
|
||||
## 2. The TDD Cycle
|
||||
|
||||
### RED Phase: Write Failing Test
|
||||
|
||||
```bash
|
||||
# Check what to do next
|
||||
$ tm autopilot next --json
|
||||
{
|
||||
"action": "generate_test",
|
||||
"currentSubtask": {
|
||||
"id": "1",
|
||||
"title": "Implement start command"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Write a test that fails:
|
||||
```typescript
|
||||
// tests/start.test.ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { StartCommand } from '../src/commands/start';
|
||||
|
||||
describe('StartCommand', () => {
|
||||
it('should initialize workflow', async () => {
|
||||
const command = new StartCommand();
|
||||
const result = await command.execute({ taskId: '7' });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
$ npm test
|
||||
# ✗ 1 test failed
|
||||
```
|
||||
|
||||
Complete RED phase:
|
||||
```bash
|
||||
$ tm autopilot complete --results '{"total":1,"passed":0,"failed":1,"skipped":0}'
|
||||
|
||||
✓ RED phase complete
|
||||
✓ Current phase: GREEN
|
||||
→ Next action: Implement code to pass tests
|
||||
```
|
||||
|
||||
### GREEN Phase: Implement Feature
|
||||
|
||||
Write minimal code to pass:
|
||||
```typescript
|
||||
// src/commands/start.ts
|
||||
export class StartCommand {
|
||||
async execute(options: { taskId: string }) {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
$ npm test
|
||||
# ✓ 1 test passed
|
||||
```
|
||||
|
||||
Complete GREEN phase:
|
||||
```bash
|
||||
$ tm autopilot complete --results '{"total":1,"passed":1,"failed":0,"skipped":0}'
|
||||
|
||||
✓ GREEN phase complete
|
||||
✓ Current phase: COMMIT
|
||||
→ Next action: Commit changes
|
||||
```
|
||||
|
||||
### COMMIT Phase: Save Progress
|
||||
|
||||
```bash
|
||||
$ tm autopilot commit
|
||||
|
||||
✓ Created commit: abc123
|
||||
✓ Message: feat(autopilot): implement start command (Task 7.1)
|
||||
✓ Advanced to subtask 2/5
|
||||
✓ Current phase: RED
|
||||
→ Next action: Write a failing test
|
||||
```
|
||||
|
||||
## 3. Continue for All Subtasks
|
||||
|
||||
Repeat the RED-GREEN-COMMIT cycle for each subtask until complete.
|
||||
|
||||
```bash
|
||||
# Check progress anytime
|
||||
$ tm autopilot status --json
|
||||
{
|
||||
"taskId": "7",
|
||||
"progress": {
|
||||
"completed": 1,
|
||||
"total": 5,
|
||||
"percentage": 20
|
||||
},
|
||||
"currentSubtask": {
|
||||
"id": "2",
|
||||
"title": "Implement resume command"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Complete the Workflow
|
||||
|
||||
When all subtasks are done:
|
||||
|
||||
```bash
|
||||
$ tm autopilot status --json
|
||||
{
|
||||
"phase": "COMPLETE",
|
||||
"progress": {
|
||||
"completed": 5,
|
||||
"total": 5,
|
||||
"percentage": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Your branch `task-7` is ready for review/merge!
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Parse Test Output
|
||||
|
||||
Your test runner outputs human-readable format - convert to JSON:
|
||||
|
||||
**Vitest:**
|
||||
```
|
||||
Tests 2 failed | 8 passed | 10 total
|
||||
```
|
||||
→ `{"total":10,"passed":8,"failed":2,"skipped":0}`
|
||||
|
||||
**Jest:**
|
||||
```
|
||||
Tests: 2 failed, 8 passed, 10 total
|
||||
```
|
||||
→ `{"total":10,"passed":8,"failed":2,"skipped":0}`
|
||||
|
||||
### Handle Errors
|
||||
|
||||
**Problem:** RED phase won't complete - "no test failures"
|
||||
|
||||
**Solution:** Your test isn't testing new behavior. Make sure it fails:
|
||||
```typescript
|
||||
// Bad - test passes immediately
|
||||
it('should exist', () => {
|
||||
expect(StartCommand).toBeDefined(); // Always passes
|
||||
});
|
||||
|
||||
// Good - test fails until feature exists
|
||||
it('should initialize workflow', async () => {
|
||||
const result = await new StartCommand().execute({ taskId: '7' });
|
||||
expect(result.success).toBe(true); // Fails until execute() is implemented
|
||||
});
|
||||
```
|
||||
|
||||
**Problem:** GREEN phase won't complete - "tests still failing"
|
||||
|
||||
**Solution:** Fix your implementation until all tests pass:
|
||||
```bash
|
||||
# Run tests to see what's failing
|
||||
$ npm test
|
||||
|
||||
# Fix the issue
|
||||
$ vim src/commands/start.ts
|
||||
|
||||
# Verify tests pass
|
||||
$ npm test
|
||||
|
||||
# Try again
|
||||
$ tm autopilot complete --results '{"total":1,"passed":1,"failed":0,"skipped":0}'
|
||||
```
|
||||
|
||||
### Resume Interrupted Work
|
||||
|
||||
```bash
|
||||
# If you interrupted the workflow
|
||||
$ tm autopilot resume
|
||||
|
||||
✓ Workflow resumed
|
||||
✓ Task 7 - subtask 3/5
|
||||
✓ Current phase: GREEN
|
||||
→ Continue from where you left off
|
||||
```
|
||||
|
||||
## JSON Output Mode
|
||||
|
||||
All commands support `--json` for programmatic use:
|
||||
|
||||
```bash
|
||||
$ tm autopilot start 7 --json | jq .
|
||||
{
|
||||
"success": true,
|
||||
"taskId": "7",
|
||||
"branchName": "task-7",
|
||||
"phase": "SUBTASK_LOOP",
|
||||
"tddPhase": "RED",
|
||||
"progress": { ... },
|
||||
"currentSubtask": { ... },
|
||||
"nextAction": "generate_test"
|
||||
}
|
||||
```
|
||||
|
||||
Perfect for:
|
||||
- CI/CD integration
|
||||
- Custom tooling
|
||||
- Automated workflows
|
||||
- Progress monitoring
|
||||
|
||||
## MCP Integration
|
||||
|
||||
For AI agents (Claude Code, etc.), use MCP tools:
|
||||
|
||||
```typescript
|
||||
// Start workflow
|
||||
await mcp.call('autopilot_start', {
|
||||
taskId: '7',
|
||||
projectRoot: '/path/to/project'
|
||||
});
|
||||
|
||||
// Get next action
|
||||
const next = await mcp.call('autopilot_next', {
|
||||
projectRoot: '/path/to/project'
|
||||
});
|
||||
|
||||
// Complete phase
|
||||
await mcp.call('autopilot_complete_phase', {
|
||||
projectRoot: '/path/to/project',
|
||||
testResults: { total: 1, passed: 0, failed: 1, skipped: 0 }
|
||||
});
|
||||
|
||||
// Commit
|
||||
await mcp.call('autopilot_commit', {
|
||||
projectRoot: '/path/to/project'
|
||||
});
|
||||
```
|
||||
|
||||
See [AI Agent Integration Guide](./ai-agent-integration.mdx) for details.
|
||||
|
||||
## Cheat Sheet
|
||||
|
||||
```bash
|
||||
# Start
|
||||
tm autopilot start <taskId> # Initialize workflow
|
||||
|
||||
# Workflow Control
|
||||
tm autopilot next # What's next?
|
||||
tm autopilot status # Current state
|
||||
tm autopilot resume # Continue interrupted work
|
||||
tm autopilot abort # Cancel and cleanup
|
||||
|
||||
# TDD Cycle
|
||||
tm autopilot complete --results '{...}' # Advance phase
|
||||
tm autopilot commit # Save progress
|
||||
|
||||
# Options
|
||||
--json # Machine-readable output
|
||||
--project-root <path> # Specify project location
|
||||
--force # Override safety checks
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read [AI Agent Integration Guide](./ai-agent-integration.mdx) for complete documentation
|
||||
- Check [Command Reference](/command-reference) for all options
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Always let tests fail first** - That's the RED phase
|
||||
2. **Write minimal code** - Just enough to pass
|
||||
3. **Commit frequently** - After each subtask
|
||||
4. **Use --json** - Better for programmatic use
|
||||
5. **Check status often** - Know where you are
|
||||
6. **Trust the workflow** - It enforces TDD rules
|
||||
|
||||
---
|
||||
|
||||
**Ready to start?** Run `tm autopilot start <taskId>` and begin your TDD journey!
|
||||
@@ -1,5 +1,28 @@
|
||||
# Change Log
|
||||
|
||||
## 0.25.6
|
||||
|
||||
## 0.25.6-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`f12a16d`](https://github.com/eyaltoledano/claude-task-master/commit/f12a16d09649f62148515f11f616157c7d0bd2d5), [`3010b90`](https://github.com/eyaltoledano/claude-task-master/commit/3010b90d98f3a7d8636caa92fc33d6ee69d4bed0), [`2a910a4`](https://github.com/eyaltoledano/claude-task-master/commit/2a910a40bac375f9f61d797bf55597303d556b48), [`aaf903f`](https://github.com/eyaltoledano/claude-task-master/commit/aaf903ff2f606c779a22e9a4b240ab57b3683815), [`90e6bdc`](https://github.com/eyaltoledano/claude-task-master/commit/90e6bdcf1c59f65ad27fcdfe3b13b9dca7e77654)]:
|
||||
- task-master-ai@0.29.0-rc.0
|
||||
|
||||
## 0.25.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b43b7ce`](https://github.com/eyaltoledano/claude-task-master/commit/b43b7ce201625eee956fb2f8cd332f238bb78c21), [`aaacc3d`](https://github.com/eyaltoledano/claude-task-master/commit/aaacc3dae36247b4de72b2d2697f49e5df6d01e3), [`0079b7d`](https://github.com/eyaltoledano/claude-task-master/commit/0079b7defdad550811f704c470fdd01955d91d4d), [`0b2c696`](https://github.com/eyaltoledano/claude-task-master/commit/0b2c6967c4605c33a100cff16f6ce8ff09ad06f0), [`4f984f8`](https://github.com/eyaltoledano/claude-task-master/commit/4f984f8a6965da9f9c7edd60ddfd6560ac022917), [`7b5a7c4`](https://github.com/eyaltoledano/claude-task-master/commit/7b5a7c4495a68b782f7407fc5d0e0d3ae81f42f5), [`caee040`](https://github.com/eyaltoledano/claude-task-master/commit/caee040907f856d31a660171c9e6d966f23c632e), [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042), [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042), [`738ec51`](https://github.com/eyaltoledano/claude-task-master/commit/738ec51c049a295a12839b2dfddaf05e23b8fede), [`d67b81d`](https://github.com/eyaltoledano/claude-task-master/commit/d67b81d25ddd927fabb6f5deb368e8993519c541), [`b5fe723`](https://github.com/eyaltoledano/claude-task-master/commit/b5fe723f8ead928e9f2dbde13b833ee70ac3382d), [`2b69936`](https://github.com/eyaltoledano/claude-task-master/commit/2b69936ee7b34346d6de5175af20e077359e2e2a), [`986ac11`](https://github.com/eyaltoledano/claude-task-master/commit/986ac117aee00bcd3e6830a0f76e1ad6d10e0bca), [`20004a3`](https://github.com/eyaltoledano/claude-task-master/commit/20004a39ea848f747e1ff48981bfe176554e4055)]:
|
||||
- task-master-ai@0.28.0
|
||||
|
||||
## 0.25.5-rc.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`aaacc3d`](https://github.com/eyaltoledano/claude-task-master/commit/aaacc3dae36247b4de72b2d2697f49e5df6d01e3), [`0079b7d`](https://github.com/eyaltoledano/claude-task-master/commit/0079b7defdad550811f704c470fdd01955d91d4d), [`0b2c696`](https://github.com/eyaltoledano/claude-task-master/commit/0b2c6967c4605c33a100cff16f6ce8ff09ad06f0), [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042), [`18aa416`](https://github.com/eyaltoledano/claude-task-master/commit/18aa416035f44345bde1c7321490345733a5d042), [`738ec51`](https://github.com/eyaltoledano/claude-task-master/commit/738ec51c049a295a12839b2dfddaf05e23b8fede), [`d67b81d`](https://github.com/eyaltoledano/claude-task-master/commit/d67b81d25ddd927fabb6f5deb368e8993519c541), [`b5fe723`](https://github.com/eyaltoledano/claude-task-master/commit/b5fe723f8ead928e9f2dbde13b833ee70ac3382d), [`2b69936`](https://github.com/eyaltoledano/claude-task-master/commit/2b69936ee7b34346d6de5175af20e077359e2e2a), [`986ac11`](https://github.com/eyaltoledano/claude-task-master/commit/986ac117aee00bcd3e6830a0f76e1ad6d10e0bca), [`20004a3`](https://github.com/eyaltoledano/claude-task-master/commit/20004a39ea848f747e1ff48981bfe176554e4055)]:
|
||||
- task-master-ai@0.28.0-rc.0
|
||||
|
||||
## 0.25.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user