Compare commits

..

8 Commits

Author SHA1 Message Date
Ralph Khreish
06b5a476b3 chore: bump gemini cli package 2025-08-28 19:11:06 +02:00
Ralph Khreish
3552f9424c chore: apply requested coderabbit changes 2025-08-28 18:51:05 +02:00
Ralph Khreish
c34d1417ca feat: add google cli integration and fix tests 2025-08-28 18:42:32 +02:00
Ralph Khreish
9f8cb08515 feat: add gemini-cli support for codebase context 2025-08-28 18:16:55 +02:00
Ralph Khreish
7fd751a5b8 chore: fix test 2025-08-28 18:16:55 +02:00
Ralph Khreish
6e5d44003d chore: format 2025-08-28 18:16:55 +02:00
Ralph Khreish
f746d84145 feat: fix CI and format + refactor 2025-08-28 18:16:55 +02:00
Ralph Khreish
e31500e9b0 feat: add support for claude code context
- code context for:
  - add-task
  - update-subtask
  - update-task
  - update
2025-08-28 18:16:27 +02:00
37 changed files with 4617 additions and 1295 deletions

View File

@@ -0,0 +1,9 @@
---
"task-master-ai": minor
---
Enhanced Gemini CLI provider with codebase-aware task generation
Added automatic codebase analysis for Gemini CLI provider in parse-prd, and analyze-complexity, add-task, udpate-task, update, update-subtask commands
When using Gemini CLI as the AI provider, Task Master now instructs the AI to analyze the project structure, existing implementations, and patterns before generating tasks or subtasks
Tasks and subtasks generated by Claude Code are now informed by actual codebase analysis, resulting in more accurate and contextual outputs

View File

@@ -0,0 +1,8 @@
---
"task-master-ai": patch
---
fix(claude-code): prevent crash/hang when the optional `@anthropic-ai/claude-code` SDK is missing by guarding `AbortError instanceof` checks and adding explicit SDK presence checks in `doGenerate`/`doStream`. Also bump the optional dependency to `^1.0.88` for improved export consistency.
Related to JSON truncation handling in #920; this change addresses a separate error-path crash reported in #1142.

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Temporarily disable streaming for improved model compatibility - will be re-enabled in upcoming release

View File

@@ -0,0 +1,14 @@
---
"task-master-ai": minor
---
Enhanced Claude Code and Google CLI integration with automatic codebase analysis for task operations
When using Claude Code as the AI provider, task management commands now automatically analyze your codebase before generating or updating tasks. This provides more accurate, context-aware implementation details that align with your project's existing architecture and patterns.
Commands contextualised:
- add-task
- update-subtask
- update-task
- update

View File

@@ -1,93 +0,0 @@
Clear all subtasks from all tasks globally.
## Global Subtask Clearing
Remove all subtasks across the entire project. Use with extreme caution.
## Execution
```bash
task-master clear-subtasks --all
```
## Pre-Clear Analysis
1. **Project-Wide Summary**
```
Global Subtask Summary
━━━━━━━━━━━━━━━━━━━━
Total parent tasks: 12
Total subtasks: 47
- Completed: 15
- In-progress: 8
- Pending: 24
Work at risk: ~120 hours
```
2. **Critical Warnings**
- In-progress subtasks that will lose work
- Completed subtasks with valuable history
- Complex dependency chains
- Integration test results
## Double Confirmation
```
⚠️ DESTRUCTIVE OPERATION WARNING ⚠️
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
This will remove ALL 47 subtasks from your project
Including 8 in-progress and 15 completed subtasks
This action CANNOT be undone
Type 'CLEAR ALL SUBTASKS' to confirm:
```
## Smart Safeguards
- Require explicit confirmation phrase
- Create automatic backup
- Log all removed data
- Option to export first
## Use Cases
Valid reasons for global clear:
- Project restructuring
- Major pivot in approach
- Starting fresh breakdown
- Switching to different task organization
## Process
1. Full project analysis
2. Create backup file
3. Show detailed impact
4. Require confirmation
5. Execute removal
6. Generate summary report
## Alternative Suggestions
Before clearing all:
- Export subtasks to file
- Clear only pending subtasks
- Clear by task category
- Archive instead of delete
## Post-Clear Report
```
Global Subtask Clear Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━
Removed: 47 subtasks from 12 tasks
Backup saved: .taskmaster/backup/subtasks-20240115.json
Parent tasks updated: 12
Time estimates adjusted: Yes
Next steps:
- Review updated task list
- Re-expand complex tasks as needed
- Check project timeline
```

View File

@@ -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
```

View File

@@ -1,57 +0,0 @@
name: Trigger Claude Documentation Update
on:
push:
branches:
- next
paths-ignore:
- "apps/docs/**"
- "*.md"
- ".github/workflows/**"
jobs:
trigger-docs-update:
# Only run if changes were merged (not direct pushes from bots)
if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2 # Need previous commit for comparison
- name: Get changed files
id: changed-files
run: |
echo "Changed files in this push:"
git diff --name-only HEAD^ HEAD | tee changed_files.txt
# Store changed files for Claude to analyze (escaped for JSON)
CHANGED_FILES=$(git diff --name-only HEAD^ HEAD | jq -Rs .)
echo "changed_files=$CHANGED_FILES" >> $GITHUB_OUTPUT
# Get the commit message (escaped for JSON)
COMMIT_MSG=$(git log -1 --pretty=%B | jq -Rs .)
echo "commit_message=$COMMIT_MSG" >> $GITHUB_OUTPUT
# Get diff for documentation context (escaped for JSON)
COMMIT_DIFF=$(git diff HEAD^ HEAD --stat | jq -Rs .)
echo "commit_diff=$COMMIT_DIFF" >> $GITHUB_OUTPUT
# Get commit SHA
echo "commit_sha=${{ github.sha }}" >> $GITHUB_OUTPUT
- name: Trigger Claude workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Trigger the Claude docs updater workflow with the change information
gh workflow run claude-docs-updater.yml \
--ref next \
-f commit_sha="${{ steps.changed-files.outputs.commit_sha }}" \
-f commit_message=${{ steps.changed-files.outputs.commit_message }} \
-f changed_files=${{ steps.changed-files.outputs.changed_files }} \
-f commit_diff=${{ steps.changed-files.outputs.commit_diff }}

View File

@@ -1,27 +1,18 @@
name: Claude Documentation Updater name: Claude Documentation Updater
on: on:
workflow_dispatch: push:
inputs: branches:
commit_sha: - next
description: 'The commit SHA that triggered this update' paths-ignore:
required: true - "apps/docs/**"
type: string - "*.md"
commit_message: - ".github/workflows/**"
description: 'The commit message'
required: true
type: string
changed_files:
description: 'List of changed files'
required: true
type: string
commit_diff:
description: 'Diff summary of changes'
required: true
type: string
jobs: jobs:
update-docs: update-docs:
# Only run if changes were merged (not direct pushes from bots)
if: github.actor != 'github-actions[bot]' && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
@@ -31,8 +22,28 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
ref: next fetch-depth: 2 # Need previous commit for comparison
fetch-depth: 0 # Need full history to checkout specific commit
- name: Get changed files
id: changed-files
run: |
echo "Changed files in this push:"
git diff --name-only HEAD^ HEAD | tee changed_files.txt
# Store changed files for Claude to analyze
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
git diff --name-only HEAD^ HEAD >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Get the commit message and changes summary
echo "commit_message<<EOF" >> $GITHUB_OUTPUT
git log -1 --pretty=%B >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Get diff for documentation context
echo "commit_diff<<EOF" >> $GITHUB_OUTPUT
git diff HEAD^ HEAD --stat >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create docs update branch - name: Create docs update branch
id: create-branch id: create-branch
@@ -60,12 +71,12 @@ jobs:
You are a documentation specialist. Analyze the recent changes pushed to the 'next' branch and update the documentation accordingly. You are a documentation specialist. Analyze the recent changes pushed to the 'next' branch and update the documentation accordingly.
Recent changes: Recent changes:
- Commit: ${{ inputs.commit_message }} - Commit: ${{ steps.changed-files.outputs.commit_message }}
- Changed files: - Changed files:
${{ inputs.changed_files }} ${{ steps.changed-files.outputs.changed_files }}
- Changes summary: - Changes summary:
${{ inputs.commit_diff }} ${{ steps.changed-files.outputs.commit_diff }}
Your task: Your task:
1. Analyze the changes to understand what functionality was added, modified, or removed 1. Analyze the changes to understand what functionality was added, modified, or removed
@@ -102,7 +113,7 @@ jobs:
This PR was automatically generated to update documentation based on recent changes. This PR was automatically generated to update documentation based on recent changes.
Original commit: ${{ inputs.commit_message }} Original commit: ${{ steps.changed-files.outputs.commit_message }}
Co-authored-by: Claude <claude-assistant@anthropic.com>" Co-authored-by: Claude <claude-assistant@anthropic.com>"
fi fi
@@ -122,12 +133,12 @@ jobs:
This PR automatically updates documentation based on recent changes merged to the \`next\` branch. This PR automatically updates documentation based on recent changes merged to the \`next\` branch.
### Original Changes ### Original Changes
**Commit:** ${{ inputs.commit_sha }} **Commit:** ${{ github.sha }}
**Message:** ${{ inputs.commit_message }} **Message:** ${{ steps.changed-files.outputs.commit_message }}
### Changed Files in Original Commit ### Changed Files in Original Commit
\`\`\` \`\`\`
${{ inputs.changed_files }} ${{ steps.changed-files.outputs.changed_files }}
\`\`\` \`\`\`
### Documentation Updates ### Documentation Updates

View File

@@ -1,103 +1,5 @@
# task-master-ai # task-master-ai
## 0.26.0
### Minor Changes
- [#1133](https://github.com/eyaltoledano/claude-task-master/pull/1133) [`df26c65`](https://github.com/eyaltoledano/claude-task-master/commit/df26c65632000874a73504963b08f18c46283144) Thanks [@neonwatty](https://github.com/neonwatty)! - Restore Taskmaster claude-code commands and move clear commands under /remove to avoid collision with the claude-code /clear command.
- [#1163](https://github.com/eyaltoledano/claude-task-master/pull/1163) [`37af0f1`](https://github.com/eyaltoledano/claude-task-master/commit/37af0f191227a68d119b7f89a377bf932ee3ac66) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhanced Gemini CLI provider with codebase-aware task generation
Added automatic codebase analysis for Gemini CLI provider in parse-prd, and analyze-complexity, add-task, udpate-task, update, update-subtask commands
When using Gemini CLI as the AI provider, Task Master now instructs the AI to analyze the project structure, existing implementations, and patterns before generating tasks or subtasks
Tasks and subtasks generated by Claude Code are now informed by actual codebase analysis, resulting in more accurate and contextual outputs
- [#1165](https://github.com/eyaltoledano/claude-task-master/pull/1165) [`c4f92f6`](https://github.com/eyaltoledano/claude-task-master/commit/c4f92f6a0aee3435c56eb8d27d9aa9204284833e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add configurable codebase analysis feature flag with multiple configuration sources
Users can now control whether codebase analysis features (Claude Code and Gemini CLI integration) are enabled through environment variables, MCP configuration, or project config files.
Priority order: .env > MCP session env > .taskmaster/config.json.
Set `TASKMASTER_ENABLE_CODEBASE_ANALYSIS=false` in `.env` to disable codebase analysis prompts and tool integration.
- [#1135](https://github.com/eyaltoledano/claude-task-master/pull/1135) [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f) Thanks [@mm-parthy](https://github.com/mm-parthy)! - feat(move): improve cross-tag move UX and safety
- CLI: print "Next Steps" tips after cross-tag moves that used --ignore-dependencies (validate/fix guidance)
- CLI: show dedicated help block on ID collisions (destination tag already has the ID)
- Core: add structured suggestions to TASK_ALREADY_EXISTS errors
- MCP: map ID collision errors to TASK_ALREADY_EXISTS and include suggestions
- Tests: cover MCP options, error suggestions, CLI tips printing, and integration error payload suggestions
***
- [#1162](https://github.com/eyaltoledano/claude-task-master/pull/1162) [`4dad2fd`](https://github.com/eyaltoledano/claude-task-master/commit/4dad2fd613ceac56a65ae9d3c1c03092b8860ac9) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhanced Claude Code and Google CLI integration with automatic codebase analysis for task operations
When using Claude Code as the AI provider, task management commands now automatically analyze your codebase before generating or updating tasks. This provides more accurate, context-aware implementation details that align with your project's existing architecture and patterns.
Commands contextualised:
- add-task
- update-subtask
- update-task
- update
### Patch Changes
- [#1135](https://github.com/eyaltoledano/claude-task-master/pull/1135) [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f) Thanks [@mm-parthy](https://github.com/mm-parthy)! - docs(move): clarify cross-tag move docs; deprecate "force"; add explicit --with-dependencies/--ignore-dependencies examples
## 0.26.0-rc.1
### Minor Changes
- [#1165](https://github.com/eyaltoledano/claude-task-master/pull/1165) [`c4f92f6`](https://github.com/eyaltoledano/claude-task-master/commit/c4f92f6a0aee3435c56eb8d27d9aa9204284833e) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add configurable codebase analysis feature flag with multiple configuration sources
Users can now control whether codebase analysis features (Claude Code and Gemini CLI integration) are enabled through environment variables, MCP configuration, or project config files.
Priority order: .env > MCP session env > .taskmaster/config.json.
Set `TASKMASTER_ENABLE_CODEBASE_ANALYSIS=false` in `.env` to disable codebase analysis prompts and tool integration.
## 0.26.0-rc.0
### Minor Changes
- [#1163](https://github.com/eyaltoledano/claude-task-master/pull/1163) [`37af0f1`](https://github.com/eyaltoledano/claude-task-master/commit/37af0f191227a68d119b7f89a377bf932ee3ac66) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhanced Gemini CLI provider with codebase-aware task generation
Added automatic codebase analysis for Gemini CLI provider in parse-prd, and analyze-complexity, add-task, udpate-task, update, update-subtask commands
When using Gemini CLI as the AI provider, Task Master now instructs the AI to analyze the project structure, existing implementations, and patterns before generating tasks or subtasks
Tasks and subtasks generated by Claude Code are now informed by actual codebase analysis, resulting in more accurate and contextual outputs
- [#1135](https://github.com/eyaltoledano/claude-task-master/pull/1135) [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f) Thanks [@mm-parthy](https://github.com/mm-parthy)! - feat(move): improve cross-tag move UX and safety
- CLI: print "Next Steps" tips after cross-tag moves that used --ignore-dependencies (validate/fix guidance)
- CLI: show dedicated help block on ID collisions (destination tag already has the ID)
- Core: add structured suggestions to TASK_ALREADY_EXISTS errors
- MCP: map ID collision errors to TASK_ALREADY_EXISTS and include suggestions
- Tests: cover MCP options, error suggestions, CLI tips printing, and integration error payload suggestions
***
- [#1162](https://github.com/eyaltoledano/claude-task-master/pull/1162) [`4dad2fd`](https://github.com/eyaltoledano/claude-task-master/commit/4dad2fd613ceac56a65ae9d3c1c03092b8860ac9) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhanced Claude Code and Google CLI integration with automatic codebase analysis for task operations
When using Claude Code as the AI provider, task management commands now automatically analyze your codebase before generating or updating tasks. This provides more accurate, context-aware implementation details that align with your project's existing architecture and patterns.
Commands contextualised:
- add-task
- update-subtask
- update-task
- update
### Patch Changes
- [#1135](https://github.com/eyaltoledano/claude-task-master/pull/1135) [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f) Thanks [@mm-parthy](https://github.com/mm-parthy)! - docs(move): clarify cross-tag move docs; deprecate "force"; add explicit --with-dependencies/--ignore-dependencies examples
## 0.25.1
### Patch Changes
- [#1152](https://github.com/eyaltoledano/claude-task-master/pull/1152) [`8933557`](https://github.com/eyaltoledano/claude-task-master/commit/89335578ffffc65504b2055c0c85aa7521e5e79b) Thanks [@ben-vargas](https://github.com/ben-vargas)! - fix(claude-code): prevent crash/hang when the optional `@anthropic-ai/claude-code` SDK is missing by guarding `AbortError instanceof` checks and adding explicit SDK presence checks in `doGenerate`/`doStream`. Also bump the optional dependency to `^1.0.88` for improved export consistency.
Related to JSON truncation handling in #920; this change addresses a separate error-path crash reported in #1142.
- [#1151](https://github.com/eyaltoledano/claude-task-master/pull/1151) [`db720a9`](https://github.com/eyaltoledano/claude-task-master/commit/db720a954d390bb44838cd021b8813dde8f3d8de) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Temporarily disable streaming for improved model compatibility - will be re-enabled in upcoming release
## 0.25.0 ## 0.25.0
### Minor Changes ### Minor Changes

View File

@@ -1,5 +1,3 @@
# docs # docs
## 0.0.2
## 0.0.1 ## 0.0.1

View File

@@ -1,6 +1,6 @@
{ {
"name": "docs", "name": "docs",
"version": "0.0.2", "version": "0.0.1",
"private": true, "private": true,
"description": "Task Master documentation powered by Mintlify", "description": "Task Master documentation powered by Mintlify",
"scripts": { "scripts": {

View File

@@ -1,33 +1,5 @@
# Change Log # Change Log
## 0.24.2
### Patch Changes
- Updated dependencies [[`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f), [`df26c65`](https://github.com/eyaltoledano/claude-task-master/commit/df26c65632000874a73504963b08f18c46283144), [`37af0f1`](https://github.com/eyaltoledano/claude-task-master/commit/37af0f191227a68d119b7f89a377bf932ee3ac66), [`c4f92f6`](https://github.com/eyaltoledano/claude-task-master/commit/c4f92f6a0aee3435c56eb8d27d9aa9204284833e), [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f), [`4dad2fd`](https://github.com/eyaltoledano/claude-task-master/commit/4dad2fd613ceac56a65ae9d3c1c03092b8860ac9)]:
- task-master-ai@0.26.0
## 0.24.2-rc.1
### Patch Changes
- Updated dependencies [[`c4f92f6`](https://github.com/eyaltoledano/claude-task-master/commit/c4f92f6a0aee3435c56eb8d27d9aa9204284833e)]:
- task-master-ai@0.26.0-rc.1
## 0.24.2-rc.0
### Patch Changes
- Updated dependencies [[`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f), [`37af0f1`](https://github.com/eyaltoledano/claude-task-master/commit/37af0f191227a68d119b7f89a377bf932ee3ac66), [`8783708`](https://github.com/eyaltoledano/claude-task-master/commit/8783708e5e3389890a78fcf685d3da0580e73b3f), [`4dad2fd`](https://github.com/eyaltoledano/claude-task-master/commit/4dad2fd613ceac56a65ae9d3c1c03092b8860ac9)]:
- task-master-ai@0.26.0-rc.0
## 0.24.1
### Patch Changes
- Updated dependencies [[`8933557`](https://github.com/eyaltoledano/claude-task-master/commit/89335578ffffc65504b2055c0c85aa7521e5e79b), [`db720a9`](https://github.com/eyaltoledano/claude-task-master/commit/db720a954d390bb44838cd021b8813dde8f3d8de)]:
- task-master-ai@0.25.1
## 0.24.0 ## 0.24.0
### Minor Changes ### Minor Changes

View File

@@ -3,7 +3,7 @@
"private": true, "private": true,
"displayName": "TaskMaster", "displayName": "TaskMaster",
"description": "A visual Kanban board interface for TaskMaster projects in VS Code", "description": "A visual Kanban board interface for TaskMaster projects in VS Code",
"version": "0.24.2", "version": "0.24.0",
"publisher": "Hamster", "publisher": "Hamster",
"icon": "assets/icon.png", "icon": "assets/icon.png",
"engines": { "engines": {
@@ -239,7 +239,7 @@
"check-types": "tsc --noEmit" "check-types": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"task-master-ai": "0.26.0" "task-master-ai": "0.25.0"
}, },
"devDependencies": { "devDependencies": {
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",

View File

@@ -1,93 +0,0 @@
Clear all subtasks from all tasks globally.
## Global Subtask Clearing
Remove all subtasks across the entire project. Use with extreme caution.
## Execution
```bash
task-master clear-subtasks --all
```
## Pre-Clear Analysis
1. **Project-Wide Summary**
```
Global Subtask Summary
━━━━━━━━━━━━━━━━━━━━
Total parent tasks: 12
Total subtasks: 47
- Completed: 15
- In-progress: 8
- Pending: 24
Work at risk: ~120 hours
```
2. **Critical Warnings**
- In-progress subtasks that will lose work
- Completed subtasks with valuable history
- Complex dependency chains
- Integration test results
## Double Confirmation
```
⚠️ DESTRUCTIVE OPERATION WARNING ⚠️
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
This will remove ALL 47 subtasks from your project
Including 8 in-progress and 15 completed subtasks
This action CANNOT be undone
Type 'CLEAR ALL SUBTASKS' to confirm:
```
## Smart Safeguards
- Require explicit confirmation phrase
- Create automatic backup
- Log all removed data
- Option to export first
## Use Cases
Valid reasons for global clear:
- Project restructuring
- Major pivot in approach
- Starting fresh breakdown
- Switching to different task organization
## Process
1. Full project analysis
2. Create backup file
3. Show detailed impact
4. Require confirmation
5. Execute removal
6. Generate summary report
## Alternative Suggestions
Before clearing all:
- Export subtasks to file
- Clear only pending subtasks
- Clear by task category
- Archive instead of delete
## Post-Clear Report
```
Global Subtask Clear Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━
Removed: 47 subtasks from 12 tasks
Backup saved: .taskmaster/backup/subtasks-20240115.json
Parent tasks updated: 12
Time estimates adjusted: Yes
Next steps:
- Review updated task list
- Re-expand complex tasks as needed
- Check project timeline
```

View File

@@ -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
```

View File

@@ -65,10 +65,11 @@ This removes the dependency relationships and moves only the specified task.
### Force Move ### Force Move
Note: Force moves are no longer supported. Instead, use one of these options: Force the move even with dependency conflicts:
- `--with-dependencies` — move dependents together ```bash
- `--ignore-dependencies` — break cross-tag dependencies task-master move --from=5 --from-tag=backlog --to-tag=in-progress --force
```
⚠️ **Warning**: This may break dependency relationships and should be used with caution. ⚠️ **Warning**: This may break dependency relationships and should be used with caution.
@@ -92,7 +93,7 @@ Resolution options:
2. Break dependencies: task-master move --from=5,6 --from-tag=backlog --to-tag=in-progress --ignore-dependencies 2. Break dependencies: task-master move --from=5,6 --from-tag=backlog --to-tag=in-progress --ignore-dependencies
3. Validate and fix dependencies: task-master validate-dependencies && task-master fix-dependencies 3. Validate and fix dependencies: task-master validate-dependencies && task-master fix-dependencies
4. Move dependencies first: task-master move --from=2,3 --from-tag=backlog --to-tag=in-progress 4. Move dependencies first: task-master move --from=2,3 --from-tag=backlog --to-tag=in-progress
5. After deciding, re-run the move with either --with-dependencies or --ignore-dependencies 5. Force move (may break dependencies): task-master move --from=5,6 --from-tag=backlog --to-tag=in-progress --force
``` ```
### Subtask Movement Restrictions ### Subtask Movement Restrictions
@@ -148,6 +149,7 @@ task-master fix-dependencies
- **`--with-dependencies`**: When you want to maintain task relationships - **`--with-dependencies`**: When you want to maintain task relationships
- **`--ignore-dependencies`**: When you want to break cross-tag dependencies - **`--ignore-dependencies`**: When you want to break cross-tag dependencies
- **`--force`**: Only when you understand the consequences
### 3. Organize by Context ### 3. Organize by Context
@@ -263,14 +265,9 @@ task-master move --from=5 --from-tag=backlog --to-tag=done --ignore-dependencies
### Scenario 3: Force Move ### Scenario 3: Force Move
Choose one of these options explicitly:
```bash ```bash
# Move together with dependencies # Force move despite conflicts
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --with-dependencies task-master move --from=5 --from-tag=backlog --to-tag=in-progress --force
# Or break dependencies
task-master move --from=5 --from-tag=backlog --to-tag=in-progress --ignore-dependencies
``` ```
### Scenario 4: Moving Subtasks ### Scenario 4: Moving Subtasks

View File

@@ -189,17 +189,6 @@ export async function moveTaskCrossTagDirect(args, log, context = {}) {
'Verify task IDs exist: task-master list', 'Verify task IDs exist: task-master list',
'Check task details: task-master show <id>' 'Check task details: task-master show <id>'
]; ];
} else if (
error.code === 'TASK_ALREADY_EXISTS' ||
error.message?.includes('already exists in target tag')
) {
// Target tag has an ID collision
errorCode = 'TASK_ALREADY_EXISTS';
suggestions = [
'Choose a different target tag without conflicting IDs',
'Move a different set of IDs (avoid existing ones)',
'If needed, move within-tag to a new ID first, then cross-tag move'
];
} }
return { return {

4504
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.26.0", "version": "0.25.0",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.", "description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View File

@@ -4103,7 +4103,12 @@ Examples:
' task-master move --from=5 --from-tag=backlog --to-tag=in-progress --ignore-dependencies' ' task-master move --from=5 --from-tag=backlog --to-tag=in-progress --ignore-dependencies'
) + ) +
'\n\n' + '\n\n' +
chalk.white(' # Force move (may break dependencies)') +
'\n' + '\n' +
chalk.white(
' task-master move --from=5 --from-tag=backlog --to-tag=in-progress --force'
) +
'\n\n' +
chalk.yellow.bold('Best Practices:') + chalk.yellow.bold('Best Practices:') +
'\n' + '\n' +
chalk.white( chalk.white(
@@ -4114,6 +4119,10 @@ Examples:
' • Use --ignore-dependencies to break cross-tag dependencies' ' • Use --ignore-dependencies to break cross-tag dependencies'
) + ) +
'\n' + '\n' +
chalk.white(
' • Use --force only when you understand the consequences'
) +
'\n' +
chalk.white( chalk.white(
' • Check dependencies first: task-master validate-dependencies' ' • Check dependencies first: task-master validate-dependencies'
) + ) +
@@ -4175,12 +4184,6 @@ Examples:
console.log(chalk.green(`${result.message}`)); console.log(chalk.green(`${result.message}`));
// Print any tips returned from the move operation (e.g., after ignoring dependencies)
if (Array.isArray(result.tips) && result.tips.length > 0) {
console.log('\n' + chalk.yellow.bold('Next Steps:'));
result.tips.forEach((t) => console.log(chalk.white(`${t}`)));
}
// Check if source tag still contains tasks before regenerating files // Check if source tag still contains tasks before regenerating files
const tasksData = readJSON( const tasksData = readJSON(
taskMaster.getTasksPath(), taskMaster.getTasksPath(),
@@ -4447,27 +4450,6 @@ Examples:
await handleWithinTagMove(moveContext); await handleWithinTagMove(moveContext);
} }
} catch (error) { } catch (error) {
const errMsg = String(error && (error.message || error));
if (errMsg.includes('already exists in target tag')) {
console.error(chalk.red(`Error: ${errMsg}`));
console.log(
'\n' +
chalk.yellow.bold('Conflict: ID already exists in target tag') +
'\n' +
chalk.white(
' • Choose a different target tag without conflicting IDs'
) +
'\n' +
chalk.white(
' • Move a different set of IDs (avoid existing ones)'
) +
'\n' +
chalk.white(
' • If needed, move within-tag to a new ID first, then cross-tag move'
)
);
process.exit(1);
}
handleMoveError(error, moveContext); handleMoveError(error, moveContext);
} }
}); });

View File

@@ -72,8 +72,7 @@ const DEFAULTS = {
projectName: 'Task Master', projectName: 'Task Master',
ollamaBaseURL: 'http://localhost:11434/api', ollamaBaseURL: 'http://localhost:11434/api',
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com', bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com',
responseLanguage: 'English', responseLanguage: 'English'
enableCodebaseAnalysis: true
}, },
claudeCode: {} claudeCode: {}
}; };
@@ -429,56 +428,15 @@ function getResearchProvider(explicitRoot = null) {
} }
/** /**
* Check if codebase analysis feature flag is enabled across all sources * Check if a codebase analysis provider is being used (Claude Code or Gemini CLI)
* Priority: .env > MCP env > config.json
* @param {object|null} session - MCP session object (optional)
* @param {string|null} projectRoot - Project root path (optional)
* @returns {boolean} True if codebase analysis is enabled
*/
function isCodebaseAnalysisEnabled(session = null, projectRoot = null) {
// Priority 1: Environment variable
const envFlag = resolveEnvVariable(
'TASKMASTER_ENABLE_CODEBASE_ANALYSIS',
session,
projectRoot
);
if (envFlag !== null && envFlag !== undefined && envFlag !== '') {
return envFlag.toLowerCase() === 'true' || envFlag === '1';
}
// Priority 2: MCP session environment
if (session?.env?.TASKMASTER_ENABLE_CODEBASE_ANALYSIS) {
const mcpFlag = session.env.TASKMASTER_ENABLE_CODEBASE_ANALYSIS;
return mcpFlag.toLowerCase() === 'true' || mcpFlag === '1';
}
// Priority 3: Configuration file
const globalConfig = getGlobalConfig(projectRoot);
return globalConfig.enableCodebaseAnalysis !== false; // Default to true
}
/**
* Check if codebase analysis is available and enabled
* @param {boolean} useResearch - Whether to check research provider or main provider * @param {boolean} useResearch - Whether to check research provider or main provider
* @param {string|null} projectRoot - Project root path (optional) * @param {string|null} projectRoot - Project root path (optional)
* @param {object|null} session - MCP session object (optional) * @returns {boolean} True if a codebase analysis provider is the current provider
* @returns {boolean} True if codebase analysis is available and enabled
*/ */
function hasCodebaseAnalysis( function hasCodebaseAnalysis(useResearch = false, projectRoot = null) {
useResearch = false,
projectRoot = null,
session = null
) {
// First check if the feature is enabled
if (!isCodebaseAnalysisEnabled(session, projectRoot)) {
return false;
}
// Then check if a codebase analysis provider is configured
const currentProvider = useResearch const currentProvider = useResearch
? getResearchProvider(projectRoot) ? getResearchProvider(projectRoot)
: getMainProvider(projectRoot); : getMainProvider(projectRoot);
return ( return (
currentProvider === CUSTOM_PROVIDERS.CLAUDE_CODE || currentProvider === CUSTOM_PROVIDERS.CLAUDE_CODE ||
currentProvider === CUSTOM_PROVIDERS.GEMINI_CLI currentProvider === CUSTOM_PROVIDERS.GEMINI_CLI
@@ -600,11 +558,6 @@ function getResponseLanguage(explicitRoot = null) {
return getGlobalConfig(explicitRoot).responseLanguage; return getGlobalConfig(explicitRoot).responseLanguage;
} }
function getCodebaseAnalysisEnabled(explicitRoot = null) {
// Directly return value from config
return getGlobalConfig(explicitRoot).enableCodebaseAnalysis;
}
/** /**
* Gets model parameters (maxTokens, temperature) for a specific role, * Gets model parameters (maxTokens, temperature) for a specific role,
* considering model-specific overrides from supported-models.json. * considering model-specific overrides from supported-models.json.
@@ -1063,8 +1016,6 @@ export {
getAzureBaseURL, getAzureBaseURL,
getBedrockBaseURL, getBedrockBaseURL,
getResponseLanguage, getResponseLanguage,
getCodebaseAnalysisEnabled,
isCodebaseAnalysisEnabled,
getParametersForRole, getParametersForRole,
getUserId, getUserId,
// API Key Checkers (still relevant) // API Key Checkers (still relevant)

View File

@@ -426,11 +426,7 @@ async function addTask(
useResearch, useResearch,
priority: effectivePriority, priority: effectivePriority,
dependencies: numericDependencies, dependencies: numericDependencies,
hasCodebaseAnalysis: hasCodebaseAnalysis( hasCodebaseAnalysis: hasCodebaseAnalysis(useResearch, projectRoot),
useResearch,
projectRoot,
session
),
projectRoot: projectRoot projectRoot: projectRoot
} }
); );

View File

@@ -419,11 +419,7 @@ async function analyzeTaskComplexity(options, context = {}) {
tasks: tasksData.tasks, tasks: tasksData.tasks,
gatheredContext: gatheredContext || '', gatheredContext: gatheredContext || '',
useResearch: useResearch, useResearch: useResearch,
hasCodebaseAnalysis: hasCodebaseAnalysis( hasCodebaseAnalysis: hasCodebaseAnalysis(useResearch, projectRoot),
useResearch,
projectRoot,
session
),
projectRoot: projectRoot || '' projectRoot: projectRoot || ''
}; };

View File

@@ -458,8 +458,7 @@ async function expandTask(
// Check if a codebase analysis provider is being used // Check if a codebase analysis provider is being used
const hasCodebaseAnalysisCapability = hasCodebaseAnalysis( const hasCodebaseAnalysisCapability = hasCodebaseAnalysis(
useResearch, useResearch,
projectRoot, projectRoot
session
); );
// Combine all context sources into a single additionalContext parameter // Combine all context sources into a single additionalContext parameter

View File

@@ -61,43 +61,6 @@ const MOVE_ERROR_CODES = {
INVALID_TARGET_TAG: 'INVALID_TARGET_TAG' INVALID_TARGET_TAG: 'INVALID_TARGET_TAG'
}; };
/**
* Normalize a dependency value to its numeric parent task ID.
* - Numbers are returned as-is (if finite)
* - Numeric strings are parsed ("5" -> 5)
* - Dotted strings return the parent portion ("5.2" -> 5)
* - Empty/invalid values return null
* - null/undefined are preserved
* @param {number|string|null|undefined} dep
* @returns {number|null|undefined}
*/
function normalizeDependency(dep) {
if (dep === null || dep === undefined) return dep;
if (typeof dep === 'number') return Number.isFinite(dep) ? dep : null;
if (typeof dep === 'string') {
const trimmed = dep.trim();
if (trimmed === '') return null;
const parentPart = trimmed.includes('.') ? trimmed.split('.')[0] : trimmed;
const parsed = parseInt(parentPart, 10);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
/**
* Normalize an array of dependency values to numeric IDs.
* Preserves null/undefined input (returns as-is) and filters out invalid entries.
* @param {Array<any>|null|undefined} deps
* @returns {Array<number>|null|undefined}
*/
function normalizeDependencies(deps) {
if (deps === null || deps === undefined) return deps;
if (!Array.isArray(deps)) return deps;
return deps
.map((d) => normalizeDependency(d))
.filter((n) => Number.isFinite(n));
}
/** /**
* Move one or more tasks/subtasks to new positions * Move one or more tasks/subtasks to new positions
* @param {string} tasksPath - Path to tasks.json file * @param {string} tasksPath - Path to tasks.json file
@@ -711,34 +674,15 @@ async function resolveDependencies(
) { ) {
const { withDependencies = false, ignoreDependencies = false } = options; const { withDependencies = false, ignoreDependencies = false } = options;
// Scope allTasks to the source tag to avoid cross-tag contamination when
// computing dependency chains for --with-dependencies
const tasksInSourceTag = Array.isArray(allTasks)
? allTasks.filter((t) => t && t.tag === sourceTag)
: [];
// Handle --with-dependencies flag first (regardless of cross-tag dependencies) // Handle --with-dependencies flag first (regardless of cross-tag dependencies)
if (withDependencies) { if (withDependencies) {
// Move dependent tasks along with main tasks // Move dependent tasks along with main tasks
// Find ALL dependencies recursively, but only using tasks from the source tag // Find ALL dependencies recursively within the same tag
const allDependentTaskIdsRaw = findAllDependenciesRecursively( const allDependentTaskIds = findAllDependenciesRecursively(
sourceTasks, sourceTasks,
tasksInSourceTag, allTasks,
{ maxDepth: 100, includeSelf: false } { maxDepth: 100, includeSelf: false }
); );
// Filter dependent IDs to those that actually exist in the source tag
const sourceTagIds = new Set(
tasksInSourceTag.map((t) =>
typeof t.id === 'string' ? parseInt(t.id, 10) : t.id
)
);
const allDependentTaskIds = allDependentTaskIdsRaw.filter((depId) => {
// Only numeric task IDs are eligible to be moved (subtasks cannot be moved cross-tag)
const normalizedId = normalizeDependency(depId);
return Number.isFinite(normalizedId) && sourceTagIds.has(normalizedId);
});
const allTaskIdsToMove = [...new Set([...taskIds, ...allDependentTaskIds])]; const allTaskIdsToMove = [...new Set([...taskIds, ...allDependentTaskIds])];
log( log(
@@ -767,31 +711,22 @@ async function resolveDependencies(
if (ignoreDependencies) { if (ignoreDependencies) {
// Break cross-tag dependencies (edge case - shouldn't normally happen) // Break cross-tag dependencies (edge case - shouldn't normally happen)
sourceTasks.forEach((task) => { sourceTasks.forEach((task) => {
const sourceTagTasks = tasksInSourceTag;
const targetTagTasks = Array.isArray(allTasks)
? allTasks.filter((t) => t && t.tag === targetTag)
: [];
task.dependencies = task.dependencies.filter((depId) => { task.dependencies = task.dependencies.filter((depId) => {
const parentTaskId = normalizeDependency(depId); // Handle both task IDs and subtask IDs (e.g., "1.2")
let depTask = null;
// If dependency resolves to a task in the source tag, drop it (would be cross-tag after move) if (typeof depId === 'string' && depId.includes('.')) {
if ( // It's a subtask ID - extract parent task ID and find the parent task
Number.isFinite(parentTaskId) && const [parentId, subtaskId] = depId
sourceTagTasks.some((t) => t.id === parentTaskId) .split('.')
) { .map((id) => parseInt(id, 10));
return false; depTask = allTasks.find((t) => t.id === parentId);
} else {
// It's a regular task ID - normalize to number for comparison
const normalizedDepId =
typeof depId === 'string' ? parseInt(depId, 10) : depId;
depTask = allTasks.find((t) => t.id === normalizedDepId);
} }
return !depTask || depTask.tag === targetTag;
// If dependency resolves to a task in the target tag, keep it
if (
Number.isFinite(parentTaskId) &&
targetTagTasks.some((t) => t.id === parentTaskId)
) {
return true;
}
// Otherwise, keep as-is (unknown/unresolved dependency)
return true;
}); });
}); });
@@ -875,16 +810,7 @@ async function executeMoveOperation(
if (existingTaskIndex !== -1) { if (existingTaskIndex !== -1) {
throw new MoveTaskError( throw new MoveTaskError(
MOVE_ERROR_CODES.TASK_ALREADY_EXISTS, MOVE_ERROR_CODES.TASK_ALREADY_EXISTS,
`Task ${taskId} already exists in target tag "${targetTag}"`, `Task ${taskId} already exists in target tag "${targetTag}"`
{
conflictingId: normalizedTaskId,
targetTag,
suggestions: [
'Choose a different target tag without conflicting IDs',
'Move a different set of IDs (avoid existing ones)',
'If needed, move within-tag to a new ID first, then cross-tag move'
]
}
); );
} }
@@ -925,8 +851,7 @@ async function finalizeMove(
tasksPath, tasksPath,
context, context,
sourceTag, sourceTag,
targetTag, targetTag
dependencyResolution
) { ) {
const { projectRoot } = context; const { projectRoot } = context;
const { rawData, movedTasks } = moveResult; const { rawData, movedTasks } = moveResult;
@@ -934,23 +859,10 @@ async function finalizeMove(
// Write the updated data // Write the updated data
writeJSON(tasksPath, rawData, projectRoot, null); writeJSON(tasksPath, rawData, projectRoot, null);
const response = { return {
message: `Successfully moved ${movedTasks.length} tasks from "${sourceTag}" to "${targetTag}"`, message: `Successfully moved ${movedTasks.length} tasks from "${sourceTag}" to "${targetTag}"`,
movedTasks movedTasks
}; };
// If we intentionally broke cross-tag dependencies, provide tips to validate & fix
if (
dependencyResolution &&
dependencyResolution.type === 'ignored-dependencies'
) {
response.tips = [
'Run "task-master validate-dependencies" to check for dependency issues.',
'Run "task-master fix-dependencies" to automatically repair dangling dependencies.'
];
}
return response;
} }
/** /**
@@ -986,7 +898,7 @@ async function moveTasksBetweenTags(
const { rawData, sourceTasks, allTasks } = await prepareTaskData(validation); const { rawData, sourceTasks, allTasks } = await prepareTaskData(validation);
// 3. Handle dependencies // 3. Handle dependencies
const { tasksToMove, dependencyResolution } = await resolveDependencies( const { tasksToMove } = await resolveDependencies(
sourceTasks, sourceTasks,
allTasks, allTasks,
options, options,
@@ -1011,8 +923,7 @@ async function moveTasksBetweenTags(
tasksPath, tasksPath,
context, context,
sourceTag, sourceTag,
targetTag, targetTag
dependencyResolution
); );
} }

View File

@@ -77,7 +77,7 @@ export class PrdParseConfig {
* Check if codebase analysis is available (Claude Code or Gemini CLI) * Check if codebase analysis is available (Claude Code or Gemini CLI)
*/ */
hasCodebaseAnalysis() { hasCodebaseAnalysis() {
return hasCodebaseAnalysis(this.research, this.projectRoot, this.session); return hasCodebaseAnalysis(this.research, this.projectRoot);
} }
} }

View File

@@ -232,11 +232,7 @@ async function updateSubtaskById(
updatePrompt: prompt, updatePrompt: prompt,
useResearch: useResearch, useResearch: useResearch,
gatheredContext: gatheredContext || '', gatheredContext: gatheredContext || '',
hasCodebaseAnalysis: hasCodebaseAnalysis( hasCodebaseAnalysis: hasCodebaseAnalysis(useResearch, projectRoot),
useResearch,
projectRoot,
session
),
projectRoot: projectRoot projectRoot: projectRoot
}; };

View File

@@ -458,11 +458,7 @@ async function updateTaskById(
useResearch: useResearch, useResearch: useResearch,
currentDetails: taskToUpdate.details || '(No existing details)', currentDetails: taskToUpdate.details || '(No existing details)',
gatheredContext: gatheredContext || '', gatheredContext: gatheredContext || '',
hasCodebaseAnalysis: hasCodebaseAnalysis( hasCodebaseAnalysis: hasCodebaseAnalysis(useResearch, projectRoot),
useResearch,
projectRoot,
session
),
projectRoot: projectRoot projectRoot: projectRoot
}; };

View File

@@ -436,11 +436,7 @@ async function updateTasks(
updatePrompt: prompt, updatePrompt: prompt,
useResearch, useResearch,
projectContext: gatheredContext, projectContext: gatheredContext,
hasCodebaseAnalysis: hasCodebaseAnalysis( hasCodebaseAnalysis: hasCodebaseAnalysis(useResearch, projectRoot),
useResearch,
projectRoot,
session
),
projectRoot: projectRoot projectRoot: projectRoot
} }
); );

View File

@@ -2876,6 +2876,9 @@ export function displayCrossTagDependencyError(
` 4. Move dependencies first: task-master move --from=${conflicts.map((c) => c.dependencyId).join(',')} --from-tag=${conflicts[0].dependencyTag} --to-tag=${targetTag}` ` 4. Move dependencies first: task-master move --from=${conflicts.map((c) => c.dependencyId).join(',')} --from-tag=${conflicts[0].dependencyTag} --to-tag=${targetTag}`
); );
} }
console.log(
` 5. Force move (may break dependencies): task-master move --from=${sourceIds} --from-tag=${sourceTag} --to-tag=${targetTag} --force`
);
} }
/** /**

View File

@@ -158,25 +158,20 @@ describe('Cross-Tag Move CLI Integration', () => {
); );
try { try {
const result = await moveTaskModule.moveTasksBetweenTags( await moveTaskModule.moveTasksBetweenTags(
tasksPath, tasksPath,
taskIds, taskIds,
sourceTag, sourceTag,
toTag, toTag,
{ {
withDependencies, withDependencies,
ignoreDependencies ignoreDependencies,
force
} }
); );
console.log(chalk.green('Successfully moved task(s) between tags')); console.log(chalk.green('Successfully moved task(s) between tags'));
// Print advisory tips when present
if (result && Array.isArray(result.tips) && result.tips.length > 0) {
console.log('Next Steps:');
result.tips.forEach((t) => console.log(`${t}`));
}
// Generate task files for both tags // Generate task files for both tags
await generateTaskFilesModule.default( await generateTaskFilesModule.default(
tasksPath, tasksPath,
@@ -190,21 +185,6 @@ describe('Cross-Tag Move CLI Integration', () => {
); );
} catch (error) { } catch (error) {
console.error(chalk.red(`Error: ${error.message}`)); console.error(chalk.red(`Error: ${error.message}`));
// Print ID collision guidance similar to CLI help block
if (
typeof error?.message === 'string' &&
error.message.includes('already exists in target tag')
) {
console.log('');
console.log('Conflict: ID already exists in target tag');
console.log(
' • Choose a different target tag without conflicting IDs'
);
console.log(' • Move a different set of IDs (avoid existing ones)');
console.log(
' • If needed, move within-tag to a new ID first, then cross-tag move'
);
}
throw error; throw error;
} }
} else { } else {
@@ -288,7 +268,8 @@ describe('Cross-Tag Move CLI Integration', () => {
'in-progress', 'in-progress',
{ {
withDependencies: undefined, withDependencies: undefined,
ignoreDependencies: undefined ignoreDependencies: undefined,
force: undefined
} }
); );
}); });
@@ -342,7 +323,8 @@ describe('Cross-Tag Move CLI Integration', () => {
'in-progress', 'in-progress',
{ {
withDependencies: true, withDependencies: true,
ignoreDependencies: undefined ignoreDependencies: undefined,
force: undefined
} }
); );
}); });
@@ -368,7 +350,8 @@ describe('Cross-Tag Move CLI Integration', () => {
'in-progress', 'in-progress',
{ {
withDependencies: undefined, withDependencies: undefined,
ignoreDependencies: true ignoreDependencies: true,
force: undefined
} }
); );
}); });
@@ -393,7 +376,8 @@ describe('Cross-Tag Move CLI Integration', () => {
'new-tag', 'new-tag',
{ {
withDependencies: undefined, withDependencies: undefined,
ignoreDependencies: undefined ignoreDependencies: undefined,
force: undefined
} }
); );
}); });
@@ -456,57 +440,6 @@ describe('Cross-Tag Move CLI Integration', () => {
restore(); restore();
}); });
it('should print advisory tips when result.tips are returned (ignore-dependencies)', async () => {
const { errorMessages, logMessages, restore } = captureConsoleAndExit();
try {
// Arrange: mock move to return tips
mockMoveTasksBetweenTags.mockResolvedValue({
message: 'ok',
tips: [
'Run "task-master validate-dependencies" to check for dependency issues.',
'Run "task-master fix-dependencies" to automatically repair dangling dependencies.'
]
});
await moveAction({
from: '2',
fromTag: 'backlog',
toTag: 'in-progress',
ignoreDependencies: true
});
const joined = logMessages.join('\n');
expect(joined).toContain('Next Steps');
expect(joined).toContain('validate-dependencies');
expect(joined).toContain('fix-dependencies');
} finally {
restore();
}
});
it('should print ID collision suggestions when target already has the ID', async () => {
const { errorMessages, logMessages, restore } = captureConsoleAndExit();
try {
// Arrange: mock move to throw collision
const err = new Error(
'Task 1 already exists in target tag "in-progress"'
);
mockMoveTasksBetweenTags.mockRejectedValue(err);
await expect(
moveAction({ from: '1', fromTag: 'backlog', toTag: 'in-progress' })
).rejects.toThrow('already exists in target tag');
const joined = logMessages.join('\n');
expect(joined).toContain('Conflict: ID already exists in target tag');
expect(joined).toContain('different target tag');
expect(joined).toContain('different set of IDs');
expect(joined).toContain('within-tag');
} finally {
restore();
}
});
it('should handle same tag error correctly', async () => { it('should handle same tag error correctly', async () => {
const options = { const options = {
from: '1', from: '1',
@@ -552,7 +485,8 @@ describe('Cross-Tag Move CLI Integration', () => {
from: '1', from: '1',
toTag: 'in-progress', toTag: 'in-progress',
withDependencies: false, withDependencies: false,
ignoreDependencies: false ignoreDependencies: false,
force: false
// fromTag is intentionally not provided to test fallback // fromTag is intentionally not provided to test fallback
}); });
@@ -564,7 +498,8 @@ describe('Cross-Tag Move CLI Integration', () => {
'in-progress', 'in-progress',
{ {
withDependencies: false, withDependencies: false,
ignoreDependencies: false ignoreDependencies: false,
force: false
} }
); );
@@ -601,7 +536,8 @@ describe('Cross-Tag Move CLI Integration', () => {
'in-progress', 'in-progress',
{ {
withDependencies: undefined, withDependencies: undefined,
ignoreDependencies: undefined ignoreDependencies: undefined,
force: undefined
} }
); );
@@ -619,7 +555,32 @@ describe('Cross-Tag Move CLI Integration', () => {
); );
}); });
// Note: --force flag is no longer supported for cross-tag moves it('should handle --force flag correctly', async () => {
// Mock successful cross-tag move with force flag
mockMoveTasksBetweenTags.mockResolvedValue(undefined);
mockGenerateTaskFiles.mockResolvedValue(undefined);
const options = {
from: '1',
fromTag: 'backlog',
toTag: 'in-progress',
force: true
};
await moveAction(options);
expect(mockMoveTasksBetweenTags).toHaveBeenCalledWith(
expect.stringContaining('tasks.json'),
[1],
'backlog',
'in-progress',
{
withDependencies: undefined,
ignoreDependencies: undefined,
force: true // Force flag should be passed through
}
);
});
it('should fail when invalid task ID is provided', async () => { it('should fail when invalid task ID is provided', async () => {
const options = { const options = {
@@ -701,11 +662,90 @@ describe('Cross-Tag Move CLI Integration', () => {
restore(); restore();
}); });
// Note: --force combinations removed it('should combine --with-dependencies and --force flags correctly', async () => {
// Mock successful cross-tag move with both flags
mockMoveTasksBetweenTags.mockResolvedValue(undefined);
mockGenerateTaskFiles.mockResolvedValue(undefined);
// Note: --force combinations removed const options = {
from: '1,2',
fromTag: 'backlog',
toTag: 'in-progress',
withDependencies: true,
force: true
};
// Note: --force combinations removed await moveAction(options);
expect(mockMoveTasksBetweenTags).toHaveBeenCalledWith(
expect.stringContaining('tasks.json'),
[1, 2],
'backlog',
'in-progress',
{
withDependencies: true,
ignoreDependencies: undefined,
force: true // Both flags should be passed
}
);
});
it('should combine --ignore-dependencies and --force flags correctly', async () => {
// Mock successful cross-tag move with both flags
mockMoveTasksBetweenTags.mockResolvedValue(undefined);
mockGenerateTaskFiles.mockResolvedValue(undefined);
const options = {
from: '1',
fromTag: 'backlog',
toTag: 'in-progress',
ignoreDependencies: true,
force: true
};
await moveAction(options);
expect(mockMoveTasksBetweenTags).toHaveBeenCalledWith(
expect.stringContaining('tasks.json'),
[1],
'backlog',
'in-progress',
{
withDependencies: undefined,
ignoreDependencies: true,
force: true // Both flags should be passed
}
);
});
it('should handle all three flags combined correctly', async () => {
// Mock successful cross-tag move with all flags
mockMoveTasksBetweenTags.mockResolvedValue(undefined);
mockGenerateTaskFiles.mockResolvedValue(undefined);
const options = {
from: '1,2,3',
fromTag: 'backlog',
toTag: 'in-progress',
withDependencies: true,
ignoreDependencies: true,
force: true
};
await moveAction(options);
expect(mockMoveTasksBetweenTags).toHaveBeenCalledWith(
expect.stringContaining('tasks.json'),
[1, 2, 3],
'backlog',
'in-progress',
{
withDependencies: true,
ignoreDependencies: true,
force: true // All three flags should be passed
}
);
});
it('should handle whitespace in comma-separated task IDs', async () => { it('should handle whitespace in comma-separated task IDs', async () => {
// Mock successful cross-tag move with whitespace // Mock successful cross-tag move with whitespace

View File

@@ -426,38 +426,6 @@ describe('Cross-Tag Task Movement Integration Tests', () => {
); );
}); });
it('should provide advisory tips when ignoreDependencies breaks deps', async () => {
// Move a task that has dependencies so cross-tag conflicts would be broken
const taskIds = [2]; // backlog:2 depends on 1
const sourceTag = 'backlog';
const targetTag = 'in-progress';
// Override cross-tag detection to simulate conflicts for this case
const depManager = await import(
'../../scripts/modules/dependency-manager.js'
);
depManager.findCrossTagDependencies.mockReturnValueOnce([
{ taskId: 2, dependencyId: 1, dependencyTag: sourceTag }
]);
const result = await moveTasksBetweenTags(
testDataPath,
taskIds,
sourceTag,
targetTag,
{ ignoreDependencies: true },
{ projectRoot: '/test/project' }
);
expect(Array.isArray(result.tips)).toBe(true);
const expectedTips = [
'Run "task-master validate-dependencies" to check for dependency issues.',
'Run "task-master fix-dependencies" to automatically repair dangling dependencies.'
];
expect(result.tips).toHaveLength(expectedTips.length);
expect(result.tips).toEqual(expect.arrayContaining(expectedTips));
});
it('should move task without cross-tag dependency conflicts (since dependencies only exist within tags)', async () => { it('should move task without cross-tag dependency conflicts (since dependencies only exist within tags)', async () => {
const taskIds = [2]; // Task 2 depends on Task 1 (both in same tag) const taskIds = [2]; // Task 2 depends on Task 1 (both in same tag)
const sourceTag = 'backlog'; const sourceTag = 'backlog';
@@ -596,25 +564,6 @@ describe('Cross-Tag Task Movement Integration Tests', () => {
{ projectRoot: '/test/project' } { projectRoot: '/test/project' }
) )
).rejects.toThrow('Task 1 already exists in target tag "in-progress"'); ).rejects.toThrow('Task 1 already exists in target tag "in-progress"');
// Validate suggestions on the error payload
try {
await moveTasksBetweenTags(
testDataPath,
taskIds,
sourceTag,
targetTag,
{},
{ projectRoot: '/test/project' }
);
} catch (err) {
expect(err.code).toBe('TASK_ALREADY_EXISTS');
expect(Array.isArray(err.data?.suggestions)).toBe(true);
const s = (err.data?.suggestions || []).join(' ');
expect(s).toContain('different target tag');
expect(s).toContain('different set of IDs');
expect(s).toContain('within-tag');
}
}); });
}); });
@@ -688,7 +637,37 @@ describe('Cross-Tag Task Movement Integration Tests', () => {
); );
}); });
// Note: force flag deprecated for cross-tag moves; covered by with/ignore dependencies tests it('should handle force flag for dependency conflicts', async () => {
const taskIds = [2]; // Task 2 depends on Task 1
const sourceTag = 'backlog';
const targetTag = 'in-progress';
const result = await moveTasksBetweenTags(
testDataPath,
taskIds,
sourceTag,
targetTag,
{ force: true },
{ projectRoot: '/test/project' }
);
// Verify task was moved despite dependency conflicts
expect(mockUtils.writeJSON).toHaveBeenCalledWith(
testDataPath,
expect.objectContaining({
'in-progress': expect.objectContaining({
tasks: expect.arrayContaining([
expect.objectContaining({
id: 2,
tag: 'in-progress'
})
])
})
}),
'/test/project',
null
);
});
}); });
describe('Complex Scenarios', () => { describe('Complex Scenarios', () => {

View File

@@ -145,7 +145,6 @@ const DEFAULT_CONFIG = {
projectName: 'Task Master', projectName: 'Task Master',
ollamaBaseURL: 'http://localhost:11434/api', ollamaBaseURL: 'http://localhost:11434/api',
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com', bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com',
enableCodebaseAnalysis: true,
responseLanguage: 'English' responseLanguage: 'English'
}, },
claudeCode: {} claudeCode: {}

View File

@@ -1,135 +0,0 @@
import { jest } from '@jest/globals';
// Mocks
const mockFindTasksPath = jest
.fn()
.mockReturnValue('/test/path/.taskmaster/tasks/tasks.json');
jest.unstable_mockModule(
'../../../../mcp-server/src/core/utils/path-utils.js',
() => ({
findTasksPath: mockFindTasksPath
})
);
const mockEnableSilentMode = jest.fn();
const mockDisableSilentMode = jest.fn();
jest.unstable_mockModule('../../../../scripts/modules/utils.js', () => ({
enableSilentMode: mockEnableSilentMode,
disableSilentMode: mockDisableSilentMode
}));
// Spyable mock for moveTasksBetweenTags
const mockMoveTasksBetweenTags = jest.fn();
jest.unstable_mockModule(
'../../../../scripts/modules/task-manager/move-task.js',
() => ({
moveTasksBetweenTags: mockMoveTasksBetweenTags
})
);
// Import after mocks
const { moveTaskCrossTagDirect } = await import(
'../../../../mcp-server/src/core/direct-functions/move-task-cross-tag.js'
);
describe('MCP Cross-Tag Move Direct Function - options & suggestions', () => {
const mockLog = { info: jest.fn(), warn: jest.fn(), error: jest.fn() };
beforeEach(() => {
jest.clearAllMocks();
});
it('passes only withDependencies/ignoreDependencies (no force) to core', async () => {
// Arrange: make core throw tag validation after call to capture params
mockMoveTasksBetweenTags.mockImplementation(() => {
const err = new Error('Source tag "invalid" not found or invalid');
err.code = 'INVALID_SOURCE_TAG';
throw err;
});
// Act
await moveTaskCrossTagDirect(
{
sourceIds: '1,2',
sourceTag: 'backlog',
targetTag: 'in-progress',
withDependencies: true,
projectRoot: '/test'
},
mockLog
);
// Assert options argument (5th param)
expect(mockMoveTasksBetweenTags).toHaveBeenCalled();
const args = mockMoveTasksBetweenTags.mock.calls[0];
const moveOptions = args[4];
expect(moveOptions).toEqual({
withDependencies: true,
ignoreDependencies: false
});
expect('force' in moveOptions).toBe(false);
});
it('returns conflict suggestions on cross-tag dependency conflicts', async () => {
// Arrange: core throws cross-tag dependency conflicts
mockMoveTasksBetweenTags.mockImplementation(() => {
const err = new Error(
'Cannot move tasks: 2 cross-tag dependency conflicts found'
);
err.code = 'CROSS_TAG_DEPENDENCY_CONFLICTS';
throw err;
});
// Act
const result = await moveTaskCrossTagDirect(
{
sourceIds: '1',
sourceTag: 'backlog',
targetTag: 'in-progress',
projectRoot: '/test'
},
mockLog
);
// Assert
expect(result.success).toBe(false);
expect(result.error.code).toBe('CROSS_TAG_DEPENDENCY_CONFLICT');
expect(Array.isArray(result.error.suggestions)).toBe(true);
// Key suggestions
const s = result.error.suggestions.join(' ');
expect(s).toContain('--with-dependencies');
expect(s).toContain('--ignore-dependencies');
expect(s).toContain('validate-dependencies');
expect(s).toContain('Move dependencies first');
});
it('returns ID collision suggestions when target tag already has the ID', async () => {
// Arrange: core throws TASK_ALREADY_EXISTS structured error
mockMoveTasksBetweenTags.mockImplementation(() => {
const err = new Error(
'Task 1 already exists in target tag "in-progress"'
);
err.code = 'TASK_ALREADY_EXISTS';
throw err;
});
// Act
const result = await moveTaskCrossTagDirect(
{
sourceIds: '1',
sourceTag: 'backlog',
targetTag: 'in-progress',
projectRoot: '/test'
},
mockLog
);
// Assert
expect(result.success).toBe(false);
expect(result.error.code).toBe('TASK_ALREADY_EXISTS');
const joined = (result.error.suggestions || []).join(' ');
expect(joined).toContain('different target tag');
expect(joined).toContain('different set of IDs');
expect(joined).toContain('within-tag');
});
});

View File

@@ -6,6 +6,7 @@ import {
createGetTagAwareFilePathMock, createGetTagAwareFilePathMock,
createSlugifyTagForFilePathMock createSlugifyTagForFilePathMock
} from './setup.js'; } from './setup.js';
import { hasCodebaseAnalysis } from '../../../../../scripts/modules/config-manager.js';
// Mock the dependencies before importing the module under test // Mock the dependencies before importing the module under test
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({ jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({

View File

@@ -6,6 +6,7 @@
import { jest } from '@jest/globals'; import { jest } from '@jest/globals';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { hasCodebaseAnalysis } from '../../../../../scripts/modules/config-manager.js';
// Mock the dependencies // Mock the dependencies
jest.unstable_mockModule('../../../../../src/utils/path-utils.js', () => ({ jest.unstable_mockModule('../../../../../src/utils/path-utils.js', () => ({

View File

@@ -219,37 +219,6 @@ describe('Cross-Tag Task Movement', () => {
}); });
}); });
// New test: ensure with-dependencies only traverses tasks from the source tag
it('should scope dependency traversal to source tag when using --with-dependencies', async () => {
findCrossTagDependencies.mockReturnValue([]);
validateSubtaskMove.mockImplementation(() => {});
const result = await moveTasksBetweenTags(
mockTasksPath,
[1], // backlog:1 depends on backlog:2
'backlog',
'in-progress',
{ withDependencies: true },
mockContext
);
// Write should include backlog:2 moved, and must NOT traverse or fetch dependencies from the target tag
expect(writeJSON).toHaveBeenCalledWith(
mockTasksPath,
expect.objectContaining({
'in-progress': expect.objectContaining({
tasks: expect.arrayContaining([
expect.objectContaining({ id: 1 }),
expect.objectContaining({ id: 2 }) // the backlog:2 now moved
// ensure existing in-progress:2 remains (by id) but we don't double-add or fetch deps from it
])
})
}),
mockContext.projectRoot,
null
);
});
describe('moveTasksBetweenTags', () => { describe('moveTasksBetweenTags', () => {
it('should move tasks without dependencies successfully', async () => { it('should move tasks without dependencies successfully', async () => {
// Mock the dependency functions to return no conflicts // Mock the dependency functions to return no conflicts