#
tokens: 45205/50000 3/821 files (page 31/38)
lines: off (toggle) GitHub
raw markdown copy
This is page 31 of 38. Use http://codebase.md/eyaltoledano/claude-task-master?page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── agents
│   │   ├── task-checker.md
│   │   ├── task-executor.md
│   │   └── task-orchestrator.md
│   ├── commands
│   │   ├── dedupe.md
│   │   └── tm
│   │       ├── add-dependency
│   │       │   └── add-dependency.md
│   │       ├── add-subtask
│   │       │   ├── add-subtask.md
│   │       │   └── convert-task-to-subtask.md
│   │       ├── add-task
│   │       │   └── add-task.md
│   │       ├── analyze-complexity
│   │       │   └── analyze-complexity.md
│   │       ├── complexity-report
│   │       │   └── complexity-report.md
│   │       ├── expand
│   │       │   ├── expand-all-tasks.md
│   │       │   └── expand-task.md
│   │       ├── fix-dependencies
│   │       │   └── fix-dependencies.md
│   │       ├── generate
│   │       │   └── generate-tasks.md
│   │       ├── help.md
│   │       ├── init
│   │       │   ├── init-project-quick.md
│   │       │   └── init-project.md
│   │       ├── learn.md
│   │       ├── list
│   │       │   ├── list-tasks-by-status.md
│   │       │   ├── list-tasks-with-subtasks.md
│   │       │   └── list-tasks.md
│   │       ├── models
│   │       │   ├── setup-models.md
│   │       │   └── view-models.md
│   │       ├── next
│   │       │   └── next-task.md
│   │       ├── parse-prd
│   │       │   ├── parse-prd-with-research.md
│   │       │   └── parse-prd.md
│   │       ├── remove-dependency
│   │       │   └── remove-dependency.md
│   │       ├── remove-subtask
│   │       │   └── remove-subtask.md
│   │       ├── remove-subtasks
│   │       │   ├── remove-all-subtasks.md
│   │       │   └── remove-subtasks.md
│   │       ├── remove-task
│   │       │   └── remove-task.md
│   │       ├── set-status
│   │       │   ├── to-cancelled.md
│   │       │   ├── to-deferred.md
│   │       │   ├── to-done.md
│   │       │   ├── to-in-progress.md
│   │       │   ├── to-pending.md
│   │       │   └── to-review.md
│   │       ├── setup
│   │       │   ├── install-taskmaster.md
│   │       │   └── quick-install-taskmaster.md
│   │       ├── show
│   │       │   └── show-task.md
│   │       ├── status
│   │       │   └── project-status.md
│   │       ├── sync-readme
│   │       │   └── sync-readme.md
│   │       ├── tm-main.md
│   │       ├── update
│   │       │   ├── update-single-task.md
│   │       │   ├── update-task.md
│   │       │   └── update-tasks-from-id.md
│   │       ├── utils
│   │       │   └── analyze-project.md
│   │       ├── validate-dependencies
│   │       │   └── validate-dependencies.md
│   │       └── workflows
│   │           ├── auto-implement-tasks.md
│   │           ├── command-pipeline.md
│   │           └── smart-workflow.md
│   └── TM_COMMANDS_GUIDE.md
├── .coderabbit.yaml
├── .cursor
│   ├── mcp.json
│   └── rules
│       ├── ai_providers.mdc
│       ├── ai_services.mdc
│       ├── architecture.mdc
│       ├── changeset.mdc
│       ├── commands.mdc
│       ├── context_gathering.mdc
│       ├── cursor_rules.mdc
│       ├── dependencies.mdc
│       ├── dev_workflow.mdc
│       ├── git_workflow.mdc
│       ├── glossary.mdc
│       ├── mcp.mdc
│       ├── new_features.mdc
│       ├── self_improve.mdc
│       ├── tags.mdc
│       ├── taskmaster.mdc
│       ├── tasks.mdc
│       ├── telemetry.mdc
│       ├── test_workflow.mdc
│       ├── tests.mdc
│       ├── ui.mdc
│       └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   ├── enhancements---feature-requests.md
│   │   └── feedback.md
│   ├── PULL_REQUEST_TEMPLATE
│   │   ├── bugfix.md
│   │   ├── config.yml
│   │   ├── feature.md
│   │   └── integration.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── scripts
│   │   ├── auto-close-duplicates.mjs
│   │   ├── backfill-duplicate-comments.mjs
│   │   ├── check-pre-release-mode.mjs
│   │   ├── parse-metrics.mjs
│   │   ├── release.mjs
│   │   ├── tag-extension.mjs
│   │   └── utils.mjs
│   └── workflows
│       ├── auto-close-duplicates.yml
│       ├── backfill-duplicate-comments.yml
│       ├── ci.yml
│       ├── claude-dedupe-issues.yml
│       ├── claude-docs-trigger.yml
│       ├── claude-docs-updater.yml
│       ├── claude-issue-triage.yml
│       ├── claude.yml
│       ├── extension-ci.yml
│       ├── extension-release.yml
│       ├── log-issue-events.yml
│       ├── pre-release.yml
│       ├── release-check.yml
│       ├── release.yml
│       ├── update-models-md.yml
│       └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│   ├── hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── settings
│   │   └── mcp.json
│   └── steering
│       ├── dev_workflow.md
│       ├── kiro_rules.md
│       ├── self_improve.md
│       ├── taskmaster_hooks_workflow.md
│       └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│   ├── CLAUDE.md
│   ├── config.json
│   ├── docs
│   │   ├── MIGRATION-ROADMAP.md
│   │   ├── prd-tm-start.txt
│   │   ├── prd.txt
│   │   ├── README.md
│   │   ├── research
│   │   │   ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│   │   │   ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│   │   │   ├── 2025-06-14_test-save-functionality.md
│   │   │   ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│   │   │   └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│   │   ├── task-template-importing-prd.txt
│   │   ├── test-prd.txt
│   │   └── tm-core-phase-1.txt
│   ├── reports
│   │   ├── task-complexity-report_cc-kiro-hooks.json
│   │   ├── task-complexity-report_test-prd-tag.json
│   │   ├── task-complexity-report_tm-core-phase-1.json
│   │   ├── task-complexity-report.json
│   │   └── tm-core-complexity.json
│   ├── state.json
│   ├── tasks
│   │   ├── task_001_tm-start.txt
│   │   ├── task_002_tm-start.txt
│   │   ├── task_003_tm-start.txt
│   │   ├── task_004_tm-start.txt
│   │   ├── task_007_tm-start.txt
│   │   └── tasks.json
│   └── templates
│       └── example_prd.txt
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── apps
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── auth.command.ts
│   │   │   │   ├── context.command.ts
│   │   │   │   ├── list.command.ts
│   │   │   │   ├── set-status.command.ts
│   │   │   │   ├── show.command.ts
│   │   │   │   └── start.command.ts
│   │   │   ├── index.ts
│   │   │   ├── ui
│   │   │   │   ├── components
│   │   │   │   │   ├── dashboard.component.ts
│   │   │   │   │   ├── header.component.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next-task.component.ts
│   │   │   │   │   ├── suggested-steps.component.ts
│   │   │   │   │   └── task-detail.component.ts
│   │   │   │   └── index.ts
│   │   │   └── utils
│   │   │       ├── auto-update.ts
│   │   │       └── ui.ts
│   │   └── tsconfig.json
│   ├── docs
│   │   ├── archive
│   │   │   ├── ai-client-utils-example.mdx
│   │   │   ├── ai-development-workflow.mdx
│   │   │   ├── command-reference.mdx
│   │   │   ├── configuration.mdx
│   │   │   ├── cursor-setup.mdx
│   │   │   ├── examples.mdx
│   │   │   └── Installation.mdx
│   │   ├── best-practices
│   │   │   ├── advanced-tasks.mdx
│   │   │   ├── configuration-advanced.mdx
│   │   │   └── index.mdx
│   │   ├── capabilities
│   │   │   ├── cli-root-commands.mdx
│   │   │   ├── index.mdx
│   │   │   ├── mcp.mdx
│   │   │   └── task-structure.mdx
│   │   ├── CHANGELOG.md
│   │   ├── docs.json
│   │   ├── favicon.svg
│   │   ├── getting-started
│   │   │   ├── contribute.mdx
│   │   │   ├── faq.mdx
│   │   │   └── quick-start
│   │   │       ├── configuration-quick.mdx
│   │   │       ├── execute-quick.mdx
│   │   │       ├── installation.mdx
│   │   │       ├── moving-forward.mdx
│   │   │       ├── prd-quick.mdx
│   │   │       ├── quick-start.mdx
│   │   │       ├── requirements.mdx
│   │   │       ├── rules-quick.mdx
│   │   │       └── tasks-quick.mdx
│   │   ├── introduction.mdx
│   │   ├── licensing.md
│   │   ├── logo
│   │   │   ├── dark.svg
│   │   │   ├── light.svg
│   │   │   └── task-master-logo.png
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── style.css
│   │   ├── vercel.json
│   │   └── whats-new.mdx
│   └── extension
│       ├── .vscodeignore
│       ├── assets
│       │   ├── banner.png
│       │   ├── icon-dark.svg
│       │   ├── icon-light.svg
│       │   ├── icon.png
│       │   ├── screenshots
│       │   │   ├── kanban-board.png
│       │   │   └── task-details.png
│       │   └── sidebar-icon.svg
│       ├── CHANGELOG.md
│       ├── components.json
│       ├── docs
│       │   ├── extension-CI-setup.md
│       │   └── extension-development-guide.md
│       ├── esbuild.js
│       ├── LICENSE
│       ├── package.json
│       ├── package.mjs
│       ├── package.publish.json
│       ├── README.md
│       ├── src
│       │   ├── components
│       │   │   ├── ConfigView.tsx
│       │   │   ├── constants.ts
│       │   │   ├── TaskDetails
│       │   │   │   ├── AIActionsSection.tsx
│       │   │   │   ├── DetailsSection.tsx
│       │   │   │   ├── PriorityBadge.tsx
│       │   │   │   ├── SubtasksSection.tsx
│       │   │   │   ├── TaskMetadataSidebar.tsx
│       │   │   │   └── useTaskDetails.ts
│       │   │   ├── TaskDetailsView.tsx
│       │   │   ├── TaskMasterLogo.tsx
│       │   │   └── ui
│       │   │       ├── badge.tsx
│       │   │       ├── breadcrumb.tsx
│       │   │       ├── button.tsx
│       │   │       ├── card.tsx
│       │   │       ├── collapsible.tsx
│       │   │       ├── CollapsibleSection.tsx
│       │   │       ├── dropdown-menu.tsx
│       │   │       ├── label.tsx
│       │   │       ├── scroll-area.tsx
│       │   │       ├── separator.tsx
│       │   │       ├── shadcn-io
│       │   │       │   └── kanban
│       │   │       │       └── index.tsx
│       │   │       └── textarea.tsx
│       │   ├── extension.ts
│       │   ├── index.ts
│       │   ├── lib
│       │   │   └── utils.ts
│       │   ├── services
│       │   │   ├── config-service.ts
│       │   │   ├── error-handler.ts
│       │   │   ├── notification-preferences.ts
│       │   │   ├── polling-service.ts
│       │   │   ├── polling-strategies.ts
│       │   │   ├── sidebar-webview-manager.ts
│       │   │   ├── task-repository.ts
│       │   │   ├── terminal-manager.ts
│       │   │   └── webview-manager.ts
│       │   ├── test
│       │   │   └── extension.test.ts
│       │   ├── utils
│       │   │   ├── configManager.ts
│       │   │   ├── connectionManager.ts
│       │   │   ├── errorHandler.ts
│       │   │   ├── event-emitter.ts
│       │   │   ├── logger.ts
│       │   │   ├── mcpClient.ts
│       │   │   ├── notificationPreferences.ts
│       │   │   └── task-master-api
│       │   │       ├── cache
│       │   │       │   └── cache-manager.ts
│       │   │       ├── index.ts
│       │   │       ├── mcp-client.ts
│       │   │       ├── transformers
│       │   │       │   └── task-transformer.ts
│       │   │       └── types
│       │   │           └── index.ts
│       │   └── webview
│       │       ├── App.tsx
│       │       ├── components
│       │       │   ├── AppContent.tsx
│       │       │   ├── EmptyState.tsx
│       │       │   ├── ErrorBoundary.tsx
│       │       │   ├── PollingStatus.tsx
│       │       │   ├── PriorityBadge.tsx
│       │       │   ├── SidebarView.tsx
│       │       │   ├── TagDropdown.tsx
│       │       │   ├── TaskCard.tsx
│       │       │   ├── TaskEditModal.tsx
│       │       │   ├── TaskMasterKanban.tsx
│       │       │   ├── ToastContainer.tsx
│       │       │   └── ToastNotification.tsx
│       │       ├── constants
│       │       │   └── index.ts
│       │       ├── contexts
│       │       │   └── VSCodeContext.tsx
│       │       ├── hooks
│       │       │   ├── useTaskQueries.ts
│       │       │   ├── useVSCodeMessages.ts
│       │       │   └── useWebviewHeight.ts
│       │       ├── index.css
│       │       ├── index.tsx
│       │       ├── providers
│       │       │   └── QueryProvider.tsx
│       │       ├── reducers
│       │       │   └── appReducer.ts
│       │       ├── sidebar.tsx
│       │       ├── types
│       │       │   └── index.ts
│       │       └── utils
│       │           ├── logger.ts
│       │           └── toast.ts
│       └── tsconfig.json
├── assets
│   ├── .windsurfrules
│   ├── AGENTS.md
│   ├── claude
│   │   ├── agents
│   │   │   ├── task-checker.md
│   │   │   ├── task-executor.md
│   │   │   └── task-orchestrator.md
│   │   ├── commands
│   │   │   └── tm
│   │   │       ├── add-dependency
│   │   │       │   └── add-dependency.md
│   │   │       ├── add-subtask
│   │   │       │   ├── add-subtask.md
│   │   │       │   └── convert-task-to-subtask.md
│   │   │       ├── add-task
│   │   │       │   └── add-task.md
│   │   │       ├── analyze-complexity
│   │   │       │   └── analyze-complexity.md
│   │   │       ├── clear-subtasks
│   │   │       │   ├── clear-all-subtasks.md
│   │   │       │   └── clear-subtasks.md
│   │   │       ├── complexity-report
│   │   │       │   └── complexity-report.md
│   │   │       ├── expand
│   │   │       │   ├── expand-all-tasks.md
│   │   │       │   └── expand-task.md
│   │   │       ├── fix-dependencies
│   │   │       │   └── fix-dependencies.md
│   │   │       ├── generate
│   │   │       │   └── generate-tasks.md
│   │   │       ├── help.md
│   │   │       ├── init
│   │   │       │   ├── init-project-quick.md
│   │   │       │   └── init-project.md
│   │   │       ├── learn.md
│   │   │       ├── list
│   │   │       │   ├── list-tasks-by-status.md
│   │   │       │   ├── list-tasks-with-subtasks.md
│   │   │       │   └── list-tasks.md
│   │   │       ├── models
│   │   │       │   ├── setup-models.md
│   │   │       │   └── view-models.md
│   │   │       ├── next
│   │   │       │   └── next-task.md
│   │   │       ├── parse-prd
│   │   │       │   ├── parse-prd-with-research.md
│   │   │       │   └── parse-prd.md
│   │   │       ├── remove-dependency
│   │   │       │   └── remove-dependency.md
│   │   │       ├── remove-subtask
│   │   │       │   └── remove-subtask.md
│   │   │       ├── remove-subtasks
│   │   │       │   ├── remove-all-subtasks.md
│   │   │       │   └── remove-subtasks.md
│   │   │       ├── remove-task
│   │   │       │   └── remove-task.md
│   │   │       ├── set-status
│   │   │       │   ├── to-cancelled.md
│   │   │       │   ├── to-deferred.md
│   │   │       │   ├── to-done.md
│   │   │       │   ├── to-in-progress.md
│   │   │       │   ├── to-pending.md
│   │   │       │   └── to-review.md
│   │   │       ├── setup
│   │   │       │   ├── install-taskmaster.md
│   │   │       │   └── quick-install-taskmaster.md
│   │   │       ├── show
│   │   │       │   └── show-task.md
│   │   │       ├── status
│   │   │       │   └── project-status.md
│   │   │       ├── sync-readme
│   │   │       │   └── sync-readme.md
│   │   │       ├── tm-main.md
│   │   │       ├── update
│   │   │       │   ├── update-single-task.md
│   │   │       │   ├── update-task.md
│   │   │       │   └── update-tasks-from-id.md
│   │   │       ├── utils
│   │   │       │   └── analyze-project.md
│   │   │       ├── validate-dependencies
│   │   │       │   └── validate-dependencies.md
│   │   │       └── workflows
│   │   │           ├── auto-implement-tasks.md
│   │   │           ├── command-pipeline.md
│   │   │           └── smart-workflow.md
│   │   └── TM_COMMANDS_GUIDE.md
│   ├── config.json
│   ├── env.example
│   ├── example_prd.txt
│   ├── gitignore
│   ├── kiro-hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── roocode
│   │   ├── .roo
│   │   │   ├── rules-architect
│   │   │   │   └── architect-rules
│   │   │   ├── rules-ask
│   │   │   │   └── ask-rules
│   │   │   ├── rules-code
│   │   │   │   └── code-rules
│   │   │   ├── rules-debug
│   │   │   │   └── debug-rules
│   │   │   ├── rules-orchestrator
│   │   │   │   └── orchestrator-rules
│   │   │   └── rules-test
│   │   │       └── test-rules
│   │   └── .roomodes
│   ├── rules
│   │   ├── cursor_rules.mdc
│   │   ├── dev_workflow.mdc
│   │   ├── self_improve.mdc
│   │   ├── taskmaster_hooks_workflow.mdc
│   │   └── taskmaster.mdc
│   └── scripts_README.md
├── bin
│   └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE.md
├── context
│   ├── chats
│   │   ├── add-task-dependencies-1.md
│   │   └── max-min-tokens.txt.md
│   ├── fastmcp-core.txt
│   ├── fastmcp-docs.txt
│   ├── MCP_INTEGRATION.md
│   ├── mcp-js-sdk-docs.txt
│   ├── mcp-protocol-repo.txt
│   ├── mcp-protocol-schema-03262025.json
│   └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│   ├── CLI-COMMANDER-PATTERN.md
│   ├── command-reference.md
│   ├── configuration.md
│   ├── contributor-docs
│   │   └── testing-roo-integration.md
│   ├── cross-tag-task-movement.md
│   ├── examples
│   │   └── claude-code-usage.md
│   ├── examples.md
│   ├── licensing.md
│   ├── mcp-provider-guide.md
│   ├── mcp-provider.md
│   ├── migration-guide.md
│   ├── models.md
│   ├── providers
│   │   └── gemini-cli.md
│   ├── README.md
│   ├── scripts
│   │   └── models-json-to-markdown.js
│   ├── task-structure.md
│   └── tutorial.md
├── images
│   └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│   ├── server.js
│   └── src
│       ├── core
│       │   ├── __tests__
│       │   │   └── context-manager.test.js
│       │   ├── context-manager.js
│       │   ├── direct-functions
│       │   │   ├── add-dependency.js
│       │   │   ├── add-subtask.js
│       │   │   ├── add-tag.js
│       │   │   ├── add-task.js
│       │   │   ├── analyze-task-complexity.js
│       │   │   ├── cache-stats.js
│       │   │   ├── clear-subtasks.js
│       │   │   ├── complexity-report.js
│       │   │   ├── copy-tag.js
│       │   │   ├── create-tag-from-branch.js
│       │   │   ├── delete-tag.js
│       │   │   ├── expand-all-tasks.js
│       │   │   ├── expand-task.js
│       │   │   ├── fix-dependencies.js
│       │   │   ├── generate-task-files.js
│       │   │   ├── initialize-project.js
│       │   │   ├── list-tags.js
│       │   │   ├── list-tasks.js
│       │   │   ├── models.js
│       │   │   ├── move-task-cross-tag.js
│       │   │   ├── move-task.js
│       │   │   ├── next-task.js
│       │   │   ├── parse-prd.js
│       │   │   ├── remove-dependency.js
│       │   │   ├── remove-subtask.js
│       │   │   ├── remove-task.js
│       │   │   ├── rename-tag.js
│       │   │   ├── research.js
│       │   │   ├── response-language.js
│       │   │   ├── rules.js
│       │   │   ├── scope-down.js
│       │   │   ├── scope-up.js
│       │   │   ├── set-task-status.js
│       │   │   ├── show-task.js
│       │   │   ├── update-subtask-by-id.js
│       │   │   ├── update-task-by-id.js
│       │   │   ├── update-tasks.js
│       │   │   ├── use-tag.js
│       │   │   └── validate-dependencies.js
│       │   ├── task-master-core.js
│       │   └── utils
│       │       ├── env-utils.js
│       │       └── path-utils.js
│       ├── custom-sdk
│       │   ├── errors.js
│       │   ├── index.js
│       │   ├── json-extractor.js
│       │   ├── language-model.js
│       │   ├── message-converter.js
│       │   └── schema-converter.js
│       ├── index.js
│       ├── logger.js
│       ├── providers
│       │   └── mcp-provider.js
│       └── tools
│           ├── add-dependency.js
│           ├── add-subtask.js
│           ├── add-tag.js
│           ├── add-task.js
│           ├── analyze.js
│           ├── clear-subtasks.js
│           ├── complexity-report.js
│           ├── copy-tag.js
│           ├── delete-tag.js
│           ├── expand-all.js
│           ├── expand-task.js
│           ├── fix-dependencies.js
│           ├── generate.js
│           ├── get-operation-status.js
│           ├── get-task.js
│           ├── get-tasks.js
│           ├── index.js
│           ├── initialize-project.js
│           ├── list-tags.js
│           ├── models.js
│           ├── move-task.js
│           ├── next-task.js
│           ├── parse-prd.js
│           ├── remove-dependency.js
│           ├── remove-subtask.js
│           ├── remove-task.js
│           ├── rename-tag.js
│           ├── research.js
│           ├── response-language.js
│           ├── rules.js
│           ├── scope-down.js
│           ├── scope-up.js
│           ├── set-task-status.js
│           ├── update-subtask.js
│           ├── update-task.js
│           ├── update.js
│           ├── use-tag.js
│           ├── utils.js
│           └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│   ├── build-config
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   └── tsdown.base.ts
│   │   └── tsconfig.json
│   └── tm-core
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── docs
│       │   └── listTasks-architecture.md
│       ├── package.json
│       ├── POC-STATUS.md
│       ├── README.md
│       ├── src
│       │   ├── auth
│       │   │   ├── auth-manager.test.ts
│       │   │   ├── auth-manager.ts
│       │   │   ├── config.ts
│       │   │   ├── credential-store.test.ts
│       │   │   ├── credential-store.ts
│       │   │   ├── index.ts
│       │   │   ├── oauth-service.ts
│       │   │   ├── supabase-session-storage.ts
│       │   │   └── types.ts
│       │   ├── clients
│       │   │   ├── index.ts
│       │   │   └── supabase-client.ts
│       │   ├── config
│       │   │   ├── config-manager.spec.ts
│       │   │   ├── config-manager.ts
│       │   │   ├── index.ts
│       │   │   └── services
│       │   │       ├── config-loader.service.spec.ts
│       │   │       ├── config-loader.service.ts
│       │   │       ├── config-merger.service.spec.ts
│       │   │       ├── config-merger.service.ts
│       │   │       ├── config-persistence.service.spec.ts
│       │   │       ├── config-persistence.service.ts
│       │   │       ├── environment-config-provider.service.spec.ts
│       │   │       ├── environment-config-provider.service.ts
│       │   │       ├── index.ts
│       │   │       ├── runtime-state-manager.service.spec.ts
│       │   │       └── runtime-state-manager.service.ts
│       │   ├── constants
│       │   │   └── index.ts
│       │   ├── entities
│       │   │   └── task.entity.ts
│       │   ├── errors
│       │   │   ├── index.ts
│       │   │   └── task-master-error.ts
│       │   ├── executors
│       │   │   ├── base-executor.ts
│       │   │   ├── claude-executor.ts
│       │   │   ├── executor-factory.ts
│       │   │   ├── executor-service.ts
│       │   │   ├── index.ts
│       │   │   └── types.ts
│       │   ├── index.ts
│       │   ├── interfaces
│       │   │   ├── ai-provider.interface.ts
│       │   │   ├── configuration.interface.ts
│       │   │   ├── index.ts
│       │   │   └── storage.interface.ts
│       │   ├── logger
│       │   │   ├── factory.ts
│       │   │   ├── index.ts
│       │   │   └── logger.ts
│       │   ├── mappers
│       │   │   └── TaskMapper.ts
│       │   ├── parser
│       │   │   └── index.ts
│       │   ├── providers
│       │   │   ├── ai
│       │   │   │   ├── base-provider.ts
│       │   │   │   └── index.ts
│       │   │   └── index.ts
│       │   ├── repositories
│       │   │   ├── supabase-task-repository.ts
│       │   │   └── task-repository.interface.ts
│       │   ├── services
│       │   │   ├── index.ts
│       │   │   ├── organization.service.ts
│       │   │   ├── task-execution-service.ts
│       │   │   └── task-service.ts
│       │   ├── storage
│       │   │   ├── api-storage.ts
│       │   │   ├── file-storage
│       │   │   │   ├── file-operations.ts
│       │   │   │   ├── file-storage.ts
│       │   │   │   ├── format-handler.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── path-resolver.ts
│       │   │   ├── index.ts
│       │   │   └── storage-factory.ts
│       │   ├── subpath-exports.test.ts
│       │   ├── task-master-core.ts
│       │   ├── types
│       │   │   ├── database.types.ts
│       │   │   ├── index.ts
│       │   │   └── legacy.ts
│       │   └── utils
│       │       ├── id-generator.ts
│       │       └── index.ts
│       ├── tests
│       │   ├── integration
│       │   │   └── list-tasks.test.ts
│       │   ├── mocks
│       │   │   └── mock-provider.ts
│       │   ├── setup.ts
│       │   └── unit
│       │       ├── base-provider.test.ts
│       │       ├── executor.test.ts
│       │       └── smoke.test.ts
│       ├── tsconfig.json
│       └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│   ├── dev.js
│   ├── init.js
│   ├── modules
│   │   ├── ai-services-unified.js
│   │   ├── commands.js
│   │   ├── config-manager.js
│   │   ├── dependency-manager.js
│   │   ├── index.js
│   │   ├── prompt-manager.js
│   │   ├── supported-models.json
│   │   ├── sync-readme.js
│   │   ├── task-manager
│   │   │   ├── add-subtask.js
│   │   │   ├── add-task.js
│   │   │   ├── analyze-task-complexity.js
│   │   │   ├── clear-subtasks.js
│   │   │   ├── expand-all-tasks.js
│   │   │   ├── expand-task.js
│   │   │   ├── find-next-task.js
│   │   │   ├── generate-task-files.js
│   │   │   ├── is-task-dependent.js
│   │   │   ├── list-tasks.js
│   │   │   ├── migrate.js
│   │   │   ├── models.js
│   │   │   ├── move-task.js
│   │   │   ├── parse-prd
│   │   │   │   ├── index.js
│   │   │   │   ├── parse-prd-config.js
│   │   │   │   ├── parse-prd-helpers.js
│   │   │   │   ├── parse-prd-non-streaming.js
│   │   │   │   ├── parse-prd-streaming.js
│   │   │   │   └── parse-prd.js
│   │   │   ├── remove-subtask.js
│   │   │   ├── remove-task.js
│   │   │   ├── research.js
│   │   │   ├── response-language.js
│   │   │   ├── scope-adjustment.js
│   │   │   ├── set-task-status.js
│   │   │   ├── tag-management.js
│   │   │   ├── task-exists.js
│   │   │   ├── update-single-task-status.js
│   │   │   ├── update-subtask-by-id.js
│   │   │   ├── update-task-by-id.js
│   │   │   └── update-tasks.js
│   │   ├── task-manager.js
│   │   ├── ui.js
│   │   ├── update-config-tokens.js
│   │   ├── utils
│   │   │   ├── contextGatherer.js
│   │   │   ├── fuzzyTaskSearch.js
│   │   │   └── git-utils.js
│   │   └── utils.js
│   ├── task-complexity-report.json
│   ├── test-claude-errors.js
│   └── test-claude.js
├── src
│   ├── ai-providers
│   │   ├── anthropic.js
│   │   ├── azure.js
│   │   ├── base-provider.js
│   │   ├── bedrock.js
│   │   ├── claude-code.js
│   │   ├── custom-sdk
│   │   │   ├── claude-code
│   │   │   │   ├── errors.js
│   │   │   │   ├── index.js
│   │   │   │   ├── json-extractor.js
│   │   │   │   ├── language-model.js
│   │   │   │   ├── message-converter.js
│   │   │   │   └── types.js
│   │   │   └── grok-cli
│   │   │       ├── errors.js
│   │   │       ├── index.js
│   │   │       ├── json-extractor.js
│   │   │       ├── language-model.js
│   │   │       ├── message-converter.js
│   │   │       └── types.js
│   │   ├── gemini-cli.js
│   │   ├── google-vertex.js
│   │   ├── google.js
│   │   ├── grok-cli.js
│   │   ├── groq.js
│   │   ├── index.js
│   │   ├── ollama.js
│   │   ├── openai.js
│   │   ├── openrouter.js
│   │   ├── perplexity.js
│   │   └── xai.js
│   ├── constants
│   │   ├── commands.js
│   │   ├── paths.js
│   │   ├── profiles.js
│   │   ├── providers.js
│   │   ├── rules-actions.js
│   │   ├── task-priority.js
│   │   └── task-status.js
│   ├── profiles
│   │   ├── amp.js
│   │   ├── base-profile.js
│   │   ├── claude.js
│   │   ├── cline.js
│   │   ├── codex.js
│   │   ├── cursor.js
│   │   ├── gemini.js
│   │   ├── index.js
│   │   ├── kilo.js
│   │   ├── kiro.js
│   │   ├── opencode.js
│   │   ├── roo.js
│   │   ├── trae.js
│   │   ├── vscode.js
│   │   ├── windsurf.js
│   │   └── zed.js
│   ├── progress
│   │   ├── base-progress-tracker.js
│   │   ├── cli-progress-factory.js
│   │   ├── parse-prd-tracker.js
│   │   ├── progress-tracker-builder.js
│   │   └── tracker-ui.js
│   ├── prompts
│   │   ├── add-task.json
│   │   ├── analyze-complexity.json
│   │   ├── expand-task.json
│   │   ├── parse-prd.json
│   │   ├── README.md
│   │   ├── research.json
│   │   ├── schemas
│   │   │   ├── parameter.schema.json
│   │   │   ├── prompt-template.schema.json
│   │   │   ├── README.md
│   │   │   └── variant.schema.json
│   │   ├── update-subtask.json
│   │   ├── update-task.json
│   │   └── update-tasks.json
│   ├── provider-registry
│   │   └── index.js
│   ├── task-master.js
│   ├── ui
│   │   ├── confirm.js
│   │   ├── indicators.js
│   │   └── parse-prd.js
│   └── utils
│       ├── asset-resolver.js
│       ├── create-mcp-config.js
│       ├── format.js
│       ├── getVersion.js
│       ├── logger-utils.js
│       ├── manage-gitignore.js
│       ├── path-utils.js
│       ├── profiles.js
│       ├── rule-transformer.js
│       ├── stream-parser.js
│       └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│   ├── e2e
│   │   ├── e2e_helpers.sh
│   │   ├── parse_llm_output.cjs
│   │   ├── run_e2e.sh
│   │   ├── run_fallback_verification.sh
│   │   └── test_llm_analysis.sh
│   ├── fixture
│   │   └── test-tasks.json
│   ├── fixtures
│   │   ├── .taskmasterconfig
│   │   ├── sample-claude-response.js
│   │   ├── sample-prd.txt
│   │   └── sample-tasks.js
│   ├── integration
│   │   ├── claude-code-optional.test.js
│   │   ├── cli
│   │   │   ├── commands.test.js
│   │   │   ├── complex-cross-tag-scenarios.test.js
│   │   │   └── move-cross-tag.test.js
│   │   ├── manage-gitignore.test.js
│   │   ├── mcp-server
│   │   │   └── direct-functions.test.js
│   │   ├── move-task-cross-tag.integration.test.js
│   │   ├── move-task-simple.integration.test.js
│   │   └── profiles
│   │       ├── amp-init-functionality.test.js
│   │       ├── claude-init-functionality.test.js
│   │       ├── cline-init-functionality.test.js
│   │       ├── codex-init-functionality.test.js
│   │       ├── cursor-init-functionality.test.js
│   │       ├── gemini-init-functionality.test.js
│   │       ├── opencode-init-functionality.test.js
│   │       ├── roo-files-inclusion.test.js
│   │       ├── roo-init-functionality.test.js
│   │       ├── rules-files-inclusion.test.js
│   │       ├── trae-init-functionality.test.js
│   │       ├── vscode-init-functionality.test.js
│   │       └── windsurf-init-functionality.test.js
│   ├── manual
│   │   ├── progress
│   │   │   ├── parse-prd-analysis.js
│   │   │   ├── test-parse-prd.js
│   │   │   └── TESTING_GUIDE.md
│   │   └── prompts
│   │       ├── prompt-test.js
│   │       └── README.md
│   ├── README.md
│   ├── setup.js
│   └── unit
│       ├── ai-providers
│       │   ├── claude-code.test.js
│       │   ├── custom-sdk
│       │   │   └── claude-code
│       │   │       └── language-model.test.js
│       │   ├── gemini-cli.test.js
│       │   ├── mcp-components.test.js
│       │   └── openai.test.js
│       ├── ai-services-unified.test.js
│       ├── commands.test.js
│       ├── config-manager.test.js
│       ├── config-manager.test.mjs
│       ├── dependency-manager.test.js
│       ├── init.test.js
│       ├── initialize-project.test.js
│       ├── kebab-case-validation.test.js
│       ├── manage-gitignore.test.js
│       ├── mcp
│       │   └── tools
│       │       ├── __mocks__
│       │       │   └── move-task.js
│       │       ├── add-task.test.js
│       │       ├── analyze-complexity.test.js
│       │       ├── expand-all.test.js
│       │       ├── get-tasks.test.js
│       │       ├── initialize-project.test.js
│       │       ├── move-task-cross-tag-options.test.js
│       │       ├── move-task-cross-tag.test.js
│       │       └── remove-task.test.js
│       ├── mcp-providers
│       │   ├── mcp-components.test.js
│       │   └── mcp-provider.test.js
│       ├── parse-prd.test.js
│       ├── profiles
│       │   ├── amp-integration.test.js
│       │   ├── claude-integration.test.js
│       │   ├── cline-integration.test.js
│       │   ├── codex-integration.test.js
│       │   ├── cursor-integration.test.js
│       │   ├── gemini-integration.test.js
│       │   ├── kilo-integration.test.js
│       │   ├── kiro-integration.test.js
│       │   ├── mcp-config-validation.test.js
│       │   ├── opencode-integration.test.js
│       │   ├── profile-safety-check.test.js
│       │   ├── roo-integration.test.js
│       │   ├── rule-transformer-cline.test.js
│       │   ├── rule-transformer-cursor.test.js
│       │   ├── rule-transformer-gemini.test.js
│       │   ├── rule-transformer-kilo.test.js
│       │   ├── rule-transformer-kiro.test.js
│       │   ├── rule-transformer-opencode.test.js
│       │   ├── rule-transformer-roo.test.js
│       │   ├── rule-transformer-trae.test.js
│       │   ├── rule-transformer-vscode.test.js
│       │   ├── rule-transformer-windsurf.test.js
│       │   ├── rule-transformer-zed.test.js
│       │   ├── rule-transformer.test.js
│       │   ├── selective-profile-removal.test.js
│       │   ├── subdirectory-support.test.js
│       │   ├── trae-integration.test.js
│       │   ├── vscode-integration.test.js
│       │   ├── windsurf-integration.test.js
│       │   └── zed-integration.test.js
│       ├── progress
│       │   └── base-progress-tracker.test.js
│       ├── prompt-manager.test.js
│       ├── prompts
│       │   └── expand-task-prompt.test.js
│       ├── providers
│       │   └── provider-registry.test.js
│       ├── scripts
│       │   └── modules
│       │       ├── commands
│       │       │   ├── move-cross-tag.test.js
│       │       │   └── README.md
│       │       ├── dependency-manager
│       │       │   ├── circular-dependencies.test.js
│       │       │   ├── cross-tag-dependencies.test.js
│       │       │   └── fix-dependencies-command.test.js
│       │       ├── task-manager
│       │       │   ├── add-subtask.test.js
│       │       │   ├── add-task.test.js
│       │       │   ├── analyze-task-complexity.test.js
│       │       │   ├── clear-subtasks.test.js
│       │       │   ├── complexity-report-tag-isolation.test.js
│       │       │   ├── expand-all-tasks.test.js
│       │       │   ├── expand-task.test.js
│       │       │   ├── find-next-task.test.js
│       │       │   ├── generate-task-files.test.js
│       │       │   ├── list-tasks.test.js
│       │       │   ├── move-task-cross-tag.test.js
│       │       │   ├── move-task.test.js
│       │       │   ├── parse-prd.test.js
│       │       │   ├── remove-subtask.test.js
│       │       │   ├── remove-task.test.js
│       │       │   ├── research.test.js
│       │       │   ├── scope-adjustment.test.js
│       │       │   ├── set-task-status.test.js
│       │       │   ├── setup.js
│       │       │   ├── update-single-task-status.test.js
│       │       │   ├── update-subtask-by-id.test.js
│       │       │   ├── update-task-by-id.test.js
│       │       │   └── update-tasks.test.js
│       │       ├── ui
│       │       │   └── cross-tag-error-display.test.js
│       │       └── utils-tag-aware-paths.test.js
│       ├── task-finder.test.js
│       ├── task-manager
│       │   ├── clear-subtasks.test.js
│       │   ├── move-task.test.js
│       │   ├── tag-boundary.test.js
│       │   └── tag-management.test.js
│       ├── task-master.test.js
│       ├── ui
│       │   └── indicators.test.js
│       ├── ui.test.js
│       ├── utils-strip-ansi.test.js
│       └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
└── turbo.json
```

# Files

--------------------------------------------------------------------------------
/.kiro/steering/taskmaster.md:
--------------------------------------------------------------------------------

```markdown
---
inclusion: always
---

# Taskmaster Tool & Command Reference

This document provides a detailed reference for interacting with Taskmaster, covering both the recommended MCP tools, suitable for integrations like Kiro, and the corresponding `task-master` CLI commands, designed for direct user interaction or fallback.

**Note:** For interacting with Taskmaster programmatically or via integrated tools, using the **MCP tools is strongly recommended** due to better performance, structured data, and error handling. The CLI commands serve as a user-friendly alternative and fallback. 

**Important:** Several MCP tools involve AI processing... The AI-powered tools include `parse_prd`, `analyze_project_complexity`, `update_subtask`, `update_task`, `update`, `expand_all`, `expand_task`, and `add_task`.

**🏷️ Tagged Task Lists System:** Task Master now supports **tagged task lists** for multi-context task management. This allows you to maintain separate, isolated lists of tasks for different features, branches, or experiments. Existing projects are seamlessly migrated to use a default "master" tag. Most commands now support a `--tag <name>` flag to specify which context to operate on. If omitted, commands use the currently active tag.

---

## Initialization & Setup

### 1. Initialize Project (`init`)

*   **MCP Tool:** `initialize_project`
*   **CLI Command:** `task-master init [options]`
*   **Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project.`
*   **Key CLI Options:**
    *   `--name <name>`: `Set the name for your project in Taskmaster's configuration.`
    *   `--description <text>`: `Provide a brief description for your project.`
    *   `--version <version>`: `Set the initial version for your project, e.g., '0.1.0'.`
    *   `-y, --yes`: `Initialize Taskmaster quickly using default settings without interactive prompts.`
*   **Usage:** Run this once at the beginning of a new project.
*   **MCP Variant Description:** `Set up the basic Taskmaster file structure and configuration in the current directory for a new project by running the 'task-master init' command.`
*   **Key MCP Parameters/Options:**
    *   `projectName`: `Set the name for your project.` (CLI: `--name <name>`)
    *   `projectDescription`: `Provide a brief description for your project.` (CLI: `--description <text>`)
    *   `projectVersion`: `Set the initial version for your project, e.g., '0.1.0'.` (CLI: `--version <version>`)
    *   `authorName`: `Author name.` (CLI: `--author <author>`)
    *   `skipInstall`: `Skip installing dependencies. Default is false.` (CLI: `--skip-install`)
    *   `addAliases`: `Add shell aliases tm and taskmaster. Default is false.` (CLI: `--aliases`)
    *   `yes`: `Skip prompts and use defaults/provided arguments. Default is false.` (CLI: `-y, --yes`)
*   **Usage:** Run this once at the beginning of a new project, typically via an integrated tool like Kiro. Operates on the current working directory of the MCP server. 
*   **Important:** Once complete, you *MUST* parse a prd in order to generate tasks. There will be no tasks files until then. The next step after initializing should be to create a PRD using the example PRD in .taskmaster/templates/example_prd.txt. 
*   **Tagging:** Use the `--tag` option to parse the PRD into a specific, non-default tag context. If the tag doesn't exist, it will be created automatically. Example: `task-master parse-prd spec.txt --tag=new-feature`.

### 2. Parse PRD (`parse_prd`)

*   **MCP Tool:** `parse_prd`
*   **CLI Command:** `task-master parse-prd [file] [options]`
*   **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.`
*   **Key Parameters/Options:**
    *   `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input <file>`)
    *   `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to '.taskmaster/tasks/tasks.json'.` (CLI: `-o, --output <file>`)
    *   `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks <number>`)
    *   `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`)
*   **Usage:** Useful for bootstrapping a project from an existing requirements document.
*   **Notes:** Task Master will strictly adhere to any specific requirements mentioned in the PRD, such as libraries, database schemas, frameworks, tech stacks, etc., while filling in any gaps where the PRD isn't fully specified. Tasks are designed to provide the most direct implementation path while avoiding over-engineering.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. If the user does not have a PRD, suggest discussing their idea and then use the example PRD in `.taskmaster/templates/example_prd.txt` as a template for creating the PRD based on their idea, for use with `parse-prd`.

---

## AI Model Configuration

### 2. Manage Models (`models`)
*   **MCP Tool:** `models`
*   **CLI Command:** `task-master models [options]`
*   **Description:** `View the current AI model configuration or set specific models for different roles (main, research, fallback). Allows setting custom model IDs for Ollama and OpenRouter.`
*   **Key MCP Parameters/Options:**
    *   `setMain <model_id>`: `Set the primary model ID for task generation/updates.` (CLI: `--set-main <model_id>`)
    *   `setResearch <model_id>`: `Set the model ID for research-backed operations.` (CLI: `--set-research <model_id>`)
    *   `setFallback <model_id>`: `Set the model ID to use if the primary fails.` (CLI: `--set-fallback <model_id>`)
    *   `ollama <boolean>`: `Indicates the set model ID is a custom Ollama model.` (CLI: `--ollama`)
    *   `openrouter <boolean>`: `Indicates the set model ID is a custom OpenRouter model.` (CLI: `--openrouter`)
    *   `listAvailableModels <boolean>`: `If true, lists available models not currently assigned to a role.` (CLI: No direct equivalent; CLI lists available automatically)
    *   `projectRoot <string>`: `Optional. Absolute path to the project root directory.` (CLI: Determined automatically)
*   **Key CLI Options:**
    *   `--set-main <model_id>`: `Set the primary model.`
    *   `--set-research <model_id>`: `Set the research model.`
    *   `--set-fallback <model_id>`: `Set the fallback model.`
    *   `--ollama`: `Specify that the provided model ID is for Ollama (use with --set-*).`
    *   `--openrouter`: `Specify that the provided model ID is for OpenRouter (use with --set-*). Validates against OpenRouter API.`
    *   `--bedrock`: `Specify that the provided model ID is for AWS Bedrock (use with --set-*).`
    *   `--setup`: `Run interactive setup to configure models, including custom Ollama/OpenRouter IDs.`
*   **Usage (MCP):** Call without set flags to get current config. Use `setMain`, `setResearch`, or `setFallback` with a valid model ID to update the configuration. Use `listAvailableModels: true` to get a list of unassigned models. To set a custom model, provide the model ID and set `ollama: true` or `openrouter: true`.
*   **Usage (CLI):** Run without flags to view current configuration and available models. Use set flags to update specific roles. Use `--setup` for guided configuration, including custom models. To set a custom model via flags, use `--set-<role>=<model_id>` along with either `--ollama` or `--openrouter`.
*   **Notes:** Configuration is stored in `.taskmaster/config.json` in the project root. This command/tool modifies that file. Use `listAvailableModels` or `task-master models` to see internally supported models. OpenRouter custom models are validated against their live API. Ollama custom models are not validated live.
*   **API note:** API keys for selected AI providers (based on their model) need to exist in the mcp.json file to be accessible in MCP context. The API keys must be present in the local .env file for the CLI to be able to read them.
*   **Model costs:** The costs in supported models are expressed in dollars. An input/output value of 3 is $3.00. A value of 0.8 is $0.80. 
*   **Warning:** DO NOT MANUALLY EDIT THE .taskmaster/config.json FILE. Use the included commands either in the MCP or CLI format as needed. Always prioritize MCP tools when available and use the CLI as a fallback.

---

## Task Listing & Viewing

### 3. Get Tasks (`get_tasks`)

*   **MCP Tool:** `get_tasks`
*   **CLI Command:** `task-master list [options]`
*   **Description:** `List your Taskmaster tasks, optionally filtering by status and showing subtasks.`
*   **Key Parameters/Options:**
    *   `status`: `Show only Taskmaster tasks matching this status (or multiple statuses, comma-separated), e.g., 'pending' or 'done,in-progress'.` (CLI: `-s, --status <status>`)
    *   `withSubtasks`: `Include subtasks indented under their parent tasks in the list.` (CLI: `--with-subtasks`)
    *   `tag`: `Specify which tag context to list tasks from. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Get an overview of the project status, often used at the start of a work session.

### 4. Get Next Task (`next_task`)

*   **MCP Tool:** `next_task`
*   **CLI Command:** `task-master next [options]`
*   **Description:** `Ask Taskmaster to show the next available task you can work on, based on status and completed dependencies.`
*   **Key Parameters/Options:**
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
    *   `tag`: `Specify which tag context to use. Defaults to the current active tag.` (CLI: `--tag <name>`)
*   **Usage:** Identify what to work on next according to the plan.

### 5. Get Task Details (`get_task`)

*   **MCP Tool:** `get_task`
*   **CLI Command:** `task-master show [id] [options]`
*   **Description:** `Display detailed information for one or more specific Taskmaster tasks or subtasks by ID.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task (e.g., '15'), subtask (e.g., '15.2'), or a comma-separated list of IDs ('1,5,10.2') you want to view.` (CLI: `[id]` positional or `-i, --id <id>`)
    *   `tag`: `Specify which tag context to get the task(s) from. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Understand the full details for a specific task. When multiple IDs are provided, a summary table is shown.
*   **CRITICAL INFORMATION** If you need to collect information from multiple tasks, use comma-separated IDs (i.e. 1,2,3) to receive an array of tasks. Do not needlessly get tasks one at a time if you need to get many as that is wasteful.

---

## Task Creation & Modification

### 6. Add Task (`add_task`)

*   **MCP Tool:** `add_task`
*   **CLI Command:** `task-master add-task [options]`
*   **Description:** `Add a new task to Taskmaster by describing it; AI will structure it.`
*   **Key Parameters/Options:**
    *   `prompt`: `Required. Describe the new task you want Taskmaster to create, e.g., "Implement user authentication using JWT".` (CLI: `-p, --prompt <text>`)
    *   `dependencies`: `Specify the IDs of any Taskmaster tasks that must be completed before this new one can start, e.g., '12,14'.` (CLI: `-d, --dependencies <ids>`)
    *   `priority`: `Set the priority for the new task: 'high', 'medium', or 'low'. Default is 'medium'.` (CLI: `--priority <priority>`)
    *   `research`: `Enable Taskmaster to use the research role for potentially more informed task creation.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context to add the task to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Quickly add newly identified tasks during development.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 7. Add Subtask (`add_subtask`)

*   **MCP Tool:** `add_subtask`
*   **CLI Command:** `task-master add-subtask [options]`
*   **Description:** `Add a new subtask to a Taskmaster parent task, or convert an existing task into a subtask.`
*   **Key Parameters/Options:**
    *   `id` / `parent`: `Required. The ID of the Taskmaster task that will be the parent.` (MCP: `id`, CLI: `-p, --parent <id>`)
    *   `taskId`: `Use this if you want to convert an existing top-level Taskmaster task into a subtask of the specified parent.` (CLI: `-i, --task-id <id>`)
    *   `title`: `Required if not using taskId. The title for the new subtask Taskmaster should create.` (CLI: `-t, --title <title>`)
    *   `description`: `A brief description for the new subtask.` (CLI: `-d, --description <text>`)
    *   `details`: `Provide implementation notes or details for the new subtask.` (CLI: `--details <text>`)
    *   `dependencies`: `Specify IDs of other tasks or subtasks, e.g., '15' or '16.1', that must be done before this new subtask.` (CLI: `--dependencies <ids>`)
    *   `status`: `Set the initial status for the new subtask. Default is 'pending'.` (CLI: `-s, --status <status>`)
    *   `generate`: `Enable Taskmaster to regenerate markdown task files after adding the subtask.` (CLI: `--generate`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Break down tasks manually or reorganize existing tasks.

### 8. Update Tasks (`update`)

*   **MCP Tool:** `update`
*   **CLI Command:** `task-master update [options]`
*   **Description:** `Update multiple upcoming tasks in Taskmaster based on new context or changes, starting from a specific task ID.`
*   **Key Parameters/Options:**
    *   `from`: `Required. The ID of the first task Taskmaster should update. All tasks with this ID or higher that are not 'done' will be considered.` (CLI: `--from <id>`)
    *   `prompt`: `Required. Explain the change or new context for Taskmaster to apply to the tasks, e.g., "We are now using React Query instead of Redux Toolkit for data fetching".` (CLI: `-p, --prompt <text>`)
    *   `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Handle significant implementation changes or pivots that affect multiple future tasks. Example CLI: `task-master update --from='18' --prompt='Switching to React Query.\nNeed to refactor data fetching...'`
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 9. Update Task (`update_task`)

*   **MCP Tool:** `update_task`
*   **CLI Command:** `task-master update-task [options]`
*   **Description:** `Modify a specific Taskmaster task by ID, incorporating new information or changes. By default, this replaces the existing task details.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The specific ID of the Taskmaster task, e.g., '15', you want to update.` (CLI: `-i, --id <id>`)
    *   `prompt`: `Required. Explain the specific changes or provide the new information Taskmaster should incorporate into this task.` (CLI: `-p, --prompt <text>`)
    *   `append`: `If true, appends the prompt content to the task's details with a timestamp, rather than replacing them. Behaves like update-subtask.` (CLI: `--append`)
    *   `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Refine a specific task based on new understanding. Use `--append` to log progress without creating subtasks.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 10. Update Subtask (`update_subtask`)

*   **MCP Tool:** `update_subtask`
*   **CLI Command:** `task-master update-subtask [options]`
*   **Description:** `Append timestamped notes or details to a specific Taskmaster subtask without overwriting existing content. Intended for iterative implementation logging.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster subtask, e.g., '5.2', to update with new information.` (CLI: `-i, --id <id>`)
    *   `prompt`: `Required. The information, findings, or progress notes to append to the subtask's details with a timestamp.` (CLI: `-p, --prompt <text>`)
    *   `research`: `Enable Taskmaster to use the research role for more informed updates. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context the subtask belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Log implementation progress, findings, and discoveries during subtask development. Each update is timestamped and appended to preserve the implementation journey.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 11. Set Task Status (`set_task_status`)

*   **MCP Tool:** `set_task_status`
*   **CLI Command:** `task-master set-status [options]`
*   **Description:** `Update the status of one or more Taskmaster tasks or subtasks, e.g., 'pending', 'in-progress', 'done'.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID(s) of the Taskmaster task(s) or subtask(s), e.g., '15', '15.2', or '16,17.1', to update.` (CLI: `-i, --id <id>`)
    *   `status`: `Required. The new status to set, e.g., 'done', 'pending', 'in-progress', 'review', 'cancelled'.` (CLI: `-s, --status <status>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Mark progress as tasks move through the development cycle.

### 12. Remove Task (`remove_task`)

*   **MCP Tool:** `remove_task`
*   **CLI Command:** `task-master remove-task [options]`
*   **Description:** `Permanently remove a task or subtask from the Taskmaster tasks list.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task, e.g., '5', or subtask, e.g., '5.2', to permanently remove.` (CLI: `-i, --id <id>`)
    *   `yes`: `Skip the confirmation prompt and immediately delete the task.` (CLI: `-y, --yes`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Permanently delete tasks or subtasks that are no longer needed in the project.
*   **Notes:** Use with caution as this operation cannot be undone. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you just want to exclude a task from active planning but keep it for reference. The command automatically cleans up dependency references in other tasks.

---

## Task Structure & Breakdown

### 13. Expand Task (`expand_task`)

*   **MCP Tool:** `expand_task`
*   **CLI Command:** `task-master expand [options]`
*   **Description:** `Use Taskmaster's AI to break down a complex task into smaller, manageable subtasks. Appends subtasks by default.`
*   **Key Parameters/Options:**
    *   `id`: `The ID of the specific Taskmaster task you want to break down into subtasks.` (CLI: `-i, --id <id>`)
    *   `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create. Uses complexity analysis/defaults otherwise.` (CLI: `-n, --num <number>`)
    *   `research`: `Enable Taskmaster to use the research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `prompt`: `Optional: Provide extra context or specific instructions to Taskmaster for generating the subtasks.` (CLI: `-p, --prompt <text>`)
    *   `force`: `Optional: If true, clear existing subtasks before generating new ones. Default is false (append).` (CLI: `--force`)
    *   `tag`: `Specify which tag context the task belongs to. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Generate a detailed implementation plan for a complex task before starting coding. Automatically uses complexity report recommendations if available and `num` is not specified.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 14. Expand All Tasks (`expand_all`)

*   **MCP Tool:** `expand_all`
*   **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag)
*   **Description:** `Tell Taskmaster to automatically expand all eligible pending/in-progress tasks based on complexity analysis or defaults. Appends subtasks by default.`
*   **Key Parameters/Options:**
    *   `num`: `Optional: Suggests how many subtasks Taskmaster should aim to create per task.` (CLI: `-n, --num <number>`)
    *   `research`: `Enable research role for more informed subtask generation. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `prompt`: `Optional: Provide extra context for Taskmaster to apply generally during expansion.` (CLI: `-p, --prompt <text>`)
    *   `force`: `Optional: If true, clear existing subtasks before generating new ones for each eligible task. Default is false (append).` (CLI: `--force`)
    *   `tag`: `Specify which tag context to expand. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 15. Clear Subtasks (`clear_subtasks`)

*   **MCP Tool:** `clear_subtasks`
*   **CLI Command:** `task-master clear-subtasks [options]`
*   **Description:** `Remove all subtasks from one or more specified Taskmaster parent tasks.`
*   **Key Parameters/Options:**
    *   `id`: `The ID(s) of the Taskmaster parent task(s) whose subtasks you want to remove, e.g., '15' or '16,18'. Required unless using 'all'.` (CLI: `-i, --id <ids>`)
    *   `all`: `Tell Taskmaster to remove subtasks from all parent tasks.` (CLI: `--all`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement.

### 16. Remove Subtask (`remove_subtask`)

*   **MCP Tool:** `remove_subtask`
*   **CLI Command:** `task-master remove-subtask [options]`
*   **Description:** `Remove a subtask from its Taskmaster parent, optionally converting it into a standalone task.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID(s) of the Taskmaster subtask(s) to remove, e.g., '15.2' or '16.1,16.3'.` (CLI: `-i, --id <id>`)
    *   `convert`: `If used, Taskmaster will turn the subtask into a regular top-level task instead of deleting it.` (CLI: `-c, --convert`)
    *   `generate`: `Enable Taskmaster to regenerate markdown task files after removing the subtask.` (CLI: `--generate`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task.

### 17. Move Task (`move_task`)

*   **MCP Tool:** `move_task`
*   **CLI Command:** `task-master move [options]`
*   **Description:** `Move a task or subtask to a new position within the task hierarchy.`
*   **Key Parameters/Options:**
    *   `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from <id>`)
    *   `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to <id>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like:
    *   Moving a task to become a subtask
    *   Moving a subtask to become a standalone task
    *   Moving a subtask to a different parent
    *   Reordering subtasks within the same parent
    *   Moving a task to a new, non-existent ID (automatically creates placeholders)
    *   Moving multiple tasks at once with comma-separated IDs
*   **Validation Features:**
    *   Allows moving tasks to non-existent destination IDs (creates placeholder tasks)
    *   Prevents moving to existing task IDs that already have content (to avoid overwriting)
    *   Validates that source tasks exist before attempting to move them
    *   Maintains proper parent-child relationships
*   **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3.
*   **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions.
*   **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches.

---

## Dependency Management

### 18. Add Dependency (`add_dependency`)

*   **MCP Tool:** `add_dependency`
*   **CLI Command:** `task-master add-dependency [options]`
*   **Description:** `Define a dependency in Taskmaster, making one task a prerequisite for another.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task that will depend on another.` (CLI: `-i, --id <id>`)
    *   `dependsOn`: `Required. The ID of the Taskmaster task that must be completed first, the prerequisite.` (CLI: `-d, --depends-on <id>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <path>`)
*   **Usage:** Establish the correct order of execution between tasks.

### 19. Remove Dependency (`remove_dependency`)

*   **MCP Tool:** `remove_dependency`
*   **CLI Command:** `task-master remove-dependency [options]`
*   **Description:** `Remove a dependency relationship between two Taskmaster tasks.`
*   **Key Parameters/Options:**
    *   `id`: `Required. The ID of the Taskmaster task you want to remove a prerequisite from.` (CLI: `-i, --id <id>`)
    *   `dependsOn`: `Required. The ID of the Taskmaster task that should no longer be a prerequisite.` (CLI: `-d, --depends-on <id>`)
    *   `tag`: `Specify which tag context to operate on. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Update task relationships when the order of execution changes.

### 20. Validate Dependencies (`validate_dependencies`)

*   **MCP Tool:** `validate_dependencies`
*   **CLI Command:** `task-master validate-dependencies [options]`
*   **Description:** `Check your Taskmaster tasks for dependency issues (like circular references or links to non-existent tasks) without making changes.`
*   **Key Parameters/Options:**
    *   `tag`: `Specify which tag context to validate. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Audit the integrity of your task dependencies.

### 21. Fix Dependencies (`fix_dependencies`)

*   **MCP Tool:** `fix_dependencies`
*   **CLI Command:** `task-master fix-dependencies [options]`
*   **Description:** `Automatically fix dependency issues (like circular references or links to non-existent tasks) in your Taskmaster tasks.`
*   **Key Parameters/Options:**
    *   `tag`: `Specify which tag context to fix dependencies in. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Clean up dependency errors automatically.

---

## Analysis & Reporting

### 22. Analyze Project Complexity (`analyze_project_complexity`)

*   **MCP Tool:** `analyze_project_complexity`
*   **CLI Command:** `task-master analyze-complexity [options]`
*   **Description:** `Have Taskmaster analyze your tasks to determine their complexity and suggest which ones need to be broken down further.`
*   **Key Parameters/Options:**
    *   `output`: `Where to save the complexity analysis report. Default is '.taskmaster/reports/task-complexity-report.json' (or '..._tagname.json' if a tag is used).` (CLI: `-o, --output <file>`)
    *   `threshold`: `The minimum complexity score (1-10) that should trigger a recommendation to expand a task.` (CLI: `-t, --threshold <number>`)
    *   `research`: `Enable research role for more accurate complexity analysis. Requires appropriate API key.` (CLI: `-r, --research`)
    *   `tag`: `Specify which tag context to analyze. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Used before breaking down tasks to identify which ones need the most attention.
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.

### 23. View Complexity Report (`complexity_report`)

*   **MCP Tool:** `complexity_report`
*   **CLI Command:** `task-master complexity-report [options]`
*   **Description:** `Display the task complexity analysis report in a readable format.`
*   **Key Parameters/Options:**
    *   `tag`: `Specify which tag context to show the report for. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to the complexity report (default: '.taskmaster/reports/task-complexity-report.json').` (CLI: `-f, --file <file>`)
*   **Usage:** Review and understand the complexity analysis results after running analyze-complexity.

---

## File Management

### 24. Generate Task Files (`generate`)

*   **MCP Tool:** `generate`
*   **CLI Command:** `task-master generate [options]`
*   **Description:** `Create or update individual Markdown files for each task based on your tasks.json.`
*   **Key Parameters/Options:**
    *   `output`: `The directory where Taskmaster should save the task files (default: in a 'tasks' directory).` (CLI: `-o, --output <directory>`)
    *   `tag`: `Specify which tag context to generate files for. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
*   **Usage:** Run this after making changes to tasks.json to keep individual task files up to date. This command is now manual and no longer runs automatically.

---

## AI-Powered Research

### 25. Research (`research`)

*   **MCP Tool:** `research`
*   **CLI Command:** `task-master research [options]`
*   **Description:** `Perform AI-powered research queries with project context to get fresh, up-to-date information beyond the AI's knowledge cutoff.`
*   **Key Parameters/Options:**
    *   `query`: `Required. Research query/prompt (e.g., "What are the latest best practices for React Query v5?").` (CLI: `[query]` positional or `-q, --query <text>`)
    *   `taskIds`: `Comma-separated list of task/subtask IDs from the current tag context (e.g., "15,16.2,17").` (CLI: `-i, --id <ids>`)
    *   `filePaths`: `Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md").` (CLI: `-f, --files <paths>`)
    *   `customContext`: `Additional custom context text to include in the research.` (CLI: `-c, --context <text>`)
    *   `includeProjectTree`: `Include project file tree structure in context (default: false).` (CLI: `--tree`)
    *   `detailLevel`: `Detail level for the research response: 'low', 'medium', 'high' (default: medium).` (CLI: `--detail <level>`)
    *   `saveTo`: `Task or subtask ID (e.g., "15", "15.2") to automatically save the research conversation to.` (CLI: `--save-to <id>`)
    *   `saveFile`: `If true, saves the research conversation to a markdown file in '.taskmaster/docs/research/'.` (CLI: `--save-file`)
    *   `noFollowup`: `Disables the interactive follow-up question menu in the CLI.` (CLI: `--no-followup`)
    *   `tag`: `Specify which tag context to use for task-based context gathering. Defaults to the current active tag.` (CLI: `--tag <name>`)
    *   `projectRoot`: `The directory of the project. Must be an absolute path.` (CLI: Determined automatically)
*   **Usage:** **This is a POWERFUL tool that agents should use FREQUENTLY** to:
    *   Get fresh information beyond knowledge cutoff dates
    *   Research latest best practices, library updates, security patches
    *   Find implementation examples for specific technologies
    *   Validate approaches against current industry standards
    *   Get contextual advice based on project files and tasks
*   **When to Consider Using Research:**
    *   **Before implementing any task** - Research current best practices
    *   **When encountering new technologies** - Get up-to-date implementation guidance (libraries, apis, etc)
    *   **For security-related tasks** - Find latest security recommendations
    *   **When updating dependencies** - Research breaking changes and migration guides
    *   **For performance optimization** - Get current performance best practices
    *   **When debugging complex issues** - Research known solutions and workarounds
*   **Research + Action Pattern:**
    *   Use `research` to gather fresh information
    *   Use `update_subtask` to commit findings with timestamps
    *   Use `update_task` to incorporate research into task details
    *   Use `add_task` with research flag for informed task creation
*   **Important:** This MCP tool makes AI calls and can take up to a minute to complete. The research provides FRESH data beyond the AI's training cutoff, making it invaluable for current best practices and recent developments.

---

## Tag Management

This new suite of commands allows you to manage different task contexts (tags).

### 26. List Tags (`tags`)

*   **MCP Tool:** `list_tags`
*   **CLI Command:** `task-master tags [options]`
*   **Description:** `List all available tags with task counts, completion status, and other metadata.`
*   **Key Parameters/Options:**
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
    *   `--show-metadata`: `Include detailed metadata in the output (e.g., creation date, description).` (CLI: `--show-metadata`)

### 27. Add Tag (`add_tag`)

*   **MCP Tool:** `add_tag`
*   **CLI Command:** `task-master add-tag <tagName> [options]`
*   **Description:** `Create a new, empty tag context, or copy tasks from another tag.`
*   **Key Parameters/Options:**
    *   `tagName`: `Name of the new tag to create (alphanumeric, hyphens, underscores).` (CLI: `<tagName>` positional)
    *   `--from-branch`: `Creates a tag with a name derived from the current git branch, ignoring the <tagName> argument.` (CLI: `--from-branch`)
    *   `--copy-from-current`: `Copy tasks from the currently active tag to the new tag.` (CLI: `--copy-from-current`)
    *   `--copy-from <tag>`: `Copy tasks from a specific source tag to the new tag.` (CLI: `--copy-from <tag>`)
    *   `--description <text>`: `Provide an optional description for the new tag.` (CLI: `-d, --description <text>`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 28. Delete Tag (`delete_tag`)

*   **MCP Tool:** `delete_tag`
*   **CLI Command:** `task-master delete-tag <tagName> [options]`
*   **Description:** `Permanently delete a tag and all of its associated tasks.`
*   **Key Parameters/Options:**
    *   `tagName`: `Name of the tag to delete.` (CLI: `<tagName>` positional)
    *   `--yes`: `Skip the confirmation prompt.` (CLI: `-y, --yes`)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 29. Use Tag (`use_tag`)

*   **MCP Tool:** `use_tag`
*   **CLI Command:** `task-master use-tag <tagName>`
*   **Description:** `Switch your active task context to a different tag.`
*   **Key Parameters/Options:**
    *   `tagName`: `Name of the tag to switch to.` (CLI: `<tagName>` positional)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 30. Rename Tag (`rename_tag`)

*   **MCP Tool:** `rename_tag`
*   **CLI Command:** `task-master rename-tag <oldName> <newName>`
*   **Description:** `Rename an existing tag.`
*   **Key Parameters/Options:**
    *   `oldName`: `The current name of the tag.` (CLI: `<oldName>` positional)
    *   `newName`: `The new name for the tag.` (CLI: `<newName>` positional)
    *   `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)

### 31. Copy Tag (`copy_tag`)

*   **MCP Tool:** `copy_tag`
*   **CLI Command:** `task-master copy-tag <sourceName> <targetName> [options]`
*   **Description:** `Copy an entire tag context, including all its tasks and metadata, to a new tag.`
*   **Key Parameters/Options:**
    *   `sourceName`: `Name of the tag to copy from.` (CLI: `<sourceName>` positional)
    *   `targetName`: `Name of the new tag to create.` (CLI: `<targetName>` positional)
    *   `--description <text>`: `Optional description for the new tag.` (CLI: `-d, --description <text>`)

---

## Miscellaneous

### 32. Sync Readme (`sync-readme`) -- experimental

*   **MCP Tool:** N/A
*   **CLI Command:** `task-master sync-readme [options]`
*   **Description:** `Exports your task list to your project's README.md file, useful for showcasing progress.`
*   **Key Parameters/Options:**
    *   `status`: `Filter tasks by status (e.g., 'pending', 'done').` (CLI: `-s, --status <status>`)
    *   `withSubtasks`: `Include subtasks in the export.` (CLI: `--with-subtasks`)
    *   `tag`: `Specify which tag context to export from. Defaults to the current active tag.` (CLI: `--tag <name>`)

---

## Environment Variables Configuration (Updated)

Taskmaster primarily uses the **`.taskmaster/config.json`** file (in project root) for configuration (models, parameters, logging level, etc.), managed via `task-master models --setup`.

Environment variables are used **only** for sensitive API keys related to AI providers and specific overrides like the Ollama base URL:

*   **API Keys (Required for corresponding provider):**
    *   `ANTHROPIC_API_KEY`
    *   `PERPLEXITY_API_KEY`
    *   `OPENAI_API_KEY`
    *   `GOOGLE_API_KEY`
    *   `MISTRAL_API_KEY`
    *   `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too)
    *   `OPENROUTER_API_KEY`
    *   `XAI_API_KEY`
    *   `OLLAMA_API_KEY` (Requires `OLLAMA_BASE_URL` too)
*   **Endpoints (Optional/Provider Specific inside .taskmaster/config.json):**
    *   `AZURE_OPENAI_ENDPOINT`
    *   `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`)

**Set API keys** in your **`.env`** file in the project root (for CLI use) or within the `env` section of your **`.kiro/mcp.json`** file (for MCP/Kiro integration). All other settings (model choice, max tokens, temperature, log level, custom endpoints) are managed in `.taskmaster/config.json` via `task-master models` command or `models` MCP tool.

---

For details on how these commands fit into the development process, see the [dev_workflow.md](.kiro/steering/dev_workflow.md).
```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/tag-management.js:
--------------------------------------------------------------------------------

```javascript
import path from 'path';
import fs from 'fs';
import inquirer from 'inquirer';
import chalk from 'chalk';
import boxen from 'boxen';
import Table from 'cli-table3';

import {
	log,
	readJSON,
	writeJSON,
	getCurrentTag,
	resolveTag,
	getTasksForTag,
	setTasksForTag,
	findProjectRoot,
	truncate
} from '../utils.js';
import { displayBanner, getStatusWithColor } from '../ui.js';
import findNextTask from './find-next-task.js';

/**
 * Create a new tag context
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} tagName - Name of the new tag to create
 * @param {Object} options - Options object
 * @param {boolean} [options.copyFromCurrent=false] - Whether to copy tasks from current tag
 * @param {string} [options.copyFromTag] - Specific tag to copy tasks from
 * @param {string} [options.description] - Optional description for the tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with tag creation details
 */
async function createTag(
	tasksPath,
	tagName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { copyFromCurrent = false, copyFromTag, description } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate tag name
		if (!tagName || typeof tagName !== 'string') {
			throw new Error('Tag name is required and must be a string');
		}

		// Validate tag name format (alphanumeric, hyphens, underscores only)
		if (!/^[a-zA-Z0-9_-]+$/.test(tagName)) {
			throw new Error(
				'Tag name can only contain letters, numbers, hyphens, and underscores'
			);
		}

		// Reserved tag names
		const reservedNames = ['master', 'main', 'default'];
		if (reservedNames.includes(tagName.toLowerCase())) {
			throw new Error(`"${tagName}" is a reserved tag name`);
		}

		logFn.info(`Creating new tag: ${tagName}`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations - ensure we get the actual tagged structure
		let rawData;
		if (data._rawTaggedData) {
			// If we have _rawTaggedData, use it (this is the clean tagged structure)
			rawData = data._rawTaggedData;
		} else if (data.tasks && !data.master) {
			// This is legacy format - create a master tag structure
			rawData = {
				master: {
					tasks: data.tasks,
					metadata: data.metadata || {
						created: new Date().toISOString(),
						updated: new Date().toISOString(),
						description: 'Tasks live here by default'
					}
				}
			};
		} else {
			// This is already in tagged format, use it directly but exclude internal fields
			rawData = {};
			for (const [key, value] of Object.entries(data)) {
				if (key !== '_rawTaggedData' && key !== 'tag') {
					rawData[key] = value;
				}
			}
		}

		// Check if tag already exists
		if (rawData[tagName]) {
			throw new Error(`Tag "${tagName}" already exists`);
		}

		// Determine source for copying tasks (only if explicitly requested)
		let sourceTasks = [];
		if (copyFromCurrent || copyFromTag) {
			const sourceTag = copyFromTag || getCurrentTag(projectRoot);
			sourceTasks = getTasksForTag(rawData, sourceTag);

			if (copyFromTag && sourceTasks.length === 0) {
				logFn.warn(`Source tag "${copyFromTag}" not found or has no tasks`);
			}

			logFn.info(`Copying ${sourceTasks.length} tasks from tag "${sourceTag}"`);
		} else {
			logFn.info('Creating empty tag (no tasks copied)');
		}

		// Create the new tag structure in raw data
		rawData[tagName] = {
			tasks: [...sourceTasks], // Create a copy of the tasks array
			metadata: {
				created: new Date().toISOString(),
				updated: new Date().toISOString(),
				description:
					description || `Tag created on ${new Date().toLocaleDateString()}`
			}
		};

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		logFn.success(`Successfully created tag "${tagName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				tagName,
				created: true,
				tasksCopied: sourceTasks.length,
				sourceTag:
					copyFromCurrent || copyFromTag
						? copyFromTag || getCurrentTag(projectRoot)
						: null,
				description:
					description || `Tag created on ${new Date().toLocaleDateString()}`
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.green.bold('✓ Tag Created Successfully') +
						`\n\nTag Name: ${chalk.cyan(tagName)}` +
						`\nTasks Copied: ${chalk.yellow(sourceTasks.length)}` +
						(copyFromCurrent || copyFromTag
							? `\nSource Tag: ${chalk.cyan(copyFromTag || getCurrentTag(projectRoot))}`
							: '') +
						(description ? `\nDescription: ${chalk.gray(description)}` : ''),
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			tagName,
			created: true,
			tasksCopied: sourceTasks.length,
			sourceTag:
				copyFromCurrent || copyFromTag
					? copyFromTag || getCurrentTag(projectRoot)
					: null,
			description:
				description || `Tag created on ${new Date().toLocaleDateString()}`
		};
	} catch (error) {
		logFn.error(`Error creating tag: ${error.message}`);
		throw error;
	}
}

/**
 * Delete an existing tag
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} tagName - Name of the tag to delete
 * @param {Object} options - Options object
 * @param {boolean} [options.yes=false] - Skip confirmation prompts
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with deletion details
 */
async function deleteTag(
	tasksPath,
	tagName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { yes = false } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate tag name
		if (!tagName || typeof tagName !== 'string') {
			throw new Error('Tag name is required and must be a string');
		}

		// Prevent deletion of master tag
		if (tagName === 'master') {
			throw new Error('Cannot delete the "master" tag');
		}

		logFn.info(`Deleting tag: ${tagName}`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations - ensure we get the actual tagged structure
		let rawData;
		if (data._rawTaggedData) {
			// If we have _rawTaggedData, use it (this is the clean tagged structure)
			rawData = data._rawTaggedData;
		} else if (data.tasks && !data.master) {
			// This is legacy format - create a master tag structure
			rawData = {
				master: {
					tasks: data.tasks,
					metadata: data.metadata || {
						created: new Date().toISOString(),
						updated: new Date().toISOString(),
						description: 'Tasks live here by default'
					}
				}
			};
		} else {
			// This is already in tagged format, use it directly but exclude internal fields
			rawData = {};
			for (const [key, value] of Object.entries(data)) {
				if (key !== '_rawTaggedData' && key !== 'tag') {
					rawData[key] = value;
				}
			}
		}

		// Check if tag exists
		if (!rawData[tagName]) {
			throw new Error(`Tag "${tagName}" does not exist`);
		}

		// Get current tag to check if we're deleting the active tag
		const currentTag = getCurrentTag(projectRoot);
		const isCurrentTag = currentTag === tagName;

		// Get task count for confirmation
		const tasks = getTasksForTag(rawData, tagName);
		const taskCount = tasks.length;

		// If not forced and has tasks, require confirmation (for CLI)
		if (!yes && taskCount > 0 && outputFormat === 'text') {
			console.log(
				boxen(
					chalk.yellow.bold('⚠ WARNING: Tag Deletion') +
						`\n\nYou are about to delete tag "${chalk.cyan(tagName)}"` +
						`\nThis will permanently delete ${chalk.red.bold(taskCount)} tasks` +
						'\n\nThis action cannot be undone!',
					{
						padding: 1,
						borderColor: 'yellow',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);

			// First confirmation
			const firstConfirm = await inquirer.prompt([
				{
					type: 'confirm',
					name: 'proceed',
					message: `Are you sure you want to delete tag "${tagName}" and its ${taskCount} tasks?`,
					default: false
				}
			]);

			if (!firstConfirm.proceed) {
				logFn.info('Tag deletion cancelled by user');
				throw new Error('Tag deletion cancelled');
			}

			// Second confirmation (double-check)
			const secondConfirm = await inquirer.prompt([
				{
					type: 'input',
					name: 'tagNameConfirm',
					message: `To confirm deletion, please type the tag name "${tagName}":`,
					validate: (input) => {
						if (input === tagName) {
							return true;
						}
						return `Please type exactly "${tagName}" to confirm deletion`;
					}
				}
			]);

			if (secondConfirm.tagNameConfirm !== tagName) {
				logFn.info('Tag deletion cancelled - incorrect tag name confirmation');
				throw new Error('Tag deletion cancelled');
			}

			logFn.info('Double confirmation received, proceeding with deletion...');
		}

		// Delete the tag
		delete rawData[tagName];

		// If we're deleting the current tag, switch to master
		if (isCurrentTag) {
			await switchCurrentTag(projectRoot, 'master');
			logFn.info('Switched current tag to "master"');
		}

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		logFn.success(`Successfully deleted tag "${tagName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				tagName,
				deleted: true,
				tasksDeleted: taskCount,
				wasCurrentTag: isCurrentTag,
				switchedToMaster: isCurrentTag
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.red.bold('✓ Tag Deleted Successfully') +
						`\n\nTag Name: ${chalk.cyan(tagName)}` +
						`\nTasks Deleted: ${chalk.yellow(taskCount)}` +
						(isCurrentTag
							? `\n${chalk.yellow('⚠ Switched current tag to "master"')}`
							: ''),
					{
						padding: 1,
						borderColor: 'red',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			tagName,
			deleted: true,
			tasksDeleted: taskCount,
			wasCurrentTag: isCurrentTag,
			switchedToMaster: isCurrentTag
		};
	} catch (error) {
		logFn.error(`Error deleting tag: ${error.message}`);
		throw error;
	}
}

/**
 * Enhance existing tags with metadata if they don't have it
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} rawData - The raw tagged data
 * @param {Object} context - Context object
 * @returns {Promise<boolean>} True if any tags were enhanced
 */
async function enhanceTagsWithMetadata(tasksPath, rawData, context = {}) {
	let enhanced = false;

	try {
		// Get file stats for creation date fallback
		let fileCreatedDate;
		try {
			const stats = fs.statSync(tasksPath);
			fileCreatedDate =
				stats.birthtime < stats.mtime ? stats.birthtime : stats.mtime;
		} catch (error) {
			fileCreatedDate = new Date();
		}

		for (const [tagName, tagData] of Object.entries(rawData)) {
			// Skip non-tag properties
			if (
				tagName === 'tasks' ||
				tagName === 'tag' ||
				tagName === '_rawTaggedData' ||
				!tagData ||
				typeof tagData !== 'object' ||
				!Array.isArray(tagData.tasks)
			) {
				continue;
			}

			// Check if tag needs metadata enhancement
			if (!tagData.metadata) {
				tagData.metadata = {};
				enhanced = true;
			}

			// Add missing metadata fields
			if (!tagData.metadata.created) {
				tagData.metadata.created = fileCreatedDate.toISOString();
				enhanced = true;
			}

			if (!tagData.metadata.description) {
				if (tagName === 'master') {
					tagData.metadata.description = 'Tasks live here by default';
				} else {
					tagData.metadata.description = `Tag created on ${new Date(tagData.metadata.created).toLocaleDateString()}`;
				}
				enhanced = true;
			}

			// Add updated field if missing (set to created date initially)
			if (!tagData.metadata.updated) {
				tagData.metadata.updated = tagData.metadata.created;
				enhanced = true;
			}
		}

		// If we enhanced any tags, write the data back
		if (enhanced) {
			// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
			const cleanData = {};
			for (const [key, value] of Object.entries(rawData)) {
				if (key !== '_rawTaggedData') {
					cleanData[key] = value;
				}
			}
			writeJSON(tasksPath, cleanData, context.projectRoot);
		}
	} catch (error) {
		// Don't throw - just log and continue
		const logFn = context.mcpLog || {
			warn: (...args) => log('warn', ...args)
		};
		logFn.warn(`Could not enhance tag metadata: ${error.message}`);
	}

	return enhanced;
}

/**
 * List all available tags with metadata
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} options - Options object
 * @param {boolean} [options.showTaskCounts=true] - Whether to show task counts
 * @param {boolean} [options.showMetadata=false] - Whether to show metadata
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with tags list
 */
async function tags(
	tasksPath,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { showTaskCounts = true, showMetadata = false } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		logFn.info('Listing available tags');

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Get current tag
		const currentTag = getCurrentTag(projectRoot);

		// Use raw tagged data if available, otherwise use the data directly
		const rawData = data._rawTaggedData || data;

		// Enhance existing tags with metadata if they don't have it
		await enhanceTagsWithMetadata(tasksPath, rawData, context);

		// Extract all tags
		const tagList = [];
		for (const [tagName, tagData] of Object.entries(rawData)) {
			// Skip non-tag properties (like legacy 'tasks' array, 'tag', '_rawTaggedData')
			if (
				tagName === 'tasks' ||
				tagName === 'tag' ||
				tagName === '_rawTaggedData' ||
				!tagData ||
				typeof tagData !== 'object' ||
				!Array.isArray(tagData.tasks)
			) {
				continue;
			}

			const tasks = tagData.tasks || [];
			const metadata = tagData.metadata || {};

			tagList.push({
				name: tagName,
				isCurrent: tagName === currentTag,
				completedTasks: tasks.filter(
					(t) => t.status === 'done' || t.status === 'completed'
				).length,
				tasks: tasks || [],
				created: metadata.created || 'Unknown',
				description: metadata.description || 'No description'
			});
		}

		// Sort tags: current tag first, then alphabetically
		tagList.sort((a, b) => {
			if (a.isCurrent) return -1;
			if (b.isCurrent) return 1;
			return a.name.localeCompare(b.name);
		});

		logFn.success(`Found ${tagList.length} tags`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				tags: tagList,
				currentTag,
				totalTags: tagList.length
			};
		}

		// For text output, display formatted table
		if (outputFormat === 'text') {
			if (tagList.length === 0) {
				console.log(
					boxen(chalk.yellow('No tags found'), {
						padding: 1,
						borderColor: 'yellow',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					})
				);
				return { tags: [], currentTag, totalTags: 0 };
			}

			// Create table headers based on options
			const headers = [chalk.cyan.bold('Tag Name')];
			if (showTaskCounts) {
				headers.push(chalk.cyan.bold('Tasks'));
				headers.push(chalk.cyan.bold('Completed'));
			}
			if (showMetadata) {
				headers.push(chalk.cyan.bold('Created'));
				headers.push(chalk.cyan.bold('Description'));
			}

			const table = new Table({
				head: headers,
				colWidths: showMetadata ? [20, 10, 12, 15, 50] : [25, 10, 12]
			});

			// Add rows
			tagList.forEach((tag) => {
				const row = [];

				// Tag name with current indicator
				const tagDisplay = tag.isCurrent
					? `${chalk.green('●')} ${chalk.green.bold(tag.name)} ${chalk.gray('(current)')}`
					: `  ${tag.name}`;
				row.push(tagDisplay);

				if (showTaskCounts) {
					row.push(chalk.white(tag.tasks.length.toString()));
					row.push(chalk.green(tag.completedTasks.toString()));
				}

				if (showMetadata) {
					const createdDate =
						tag.created !== 'Unknown'
							? new Date(tag.created).toLocaleDateString()
							: 'Unknown';
					row.push(chalk.gray(createdDate));
					row.push(chalk.gray(truncate(tag.description, 50)));
				}

				table.push(row);
			});

			// console.log(
			// 	boxen(
			// 		chalk.white.bold('Available Tags') +
			// 			`\n\nCurrent Tag: ${chalk.green.bold(currentTag)}`,
			// 		{
			// 			padding: { top: 0, bottom: 1, left: 1, right: 1 },
			// 			borderColor: 'blue',
			// 			borderStyle: 'round',
			// 			margin: { top: 1, bottom: 0 }
			// 		}
			// 	)
			// );

			console.log(table.toString());
		}

		return {
			tags: tagList,
			currentTag,
			totalTags: tagList.length
		};
	} catch (error) {
		logFn.error(`Error listing tags: ${error.message}`);
		throw error;
	}
}

/**
 * Switch to a different tag context
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} tagName - Name of the tag to switch to
 * @param {Object} options - Options object
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with switch details
 */
async function useTag(
	tasksPath,
	tagName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate tag name
		if (!tagName || typeof tagName !== 'string') {
			throw new Error('Tag name is required and must be a string');
		}

		logFn.info(`Switching to tag: ${tagName}`);

		// Read current tasks data to verify tag exists
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data to check if tag exists
		const rawData = data._rawTaggedData || data;

		// Check if tag exists
		if (!rawData[tagName]) {
			throw new Error(`Tag "${tagName}" does not exist`);
		}

		// Get current tag
		const previousTag = getCurrentTag(projectRoot);

		// Switch to the new tag
		await switchCurrentTag(projectRoot, tagName);

		// Get task count for the new tag - read tasks specifically for this tag
		const tagData = readJSON(tasksPath, projectRoot, tagName);
		const tasks = tagData ? tagData.tasks || [] : [];
		const taskCount = tasks.length;

		// Find the next task to work on in this tag
		const nextTask = findNextTask(tasks);

		logFn.success(`Successfully switched to tag "${tagName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				previousTag,
				currentTag: tagName,
				switched: true,
				taskCount,
				nextTask
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			let nextTaskInfo = '';
			if (nextTask) {
				nextTaskInfo = `\nNext Task: ${chalk.cyan(`#${nextTask.id}`)} - ${chalk.white(nextTask.title)}`;
			} else {
				nextTaskInfo = `\nNext Task: ${chalk.gray('No eligible tasks available')}`;
			}

			console.log(
				boxen(
					chalk.green.bold('✓ Tag Switched Successfully') +
						`\n\nPrevious Tag: ${chalk.cyan(previousTag)}` +
						`\nCurrent Tag: ${chalk.green.bold(tagName)}` +
						`\nAvailable Tasks: ${chalk.yellow(taskCount)}` +
						nextTaskInfo,
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			previousTag,
			currentTag: tagName,
			switched: true,
			taskCount,
			nextTask
		};
	} catch (error) {
		logFn.error(`Error switching tag: ${error.message}`);
		throw error;
	}
}

/**
 * Rename an existing tag
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} oldName - Current name of the tag
 * @param {string} newName - New name for the tag
 * @param {Object} options - Options object
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with rename details
 */
async function renameTag(
	tasksPath,
	oldName,
	newName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate parameters
		if (!oldName || typeof oldName !== 'string') {
			throw new Error('Old tag name is required and must be a string');
		}
		if (!newName || typeof newName !== 'string') {
			throw new Error('New tag name is required and must be a string');
		}

		// Validate new tag name format
		if (!/^[a-zA-Z0-9_-]+$/.test(newName)) {
			throw new Error(
				'New tag name can only contain letters, numbers, hyphens, and underscores'
			);
		}

		// Prevent renaming master tag
		if (oldName === 'master') {
			throw new Error('Cannot rename the "master" tag');
		}

		// Reserved tag names
		const reservedNames = ['master', 'main', 'default'];
		if (reservedNames.includes(newName.toLowerCase())) {
			throw new Error(`"${newName}" is a reserved tag name`);
		}

		logFn.info(`Renaming tag from "${oldName}" to "${newName}"`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations
		const rawData = data._rawTaggedData || data;

		// Check if old tag exists
		if (!rawData[oldName]) {
			throw new Error(`Tag "${oldName}" does not exist`);
		}

		// Check if new tag name already exists
		if (rawData[newName]) {
			throw new Error(`Tag "${newName}" already exists`);
		}

		// Get current tag to check if we're renaming the active tag
		const currentTag = getCurrentTag(projectRoot);
		const isCurrentTag = currentTag === oldName;

		// Rename the tag by copying data and deleting old
		rawData[newName] = { ...rawData[oldName] };

		// Update metadata if it exists
		if (rawData[newName].metadata) {
			rawData[newName].metadata.renamed = {
				from: oldName,
				date: new Date().toISOString()
			};
		}

		delete rawData[oldName];

		// If we're renaming the current tag, update the current tag reference
		if (isCurrentTag) {
			await switchCurrentTag(projectRoot, newName);
			logFn.info(`Updated current tag reference to "${newName}"`);
		}

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		// Get task count
		const tasks = getTasksForTag(rawData, newName);
		const taskCount = tasks.length;

		logFn.success(`Successfully renamed tag from "${oldName}" to "${newName}"`);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				oldName,
				newName,
				renamed: true,
				taskCount,
				wasCurrentTag: isCurrentTag,
				isCurrentTag: isCurrentTag
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.green.bold('✓ Tag Renamed Successfully') +
						`\n\nOld Name: ${chalk.cyan(oldName)}` +
						`\nNew Name: ${chalk.green.bold(newName)}` +
						`\nTasks: ${chalk.yellow(taskCount)}` +
						(isCurrentTag ? `\n${chalk.green('✓ Current tag updated')}` : ''),
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			oldName,
			newName,
			renamed: true,
			taskCount,
			wasCurrentTag: isCurrentTag,
			isCurrentTag: isCurrentTag
		};
	} catch (error) {
		logFn.error(`Error renaming tag: ${error.message}`);
		throw error;
	}
}

/**
 * Copy an existing tag to create a new tag with the same tasks
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} sourceName - Name of the source tag to copy from
 * @param {string} targetName - Name of the new tag to create
 * @param {Object} options - Options object
 * @param {string} [options.description] - Optional description for the new tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with copy details
 */
async function copyTag(
	tasksPath,
	sourceName,
	targetName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { description } = options;

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate parameters
		if (!sourceName || typeof sourceName !== 'string') {
			throw new Error('Source tag name is required and must be a string');
		}
		if (!targetName || typeof targetName !== 'string') {
			throw new Error('Target tag name is required and must be a string');
		}

		// Validate target tag name format
		if (!/^[a-zA-Z0-9_-]+$/.test(targetName)) {
			throw new Error(
				'Target tag name can only contain letters, numbers, hyphens, and underscores'
			);
		}

		// Reserved tag names
		const reservedNames = ['master', 'main', 'default'];
		if (reservedNames.includes(targetName.toLowerCase())) {
			throw new Error(`"${targetName}" is a reserved tag name`);
		}

		logFn.info(`Copying tag from "${sourceName}" to "${targetName}"`);

		// Read current tasks data
		const data = readJSON(tasksPath, projectRoot);
		if (!data) {
			throw new Error(`Could not read tasks file at ${tasksPath}`);
		}

		// Use raw tagged data for tag operations
		const rawData = data._rawTaggedData || data;

		// Check if source tag exists
		if (!rawData[sourceName]) {
			throw new Error(`Source tag "${sourceName}" does not exist`);
		}

		// Check if target tag already exists
		if (rawData[targetName]) {
			throw new Error(`Target tag "${targetName}" already exists`);
		}

		// Get source tasks
		const sourceTasks = getTasksForTag(rawData, sourceName);

		// Create deep copy of the source tag data
		rawData[targetName] = {
			tasks: JSON.parse(JSON.stringify(sourceTasks)), // Deep copy tasks
			metadata: {
				created: new Date().toISOString(),
				updated: new Date().toISOString(),
				description:
					description ||
					`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`,
				copiedFrom: {
					tag: sourceName,
					date: new Date().toISOString()
				}
			}
		};

		// Create clean data for writing (exclude _rawTaggedData to prevent corruption)
		const cleanData = {};
		for (const [key, value] of Object.entries(rawData)) {
			if (key !== '_rawTaggedData') {
				cleanData[key] = value;
			}
		}

		// Write the clean data back to file with proper context to avoid tag corruption
		writeJSON(tasksPath, cleanData, projectRoot);

		logFn.success(
			`Successfully copied tag from "${sourceName}" to "${targetName}"`
		);

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				sourceName,
				targetName,
				copied: true,
				description:
					description ||
					`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`
			};
		}

		// For text output, display success message
		if (outputFormat === 'text') {
			console.log(
				boxen(
					chalk.green.bold('✓ Tag Copied Successfully') +
						`\n\nSource Tag: ${chalk.cyan(sourceName)}` +
						`\nTarget Tag: ${chalk.green.bold(targetName)}` +
						`\nTasks Copied: ${chalk.yellow(sourceTasks.length)}` +
						(description ? `\nDescription: ${chalk.gray(description)}` : ''),
					{
						padding: 1,
						borderColor: 'green',
						borderStyle: 'round',
						margin: { top: 1, bottom: 1 }
					}
				)
			);
		}

		return {
			sourceName,
			targetName,
			copied: true,
			description:
				description ||
				`Copy of "${sourceName}" created on ${new Date().toLocaleDateString()}`
		};
	} catch (error) {
		logFn.error(`Error copying tag: ${error.message}`);
		throw error;
	}
}

/**
 * Helper function to switch the current tag in state.json
 * @param {string} projectRoot - Project root directory
 * @param {string} tagName - Name of the tag to switch to
 * @returns {Promise<void>}
 */
async function switchCurrentTag(projectRoot, tagName) {
	try {
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');

		// Read current state or create default
		let state = {};
		if (fs.existsSync(statePath)) {
			const rawState = fs.readFileSync(statePath, 'utf8');
			state = JSON.parse(rawState);
		}

		// Update current tag and timestamp
		state.currentTag = tagName;
		state.lastSwitched = new Date().toISOString();

		// Ensure other required state properties exist
		if (!state.branchTagMapping) {
			state.branchTagMapping = {};
		}
		if (state.migrationNoticeShown === undefined) {
			state.migrationNoticeShown = false;
		}

		// Write updated state
		fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
	} catch (error) {
		log('warn', `Could not update current tag in state.json: ${error.message}`);
		// Don't throw - this is not critical for tag operations
	}
}

/**
 * Update branch-tag mapping in state.json
 * @param {string} projectRoot - Project root directory
 * @param {string} branchName - Git branch name
 * @param {string} tagName - Tag name to map to
 * @returns {Promise<void>}
 */
async function updateBranchTagMapping(projectRoot, branchName, tagName) {
	try {
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');

		// Read current state or create default
		let state = {};
		if (fs.existsSync(statePath)) {
			const rawState = fs.readFileSync(statePath, 'utf8');
			state = JSON.parse(rawState);
		}

		// Ensure branchTagMapping exists
		if (!state.branchTagMapping) {
			state.branchTagMapping = {};
		}

		// Update the mapping
		state.branchTagMapping[branchName] = tagName;

		// Write updated state
		fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
	} catch (error) {
		log('warn', `Could not update branch-tag mapping: ${error.message}`);
		// Don't throw - this is not critical for tag operations
	}
}

/**
 * Get tag name for a git branch from state.json mapping
 * @param {string} projectRoot - Project root directory
 * @param {string} branchName - Git branch name
 * @returns {Promise<string|null>} Mapped tag name or null if not found
 */
async function getTagForBranch(projectRoot, branchName) {
	try {
		const statePath = path.join(projectRoot, '.taskmaster', 'state.json');

		if (!fs.existsSync(statePath)) {
			return null;
		}

		const rawState = fs.readFileSync(statePath, 'utf8');
		const state = JSON.parse(rawState);

		return state.branchTagMapping?.[branchName] || null;
	} catch (error) {
		return null;
	}
}

/**
 * Create a tag from a git branch name
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {string} branchName - Git branch name to create tag from
 * @param {Object} options - Options object
 * @param {boolean} [options.copyFromCurrent] - Copy tasks from current tag
 * @param {string} [options.copyFromTag] - Copy tasks from specific tag
 * @param {string} [options.description] - Custom description for the tag
 * @param {boolean} [options.autoSwitch] - Automatically switch to the new tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with creation details
 */
async function createTagFromBranch(
	tasksPath,
	branchName,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { copyFromCurrent, copyFromTag, description, autoSwitch } = options;

	// Import git utilities
	const { sanitizeBranchNameForTag, isValidBranchForTag } = await import(
		'../utils/git-utils.js'
	);

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Validate branch name
		if (!branchName || typeof branchName !== 'string') {
			throw new Error('Branch name is required and must be a string');
		}

		// Check if branch name is valid for tag creation
		if (!isValidBranchForTag(branchName)) {
			throw new Error(
				`Branch "${branchName}" cannot be converted to a valid tag name`
			);
		}

		// Sanitize branch name to create tag name
		const tagName = sanitizeBranchNameForTag(branchName);

		logFn.info(`Creating tag "${tagName}" from git branch "${branchName}"`);

		// Create the tag using existing createTag function
		const createResult = await createTag(
			tasksPath,
			tagName,
			{
				copyFromCurrent,
				copyFromTag,
				description:
					description || `Tag created from git branch "${branchName}"`
			},
			context,
			outputFormat
		);

		// Update branch-tag mapping
		await updateBranchTagMapping(projectRoot, branchName, tagName);
		logFn.info(`Updated branch-tag mapping: ${branchName} -> ${tagName}`);

		// Auto-switch to the new tag if requested
		if (autoSwitch) {
			await switchCurrentTag(projectRoot, tagName);
			logFn.info(`Automatically switched to tag "${tagName}"`);
		}

		// For JSON output, return structured data
		if (outputFormat === 'json') {
			return {
				...createResult,
				branchName,
				tagName,
				mappingUpdated: true,
				autoSwitched: autoSwitch || false
			};
		}

		// For text output, the createTag function already handles display
		return {
			branchName,
			tagName,
			created: true,
			mappingUpdated: true,
			autoSwitched: autoSwitch || false
		};
	} catch (error) {
		logFn.error(`Error creating tag from branch: ${error.message}`);
		throw error;
	}
}

/**
 * Automatically switch tag based on current git branch
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} options - Options object
 * @param {boolean} [options.createIfMissing] - Create tag if it doesn't exist
 * @param {boolean} [options.copyFromCurrent] - Copy tasks when creating new tag
 * @param {Object} context - Context object containing session and projectRoot
 * @param {string} [context.projectRoot] - Project root path
 * @param {Object} [context.mcpLog] - MCP logger object (optional)
 * @param {string} outputFormat - Output format (text or json)
 * @returns {Promise<Object>} Result object with switch details
 */
async function autoSwitchTagForBranch(
	tasksPath,
	options = {},
	context = {},
	outputFormat = 'text'
) {
	const { mcpLog, projectRoot } = context;
	const { createIfMissing, copyFromCurrent } = options;

	// Import git utilities
	const {
		getCurrentBranch,
		isGitRepository,
		sanitizeBranchNameForTag,
		isValidBranchForTag
	} = await import('../utils/git-utils.js');

	// Create a consistent logFn object regardless of context
	const logFn = mcpLog || {
		info: (...args) => log('info', ...args),
		warn: (...args) => log('warn', ...args),
		error: (...args) => log('error', ...args),
		debug: (...args) => log('debug', ...args),
		success: (...args) => log('success', ...args)
	};

	try {
		// Check if we're in a git repository
		if (!(await isGitRepository(projectRoot))) {
			logFn.warn('Not in a git repository, cannot auto-switch tags');
			return { switched: false, reason: 'not_git_repo' };
		}

		// Get current git branch
		const currentBranch = await getCurrentBranch(projectRoot);
		if (!currentBranch) {
			logFn.warn('Could not determine current git branch');
			return { switched: false, reason: 'no_current_branch' };
		}

		logFn.info(`Current git branch: ${currentBranch}`);

		// Check if branch is valid for tag creation
		if (!isValidBranchForTag(currentBranch)) {
			logFn.info(`Branch "${currentBranch}" is not suitable for tag creation`);
			return {
				switched: false,
				reason: 'invalid_branch_for_tag',
				branchName: currentBranch
			};
		}

		// Check if there's already a mapping for this branch
		let tagName = await getTagForBranch(projectRoot, currentBranch);

		if (!tagName) {
			// No mapping exists, create tag name from branch
			tagName = sanitizeBranchNameForTag(currentBranch);
		}

		// Check if tag exists
		const data = readJSON(tasksPath, projectRoot);
		const rawData = data._rawTaggedData || data;
		const tagExists = rawData[tagName];

		if (!tagExists && createIfMissing) {
			// Create the tag from branch
			logFn.info(`Creating new tag "${tagName}" for branch "${currentBranch}"`);

			const createResult = await createTagFromBranch(
				tasksPath,
				currentBranch,
				{
					copyFromCurrent,
					autoSwitch: true
				},
				context,
				outputFormat
			);

			return {
				switched: true,
				created: true,
				branchName: currentBranch,
				tagName,
				...createResult
			};
		} else if (tagExists) {
			// Tag exists, switch to it
			logFn.info(
				`Switching to existing tag "${tagName}" for branch "${currentBranch}"`
			);

			const switchResult = await useTag(
				tasksPath,
				tagName,
				{},
				context,
				outputFormat
			);

			// Update mapping if it didn't exist
			if (!(await getTagForBranch(projectRoot, currentBranch))) {
				await updateBranchTagMapping(projectRoot, currentBranch, tagName);
			}

			return {
				switched: true,
				created: false,
				branchName: currentBranch,
				tagName,
				...switchResult
			};
		} else {
			// Tag doesn't exist and createIfMissing is false
			logFn.warn(
				`Tag "${tagName}" for branch "${currentBranch}" does not exist`
			);
			return {
				switched: false,
				reason: 'tag_not_found',
				branchName: currentBranch,
				tagName
			};
		}
	} catch (error) {
		logFn.error(`Error in auto-switch tag for branch: ${error.message}`);
		throw error;
	}
}

/**
 * Check git workflow configuration and perform auto-switch if enabled
 * @param {string} projectRoot - Project root directory
 * @param {string} tasksPath - Path to the tasks.json file
 * @param {Object} context - Context object
 * @returns {Promise<Object|null>} Switch result or null if not enabled
 */
async function checkAndAutoSwitchTag(projectRoot, tasksPath, context = {}) {
	try {
		// Read configuration
		const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
		if (!fs.existsSync(configPath)) {
			return null;
		}

		const rawConfig = fs.readFileSync(configPath, 'utf8');
		const config = JSON.parse(rawConfig);

		// Git workflow has been removed - return null to disable auto-switching
		return null;

		// Perform auto-switch
		return await autoSwitchTagForBranch(
			tasksPath,
			{ createIfMissing: true, copyFromCurrent: false },
			context,
			'json'
		);
	} catch (error) {
		// Silently fail - this is not critical
		return null;
	}
}

// Export all tag management functions
export {
	createTag,
	deleteTag,
	tags,
	useTag,
	renameTag,
	copyTag,
	switchCurrentTag,
	updateBranchTagMapping,
	getTagForBranch,
	createTagFromBranch,
	autoSwitchTagForBranch,
	checkAndAutoSwitchTag
};

```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/parse-prd.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Tests for the parse-prd.js module
 */
import { jest } from '@jest/globals';

// Mock the dependencies before importing the module under test
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
	readJSON: jest.fn(),
	writeJSON: jest.fn(),
	log: jest.fn(),
	CONFIG: {
		model: 'mock-claude-model',
		maxTokens: 4000,
		temperature: 0.7,
		debug: false
	},
	sanitizePrompt: jest.fn((prompt) => prompt),
	truncate: jest.fn((text) => text),
	isSilentMode: jest.fn(() => false),
	enableSilentMode: jest.fn(),
	disableSilentMode: jest.fn(),
	findTaskById: jest.fn(),
	ensureTagMetadata: jest.fn((tagObj) => tagObj),
	getCurrentTag: jest.fn(() => 'master'),
	promptYesNo: jest.fn()
}));

jest.unstable_mockModule(
	'../../../../../scripts/modules/ai-services-unified.js',
	() => ({
		generateObjectService: jest.fn().mockResolvedValue({
			tasks: [
				{
					id: 1,
					title: 'Test Task 1',
					priority: 'high',
					description: 'Test description 1',
					status: 'pending',
					dependencies: []
				},
				{
					id: 2,
					title: 'Test Task 2',
					priority: 'medium',
					description: 'Test description 2',
					status: 'pending',
					dependencies: []
				},
				{
					id: 3,
					title: 'Test Task 3',
					priority: 'low',
					description: 'Test description 3',
					status: 'pending',
					dependencies: []
				}
			]
		}),
		streamObjectService: jest.fn().mockImplementation(async () => {
			// Return an object with partialObjectStream as a getter that returns the async generator
			return {
				mainResult: {
					get partialObjectStream() {
						return (async function* () {
							yield { tasks: [] };
							yield {
								tasks: [
									{
										id: 1,
										title: 'Test Task 1',
										priority: 'high',
										description: 'Test description 1',
										status: 'pending',
										dependencies: []
									}
								]
							};
							yield {
								tasks: [
									{
										id: 1,
										title: 'Test Task 1',
										priority: 'high',
										description: 'Test description 1',
										status: 'pending',
										dependencies: []
									},
									{
										id: 2,
										title: 'Test Task 2',
										priority: 'medium',
										description: 'Test description 2',
										status: 'pending',
										dependencies: []
									}
								]
							};
							yield {
								tasks: [
									{
										id: 1,
										title: 'Test Task 1',
										priority: 'high',
										description: 'Test description 1',
										status: 'pending',
										dependencies: []
									},
									{
										id: 2,
										title: 'Test Task 2',
										priority: 'medium',
										description: 'Test description 2',
										status: 'pending',
										dependencies: []
									},
									{
										id: 3,
										title: 'Test Task 3',
										priority: 'low',
										description: 'Test description 3',
										status: 'pending',
										dependencies: []
									}
								]
							};
						})();
					},
					usage: Promise.resolve({
						promptTokens: 100,
						completionTokens: 200,
						totalTokens: 300
					}),
					object: Promise.resolve({
						tasks: [
							{
								id: 1,
								title: 'Test Task 1',
								priority: 'high',
								description: 'Test description 1',
								status: 'pending',
								dependencies: []
							},
							{
								id: 2,
								title: 'Test Task 2',
								priority: 'medium',
								description: 'Test description 2',
								status: 'pending',
								dependencies: []
							},
							{
								id: 3,
								title: 'Test Task 3',
								priority: 'low',
								description: 'Test description 3',
								status: 'pending',
								dependencies: []
							}
						]
					})
				},
				providerName: 'anthropic',
				modelId: 'claude-3-5-sonnet-20241022',
				telemetryData: {}
			};
		})
	})
);

jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
	getStatusWithColor: jest.fn((status) => status),
	startLoadingIndicator: jest.fn(),
	stopLoadingIndicator: jest.fn(),
	displayAiUsageSummary: jest.fn()
}));

jest.unstable_mockModule(
	'../../../../../scripts/modules/config-manager.js',
	() => ({
		getDebugFlag: jest.fn(() => false),
		getMainModelId: jest.fn(() => 'claude-3-5-sonnet'),
		getResearchModelId: jest.fn(() => 'claude-3-5-sonnet'),
		getParametersForRole: jest.fn(() => ({
			provider: 'anthropic',
			modelId: 'claude-3-5-sonnet'
		})),
		getDefaultNumTasks: jest.fn(() => 10),
		getDefaultPriority: jest.fn(() => 'medium'),
		getMainProvider: jest.fn(() => 'openai'),
		getResearchProvider: jest.fn(() => 'perplexity'),
		hasCodebaseAnalysis: jest.fn(() => false)
	})
);

jest.unstable_mockModule(
	'../../../../../scripts/modules/task-manager/generate-task-files.js',
	() => ({
		default: jest.fn().mockResolvedValue()
	})
);

jest.unstable_mockModule(
	'../../../../../scripts/modules/task-manager/models.js',
	() => ({
		getModelConfiguration: jest.fn(() => ({
			model: 'mock-model',
			maxTokens: 4000,
			temperature: 0.7
		}))
	})
);

jest.unstable_mockModule(
	'../../../../../scripts/modules/prompt-manager.js',
	() => ({
		getPromptManager: jest.fn().mockReturnValue({
			loadPrompt: jest.fn().mockImplementation((templateName, params) => {
				// Create dynamic mock prompts based on the parameters
				const { numTasks } = params || {};
				let numTasksText = '';

				if (numTasks > 0) {
					numTasksText = `approximately ${numTasks}`;
				} else {
					numTasksText = 'an appropriate number of';
				}

				return Promise.resolve({
					systemPrompt: 'Mocked system prompt for parse-prd',
					userPrompt: `Generate ${numTasksText} top-level development tasks from the PRD content.`
				});
			})
		})
	})
);

// Mock fs module
jest.unstable_mockModule('fs', () => ({
	default: {
		readFileSync: jest.fn(),
		existsSync: jest.fn(),
		mkdirSync: jest.fn(),
		writeFileSync: jest.fn(),
		promises: {
			readFile: jest.fn()
		}
	},
	readFileSync: jest.fn(),
	existsSync: jest.fn(),
	mkdirSync: jest.fn(),
	writeFileSync: jest.fn()
}));

// Mock path module
jest.unstable_mockModule('path', () => ({
	default: {
		dirname: jest.fn(),
		join: jest.fn((dir, file) => `${dir}/${file}`)
	},
	dirname: jest.fn(),
	join: jest.fn((dir, file) => `${dir}/${file}`)
}));

// Mock JSONParser for streaming tests
jest.unstable_mockModule('@streamparser/json', () => ({
	JSONParser: jest.fn().mockImplementation(() => ({
		onValue: jest.fn(),
		onError: jest.fn(),
		write: jest.fn(),
		end: jest.fn()
	}))
}));

// Mock stream-parser functions
jest.unstable_mockModule('../../../../../src/utils/stream-parser.js', () => {
	// Define mock StreamingError class
	class StreamingError extends Error {
		constructor(message, code) {
			super(message);
			this.name = 'StreamingError';
			this.code = code;
		}
	}

	// Define mock error codes
	const STREAMING_ERROR_CODES = {
		NOT_ASYNC_ITERABLE: 'STREAMING_NOT_SUPPORTED',
		STREAM_PROCESSING_FAILED: 'STREAM_PROCESSING_FAILED',
		STREAM_NOT_ITERABLE: 'STREAM_NOT_ITERABLE'
	};

	return {
		parseStream: jest.fn().mockResolvedValue({
			items: [{ id: 1, title: 'Test Task', priority: 'high' }],
			accumulatedText:
				'{"tasks":[{"id":1,"title":"Test Task","priority":"high"}]}',
			estimatedTokens: 50,
			usedFallback: false
		}),
		createTaskProgressCallback: jest.fn().mockReturnValue(jest.fn()),
		createConsoleProgressCallback: jest.fn().mockReturnValue(jest.fn()),
		StreamingError,
		STREAMING_ERROR_CODES
	};
});

// Mock progress tracker to prevent intervals
jest.unstable_mockModule(
	'../../../../../src/progress/parse-prd-tracker.js',
	() => ({
		createParsePrdTracker: jest.fn().mockReturnValue({
			start: jest.fn(),
			stop: jest.fn(),
			cleanup: jest.fn(),
			updateTokens: jest.fn(),
			addTaskLine: jest.fn(),
			trackTaskPriority: jest.fn(),
			getSummary: jest.fn().mockReturnValue({
				taskPriorities: { high: 0, medium: 0, low: 0 },
				elapsedTime: 0,
				actionVerb: 'generated'
			})
		})
	})
);

// Mock UI functions to prevent any display delays
jest.unstable_mockModule('../../../../../src/ui/parse-prd.js', () => ({
	displayParsePrdStart: jest.fn(),
	displayParsePrdSummary: jest.fn()
}));

// Import the mocked modules
const { readJSON, promptYesNo } = await import(
	'../../../../../scripts/modules/utils.js'
);

const { generateObjectService, streamObjectService } = await import(
	'../../../../../scripts/modules/ai-services-unified.js'
);

const { JSONParser } = await import('@streamparser/json');

const { parseStream, StreamingError, STREAMING_ERROR_CODES } = await import(
	'../../../../../src/utils/stream-parser.js'
);

const { createParsePrdTracker } = await import(
	'../../../../../src/progress/parse-prd-tracker.js'
);

const { displayParsePrdStart, displayParsePrdSummary } = await import(
	'../../../../../src/ui/parse-prd.js'
);

// Note: getDefaultNumTasks validation happens at CLI/MCP level, not in the main parse-prd module
const generateTaskFiles = (
	await import(
		'../../../../../scripts/modules/task-manager/generate-task-files.js'
	)
).default;

const fs = await import('fs');
const path = await import('path');

// Import the module under test
const { default: parsePRD } = await import(
	'../../../../../scripts/modules/task-manager/parse-prd/parse-prd.js'
);

// Sample data for tests (from main test file)
const sampleClaudeResponse = {
	tasks: [
		{
			id: 1,
			title: 'Setup Project Structure',
			description: 'Initialize the project with necessary files and folders',
			status: 'pending',
			dependencies: [],
			priority: 'high'
		},
		{
			id: 2,
			title: 'Implement Core Features',
			description: 'Build the main functionality',
			status: 'pending',
			dependencies: [1],
			priority: 'high'
		}
	],
	metadata: {
		projectName: 'Test Project',
		totalTasks: 2,
		sourceFile: 'path/to/prd.txt',
		generatedAt: expect.any(String)
	}
};

describe('parsePRD', () => {
	// Mock the sample PRD content
	const samplePRDContent = '# Sample PRD for Testing';

	// Mock existing tasks for append test - TAGGED FORMAT
	const existingTasksData = {
		master: {
			tasks: [
				{ id: 1, title: 'Existing Task 1', status: 'done' },
				{ id: 2, title: 'Existing Task 2', status: 'pending' }
			]
		}
	};

	// Mock new tasks with continuing IDs for append test
	const newTasksClaudeResponse = {
		tasks: [
			{ id: 3, title: 'New Task 3' },
			{ id: 4, title: 'New Task 4' }
		],
		metadata: {
			projectName: 'Test Project',
			totalTasks: 2,
			sourceFile: 'path/to/prd.txt',
			generatedAt: expect.any(String)
		}
	};

	beforeEach(() => {
		// Reset all mocks
		jest.clearAllMocks();

		// Set up mocks for fs, path and other modules
		fs.default.readFileSync.mockReturnValue(samplePRDContent);
		fs.default.promises.readFile.mockResolvedValue(samplePRDContent);
		fs.default.existsSync.mockReturnValue(true);
		path.default.dirname.mockReturnValue('tasks');
		generateObjectService.mockResolvedValue({
			mainResult: sampleClaudeResponse,
			telemetryData: {}
		});
		// Reset streamObjectService mock to working implementation
		streamObjectService.mockImplementation(async () => {
			return {
				mainResult: {
					get partialObjectStream() {
						return (async function* () {
							yield { tasks: [] };
							yield { tasks: [sampleClaudeResponse.tasks[0]] };
							yield {
								tasks: [
									sampleClaudeResponse.tasks[0],
									sampleClaudeResponse.tasks[1]
								]
							};
							yield sampleClaudeResponse;
						})();
					},
					usage: Promise.resolve({
						promptTokens: 100,
						completionTokens: 200,
						totalTokens: 300
					}),
					object: Promise.resolve(sampleClaudeResponse)
				},
				providerName: 'anthropic',
				modelId: 'claude-3-5-sonnet-20241022',
				telemetryData: {}
			};
		});
		// generateTaskFiles.mockResolvedValue(undefined);
		promptYesNo.mockResolvedValue(true); // Default to "yes" for confirmation

		// Mock process.exit to prevent actual exit and throw error instead for CLI tests
		jest.spyOn(process, 'exit').mockImplementation((code) => {
			throw new Error(`process.exit was called with code ${code}`);
		});

		// Mock console.error to prevent output
		jest.spyOn(console, 'error').mockImplementation(() => {});
		jest.spyOn(console, 'log').mockImplementation(() => {});
	});

	afterEach(() => {
		// Restore all mocks after each test
		jest.restoreAllMocks();
	});

	test('should parse a PRD file and generate tasks', async () => {
		// Setup mocks to simulate normal conditions (no existing output file)
		fs.default.existsSync.mockImplementation((p) => {
			if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
			if (p === 'tasks') return true; // Directory exists
			return false;
		});

		// Also mock the other fs methods that might be called
		fs.default.readFileSync.mockReturnValue(samplePRDContent);
		fs.default.promises.readFile.mockResolvedValue(samplePRDContent);

		// Call the function with mcpLog to force non-streaming mode
		const result = await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
			tag: 'master',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// Verify fs.readFileSync was called with the correct arguments
		expect(fs.default.readFileSync).toHaveBeenCalledWith(
			'path/to/prd.txt',
			'utf8'
		);

		// Verify generateObjectService was called
		expect(generateObjectService).toHaveBeenCalled();

		// Verify directory check
		expect(fs.default.existsSync).toHaveBeenCalledWith('tasks');

		// Verify fs.writeFileSync was called with the correct arguments in tagged format
		expect(fs.default.writeFileSync).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.stringContaining('"master"')
		);

		// Verify result
		expect(result).toEqual({
			success: true,
			tasksPath: 'tasks/tasks.json',
			telemetryData: {}
		});

		// Verify that the written data contains 2 tasks from sampleClaudeResponse in the correct tag
		const writtenDataString = fs.default.writeFileSync.mock.calls[0][1];
		const writtenData = JSON.parse(writtenDataString);
		expect(writtenData.master.tasks.length).toBe(2);
	});

	test('should create the tasks directory if it does not exist', async () => {
		// Mock existsSync to return false specifically for the directory check
		// but true for the output file check (so we don't trigger confirmation path)
		fs.default.existsSync.mockImplementation((p) => {
			if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
			if (p === 'tasks') return false; // Directory doesn't exist
			return true; // Default for other paths
		});

		// Call the function with mcpLog to force non-streaming mode
		await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
			tag: 'master',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// Verify mkdir was called
		expect(fs.default.mkdirSync).toHaveBeenCalledWith('tasks', {
			recursive: true
		});
	});

	test('should handle errors in the PRD parsing process', async () => {
		// Mock an error in generateObjectService
		const testError = new Error('Test error in AI API call');
		generateObjectService.mockRejectedValueOnce(testError);

		// Setup mocks to simulate normal file conditions (no existing file)
		fs.default.existsSync.mockImplementation((p) => {
			if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
			if (p === 'tasks') return true; // Directory exists
			return false;
		});

		// Call the function with mcpLog to make it think it's in MCP mode (which throws instead of process.exit)
		await expect(
			parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			})
		).rejects.toThrow('Test error in AI API call');
	});

	test('should generate individual task files after creating tasks.json', async () => {
		// Setup mocks to simulate normal conditions (no existing output file)
		fs.default.existsSync.mockImplementation((p) => {
			if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
			if (p === 'tasks') return true; // Directory exists
			return false;
		});

		// Call the function with mcpLog to force non-streaming mode
		await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
			tag: 'master',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// generateTaskFiles is currently commented out in parse-prd.js
	});

	test('should overwrite tasks.json when force flag is true', async () => {
		// Setup mocks to simulate tasks.json already exists
		fs.default.existsSync.mockImplementation((p) => {
			if (p === 'tasks/tasks.json') return true; // Output file exists
			if (p === 'tasks') return true; // Directory exists
			return false;
		});

		// Call the function with force=true to allow overwrite and mcpLog to force non-streaming mode
		await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
			force: true,
			tag: 'master',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// Verify prompt was NOT called (confirmation happens at CLI level, not in core function)
		expect(promptYesNo).not.toHaveBeenCalled();

		// Verify the file was written after force overwrite
		expect(fs.default.writeFileSync).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.stringContaining('"master"')
		);
	});

	test('should throw error when tasks in tag exist without force flag in MCP mode', async () => {
		// Setup mocks to simulate tasks.json already exists with tasks in the target tag
		fs.default.existsSync.mockReturnValue(true);
		// Mock readFileSync to return data with tasks in the 'master' tag
		fs.default.readFileSync.mockReturnValueOnce(
			JSON.stringify(existingTasksData)
		);

		// Call the function with mcpLog to make it think it's in MCP mode (which throws instead of process.exit)
		await expect(
			parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			})
		).rejects.toThrow('already contains');

		// Verify prompt was NOT called
		expect(promptYesNo).not.toHaveBeenCalled();

		// Verify the file was NOT written
		expect(fs.default.writeFileSync).not.toHaveBeenCalled();
	});

	test('should throw error when tasks in tag exist without force flag in CLI mode', async () => {
		// Setup mocks to simulate tasks.json already exists with tasks in the target tag
		fs.default.existsSync.mockReturnValue(true);
		fs.default.readFileSync.mockReturnValueOnce(
			JSON.stringify(existingTasksData)
		);

		// Call the function without mcpLog (CLI mode) and expect it to throw an error
		// In test environment, process.exit is prevented and error is thrown instead
		await expect(
			parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, { tag: 'master' })
		).rejects.toThrow('process.exit was called with code 1');

		// Verify the file was NOT written
		expect(fs.default.writeFileSync).not.toHaveBeenCalled();
	});

	test('should append new tasks when append option is true', async () => {
		// Setup mocks to simulate tasks.json already exists
		fs.default.existsSync.mockReturnValue(true);

		// Mock for reading existing tasks in tagged format
		readJSON.mockReturnValue(existingTasksData);
		// Mock readFileSync to return the raw content for the initial check
		fs.default.readFileSync.mockReturnValueOnce(
			JSON.stringify(existingTasksData)
		);

		// Mock generateObjectService to return new tasks with continuing IDs
		generateObjectService.mockResolvedValueOnce({
			mainResult: { object: newTasksClaudeResponse },
			telemetryData: {}
		});

		// Call the function with append option and mcpLog to force non-streaming mode
		const result = await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 2, {
			tag: 'master',
			append: true,
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// Verify prompt was NOT called (no confirmation needed for append)
		expect(promptYesNo).not.toHaveBeenCalled();

		// Verify the file was written with merged tasks in the correct tag
		expect(fs.default.writeFileSync).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.stringContaining('"master"')
		);

		// Verify the result contains merged tasks
		expect(result).toEqual({
			success: true,
			tasksPath: 'tasks/tasks.json',
			telemetryData: {}
		});

		// Verify that the written data contains 4 tasks (2 existing + 2 new)
		const writtenDataString = fs.default.writeFileSync.mock.calls[0][1];
		const writtenData = JSON.parse(writtenDataString);
		expect(writtenData.master.tasks.length).toBe(4);
	});

	test('should skip prompt and not overwrite when append is true', async () => {
		// Setup mocks to simulate tasks.json already exists
		fs.default.existsSync.mockReturnValue(true);
		fs.default.readFileSync.mockReturnValueOnce(
			JSON.stringify(existingTasksData)
		);

		// Ensure generateObjectService returns proper tasks
		generateObjectService.mockResolvedValue({
			mainResult: {
				tasks: [
					{
						id: 1,
						title: 'Test Task 1',
						priority: 'high',
						description: 'Test description 1',
						status: 'pending',
						dependencies: []
					},
					{
						id: 2,
						title: 'Test Task 2',
						priority: 'medium',
						description: 'Test description 2',
						status: 'pending',
						dependencies: []
					},
					{
						id: 3,
						title: 'Test Task 3',
						priority: 'low',
						description: 'Test description 3',
						status: 'pending',
						dependencies: []
					}
				]
			},
			telemetryData: {}
		});

		// Call the function with append option
		await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
			tag: 'master',
			append: true
		});

		// Verify prompt was NOT called with append flag
		expect(promptYesNo).not.toHaveBeenCalled();
	});

	describe('Streaming vs Non-Streaming Modes', () => {
		test('should use non-streaming when reportProgress function is provided (streaming disabled)', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Mock progress reporting function
			const mockReportProgress = jest.fn(() => Promise.resolve());

			// Mock JSONParser instance
			const mockParser = {
				onValue: jest.fn(),
				onError: jest.fn(),
				write: jest.fn(),
				end: jest.fn()
			};
			JSONParser.mockReturnValue(mockParser);

			// Call the function with reportProgress - with streaming disabled, should use non-streaming
			const result = await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				reportProgress: mockReportProgress
			});

			// With streaming disabled, should use generateObjectService instead
			expect(generateObjectService).toHaveBeenCalled();

			// Verify streamObjectService was NOT called (streaming is disabled)
			expect(streamObjectService).not.toHaveBeenCalled();

			// Verify progress reporting was still called
			expect(mockReportProgress).toHaveBeenCalled();

			// Verify result structure
			expect(result).toEqual({
				success: true,
				tasksPath: 'tasks/tasks.json',
				telemetryData: {}
			});
		});

		test.skip('should fallback to non-streaming when streaming fails with specific errors (streaming disabled)', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Mock progress reporting function
			const mockReportProgress = jest.fn(() => Promise.resolve());

			// Mock streamObjectService to return a stream that fails during processing
			streamObjectService.mockImplementationOnce(async () => {
				return {
					mainResult: {
						get partialObjectStream() {
							return (async function* () {
								throw new Error('Stream processing failed');
							})();
						},
						usage: Promise.resolve(null),
						object: Promise.resolve(null)
					},
					providerName: 'anthropic',
					modelId: 'claude-3-5-sonnet-20241022',
					telemetryData: {}
				};
			});

			// Ensure generateObjectService returns tasks for fallback
			generateObjectService.mockResolvedValue({
				mainResult: {
					tasks: [
						{
							id: 1,
							title: 'Test Task 1',
							priority: 'high',
							description: 'Test description 1',
							status: 'pending',
							dependencies: []
						},
						{
							id: 2,
							title: 'Test Task 2',
							priority: 'medium',
							description: 'Test description 2',
							status: 'pending',
							dependencies: []
						},
						{
							id: 3,
							title: 'Test Task 3',
							priority: 'low',
							description: 'Test description 3',
							status: 'pending',
							dependencies: []
						}
					]
				},
				telemetryData: {}
			});

			// Call the function with reportProgress to trigger streaming path
			const result = await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				reportProgress: mockReportProgress
			});

			// Verify streamObjectService was called first (streaming attempt)
			expect(streamObjectService).toHaveBeenCalled();

			// Verify generateObjectService was called as fallback
			expect(generateObjectService).toHaveBeenCalled();

			// Verify result structure (should succeed via fallback)
			expect(result).toEqual({
				success: true,
				tasksPath: 'tasks/tasks.json',
				telemetryData: {}
			});
		});

		test('should use non-streaming when reportProgress is not provided', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Call the function without reportProgress but with mcpLog to force non-streaming path
			const result = await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify generateObjectService was called (non-streaming path)
			expect(generateObjectService).toHaveBeenCalled();

			// Verify streamObjectService was NOT called (streaming path)
			expect(streamObjectService).not.toHaveBeenCalled();

			// Verify result structure
			expect(result).toEqual({
				success: true,
				tasksPath: 'tasks/tasks.json',
				telemetryData: {}
			});
		});

		test('should handle research flag with non-streaming (streaming disabled)', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Mock progress reporting function
			const mockReportProgress = jest.fn(() => Promise.resolve());

			// Call with reportProgress + research - with streaming disabled, should use non-streaming
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				reportProgress: mockReportProgress,
				research: true
			});

			// With streaming disabled, should use generateObjectService with research role
			expect(generateObjectService).toHaveBeenCalledWith(
				expect.objectContaining({
					role: 'research'
				})
			);
			expect(streamObjectService).not.toHaveBeenCalled();
		});

		test('should handle research flag with non-streaming', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Call without reportProgress but with mcpLog (non-streaming) + research
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				research: true,
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify non-streaming path was used with research role
			expect(generateObjectService).toHaveBeenCalledWith(
				expect.objectContaining({
					role: 'research'
				})
			);
			expect(streamObjectService).not.toHaveBeenCalled();
		});

		test('should use non-streaming for CLI text mode (streaming disabled)', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Call without mcpLog and without reportProgress (CLI text mode)
			const result = await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);

			// With streaming disabled, should use generateObjectService even in CLI text mode
			expect(generateObjectService).toHaveBeenCalled();
			expect(streamObjectService).not.toHaveBeenCalled();

			// Progress tracker components may still be called for CLI mode display
			// but the actual parsing uses non-streaming

			expect(result).toEqual({
				success: true,
				tasksPath: 'tasks/tasks.json',
				telemetryData: {}
			});
		});

		test.skip('should handle parseStream with usedFallback flag - needs rewrite for streamObject', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Mock progress reporting function
			const mockReportProgress = jest.fn(() => Promise.resolve());

			// Mock parseStream to return usedFallback: true
			parseStream.mockResolvedValueOnce({
				items: [{ id: 1, title: 'Test Task', priority: 'high' }],
				accumulatedText:
					'{"tasks":[{"id":1,"title":"Test Task","priority":"high"}]}',
				estimatedTokens: 50,
				usedFallback: true // This triggers fallback reporting
			});

			// Call with streaming
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				reportProgress: mockReportProgress
			});

			// Verify that usedFallback scenario was handled
			expect(parseStream).toHaveBeenCalledWith(
				expect.anything(),
				expect.objectContaining({
					jsonPaths: ['$.tasks.*'],
					onProgress: expect.any(Function),
					onError: expect.any(Function),
					estimateTokens: expect.any(Function),
					expectedTotal: 3,
					fallbackItemExtractor: expect.any(Function)
				})
			);
		});

		test.skip('should handle StreamingError types for fallback - needs rewrite for streamObject', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Test different StreamingError types that should trigger fallback
			const streamingErrors = [
				{
					message: 'Stream object is not iterable',
					code: STREAMING_ERROR_CODES.STREAM_NOT_ITERABLE
				},
				{
					message: 'Failed to process AI text stream',
					code: STREAMING_ERROR_CODES.STREAM_PROCESSING_FAILED
				},
				{
					message: 'textStream is not async iterable',
					code: STREAMING_ERROR_CODES.NOT_ASYNC_ITERABLE
				}
			];

			for (const errorConfig of streamingErrors) {
				// Clear mocks for each iteration
				jest.clearAllMocks();

				// Setup mocks again
				fs.default.existsSync.mockImplementation((path) => {
					if (path === 'tasks/tasks.json') return false;
					if (path === 'tasks') return true;
					return false;
				});
				fs.default.readFileSync.mockReturnValue(samplePRDContent);
				generateObjectService.mockResolvedValue({
					mainResult: { object: sampleClaudeResponse },
					telemetryData: {}
				});

				// Mock streamTextService to fail with StreamingError
				const error = new StreamingError(errorConfig.message, errorConfig.code);
				streamTextService.mockRejectedValueOnce(error);

				// Mock progress reporting function
				const mockReportProgress = jest.fn(() => Promise.resolve());

				// Call with streaming (should fallback to non-streaming)
				const result = await parsePRD(
					'path/to/prd.txt',
					'tasks/tasks.json',
					3,
					{
						reportProgress: mockReportProgress
					}
				);

				// Verify streaming was attempted first
				expect(streamTextService).toHaveBeenCalled();

				// Verify fallback to non-streaming occurred
				expect(generateObjectService).toHaveBeenCalled();

				// Verify successful result despite streaming failure
				expect(result).toEqual({
					success: true,
					tasksPath: 'tasks/tasks.json',
					telemetryData: {}
				});
			}
		});

		test.skip('should handle progress tracker integration in CLI streaming mode - needs rewrite for streamObject', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Mock progress tracker methods
			const mockProgressTracker = {
				start: jest.fn(),
				stop: jest.fn(),
				cleanup: jest.fn(),
				addTaskLine: jest.fn(),
				updateTokens: jest.fn(),
				getSummary: jest.fn().mockReturnValue({
					taskPriorities: { high: 1, medium: 0, low: 0 },
					elapsedTime: 1000,
					actionVerb: 'generated'
				})
			};
			createParsePrdTracker.mockReturnValue(mockProgressTracker);

			// Call in CLI text mode (no mcpLog, no reportProgress)
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);

			// Verify progress tracker was created and used
			expect(createParsePrdTracker).toHaveBeenCalledWith({
				numUnits: 3,
				unitName: 'task',
				append: false
			});
			expect(mockProgressTracker.start).toHaveBeenCalled();
			expect(mockProgressTracker.cleanup).toHaveBeenCalled();

			// Verify UI display functions were called
			expect(displayParsePrdStart).toHaveBeenCalled();
			expect(displayParsePrdSummary).toHaveBeenCalled();
		});

		test.skip('should handle onProgress callback during streaming - needs rewrite for streamObject', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Mock progress reporting function
			const mockReportProgress = jest.fn(() => Promise.resolve());

			// Mock parseStream to call onProgress
			parseStream.mockImplementation(async (stream, options) => {
				// Simulate calling onProgress during parsing
				if (options.onProgress) {
					await options.onProgress(
						{ title: 'Test Task', priority: 'high' },
						{ currentCount: 1, estimatedTokens: 50 }
					);
				}
				return {
					items: [{ id: 1, title: 'Test Task', priority: 'high' }],
					accumulatedText:
						'{"tasks":[{"id":1,"title":"Test Task","priority":"high"}]}',
					estimatedTokens: 50,
					usedFallback: false
				};
			});

			// Call with streaming
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
				reportProgress: mockReportProgress
			});

			// Verify parseStream was called with correct onProgress callback
			expect(parseStream).toHaveBeenCalledWith(
				expect.anything(),
				expect.objectContaining({
					onProgress: expect.any(Function)
				})
			);

			// Verify progress was reported during streaming
			expect(mockReportProgress).toHaveBeenCalled();
		});

		test.skip('should not re-throw non-streaming errors during fallback - needs rewrite for streamObject', async () => {
			// Setup mocks to simulate normal conditions
			fs.default.existsSync.mockImplementation((path) => {
				if (path === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (path === 'tasks') return true; // Directory exists
				return false;
			});

			// Mock progress reporting function
			const mockReportProgress = jest.fn(() => Promise.resolve());

			// Mock streamTextService to fail with NON-streaming error
			streamTextService.mockRejectedValueOnce(
				new Error('AI API rate limit exceeded')
			);

			// Call with streaming - should re-throw non-streaming errors
			await expect(
				parsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
					reportProgress: mockReportProgress
				})
			).rejects.toThrow('AI API rate limit exceeded');

			// Verify streaming was attempted
			expect(streamTextService).toHaveBeenCalled();

			// Verify fallback was NOT attempted (error was re-thrown)
			expect(generateObjectService).not.toHaveBeenCalled();
		});
	});

	describe('Dynamic Task Generation', () => {
		test('should use dynamic prompting when numTasks is 0', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((p) => {
				if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (p === 'tasks') return true; // Directory exists
				return false;
			});

			// Call the function with numTasks=0 for dynamic generation and mcpLog to force non-streaming mode
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 0, {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify generateObjectService was called
			expect(generateObjectService).toHaveBeenCalled();

			// Get the call arguments to verify the prompt
			const callArgs = generateObjectService.mock.calls[0][0];
			expect(callArgs.prompt).toContain('an appropriate number of');
			expect(callArgs.prompt).not.toContain('approximately 0');
		});

		test('should use specific count prompting when numTasks is positive', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((p) => {
				if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (p === 'tasks') return true; // Directory exists
				return false;
			});

			// Call the function with specific numTasks and mcpLog to force non-streaming mode
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 5, {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify generateObjectService was called
			expect(generateObjectService).toHaveBeenCalled();

			// Get the call arguments to verify the prompt
			const callArgs = generateObjectService.mock.calls[0][0];
			expect(callArgs.prompt).toContain('approximately 5');
			expect(callArgs.prompt).not.toContain('an appropriate number of');
		});

		test('should accept 0 as valid numTasks value', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((p) => {
				if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (p === 'tasks') return true; // Directory exists
				return false;
			});

			// Call the function with numTasks=0 and mcpLog to force non-streaming mode - should not throw error
			const result = await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 0, {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify it completed successfully
			expect(result).toEqual({
				success: true,
				tasksPath: 'tasks/tasks.json',
				telemetryData: {}
			});
		});

		test('should use dynamic prompting when numTasks is negative (no validation in main module)', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((p) => {
				if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (p === 'tasks') return true; // Directory exists
				return false;
			});

			// Call the function with negative numTasks and mcpLog to force non-streaming mode
			// Note: The main parse-prd.js module doesn't validate numTasks - validation happens at CLI/MCP level
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', -5, {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify generateObjectService was called
			expect(generateObjectService).toHaveBeenCalled();
			const callArgs = generateObjectService.mock.calls[0][0];
			// Negative values are treated as <= 0, so should use dynamic prompting
			expect(callArgs.prompt).toContain('an appropriate number of');
			expect(callArgs.prompt).not.toContain('approximately -5');
		});
	});

	describe('Configuration Integration', () => {
		test('should use dynamic prompting when numTasks is null', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((p) => {
				if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (p === 'tasks') return true; // Directory exists
				return false;
			});

			// Call the function with null numTasks and mcpLog to force non-streaming mode
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', null, {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify generateObjectService was called with dynamic prompting
			expect(generateObjectService).toHaveBeenCalled();
			const callArgs = generateObjectService.mock.calls[0][0];
			expect(callArgs.prompt).toContain('an appropriate number of');
		});

		test('should use dynamic prompting when numTasks is invalid string', async () => {
			// Setup mocks to simulate normal conditions (no existing output file)
			fs.default.existsSync.mockImplementation((p) => {
				if (p === 'tasks/tasks.json') return false; // Output file doesn't exist
				if (p === 'tasks') return true; // Directory exists
				return false;
			});

			// Call the function with invalid numTasks (string that's not a number) and mcpLog to force non-streaming mode
			await parsePRD('path/to/prd.txt', 'tasks/tasks.json', 'invalid', {
				tag: 'master',
				mcpLog: {
					info: jest.fn(),
					warn: jest.fn(),
					error: jest.fn(),
					debug: jest.fn(),
					success: jest.fn()
				}
			});

			// Verify generateObjectService was called with dynamic prompting
			// Note: The main module doesn't validate - it just uses the value as-is
			// Since 'invalid' > 0 is false, it uses dynamic prompting
			expect(generateObjectService).toHaveBeenCalled();
			const callArgs = generateObjectService.mock.calls[0][0];
			expect(callArgs.prompt).toContain('an appropriate number of');
		});
	});
});

```
Page 31/38FirstPrevNextLast