#
tokens: 49430/50000 17/975 files (page 18/50)
lines: off (toggle) GitHub
raw markdown copy
This is page 18 of 50. Use http://codebase.md/eyaltoledano/claude-task-master?page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── commands
│   │   └── dedupe.md
│   └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│   └── marketplace.json
├── .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
│   │   └── validate-changesets.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
│   │   ├── autonomous-tdd-git-workflow.md
│   │   ├── 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
│   │   ├── tdd-workflow-phase-0-spike.md
│   │   ├── tdd-workflow-phase-1-core-rails.md
│   │   ├── tdd-workflow-phase-1-orchestrator.md
│   │   ├── tdd-workflow-phase-2-pr-resumability.md
│   │   ├── tdd-workflow-phase-3-extensibility-guardrails.md
│   │   ├── test-prd.txt
│   │   └── tm-core-phase-1.txt
│   ├── reports
│   │   ├── task-complexity-report_autonomous-tdd-git-workflow.json
│   │   ├── task-complexity-report_cc-kiro-hooks.json
│   │   ├── task-complexity-report_tdd-phase-1-core-rails.json
│   │   ├── task-complexity-report_tdd-workflow-phase-0.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_rpg.md
│       └── example_prd.md
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── apps
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── command-registry.ts
│   │   │   ├── commands
│   │   │   │   ├── auth.command.ts
│   │   │   │   ├── autopilot
│   │   │   │   │   ├── abort.command.ts
│   │   │   │   │   ├── commit.command.ts
│   │   │   │   │   ├── complete.command.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next.command.ts
│   │   │   │   │   ├── resume.command.ts
│   │   │   │   │   ├── shared.ts
│   │   │   │   │   ├── start.command.ts
│   │   │   │   │   └── status.command.ts
│   │   │   │   ├── briefs.command.ts
│   │   │   │   ├── context.command.ts
│   │   │   │   ├── export.command.ts
│   │   │   │   ├── list.command.ts
│   │   │   │   ├── models
│   │   │   │   │   ├── custom-providers.ts
│   │   │   │   │   ├── fetchers.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── prompts.ts
│   │   │   │   │   ├── setup.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── next.command.ts
│   │   │   │   ├── set-status.command.ts
│   │   │   │   ├── show.command.ts
│   │   │   │   ├── start.command.ts
│   │   │   │   └── tags.command.ts
│   │   │   ├── index.ts
│   │   │   ├── lib
│   │   │   │   └── model-management.ts
│   │   │   ├── types
│   │   │   │   └── tag-management.d.ts
│   │   │   ├── ui
│   │   │   │   ├── components
│   │   │   │   │   ├── cardBox.component.ts
│   │   │   │   │   ├── dashboard.component.ts
│   │   │   │   │   ├── header.component.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next-task.component.ts
│   │   │   │   │   ├── suggested-steps.component.ts
│   │   │   │   │   └── task-detail.component.ts
│   │   │   │   ├── display
│   │   │   │   │   ├── messages.ts
│   │   │   │   │   └── tables.ts
│   │   │   │   ├── formatters
│   │   │   │   │   ├── complexity-formatters.ts
│   │   │   │   │   ├── dependency-formatters.ts
│   │   │   │   │   ├── priority-formatters.ts
│   │   │   │   │   ├── status-formatters.spec.ts
│   │   │   │   │   └── status-formatters.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── layout
│   │   │   │       ├── helpers.spec.ts
│   │   │   │       └── helpers.ts
│   │   │   └── utils
│   │   │       ├── auth-helpers.ts
│   │   │       ├── auto-update.ts
│   │   │       ├── brief-selection.ts
│   │   │       ├── display-helpers.ts
│   │   │       ├── error-handler.ts
│   │   │       ├── index.ts
│   │   │       ├── project-root.ts
│   │   │       ├── task-status.ts
│   │   │       ├── ui.spec.ts
│   │   │       └── ui.ts
│   │   ├── tests
│   │   │   ├── integration
│   │   │   │   └── commands
│   │   │   │       └── autopilot
│   │   │   │           └── workflow.test.ts
│   │   │   └── unit
│   │   │       ├── commands
│   │   │       │   ├── autopilot
│   │   │       │   │   └── shared.test.ts
│   │   │       │   ├── list.command.spec.ts
│   │   │       │   └── show.command.spec.ts
│   │   │       └── ui
│   │   │           └── dashboard.component.spec.ts
│   │   ├── tsconfig.json
│   │   └── vitest.config.ts
│   ├── 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
│   │   │   ├── rpg-method.mdx
│   │   │   └── task-structure.mdx
│   │   ├── CHANGELOG.md
│   │   ├── command-reference.mdx
│   │   ├── configuration.mdx
│   │   ├── docs.json
│   │   ├── favicon.svg
│   │   ├── getting-started
│   │   │   ├── api-keys.mdx
│   │   │   ├── 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
│   │   ├── tdd-workflow
│   │   │   ├── ai-agent-integration.mdx
│   │   │   └── quickstart.mdx
│   │   ├── 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
│   └── mcp
│       ├── CHANGELOG.md
│       ├── package.json
│       ├── src
│       │   ├── index.ts
│       │   ├── shared
│       │   │   ├── types.ts
│       │   │   └── utils.ts
│       │   └── tools
│       │       ├── autopilot
│       │       │   ├── abort.tool.ts
│       │       │   ├── commit.tool.ts
│       │       │   ├── complete.tool.ts
│       │       │   ├── finalize.tool.ts
│       │       │   ├── index.ts
│       │       │   ├── next.tool.ts
│       │       │   ├── resume.tool.ts
│       │       │   ├── start.tool.ts
│       │       │   └── status.tool.ts
│       │       ├── README-ZOD-V3.md
│       │       └── tasks
│       │           ├── get-task.tool.ts
│       │           ├── get-tasks.tool.ts
│       │           └── index.ts
│       ├── tsconfig.json
│       └── vitest.config.ts
├── assets
│   ├── .windsurfrules
│   ├── AGENTS.md
│   ├── claude
│   │   └── TM_COMMANDS_GUIDE.md
│   ├── config.json
│   ├── env.example
│   ├── example_prd_rpg.txt
│   ├── example_prd.txt
│   ├── GEMINI.md
│   ├── 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_CODE_PLUGIN.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
│   ├── claude-code-integration.md
│   ├── CLI-COMMANDER-PATTERN.md
│   ├── command-reference.md
│   ├── configuration.md
│   ├── contributor-docs
│   │   ├── testing-roo-integration.md
│   │   └── worktree-setup.md
│   ├── cross-tag-task-movement.md
│   ├── examples
│   │   ├── claude-code-usage.md
│   │   └── codex-cli-usage.md
│   ├── examples.md
│   ├── licensing.md
│   ├── mcp-provider-guide.md
│   ├── mcp-provider.md
│   ├── migration-guide.md
│   ├── models.md
│   ├── providers
│   │   ├── codex-cli.md
│   │   └── gemini-cli.md
│   ├── README.md
│   ├── scripts
│   │   └── models-json-to-markdown.js
│   ├── task-structure.md
│   └── tutorial.md
├── images
│   ├── hamster-hiring.png
│   └── 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
│       │   │   ├── 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
│       │   │   ├── 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
│           ├── index.js
│           ├── initialize-project.js
│           ├── list-tags.js
│           ├── models.js
│           ├── move-task.js
│           ├── next-task.js
│           ├── parse-prd.js
│           ├── README-ZOD-V3.md
│           ├── 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
│           ├── tool-registry.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
│   ├── ai-sdk-provider-grok-cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── errors.test.ts
│   │   │   ├── errors.ts
│   │   │   ├── grok-cli-language-model.ts
│   │   │   ├── grok-cli-provider.test.ts
│   │   │   ├── grok-cli-provider.ts
│   │   │   ├── index.ts
│   │   │   ├── json-extractor.test.ts
│   │   │   ├── json-extractor.ts
│   │   │   ├── message-converter.test.ts
│   │   │   ├── message-converter.ts
│   │   │   └── types.ts
│   │   └── tsconfig.json
│   ├── build-config
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   └── tsdown.base.ts
│   │   └── tsconfig.json
│   ├── claude-code-plugin
│   │   ├── .claude-plugin
│   │   │   └── plugin.json
│   │   ├── .gitignore
│   │   ├── agents
│   │   │   ├── task-checker.md
│   │   │   ├── task-executor.md
│   │   │   └── task-orchestrator.md
│   │   ├── CHANGELOG.md
│   │   ├── commands
│   │   │   ├── add-dependency.md
│   │   │   ├── add-subtask.md
│   │   │   ├── add-task.md
│   │   │   ├── analyze-complexity.md
│   │   │   ├── analyze-project.md
│   │   │   ├── auto-implement-tasks.md
│   │   │   ├── command-pipeline.md
│   │   │   ├── complexity-report.md
│   │   │   ├── convert-task-to-subtask.md
│   │   │   ├── expand-all-tasks.md
│   │   │   ├── expand-task.md
│   │   │   ├── fix-dependencies.md
│   │   │   ├── generate-tasks.md
│   │   │   ├── help.md
│   │   │   ├── init-project-quick.md
│   │   │   ├── init-project.md
│   │   │   ├── install-taskmaster.md
│   │   │   ├── learn.md
│   │   │   ├── list-tasks-by-status.md
│   │   │   ├── list-tasks-with-subtasks.md
│   │   │   ├── list-tasks.md
│   │   │   ├── next-task.md
│   │   │   ├── parse-prd-with-research.md
│   │   │   ├── parse-prd.md
│   │   │   ├── project-status.md
│   │   │   ├── quick-install-taskmaster.md
│   │   │   ├── remove-all-subtasks.md
│   │   │   ├── remove-dependency.md
│   │   │   ├── remove-subtask.md
│   │   │   ├── remove-subtasks.md
│   │   │   ├── remove-task.md
│   │   │   ├── setup-models.md
│   │   │   ├── show-task.md
│   │   │   ├── smart-workflow.md
│   │   │   ├── sync-readme.md
│   │   │   ├── tm-main.md
│   │   │   ├── to-cancelled.md
│   │   │   ├── to-deferred.md
│   │   │   ├── to-done.md
│   │   │   ├── to-in-progress.md
│   │   │   ├── to-pending.md
│   │   │   ├── to-review.md
│   │   │   ├── update-single-task.md
│   │   │   ├── update-task.md
│   │   │   ├── update-tasks-from-id.md
│   │   │   ├── validate-dependencies.md
│   │   │   └── view-models.md
│   │   ├── mcp.json
│   │   └── package.json
│   ├── tm-bridge
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── add-tag-bridge.ts
│   │   │   ├── bridge-types.ts
│   │   │   ├── bridge-utils.ts
│   │   │   ├── expand-bridge.ts
│   │   │   ├── index.ts
│   │   │   ├── tags-bridge.ts
│   │   │   ├── update-bridge.ts
│   │   │   └── use-tag-bridge.ts
│   │   └── tsconfig.json
│   └── tm-core
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── docs
│       │   └── listTasks-architecture.md
│       ├── package.json
│       ├── POC-STATUS.md
│       ├── README.md
│       ├── src
│       │   ├── common
│       │   │   ├── constants
│       │   │   │   ├── index.ts
│       │   │   │   ├── paths.ts
│       │   │   │   └── providers.ts
│       │   │   ├── errors
│       │   │   │   ├── index.ts
│       │   │   │   └── task-master-error.ts
│       │   │   ├── interfaces
│       │   │   │   ├── configuration.interface.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── storage.interface.ts
│       │   │   ├── logger
│       │   │   │   ├── factory.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── logger.spec.ts
│       │   │   │   └── logger.ts
│       │   │   ├── mappers
│       │   │   │   ├── TaskMapper.test.ts
│       │   │   │   └── TaskMapper.ts
│       │   │   ├── types
│       │   │   │   ├── database.types.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── legacy.ts
│       │   │   │   └── repository-types.ts
│       │   │   └── utils
│       │   │       ├── git-utils.ts
│       │   │       ├── id-generator.ts
│       │   │       ├── index.ts
│       │   │       ├── path-helpers.ts
│       │   │       ├── path-normalizer.spec.ts
│       │   │       ├── path-normalizer.ts
│       │   │       ├── project-root-finder.spec.ts
│       │   │       ├── project-root-finder.ts
│       │   │       ├── run-id-generator.spec.ts
│       │   │       └── run-id-generator.ts
│       │   ├── index.ts
│       │   ├── modules
│       │   │   ├── ai
│       │   │   │   ├── index.ts
│       │   │   │   ├── interfaces
│       │   │   │   │   └── ai-provider.interface.ts
│       │   │   │   └── providers
│       │   │   │       ├── base-provider.ts
│       │   │   │       └── index.ts
│       │   │   ├── auth
│       │   │   │   ├── auth-domain.spec.ts
│       │   │   │   ├── auth-domain.ts
│       │   │   │   ├── config.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── managers
│       │   │   │   │   ├── auth-manager.spec.ts
│       │   │   │   │   └── auth-manager.ts
│       │   │   │   ├── services
│       │   │   │   │   ├── context-store.ts
│       │   │   │   │   ├── oauth-service.ts
│       │   │   │   │   ├── organization.service.ts
│       │   │   │   │   ├── supabase-session-storage.spec.ts
│       │   │   │   │   └── supabase-session-storage.ts
│       │   │   │   └── types.ts
│       │   │   ├── briefs
│       │   │   │   ├── briefs-domain.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── services
│       │   │   │   │   └── brief-service.ts
│       │   │   │   ├── types.ts
│       │   │   │   └── utils
│       │   │   │       └── url-parser.ts
│       │   │   ├── commands
│       │   │   │   └── index.ts
│       │   │   ├── config
│       │   │   │   ├── config-domain.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── managers
│       │   │   │   │   ├── config-manager.spec.ts
│       │   │   │   │   └── config-manager.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
│       │   │   ├── dependencies
│       │   │   │   └── index.ts
│       │   │   ├── execution
│       │   │   │   ├── executors
│       │   │   │   │   ├── base-executor.ts
│       │   │   │   │   ├── claude-executor.ts
│       │   │   │   │   └── executor-factory.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── services
│       │   │   │   │   └── executor-service.ts
│       │   │   │   └── types.ts
│       │   │   ├── git
│       │   │   │   ├── adapters
│       │   │   │   │   ├── git-adapter.test.ts
│       │   │   │   │   └── git-adapter.ts
│       │   │   │   ├── git-domain.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── services
│       │   │   │       ├── branch-name-generator.spec.ts
│       │   │   │       ├── branch-name-generator.ts
│       │   │   │       ├── commit-message-generator.test.ts
│       │   │   │       ├── commit-message-generator.ts
│       │   │   │       ├── scope-detector.test.ts
│       │   │   │       ├── scope-detector.ts
│       │   │   │       ├── template-engine.test.ts
│       │   │   │       └── template-engine.ts
│       │   │   ├── integration
│       │   │   │   ├── clients
│       │   │   │   │   ├── index.ts
│       │   │   │   │   └── supabase-client.ts
│       │   │   │   ├── integration-domain.ts
│       │   │   │   └── services
│       │   │   │       ├── export.service.ts
│       │   │   │       ├── task-expansion.service.ts
│       │   │   │       └── task-retrieval.service.ts
│       │   │   ├── reports
│       │   │   │   ├── index.ts
│       │   │   │   ├── managers
│       │   │   │   │   └── complexity-report-manager.ts
│       │   │   │   └── types.ts
│       │   │   ├── storage
│       │   │   │   ├── adapters
│       │   │   │   │   ├── activity-logger.ts
│       │   │   │   │   ├── api-storage.ts
│       │   │   │   │   └── file-storage
│       │   │   │   │       ├── file-operations.ts
│       │   │   │   │       ├── file-storage.ts
│       │   │   │   │       ├── format-handler.ts
│       │   │   │   │       ├── index.ts
│       │   │   │   │       └── path-resolver.ts
│       │   │   │   ├── index.ts
│       │   │   │   ├── services
│       │   │   │   │   └── storage-factory.ts
│       │   │   │   └── utils
│       │   │   │       └── api-client.ts
│       │   │   ├── tasks
│       │   │   │   ├── entities
│       │   │   │   │   └── task.entity.ts
│       │   │   │   ├── parser
│       │   │   │   │   └── index.ts
│       │   │   │   ├── repositories
│       │   │   │   │   ├── supabase
│       │   │   │   │   │   ├── dependency-fetcher.ts
│       │   │   │   │   │   ├── index.ts
│       │   │   │   │   │   └── supabase-repository.ts
│       │   │   │   │   └── task-repository.interface.ts
│       │   │   │   ├── services
│       │   │   │   │   ├── preflight-checker.service.ts
│       │   │   │   │   ├── tag.service.ts
│       │   │   │   │   ├── task-execution-service.ts
│       │   │   │   │   ├── task-loader.service.ts
│       │   │   │   │   └── task-service.ts
│       │   │   │   └── tasks-domain.ts
│       │   │   ├── ui
│       │   │   │   └── index.ts
│       │   │   └── workflow
│       │   │       ├── managers
│       │   │       │   ├── workflow-state-manager.spec.ts
│       │   │       │   └── workflow-state-manager.ts
│       │   │       ├── orchestrators
│       │   │       │   ├── workflow-orchestrator.test.ts
│       │   │       │   └── workflow-orchestrator.ts
│       │   │       ├── services
│       │   │       │   ├── test-result-validator.test.ts
│       │   │       │   ├── test-result-validator.ts
│       │   │       │   ├── test-result-validator.types.ts
│       │   │       │   ├── workflow-activity-logger.ts
│       │   │       │   └── workflow.service.ts
│       │   │       ├── types.ts
│       │   │       └── workflow-domain.ts
│       │   ├── subpath-exports.test.ts
│       │   ├── tm-core.ts
│       │   └── utils
│       │       └── time.utils.ts
│       ├── tests
│       │   ├── auth
│       │   │   └── auth-refresh.test.ts
│       │   ├── integration
│       │   │   ├── auth-token-refresh.test.ts
│       │   │   ├── list-tasks.test.ts
│       │   │   └── storage
│       │   │       └── activity-logger.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
│   ├── create-worktree.sh
│   ├── dev.js
│   ├── init.js
│   ├── list-worktrees.sh
│   ├── modules
│   │   ├── ai-services-unified.js
│   │   ├── bridge-utils.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
├── sonar-project.properties
├── src
│   ├── ai-providers
│   │   ├── anthropic.js
│   │   ├── azure.js
│   │   ├── base-provider.js
│   │   ├── bedrock.js
│   │   ├── claude-code.js
│   │   ├── codex-cli.js
│   │   ├── gemini-cli.js
│   │   ├── google-vertex.js
│   │   ├── google.js
│   │   ├── grok-cli.js
│   │   ├── groq.js
│   │   ├── index.js
│   │   ├── lmstudio.js
│   │   ├── ollama.js
│   │   ├── openai-compatible.js
│   │   ├── openai.js
│   │   ├── openrouter.js
│   │   ├── perplexity.js
│   │   ├── xai.js
│   │   ├── zai-coding.js
│   │   └── zai.js
│   ├── constants
│   │   ├── commands.js
│   │   ├── paths.js
│   │   ├── profiles.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
│   ├── schemas
│   │   ├── add-task.js
│   │   ├── analyze-complexity.js
│   │   ├── base-schemas.js
│   │   ├── expand-task.js
│   │   ├── parse-prd.js
│   │   ├── registry.js
│   │   ├── update-subtask.js
│   │   ├── update-task.js
│   │   └── update-tasks.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
│   ├── fixtures
│   │   ├── .taskmasterconfig
│   │   ├── sample-claude-response.js
│   │   ├── sample-prd.txt
│   │   └── sample-tasks.js
│   ├── helpers
│   │   └── tool-counts.js
│   ├── integration
│   │   ├── claude-code-error-handling.test.js
│   │   ├── 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
│   │   └── providers
│   │       └── temperature-support.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
│       │   ├── base-provider.test.js
│       │   ├── claude-code.test.js
│       │   ├── codex-cli.test.js
│       │   ├── gemini-cli.test.js
│       │   ├── lmstudio.test.js
│       │   ├── mcp-components.test.js
│       │   ├── openai-compatible.test.js
│       │   ├── openai.test.js
│       │   ├── provider-registry.test.js
│       │   ├── zai-coding.test.js
│       │   ├── zai-provider.test.js
│       │   ├── zai-schema-introspection.test.js
│       │   └── zai.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
│       │       └── tool-registration.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
│       │   └── prompt-migration.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
│       │       │   ├── models-baseurl.test.js
│       │       │   ├── move-task-cross-tag.test.js
│       │       │   ├── move-task.test.js
│       │       │   ├── parse-prd-schema.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
└── update-task-migration-plan.md
```

# Files

--------------------------------------------------------------------------------
/apps/extension/docs/extension-CI-setup.md:
--------------------------------------------------------------------------------

```markdown
# VS Code Extension CI/CD Setup

This document explains the CI/CD setup for the Task Master VS Code extension using automated changesets.

## 🔄 Workflows Overview

### 1. Extension CI (`extension-ci.yml`)

#### Triggers

- Push to `main` or `next` branches (only when extension files change)
- Pull requests to `main` or `next` (only when extension files change)

#### What it does

- ✅ Lints and type-checks the extension code
- 🔨 Builds the extension (`npm run build`)
- 📦 Creates a clean package (`npm run package`)
- 🧪 Runs tests with VS Code test framework
- 📋 Creates a test VSIX package to verify packaging works
- 💾 Uploads build artifacts for inspection

### 2. Version & Publish (`version.yml`)

**Triggers:**
- Push to `main` branch

**What it does:**
- 🔍 Detects changeset files for pending releases
- 📝 Creates "Version Packages" PR with updated versions and CHANGELOG
- 🤖 When Version PR is merged, automatically:
  - 🔨 Builds and packages the extension
  - 🏷️ Creates git tags with changeset automation
  - 📤 Publishes to VS Code Marketplace
  - 🌍 Publishes to Open VSX Registry
  - 📊 Updates package versions and CHANGELOG

## 🚀 Changeset Workflow

### Creating Changes
When making changes to the extension:

1. **Make your code changes**
2. **Create a changeset**:
   ```bash
   # From project root
   npx changeset add
   ```
3. **Select the extension package**: Choose `taskr-kanban` when prompted
4. **Select version bump type**:
   - `patch`: Bug fixes, minor updates
   - `minor`: New features, backwards compatible
   - `major`: Breaking changes
5. **Write a summary**: Describe what changed for users
6. **Commit changeset file** along with your code changes
7. **Push to feature branch** and create PR

### Automated Publishing Process
1. **PR with changeset** gets merged to `main`
2. **Version workflow** detects changesets and creates "Version Packages" PR
3. **Review and merge** the Version PR
4. **Automated publishing** happens immediately:
   - Extension is built using 3-file packaging system
   - VSIX package is created and tested
   - Published to VS Code Marketplace (if `VSCE_PAT` is set)
   - Published to Open VSX Registry (if `OVSX_PAT` is set)
   - Git tags are created: `[email protected]`
   - CHANGELOG is updated automatically

## 🔑 Required Secrets

To use the automated publishing, you need to set up these GitHub repository secrets:

### `VSCE_PAT` (VS Code Marketplace Personal Access Token)
1. Go to [Azure DevOps](https://dev.azure.com/)
2. Sign in with your Microsoft account
3. Create a Personal Access Token:
   - **Name**: VS Code Extension Publishing
   - **Organization**: All accessible organizations
   - **Expiration**: Custom (recommend 1 year)
   - **Scopes**: Custom defined → **Marketplace** → **Manage**
4. Copy the token and add it to GitHub Secrets as `VSCE_PAT`

### `OVSX_PAT` (Open VSX Registry Personal Access Token)
1. Go to [Open VSX Registry](https://open-vsx.org/)
2. Sign in with your GitHub account
3. Go to your [User Settings](https://open-vsx.org/user-settings/tokens)
4. Create a new Access Token:
   - **Description**: VS Code Extension Publishing
   - **Scopes**: Leave default (full access)
5. Copy the token and add it to GitHub Secrets as `OVSX_PAT`

### `GITHUB_TOKEN` (automatically provided)
This is automatically available in GitHub Actions - no setup required.

## 📋 Version Management

### Changeset-Based Versioning
Versions are automatically managed by changesets:

- **No manual version updates needed** - changesets handle this automatically
- **Semantic versioning** is enforced based on changeset types
- **Changelog generation** happens automatically
- **Git tagging** is handled by the automation

### Critical Fields Sync
The automation ensures these fields stay in sync between `package.json` and `package.publish.json`:

```json
{
  "version": "1.0.2",                    // ✅ AUTO-SYNCED
  "publisher": "Hamster",        // ⚠️ MUST MATCH MANUALLY
  "displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH MANUALLY
  "description": "...",                  // ⚠️ MUST MATCH MANUALLY
  "engines": { "vscode": "^1.93.0" },   // ⚠️ MUST MATCH MANUALLY
  "categories": [...],                   // ⚠️ MUST MATCH MANUALLY
  "contributes": { ... }                 // ⚠️ MUST MATCH MANUALLY
}
```

**Note**: Only `version` is automatically synced. Other fields must be manually kept in sync.

## 🔍 Monitoring Builds

### CI Status

- **Green ✅**: Extension builds and tests successfully
- **Red ❌**: Build/test failures - check logs for details
- **Yellow 🟡**: Partial success - some jobs may have warnings

### Version PR Status

- **Version PR Created**: Changesets detected, review and merge to publish
- **No Version PR**: No changesets found, no releases pending
- **Version PR Merged**: Automated publishing triggered

### Release Status

- **Published 🎉**: Extension live on VS Code Marketplace and Open VSX
- **Skipped ℹ️**: No changesets found, no release needed
- **Failed ❌**: Check logs - often missing secrets or build issues

### Artifacts

Workflows upload artifacts that you can download:

- **CI**: Test results, built files, and VSIX package
- **Version**: Final VSIX package and published extension

## 🛠️ Troubleshooting

### Common Issues

#### No Version PR Created

- **Check**: Changeset files exist in `.changeset/` directory
- **Check**: Changeset refers to `taskr-kanban` package name
- **Check**: Changes were pushed to `main` branch
- **Solution**: Create changeset with `npx changeset add`

#### Version PR Not Publishing

- **Check**: Version PR was actually merged (not just closed)
- **Check**: Required secrets (`VSCE_PAT`, `OVSX_PAT`) are set
- **Check**: No build failures in workflow logs
- **Solution**: Re-run workflow or check secret configuration

#### `VSCE_PAT` is not set Error

- Ensure `VSCE_PAT` secret is added to repository
- Check token hasn't expired
- Verify token has Marketplace → Manage permissions

#### `OVSX_PAT` is not set Error

- Ensure `OVSX_PAT` secret is added to repository
- Check token hasn't expired
- Verify you're signed in to Open VSX Registry with GitHub

#### Build Failures

- Check extension code compiles locally: `cd apps/extension && npm run build`
- Verify tests pass locally: `npm run test`
- Check for TypeScript errors: `npm run typecheck`

#### Packaging Failures

- Ensure clean package builds: `npm run package`
- Check vsix-build structure is correct
- Verify `package.publish.json` has correct `repository` field

#### Changeset Issues

- **Wrong package name**: Ensure changeset refers to `taskr-kanban`
- **Invalid format**: Check changeset markdown format is correct
- **Merge conflicts**: Resolve any conflicts in changeset files

## 📁 File Structure Impact

The CI workflows respect the 3-file packaging system:
- **Development**: Uses `package.json` for dependencies and scripts
- **Release**: Uses `package.publish.json` for clean marketplace package
- **Build**: Uses `package.mjs` to create `vsix-build/` for final packaging
- **Changesets**: Automatically manage versions across the system

## 🌍 Dual Registry Publishing

Your extension will be automatically published to both:
- **VS Code Marketplace** - For official VS Code users
- **Open VSX Registry** - For Cursor, Windsurf, VSCodium, Gitpod, Eclipse Theia, and other compatible editors

## 🎯 Benefits of Changeset Automation

- ✅ **Automated versioning**: No manual version bumps needed
- ✅ **Generated changelogs**: Automatic, accurate release notes
- ✅ **Semantic versioning**: Enforced through changeset types
- ✅ **Git tagging**: Proper tags for extension releases
- ✅ **Conflict prevention**: Clear separation of extension vs. main package versions
- ✅ **Review process**: Version changes are reviewable via PR
- ✅ **Rollback capability**: Easy to revert if issues arise

This ensures clean, predictable, and fully automated publishing to both registries! 🚀

```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/add-task.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Tests for the add-task MCP tool
 *
 * Note: This test does NOT test the actual implementation. It tests that:
 * 1. The tool is registered correctly with the correct parameters
 * 2. Arguments are passed correctly to addTaskDirect
 * 3. Error handling works as expected
 *
 * We do NOT import the real implementation - everything is mocked
 */

import { jest } from '@jest/globals';
import {
	sampleTasks,
	emptySampleTasks
} from '../../../fixtures/sample-tasks.js';

// Mock EVERYTHING
const mockAddTaskDirect = jest.fn();
jest.mock('../../../../mcp-server/src/core/task-master-core.js', () => ({
	addTaskDirect: mockAddTaskDirect
}));

const mockHandleApiResult = jest.fn((result) => result);
const mockGetProjectRootFromSession = jest.fn(() => '/mock/project/root');
const mockCreateErrorResponse = jest.fn((msg) => ({
	success: false,
	error: { code: 'ERROR', message: msg }
}));

jest.mock('../../../../mcp-server/src/tools/utils.js', () => ({
	getProjectRootFromSession: mockGetProjectRootFromSession,
	handleApiResult: mockHandleApiResult,
	createErrorResponse: mockCreateErrorResponse,
	createContentResponse: jest.fn((content) => ({
		success: true,
		data: content
	})),
	executeTaskMasterCommand: jest.fn()
}));

// Mock the z object from zod
const mockZod = {
	object: jest.fn(() => mockZod),
	string: jest.fn(() => mockZod),
	boolean: jest.fn(() => mockZod),
	optional: jest.fn(() => mockZod),
	describe: jest.fn(() => mockZod),
	_def: {
		shape: () => ({
			prompt: {},
			dependencies: {},
			priority: {},
			research: {},
			file: {},
			projectRoot: {}
		})
	}
};

jest.mock('zod', () => ({
	z: mockZod
}));

// DO NOT import the real module - create a fake implementation
// This is the fake implementation of registerAddTaskTool
const registerAddTaskTool = (server) => {
	// Create simplified version of the tool config
	const toolConfig = {
		name: 'add_task',
		description: 'Add a new task using AI',
		parameters: mockZod,

		// Create a simplified mock of the execute function
		execute: (args, context) => {
			const { log, reportProgress, session } = context;

			try {
				log.info &&
					log.info(`Starting add-task with args: ${JSON.stringify(args)}`);

				// Get project root
				const rootFolder = mockGetProjectRootFromSession(session, log);

				// Call addTaskDirect
				const result = mockAddTaskDirect(
					{
						...args,
						projectRoot: rootFolder
					},
					log,
					{ reportProgress, session }
				);

				// Handle result
				return mockHandleApiResult(result, log);
			} catch (error) {
				log.error && log.error(`Error in add-task tool: ${error.message}`);
				return mockCreateErrorResponse(error.message);
			}
		}
	};

	// Register the tool with the server
	server.addTool(toolConfig);
};

describe('MCP Tool: add-task', () => {
	// Create mock server
	let mockServer;
	let executeFunction;

	// Create mock logger
	const mockLogger = {
		debug: jest.fn(),
		info: jest.fn(),
		warn: jest.fn(),
		error: jest.fn()
	};

	// Test data
	const validArgs = {
		prompt: 'Create a new task',
		dependencies: '1,2',
		priority: 'high',
		research: true
	};

	// Standard responses
	const successResponse = {
		success: true,
		data: {
			taskId: '5',
			message: 'Successfully added new task #5'
		}
	};

	const errorResponse = {
		success: false,
		error: {
			code: 'ADD_TASK_ERROR',
			message: 'Failed to add task'
		}
	};

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

		// Create mock server
		mockServer = {
			addTool: jest.fn((config) => {
				executeFunction = config.execute;
			})
		};

		// Setup default successful response
		mockAddTaskDirect.mockReturnValue(successResponse);

		// Register the tool
		registerAddTaskTool(mockServer);
	});

	test('should register the tool correctly', () => {
		// Verify tool was registered
		expect(mockServer.addTool).toHaveBeenCalledWith(
			expect.objectContaining({
				name: 'add_task',
				description: 'Add a new task using AI',
				parameters: expect.any(Object),
				execute: expect.any(Function)
			})
		);

		// Verify the tool config was passed
		const toolConfig = mockServer.addTool.mock.calls[0][0];
		expect(toolConfig).toHaveProperty('parameters');
		expect(toolConfig).toHaveProperty('execute');
	});

	test('should execute the tool with valid parameters', () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			reportProgress: jest.fn(),
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		executeFunction(validArgs, mockContext);

		// Verify getProjectRootFromSession was called
		expect(mockGetProjectRootFromSession).toHaveBeenCalledWith(
			mockContext.session,
			mockLogger
		);

		// Verify addTaskDirect was called with correct arguments
		expect(mockAddTaskDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				...validArgs,
				projectRoot: '/mock/project/root'
			}),
			mockLogger,
			{
				reportProgress: mockContext.reportProgress,
				session: mockContext.session
			}
		);

		// Verify handleApiResult was called
		expect(mockHandleApiResult).toHaveBeenCalledWith(
			successResponse,
			mockLogger
		);
	});

	test('should handle errors from addTaskDirect', () => {
		// Setup error response
		mockAddTaskDirect.mockReturnValueOnce(errorResponse);

		// Setup context
		const mockContext = {
			log: mockLogger,
			reportProgress: jest.fn(),
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		executeFunction(validArgs, mockContext);

		// Verify addTaskDirect was called
		expect(mockAddTaskDirect).toHaveBeenCalled();

		// Verify handleApiResult was called with error response
		expect(mockHandleApiResult).toHaveBeenCalledWith(errorResponse, mockLogger);
	});

	test('should handle unexpected errors', () => {
		// Setup error
		const testError = new Error('Unexpected error');
		mockAddTaskDirect.mockImplementationOnce(() => {
			throw testError;
		});

		// Setup context
		const mockContext = {
			log: mockLogger,
			reportProgress: jest.fn(),
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		executeFunction(validArgs, mockContext);

		// Verify error was logged
		expect(mockLogger.error).toHaveBeenCalledWith(
			'Error in add-task tool: Unexpected error'
		);

		// Verify error response was created
		expect(mockCreateErrorResponse).toHaveBeenCalledWith('Unexpected error');
	});

	test('should pass research parameter correctly', () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			reportProgress: jest.fn(),
			session: { workingDirectory: '/mock/dir' }
		};

		// Test with research=true
		executeFunction(
			{
				...validArgs,
				research: true
			},
			mockContext
		);

		// Verify addTaskDirect was called with research=true
		expect(mockAddTaskDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				research: true
			}),
			expect.any(Object),
			expect.any(Object)
		);

		// Reset mocks
		jest.clearAllMocks();

		// Test with research=false
		executeFunction(
			{
				...validArgs,
				research: false
			},
			mockContext
		);

		// Verify addTaskDirect was called with research=false
		expect(mockAddTaskDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				research: false
			}),
			expect.any(Object),
			expect.any(Object)
		);
	});

	test('should pass priority parameter correctly', () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			reportProgress: jest.fn(),
			session: { workingDirectory: '/mock/dir' }
		};

		// Test different priority values
		['high', 'medium', 'low'].forEach((priority) => {
			// Reset mocks
			jest.clearAllMocks();

			// Execute with specific priority
			executeFunction(
				{
					...validArgs,
					priority
				},
				mockContext
			);

			// Verify addTaskDirect was called with correct priority
			expect(mockAddTaskDirect).toHaveBeenCalledWith(
				expect.objectContaining({
					priority
				}),
				expect.any(Object),
				expect.any(Object)
			);
		});
	});
});

```

--------------------------------------------------------------------------------
/apps/docs/getting-started/api-keys.mdx:
--------------------------------------------------------------------------------

```markdown
# API Keys Configuration

Task Master supports multiple AI providers through environment variables. This page lists all available API keys and their configuration requirements.

## Required API Keys

> **Note**: At least one required API key must be configured for Task Master to function.
>
> "Required: Yes" below means "required to use that specific provider," not "required globally." You only need at least one provider configured.

### ANTHROPIC_API_KEY (Recommended)
- **Provider**: Anthropic Claude models
- **Format**: `sk-ant-api03-...`
- **Required**: ✅ **Yes**
- **Models**: Claude 3.5 Sonnet, Claude 3 Haiku, Claude 3 Opus
- **Get Key**: [Anthropic Console](https://console.anthropic.com/)

```bash
ANTHROPIC_API_KEY="sk-ant-api03-your-key-here"
```

### PERPLEXITY_API_KEY (Highly Recommended for Research)
- **Provider**: Perplexity AI (Research features)
- **Format**: `pplx-...`
- **Required**: ✅ **Yes**
- **Purpose**: Enables research-backed task expansions and updates
- **Models**: Perplexity Sonar models
- **Get Key**: [Perplexity API](https://www.perplexity.ai/settings/api)

```bash
PERPLEXITY_API_KEY="pplx-your-key-here"
```

### OPENAI_API_KEY
- **Provider**: OpenAI GPT models
- **Format**: `sk-proj-...` or `sk-...`
- **Required**: ✅ **Yes**
- **Models**: GPT-4, GPT-4 Turbo, GPT-3.5 Turbo, O1 models
- **Get Key**: [OpenAI Platform](https://platform.openai.com/api-keys)

```bash
OPENAI_API_KEY="sk-proj-your-key-here"
```

### GOOGLE_API_KEY
- **Provider**: Google Gemini models
- **Format**: Various formats
- **Required**: ✅ **Yes**
- **Models**: Gemini Pro, Gemini Flash, Gemini Ultra
- **Get Key**: [Google AI Studio](https://aistudio.google.com/app/apikey)
- **Alternative**: Use `GOOGLE_APPLICATION_CREDENTIALS` for service account (Google Vertex)

```bash
GOOGLE_API_KEY="your-google-api-key-here"
```

### GROQ_API_KEY
- **Provider**: Groq (High-performance inference)
- **Required**: ✅ **Yes**
- **Models**: Llama models, Mixtral models (via Groq)
- **Get Key**: [Groq Console](https://console.groq.com/keys)

```bash
GROQ_API_KEY="your-groq-key-here"
```

### OPENROUTER_API_KEY
- **Provider**: OpenRouter (Multiple model access)
- **Required**: ✅ **Yes**
- **Models**: Access to various models through single API
- **Get Key**: [OpenRouter](https://openrouter.ai/keys)

```bash
OPENROUTER_API_KEY="your-openrouter-key-here"
```

### AZURE_OPENAI_API_KEY
- **Provider**: Azure OpenAI Service
- **Required**: ✅ **Yes**
- **Requirements**: Also requires `AZURE_OPENAI_ENDPOINT` configuration
- **Models**: GPT models via Azure
- **Get Key**: [Azure Portal](https://portal.azure.com/)

```bash
AZURE_OPENAI_API_KEY="your-azure-key-here"
```

### XAI_API_KEY
- **Provider**: xAI (Grok) models
- **Required**: ✅ **Yes**
- **Models**: Grok models
- **Get Key**: [xAI Console](https://console.x.ai/)

```bash
XAI_API_KEY="your-xai-key-here"
```

## Optional API Keys

> **Note**: These API keys are optional - providers will work without them or use alternative authentication methods.

### AWS_ACCESS_KEY_ID (Bedrock)
- **Provider**: AWS Bedrock
- **Required**: ❌ **No** (uses AWS credential chain)
- **Models**: Claude models via AWS Bedrock
- **Authentication**: Uses AWS credential chain (profiles, IAM roles, etc.)
- **Get Key**: [AWS Console](https://console.aws.amazon.com/iam/)

```bash
# Optional - AWS credential chain is preferred
AWS_ACCESS_KEY_ID="your-aws-access-key"
AWS_SECRET_ACCESS_KEY="your-aws-secret-key"
```

### CLAUDE_CODE_API_KEY
- **Provider**: Claude Code CLI
- **Required**: ❌ **No** (uses OAuth tokens)
- **Purpose**: Integration with local Claude Code CLI
- **Authentication**: Uses OAuth tokens, no API key needed

```bash
# Not typically needed
CLAUDE_CODE_API_KEY="not-usually-required"
```

### GEMINI_API_KEY
- **Provider**: Gemini CLI
- **Required**: ❌ **No** (uses OAuth authentication)
- **Purpose**: Integration with Gemini CLI
- **Authentication**: Primarily uses OAuth via CLI, API key is optional

```bash
# Optional - OAuth via CLI is preferred
GEMINI_API_KEY="your-gemini-key-here"
```

### GROK_CLI_API_KEY
- **Provider**: Grok CLI
- **Required**: ❌ **No** (can use CLI config)
- **Purpose**: Integration with Grok CLI
- **Authentication**: Can use Grok CLI's own config file

```bash
# Optional - CLI config is preferred
GROK_CLI_API_KEY="your-grok-cli-key"
```

### OLLAMA_API_KEY
- **Provider**: Ollama (Local/Remote)
- **Required**: ❌ **No** (local installation doesn't need key)
- **Purpose**: For remote Ollama servers that require authentication
- **Requirements**: Only needed for remote servers with authentication
- **Note**: Not needed for local Ollama installations

```bash
# Only needed for remote Ollama servers
OLLAMA_API_KEY="your-ollama-api-key-here"
```

### GITHUB_API_KEY
- **Provider**: GitHub (Import/Export features)
- **Format**: `ghp_...` or `github_pat_...`
- **Required**: ❌ **No** (for GitHub features only)
- **Purpose**: GitHub import/export features
- **Get Key**: [GitHub Settings](https://github.com/settings/tokens)

```bash
GITHUB_API_KEY="ghp-your-github-key-here"
```

## Configuration Methods

### Method 1: Environment File (.env)
Create a `.env` file in your project root:

```bash
# Copy from .env.example
cp .env.example .env

# Edit with your keys
vim .env
```

### Method 2: System Environment Variables
```bash
export ANTHROPIC_API_KEY="your-key-here"
export PERPLEXITY_API_KEY="your-key-here"
# ... other keys
```

### Method 3: MCP Server Configuration
For Claude Code integration, configure keys in `.mcp.json`:

```json
{
  "mcpServers": {
    "task-master-ai": {
      "command": "npx",
      "args": ["-y", "task-master-ai"],
      "env": {
        "ANTHROPIC_API_KEY": "your-key-here",
        "PERPLEXITY_API_KEY": "your-key-here",
        "OPENAI_API_KEY": "your-key-here"
      }
    }
  }
}
```

## Key Requirements

### Minimum Requirements
- **At least one** AI provider key is required
- **ANTHROPIC_API_KEY** is recommended as the primary provider
- **PERPLEXITY_API_KEY** is highly recommended for research features

### Provider-Specific Requirements
- **Azure OpenAI**: Requires both `AZURE_OPENAI_API_KEY` and `AZURE_OPENAI_ENDPOINT` configuration
- **Google Vertex**: Requires `VERTEX_PROJECT_ID` and `VERTEX_LOCATION` environment variables
- **AWS Bedrock**: Uses AWS credential chain (profiles, IAM roles, etc.) instead of API keys
- **Ollama**: Only needs API key for remote servers with authentication
- **CLI Providers**: Gemini CLI, Grok CLI, and Claude Code use OAuth/CLI config instead of API keys

## Model Configuration

After setting up API keys, configure which models to use:

```bash
# Interactive model setup
task-master models --setup

# Set specific models
task-master models --set-main claude-3-5-sonnet-20241022
task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online
task-master models --set-fallback gpt-4o-mini
```

## Security Best Practices

1. **Never commit API keys** to version control
2. **Use .env files** and add them to `.gitignore`
3. **Rotate keys regularly** especially if compromised
4. **Use minimal permissions** for service accounts
5. **Monitor usage** to detect unauthorized access

## Troubleshooting

### Key Validation
```bash
# Check if keys are properly configured
task-master models

# Test specific provider
task-master add-task --prompt="test task" --model=claude-3-5-sonnet-20241022
```

### Common Issues
- **Invalid key format**: Check the expected format for each provider
- **Insufficient permissions**: Ensure keys have necessary API access
- **Rate limits**: Some providers have usage limits
- **Regional restrictions**: Some models may not be available in all regions

### Getting Help
If you encounter issues with API key configuration:
- Check the [FAQ](/getting-started/faq) for common solutions
- Join our [Discord community](https://discord.gg/fWJkU7rf) for support
- Report issues on [GitHub](https://github.com/eyaltoledano/claude-task-master/issues)
```

--------------------------------------------------------------------------------
/src/utils/manage-gitignore.js:
--------------------------------------------------------------------------------

```javascript
// Utility to manage .gitignore files with task file preferences and template merging
import fs from 'fs';
import path from 'path';

// Constants
const TASK_FILES_COMMENT = '# Task files';
const TASK_JSON_PATTERN = 'tasks.json';
const TASK_DIR_PATTERN = 'tasks/';

/**
 * Normalizes a line by removing comments and trimming whitespace
 * @param {string} line - Line to normalize
 * @returns {string} Normalized line
 */
function normalizeLine(line) {
	return line.trim().replace(/^#/, '').trim();
}

/**
 * Checks if a line is task-related (tasks.json or tasks/)
 * @param {string} line - Line to check
 * @returns {boolean} True if line is task-related
 */
function isTaskLine(line) {
	const normalized = normalizeLine(line);
	return normalized === TASK_JSON_PATTERN || normalized === TASK_DIR_PATTERN;
}

/**
 * Adjusts task-related lines in template based on storage preference
 * @param {string[]} templateLines - Array of template lines
 * @param {boolean} storeTasksInGit - Whether to comment out task lines
 * @returns {string[]} Adjusted template lines
 */
function adjustTaskLinesInTemplate(templateLines, storeTasksInGit) {
	return templateLines.map((line) => {
		if (isTaskLine(line)) {
			const normalized = normalizeLine(line);
			// Preserve original trailing whitespace from the line
			const originalTrailingSpace = line.match(/\s*$/)[0];
			return storeTasksInGit
				? `# ${normalized}${originalTrailingSpace}`
				: `${normalized}${originalTrailingSpace}`;
		}
		return line;
	});
}

/**
 * Removes existing task files section from content
 * @param {string[]} existingLines - Existing file lines
 * @returns {string[]} Lines with task section removed
 */
function removeExistingTaskSection(existingLines) {
	const cleanedLines = [];
	let inTaskSection = false;

	for (const line of existingLines) {
		// Start of task files section
		if (line.trim() === TASK_FILES_COMMENT) {
			inTaskSection = true;
			continue;
		}

		// Task lines (commented or not)
		if (isTaskLine(line)) {
			continue;
		}

		// Empty lines within task section
		if (inTaskSection && !line.trim()) {
			continue;
		}

		// End of task section (any non-empty, non-task line)
		if (inTaskSection && line.trim() && !isTaskLine(line)) {
			inTaskSection = false;
		}

		// Keep all other lines
		if (!inTaskSection) {
			cleanedLines.push(line);
		}
	}

	return cleanedLines;
}

/**
 * Filters template lines to only include new content not already present
 * @param {string[]} templateLines - Template lines
 * @param {Set<string>} existingLinesSet - Set of existing trimmed lines
 * @returns {string[]} New lines to add
 */
function filterNewTemplateLines(templateLines, existingLinesSet) {
	return templateLines.filter((line) => {
		const trimmed = line.trim();
		if (!trimmed) return false;

		// Skip task-related lines (handled separately)
		if (isTaskLine(line) || trimmed === TASK_FILES_COMMENT) {
			return false;
		}

		// Include only if not already present
		return !existingLinesSet.has(trimmed);
	});
}

/**
 * Builds the task files section based on storage preference
 * @param {boolean} storeTasksInGit - Whether to comment out task lines
 * @returns {string[]} Task files section lines
 */
function buildTaskFilesSection(storeTasksInGit) {
	const section = [TASK_FILES_COMMENT];

	if (storeTasksInGit) {
		section.push(`# ${TASK_JSON_PATTERN}`, `# ${TASK_DIR_PATTERN} `);
	} else {
		section.push(TASK_JSON_PATTERN, `${TASK_DIR_PATTERN} `);
	}

	return section;
}

/**
 * Adds a separator line if needed (avoids double spacing)
 * @param {string[]} lines - Current lines array
 */
function addSeparatorIfNeeded(lines) {
	if (lines.some((line) => line.trim())) {
		const lastLine = lines[lines.length - 1];
		if (lastLine && lastLine.trim()) {
			lines.push('');
		}
	}
}

/**
 * Validates input parameters
 * @param {string} targetPath - Path to .gitignore file
 * @param {string} content - Template content
 * @param {boolean} storeTasksInGit - Storage preference
 * @throws {Error} If validation fails
 */
function validateInputs(targetPath, content, storeTasksInGit) {
	if (!targetPath || typeof targetPath !== 'string') {
		throw new Error('targetPath must be a non-empty string');
	}

	if (!targetPath.endsWith('.gitignore')) {
		throw new Error('targetPath must end with .gitignore');
	}

	if (!content || typeof content !== 'string') {
		throw new Error('content must be a non-empty string');
	}

	if (typeof storeTasksInGit !== 'boolean') {
		throw new Error('storeTasksInGit must be a boolean');
	}
}

/**
 * Creates a new .gitignore file from template
 * @param {string} targetPath - Path to create file at
 * @param {string[]} templateLines - Adjusted template lines
 * @param {function} log - Logging function
 */
function createNewGitignoreFile(targetPath, templateLines, log) {
	try {
		fs.writeFileSync(targetPath, templateLines.join('\n') + '\n');
		if (typeof log === 'function') {
			log('success', `Created ${targetPath} with full template`);
		}
	} catch (error) {
		if (typeof log === 'function') {
			log('error', `Failed to create ${targetPath}: ${error.message}`);
		}
		throw error;
	}
}

/**
 * Merges template content with existing .gitignore file
 * @param {string} targetPath - Path to existing file
 * @param {string[]} templateLines - Adjusted template lines
 * @param {boolean} storeTasksInGit - Storage preference
 * @param {function} log - Logging function
 */
function mergeWithExistingFile(
	targetPath,
	templateLines,
	storeTasksInGit,
	log
) {
	try {
		// Read and process existing file
		const existingContent = fs.readFileSync(targetPath, 'utf8');
		const existingLines = existingContent.split('\n');

		// Remove existing task section
		const cleanedExistingLines = removeExistingTaskSection(existingLines);

		// Find new template lines to add
		const existingLinesSet = new Set(
			cleanedExistingLines.map((line) => line.trim()).filter((line) => line)
		);
		const newLines = filterNewTemplateLines(templateLines, existingLinesSet);

		// Build final content
		const finalLines = [...cleanedExistingLines];

		// Add new template content
		if (newLines.length > 0) {
			addSeparatorIfNeeded(finalLines);
			finalLines.push(...newLines);
		}

		// Add task files section
		addSeparatorIfNeeded(finalLines);
		finalLines.push(...buildTaskFilesSection(storeTasksInGit));

		// Write result
		fs.writeFileSync(targetPath, finalLines.join('\n') + '\n');

		if (typeof log === 'function') {
			const hasNewContent =
				newLines.length > 0 ? ' and merged new content' : '';
			log(
				'success',
				`Updated ${targetPath} according to user preference${hasNewContent}`
			);
		}
	} catch (error) {
		if (typeof log === 'function') {
			log(
				'error',
				`Failed to merge content with ${targetPath}: ${error.message}`
			);
		}
		throw error;
	}
}

/**
 * Manages .gitignore file creation and updates with task file preferences
 * @param {string} targetPath - Path to the .gitignore file
 * @param {string} content - Template content for .gitignore
 * @param {boolean} storeTasksInGit - Whether to store tasks in git or not
 * @param {function} log - Logging function (level, message)
 * @throws {Error} If validation or file operations fail
 */
function manageGitignoreFile(
	targetPath,
	content,
	storeTasksInGit = true,
	log = null
) {
	// Validate inputs
	validateInputs(targetPath, content, storeTasksInGit);

	// Process template with task preference
	const templateLines = content.split('\n');
	const adjustedTemplateLines = adjustTaskLinesInTemplate(
		templateLines,
		storeTasksInGit
	);

	// Handle file creation or merging
	if (!fs.existsSync(targetPath)) {
		createNewGitignoreFile(targetPath, adjustedTemplateLines, log);
	} else {
		mergeWithExistingFile(
			targetPath,
			adjustedTemplateLines,
			storeTasksInGit,
			log
		);
	}
}

export default manageGitignoreFile;
export {
	manageGitignoreFile,
	normalizeLine,
	isTaskLine,
	buildTaskFilesSection,
	TASK_FILES_COMMENT,
	TASK_JSON_PATTERN,
	TASK_DIR_PATTERN
};

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/services/template-engine.test.ts:
--------------------------------------------------------------------------------

```typescript
import { beforeEach, describe, expect, it } from 'vitest';
import { TemplateEngine } from './template-engine.js';

describe('TemplateEngine', () => {
	let templateEngine: TemplateEngine;

	beforeEach(() => {
		templateEngine = new TemplateEngine();
	});

	describe('constructor and initialization', () => {
		it('should initialize with default templates', () => {
			expect(templateEngine).toBeDefined();
		});

		it('should accept custom templates in constructor', () => {
			const customTemplate = '{{type}}({{scope}}): {{description}}';
			const engine = new TemplateEngine({ commitMessage: customTemplate });

			const result = engine.render('commitMessage', {
				type: 'feat',
				scope: 'core',
				description: 'add feature'
			});

			expect(result).toBe('feat(core): add feature');
		});
	});

	describe('render', () => {
		it('should render simple template with single variable', () => {
			const template = 'Hello {{name}}';
			const result = templateEngine.render('test', { name: 'World' }, template);

			expect(result).toBe('Hello World');
		});

		it('should render template with multiple variables', () => {
			const template = '{{type}}({{scope}}): {{description}}';
			const result = templateEngine.render(
				'test',
				{
					type: 'feat',
					scope: 'api',
					description: 'add endpoint'
				},
				template
			);

			expect(result).toBe('feat(api): add endpoint');
		});

		it('should handle missing variables by leaving placeholder', () => {
			const template = 'Hello {{name}} from {{location}}';
			const result = templateEngine.render('test', { name: 'Alice' }, template);

			expect(result).toBe('Hello Alice from {{location}}');
		});

		it('should handle empty variable values', () => {
			const template = '{{prefix}}{{message}}';
			const result = templateEngine.render(
				'test',
				{
					prefix: '',
					message: 'hello'
				},
				template
			);

			expect(result).toBe('hello');
		});

		it('should handle numeric values', () => {
			const template = 'Count: {{count}}';
			const result = templateEngine.render('test', { count: 42 }, template);

			expect(result).toBe('Count: 42');
		});

		it('should handle boolean values', () => {
			const template = 'Active: {{active}}';
			const result = templateEngine.render('test', { active: true }, template);

			expect(result).toBe('Active: true');
		});
	});

	describe('setTemplate', () => {
		it('should set and use custom template', () => {
			templateEngine.setTemplate('custom', 'Value: {{value}}');
			const result = templateEngine.render('custom', { value: '123' });

			expect(result).toBe('Value: 123');
		});

		it('should override existing template', () => {
			templateEngine.setTemplate('commitMessage', 'Custom: {{msg}}');
			const result = templateEngine.render('commitMessage', { msg: 'hello' });

			expect(result).toBe('Custom: hello');
		});
	});

	describe('getTemplate', () => {
		it('should return existing template', () => {
			templateEngine.setTemplate('test', 'Template: {{value}}');
			const template = templateEngine.getTemplate('test');

			expect(template).toBe('Template: {{value}}');
		});

		it('should return undefined for non-existent template', () => {
			const template = templateEngine.getTemplate('nonexistent');

			expect(template).toBeUndefined();
		});
	});

	describe('hasTemplate', () => {
		it('should return true for existing template', () => {
			templateEngine.setTemplate('test', 'Template');

			expect(templateEngine.hasTemplate('test')).toBe(true);
		});

		it('should return false for non-existent template', () => {
			expect(templateEngine.hasTemplate('nonexistent')).toBe(false);
		});
	});

	describe('validateTemplate', () => {
		it('should validate template with all required variables', () => {
			const template = '{{type}}({{scope}}): {{description}}';
			const requiredVars = ['type', 'scope', 'description'];

			const result = templateEngine.validateTemplate(template, requiredVars);

			expect(result.isValid).toBe(true);
			expect(result.missingVars).toEqual([]);
		});

		it('should detect missing required variables', () => {
			const template = '{{type}}: {{description}}';
			const requiredVars = ['type', 'scope', 'description'];

			const result = templateEngine.validateTemplate(template, requiredVars);

			expect(result.isValid).toBe(false);
			expect(result.missingVars).toEqual(['scope']);
		});

		it('should detect multiple missing variables', () => {
			const template = '{{type}}';
			const requiredVars = ['type', 'scope', 'description'];

			const result = templateEngine.validateTemplate(template, requiredVars);

			expect(result.isValid).toBe(false);
			expect(result.missingVars).toEqual(['scope', 'description']);
		});

		it('should handle optional variables in template', () => {
			const template = '{{type}}({{scope}}): {{description}} [{{taskId}}]';
			const requiredVars = ['type', 'scope', 'description'];

			const result = templateEngine.validateTemplate(template, requiredVars);

			expect(result.isValid).toBe(true);
			expect(result.missingVars).toEqual([]);
		});
	});

	describe('extractVariables', () => {
		it('should extract all variables from template', () => {
			const template = '{{type}}({{scope}}): {{description}}';
			const variables = templateEngine.extractVariables(template);

			expect(variables).toEqual(['type', 'scope', 'description']);
		});

		it('should extract unique variables only', () => {
			const template = '{{name}} and {{name}} with {{other}}';
			const variables = templateEngine.extractVariables(template);

			expect(variables).toEqual(['name', 'other']);
		});

		it('should return empty array for template without variables', () => {
			const template = 'Static text with no variables';
			const variables = templateEngine.extractVariables(template);

			expect(variables).toEqual([]);
		});

		it('should handle template with whitespace in placeholders', () => {
			const template = '{{ type }} and {{ scope }}';
			const variables = templateEngine.extractVariables(template);

			expect(variables).toEqual(['type', 'scope']);
		});
	});

	describe('edge cases', () => {
		it('should handle empty template', () => {
			const result = templateEngine.render('test', { name: 'value' }, '');

			expect(result).toBe('');
		});

		it('should handle template with no variables', () => {
			const template = 'Static text';
			const result = templateEngine.render('test', {}, template);

			expect(result).toBe('Static text');
		});

		it('should handle empty variables object', () => {
			const template = 'Hello {{name}}';
			const result = templateEngine.render('test', {}, template);

			expect(result).toBe('Hello {{name}}');
		});

		it('should handle special characters in values', () => {
			const template = 'Value: {{value}}';
			const result = templateEngine.render(
				'test',
				{
					value: 'hello$world{test}'
				},
				template
			);

			expect(result).toBe('Value: hello$world{test}');
		});

		it('should handle multiline templates', () => {
			const template = '{{type}}: {{description}}\n\n{{body}}';
			const result = templateEngine.render(
				'test',
				{
					type: 'feat',
					description: 'add feature',
					body: 'Details here'
				},
				template
			);

			expect(result).toBe('feat: add feature\n\nDetails here');
		});
	});

	describe('default commit message template', () => {
		it('should have default commit message template', () => {
			const template = templateEngine.getTemplate('commitMessage');

			expect(template).toBeDefined();
			expect(template).toContain('{{type}}');
			expect(template).toContain('{{description}}');
		});

		it('should render default commit message template', () => {
			const result = templateEngine.render('commitMessage', {
				type: 'feat',
				scope: 'core',
				description: 'implement feature',
				body: 'Additional details',
				taskId: '5.1'
			});

			expect(result).toContain('feat');
			expect(result).toContain('core');
			expect(result).toContain('implement feature');
		});
	});
});

```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/expand-all.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Tests for the expand-all MCP tool
 *
 * Note: This test does NOT test the actual implementation. It tests that:
 * 1. The tool is registered correctly with the correct parameters
 * 2. Arguments are passed correctly to expandAllTasksDirect
 * 3. Error handling works as expected
 *
 * We do NOT import the real implementation - everything is mocked
 */

import { jest } from '@jest/globals';

// Mock EVERYTHING
const mockExpandAllTasksDirect = jest.fn();
jest.mock('../../../../mcp-server/src/core/task-master-core.js', () => ({
	expandAllTasksDirect: mockExpandAllTasksDirect
}));

const mockHandleApiResult = jest.fn((result) => result);
const mockGetProjectRootFromSession = jest.fn(() => '/mock/project/root');
const mockCreateErrorResponse = jest.fn((msg) => ({
	success: false,
	error: { code: 'ERROR', message: msg }
}));
const mockWithNormalizedProjectRoot = jest.fn((fn) => fn);

jest.mock('../../../../mcp-server/src/tools/utils.js', () => ({
	getProjectRootFromSession: mockGetProjectRootFromSession,
	handleApiResult: mockHandleApiResult,
	createErrorResponse: mockCreateErrorResponse,
	withNormalizedProjectRoot: mockWithNormalizedProjectRoot
}));

// Mock the z object from zod
const mockZod = {
	object: jest.fn(() => mockZod),
	string: jest.fn(() => mockZod),
	number: jest.fn(() => mockZod),
	boolean: jest.fn(() => mockZod),
	optional: jest.fn(() => mockZod),
	describe: jest.fn(() => mockZod),
	_def: {
		shape: () => ({
			num: {},
			research: {},
			prompt: {},
			force: {},
			tag: {},
			projectRoot: {}
		})
	}
};

jest.mock('zod', () => ({
	z: mockZod
}));

// DO NOT import the real module - create a fake implementation
// This is the fake implementation of registerExpandAllTool
const registerExpandAllTool = (server) => {
	// Create simplified version of the tool config
	const toolConfig = {
		name: 'expand_all',
		description: 'Use Taskmaster to expand all eligible pending tasks',
		parameters: mockZod,

		// Create a simplified mock of the execute function
		execute: mockWithNormalizedProjectRoot(async (args, context) => {
			const { log, session } = context;

			try {
				log.info &&
					log.info(`Starting expand-all with args: ${JSON.stringify(args)}`);

				// Call expandAllTasksDirect
				const result = await mockExpandAllTasksDirect(args, log, { session });

				// Handle result
				return mockHandleApiResult(result, log);
			} catch (error) {
				log.error && log.error(`Error in expand-all tool: ${error.message}`);
				return mockCreateErrorResponse(error.message);
			}
		})
	};

	// Register the tool with the server
	server.addTool(toolConfig);
};

describe('MCP Tool: expand-all', () => {
	// Create mock server
	let mockServer;
	let executeFunction;

	// Create mock logger
	const mockLogger = {
		debug: jest.fn(),
		info: jest.fn(),
		warn: jest.fn(),
		error: jest.fn()
	};

	// Test data
	const validArgs = {
		num: 3,
		research: true,
		prompt: 'additional context',
		force: false,
		tag: 'master',
		projectRoot: '/test/project'
	};

	// Standard responses
	const successResponse = {
		success: true,
		data: {
			message:
				'Expand all operation completed. Expanded: 2, Failed: 0, Skipped: 1',
			details: {
				expandedCount: 2,
				failedCount: 0,
				skippedCount: 1,
				tasksToExpand: 3,
				telemetryData: {
					commandName: 'expand-all-tasks',
					totalCost: 0.15,
					totalTokens: 2500
				}
			}
		}
	};

	const errorResponse = {
		success: false,
		error: {
			code: 'EXPAND_ALL_ERROR',
			message: 'Failed to expand tasks'
		}
	};

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

		// Create mock server
		mockServer = {
			addTool: jest.fn((config) => {
				executeFunction = config.execute;
			})
		};

		// Setup default successful response
		mockExpandAllTasksDirect.mockResolvedValue(successResponse);

		// Register the tool
		registerExpandAllTool(mockServer);
	});

	test('should register the tool correctly', () => {
		// Verify tool was registered
		expect(mockServer.addTool).toHaveBeenCalledWith(
			expect.objectContaining({
				name: 'expand_all',
				description: expect.stringContaining('expand all eligible pending'),
				parameters: expect.any(Object),
				execute: expect.any(Function)
			})
		);

		// Verify the tool config was passed
		const toolConfig = mockServer.addTool.mock.calls[0][0];
		expect(toolConfig).toHaveProperty('parameters');
		expect(toolConfig).toHaveProperty('execute');
	});

	test('should execute the tool with valid parameters', async () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Execute the function
		const result = await executeFunction(validArgs, mockContext);

		// Verify expandAllTasksDirect was called with correct arguments
		expect(mockExpandAllTasksDirect).toHaveBeenCalledWith(
			validArgs,
			mockLogger,
			{ session: mockContext.session }
		);

		// Verify handleApiResult was called
		expect(mockHandleApiResult).toHaveBeenCalledWith(
			successResponse,
			mockLogger
		);
		expect(result).toEqual(successResponse);
	});

	test('should handle expand all with no eligible tasks', async () => {
		// Arrange
		const mockDirectResult = {
			success: true,
			data: {
				message:
					'Expand all operation completed. Expanded: 0, Failed: 0, Skipped: 0',
				details: {
					expandedCount: 0,
					failedCount: 0,
					skippedCount: 0,
					tasksToExpand: 0,
					telemetryData: null
				}
			}
		};

		mockExpandAllTasksDirect.mockResolvedValue(mockDirectResult);
		mockHandleApiResult.mockReturnValue({
			success: true,
			data: mockDirectResult.data
		});

		// Act
		const result = await executeFunction(validArgs, {
			log: mockLogger,
			session: { workingDirectory: '/test' }
		});

		// Assert
		expect(result.success).toBe(true);
		expect(result.data.details.expandedCount).toBe(0);
		expect(result.data.details.tasksToExpand).toBe(0);
	});

	test('should handle expand all with mixed success/failure', async () => {
		// Arrange
		const mockDirectResult = {
			success: true,
			data: {
				message:
					'Expand all operation completed. Expanded: 2, Failed: 1, Skipped: 0',
				details: {
					expandedCount: 2,
					failedCount: 1,
					skippedCount: 0,
					tasksToExpand: 3,
					telemetryData: {
						commandName: 'expand-all-tasks',
						totalCost: 0.1,
						totalTokens: 1500
					}
				}
			}
		};

		mockExpandAllTasksDirect.mockResolvedValue(mockDirectResult);
		mockHandleApiResult.mockReturnValue({
			success: true,
			data: mockDirectResult.data
		});

		// Act
		const result = await executeFunction(validArgs, {
			log: mockLogger,
			session: { workingDirectory: '/test' }
		});

		// Assert
		expect(result.success).toBe(true);
		expect(result.data.details.expandedCount).toBe(2);
		expect(result.data.details.failedCount).toBe(1);
	});

	test('should handle errors from expandAllTasksDirect', async () => {
		// Arrange
		mockExpandAllTasksDirect.mockRejectedValue(
			new Error('Direct function error')
		);

		// Act
		const result = await executeFunction(validArgs, {
			log: mockLogger,
			session: { workingDirectory: '/test' }
		});

		// Assert
		expect(mockLogger.error).toHaveBeenCalledWith(
			expect.stringContaining('Error in expand-all tool')
		);
		expect(mockCreateErrorResponse).toHaveBeenCalledWith(
			'Direct function error'
		);
	});

	test('should handle different argument combinations', async () => {
		// Test with minimal args
		const minimalArgs = {
			projectRoot: '/test/project'
		};

		// Act
		await executeFunction(minimalArgs, {
			log: mockLogger,
			session: { workingDirectory: '/test' }
		});

		// Assert
		expect(mockExpandAllTasksDirect).toHaveBeenCalledWith(
			minimalArgs,
			mockLogger,
			expect.any(Object)
		);
	});

	test('should use withNormalizedProjectRoot wrapper correctly', () => {
		// Verify that the execute function is wrapped with withNormalizedProjectRoot
		expect(mockWithNormalizedProjectRoot).toHaveBeenCalledWith(
			expect.any(Function)
		);
	});
});

```

--------------------------------------------------------------------------------
/apps/extension/package.json:
--------------------------------------------------------------------------------

```json
{
	"name": "extension",
	"private": true,
	"displayName": "TaskMaster",
	"description": "A visual Kanban board interface for TaskMaster projects in VS Code",
	"version": "0.26.0",
	"publisher": "Hamster",
	"icon": "assets/icon.png",
	"engines": {
		"vscode": "^1.93.0"
	},
	"categories": ["AI", "Visualization", "Education", "Other"],
	"main": "./dist/extension.js",
	"activationEvents": ["onStartupFinished", "workspaceContains:.taskmaster/**"],
	"contributes": {
		"viewsContainers": {
			"activitybar": [
				{
					"id": "taskmaster",
					"title": "TaskMaster",
					"icon": "assets/sidebar-icon.svg"
				}
			]
		},
		"views": {
			"taskmaster": [
				{
					"id": "taskmaster.welcome",
					"name": "TaskMaster",
					"type": "webview"
				}
			]
		},
		"commands": [
			{
				"command": "tm.showKanbanBoard",
				"title": "TaskMaster: Show Board",
				"icon": "$(checklist)"
			},
			{
				"command": "tm.checkConnection",
				"title": "TaskMaster: Check Connection"
			},
			{
				"command": "tm.reconnect",
				"title": "TaskMaster: Reconnect"
			},
			{
				"command": "tm.openSettings",
				"title": "TaskMaster: Open Settings"
			}
		],
		"menus": {
			"view/title": [
				{
					"command": "tm.showKanbanBoard",
					"when": "view == taskmaster.welcome",
					"group": "navigation"
				}
			]
		},
		"configuration": {
			"title": "TaskMaster Kanban",
			"properties": {
				"taskmaster.mcp.command": {
					"type": "string",
					"default": "node",
					"description": "The command to execute for the MCP server (e.g., 'node' for bundled server or 'npx' for remote)."
				},
				"taskmaster.mcp.args": {
					"type": "array",
					"items": {
						"type": "string"
					},
					"default": [],
					"description": "Arguments for the MCP server (leave empty to use bundled server)."
				},
				"taskmaster.mcp.cwd": {
					"type": "string",
					"description": "Working directory for the TaskMaster MCP server (defaults to workspace root)"
				},
				"taskmaster.mcp.env": {
					"type": "object",
					"description": "Environment variables for the TaskMaster MCP server"
				},
				"taskmaster.mcp.timeout": {
					"type": "number",
					"default": 30000,
					"minimum": 1000,
					"maximum": 300000,
					"description": "Connection timeout in milliseconds"
				},
				"taskmaster.mcp.maxReconnectAttempts": {
					"type": "number",
					"default": 5,
					"minimum": 1,
					"maximum": 20,
					"description": "Maximum number of reconnection attempts"
				},
				"taskmaster.mcp.reconnectBackoffMs": {
					"type": "number",
					"default": 1000,
					"minimum": 100,
					"maximum": 10000,
					"description": "Initial reconnection backoff delay in milliseconds"
				},
				"taskmaster.mcp.maxBackoffMs": {
					"type": "number",
					"default": 30000,
					"minimum": 1000,
					"maximum": 300000,
					"description": "Maximum reconnection backoff delay in milliseconds"
				},
				"taskmaster.mcp.healthCheckIntervalMs": {
					"type": "number",
					"default": 15000,
					"minimum": 5000,
					"maximum": 60000,
					"description": "Health check interval in milliseconds"
				},
				"taskmaster.mcp.requestTimeoutMs": {
					"type": "number",
					"default": 300000,
					"minimum": 30000,
					"maximum": 600000,
					"description": "MCP request timeout in milliseconds (default: 5 minutes)"
				},
				"taskmaster.ui.autoRefresh": {
					"type": "boolean",
					"default": true,
					"description": "Automatically refresh tasks from the server"
				},
				"taskmaster.ui.refreshIntervalMs": {
					"type": "number",
					"default": 10000,
					"minimum": 1000,
					"maximum": 300000,
					"description": "Auto-refresh interval in milliseconds"
				},
				"taskmaster.ui.theme": {
					"type": "string",
					"enum": ["auto", "light", "dark"],
					"default": "auto",
					"description": "UI theme preference"
				},
				"taskmaster.ui.showCompletedTasks": {
					"type": "boolean",
					"default": true,
					"description": "Show completed tasks in the Kanban board"
				},
				"taskmaster.ui.taskDisplayLimit": {
					"type": "number",
					"default": 100,
					"minimum": 1,
					"maximum": 1000,
					"description": "Maximum number of tasks to display"
				},
				"taskmaster.ui.showPriority": {
					"type": "boolean",
					"default": true,
					"description": "Show task priority indicators"
				},
				"taskmaster.ui.showTaskIds": {
					"type": "boolean",
					"default": true,
					"description": "Show task IDs in the interface"
				},
				"taskmaster.performance.maxConcurrentRequests": {
					"type": "number",
					"default": 5,
					"minimum": 1,
					"maximum": 20,
					"description": "Maximum number of concurrent MCP requests"
				},
				"taskmaster.performance.requestTimeoutMs": {
					"type": "number",
					"default": 30000,
					"minimum": 1000,
					"maximum": 300000,
					"description": "Request timeout in milliseconds"
				},
				"taskmaster.performance.cacheTasksMs": {
					"type": "number",
					"default": 5000,
					"minimum": 0,
					"maximum": 60000,
					"description": "Task cache duration in milliseconds"
				},
				"taskmaster.performance.lazyLoadThreshold": {
					"type": "number",
					"default": 50,
					"minimum": 10,
					"maximum": 500,
					"description": "Number of tasks before enabling lazy loading"
				},
				"taskmaster.debug.enableLogging": {
					"type": "boolean",
					"default": true,
					"description": "Enable debug logging"
				},
				"taskmaster.debug.logLevel": {
					"type": "string",
					"enum": ["error", "warn", "info", "debug"],
					"default": "info",
					"description": "Logging level"
				},
				"taskmaster.debug.enableConnectionMetrics": {
					"type": "boolean",
					"default": true,
					"description": "Enable connection performance metrics"
				},
				"taskmaster.debug.saveEventLogs": {
					"type": "boolean",
					"default": false,
					"description": "Save event logs to files"
				},
				"taskmaster.debug.maxEventLogSize": {
					"type": "number",
					"default": 1000,
					"minimum": 10,
					"maximum": 10000,
					"description": "Maximum number of events to keep in memory"
				}
			}
		}
	},
	"scripts": {
		"vscode:prepublish": "npm run build",
		"build": "npm run build:js && npm run build:css",
		"build:js": "node ./esbuild.js --production",
		"build:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --minify",
		"dev": "npm run watch",
		"package": "npm exec node ./package.mjs",
		"package:direct": "node ./package.mjs",
		"debug:env": "node ./debug-env.mjs",
		"compile": "node ./esbuild.js",
		"watch": "npm run watch:js & npm run watch:css",
		"watch:js": "node ./esbuild.js --watch",
		"watch:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --watch",
		"typecheck": "tsc --noEmit"
	},
	"devDependencies": {
		"@dnd-kit/core": "^6.3.1",
		"@dnd-kit/modifiers": "^9.0.0",
		"@modelcontextprotocol/sdk": "1.13.3",
		"@radix-ui/react-collapsible": "^1.1.11",
		"@radix-ui/react-dropdown-menu": "^2.1.15",
		"@radix-ui/react-label": "^2.1.7",
		"@radix-ui/react-portal": "^1.1.9",
		"@radix-ui/react-scroll-area": "^1.2.9",
		"@radix-ui/react-separator": "^1.1.7",
		"@radix-ui/react-slot": "^1.2.3",
		"@tailwindcss/postcss": "^4.1.11",
		"@tanstack/react-query": "^5.83.0",
		"@types/mocha": "^10.0.10",
		"@types/node": "^22.10.5",
		"@types/react": "19.1.8",
		"@types/react-dom": "19.1.6",
		"@types/vscode": "^1.101.0",
		"@vscode/test-cli": "^0.0.11",
		"@vscode/test-electron": "^2.5.2",
		"@vscode/vsce": "^2.32.0",
		"autoprefixer": "10.4.21",
		"class-variance-authority": "^0.7.1",
		"clsx": "^2.1.1",
		"esbuild": "^0.25.3",
		"esbuild-postcss": "^0.0.4",
		"fs-extra": "^11.3.0",
		"lucide-react": "^0.525.0",
		"npm-run-all": "^4.1.5",
		"postcss": "8.5.6",
		"react": "^19.0.0",
		"react-dom": "^19.0.0",
		"tailwind-merge": "^3.3.1",
		"tailwindcss": "4.1.11",
		"typescript": "^5.9.2",
		"@tm/core": "*",
		"task-master-ai": "*"
	},
	"overrides": {
		"glob@<8": "^10.4.5",
		"inflight": "npm:@tootallnate/once@2"
	}
}

```

--------------------------------------------------------------------------------
/apps/extension/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------

```typescript
'use client';

import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
import type * as React from 'react';

import { cn } from '@/lib/utils';

const DROPDOWN_MENU_ITEM_CLASSES =
	"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4";

const DROPDOWN_MENU_SUB_CONTENT_CLASSES =
	'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg';

function DropdownMenu({
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
	return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}

function DropdownMenuPortal({
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
	return (
		<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
	);
}

function DropdownMenuTrigger({
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
	return (
		<DropdownMenuPrimitive.Trigger
			data-slot="dropdown-menu-trigger"
			{...props}
		/>
	);
}

function DropdownMenuContent({
	className,
	sideOffset = 4,
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
	return (
		<DropdownMenuPrimitive.Portal>
			<DropdownMenuPrimitive.Content
				data-slot="dropdown-menu-content"
				sideOffset={sideOffset}
				className={cn(
					'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
					className
				)}
				{...props}
			/>
		</DropdownMenuPrimitive.Portal>
	);
}

function DropdownMenuGroup({
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
	return (
		<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
	);
}

function DropdownMenuItem({
	className,
	inset,
	variant = 'default',
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
	inset?: boolean;
	variant?: 'default' | 'destructive';
}) {
	return (
		<DropdownMenuPrimitive.Item
			data-slot="dropdown-menu-item"
			data-inset={inset}
			data-variant={variant}
			className={cn(DROPDOWN_MENU_ITEM_CLASSES, className)}
			{...props}
		/>
	);
}

function DropdownMenuCheckboxItem({
	className,
	children,
	checked,
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
	return (
		<DropdownMenuPrimitive.CheckboxItem
			data-slot="dropdown-menu-checkbox-item"
			className={cn(
				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
				className
			)}
			checked={checked}
			{...props}
		>
			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
				<DropdownMenuPrimitive.ItemIndicator>
					<CheckIcon className="size-4" />
				</DropdownMenuPrimitive.ItemIndicator>
			</span>
			{children}
		</DropdownMenuPrimitive.CheckboxItem>
	);
}

function DropdownMenuRadioGroup({
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
	return (
		<DropdownMenuPrimitive.RadioGroup
			data-slot="dropdown-menu-radio-group"
			{...props}
		/>
	);
}

function DropdownMenuRadioItem({
	className,
	children,
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
	return (
		<DropdownMenuPrimitive.RadioItem
			data-slot="dropdown-menu-radio-item"
			className={cn(
				"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
				className
			)}
			{...props}
		>
			<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
				<DropdownMenuPrimitive.ItemIndicator>
					<CircleIcon className="size-2 fill-current" />
				</DropdownMenuPrimitive.ItemIndicator>
			</span>
			{children}
		</DropdownMenuPrimitive.RadioItem>
	);
}

function DropdownMenuLabel({
	className,
	inset,
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
	inset?: boolean;
}) {
	return (
		<DropdownMenuPrimitive.Label
			data-slot="dropdown-menu-label"
			data-inset={inset}
			className={cn(
				'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
				className
			)}
			{...props}
		/>
	);
}

function DropdownMenuSeparator({
	className,
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
	return (
		<DropdownMenuPrimitive.Separator
			data-slot="dropdown-menu-separator"
			className={cn('bg-border -mx-1 my-1 h-px', className)}
			{...props}
		/>
	);
}

function DropdownMenuShortcut({
	className,
	...props
}: React.ComponentProps<'span'>) {
	return (
		<span
			data-slot="dropdown-menu-shortcut"
			className={cn(
				'text-muted-foreground ml-auto text-xs tracking-widest',
				className
			)}
			{...props}
		/>
	);
}

function DropdownMenuSub({
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
	return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}

function DropdownMenuSubTrigger({
	className,
	inset,
	children,
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
	inset?: boolean;
}) {
	return (
		<DropdownMenuPrimitive.SubTrigger
			data-slot="dropdown-menu-sub-trigger"
			data-inset={inset}
			className={cn(
				'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
				className
			)}
			{...props}
		>
			{children}
			<ChevronRightIcon className="ml-auto size-4" />
		</DropdownMenuPrimitive.SubTrigger>
	);
}

function DropdownMenuSubContent({
	className,
	...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
	return (
		<DropdownMenuPrimitive.SubContent
			data-slot="dropdown-menu-sub-content"
			className={cn(DROPDOWN_MENU_SUB_CONTENT_CLASSES, className)}
			{...props}
		/>
	);
}

export {
	DropdownMenu,
	DropdownMenuPortal,
	DropdownMenuTrigger,
	DropdownMenuContent,
	DropdownMenuGroup,
	DropdownMenuLabel,
	DropdownMenuItem,
	DropdownMenuCheckboxItem,
	DropdownMenuRadioGroup,
	DropdownMenuRadioItem,
	DropdownMenuSeparator,
	DropdownMenuShortcut,
	DropdownMenuSub,
	DropdownMenuSubTrigger,
	DropdownMenuSubContent
};

```

--------------------------------------------------------------------------------
/src/prompts/update-task.json:
--------------------------------------------------------------------------------

```json
{
	"id": "update-task",
	"version": "1.0.0",
	"description": "Update a single task with new information, supporting full updates and append mode",
	"metadata": {
		"author": "system",
		"created": "2024-01-01T00:00:00Z",
		"updated": "2024-01-01T00:00:00Z",
		"tags": ["update", "single-task", "modification", "append"]
	},
	"parameters": {
		"task": {
			"type": "object",
			"required": true,
			"description": "The task to update"
		},
		"taskJson": {
			"type": "string",
			"required": true,
			"description": "JSON string representation of the task"
		},
		"updatePrompt": {
			"type": "string",
			"required": true,
			"description": "Description of changes to apply"
		},
		"appendMode": {
			"type": "boolean",
			"default": false,
			"description": "Whether to append to details or do full update"
		},
		"useResearch": {
			"type": "boolean",
			"default": false,
			"description": "Use research mode"
		},
		"currentDetails": {
			"type": "string",
			"default": "(No existing details)",
			"description": "Current task details for context"
		},
		"gatheredContext": {
			"type": "string",
			"default": "",
			"description": "Additional project context"
		},
		"hasCodebaseAnalysis": {
			"type": "boolean",
			"required": false,
			"default": false,
			"description": "Whether codebase analysis is available"
		},
		"projectRoot": {
			"type": "string",
			"required": false,
			"default": "",
			"description": "Project root path for context"
		}
	},
	"prompts": {
		"default": {
			"system": "You are an AI assistant helping to update a software development task based on new context.{{#if useResearch}} You have access to current best practices and latest technical information to provide research-backed updates.{{/if}}\nYou will be given a task and a prompt describing changes or new implementation details.\nYour job is to update the task to reflect these changes, while preserving its basic structure.\n\nGuidelines:\n1. VERY IMPORTANT: NEVER change the title of the task - keep it exactly as is\n2. Maintain the same ID, status, and dependencies unless specifically mentioned in the prompt{{#if useResearch}}\n3. Research and update the description, details, and test strategy with current best practices\n4. Include specific versions, libraries, and approaches that are current and well-tested{{/if}}{{#if (not useResearch)}}\n3. Update the description, details, and test strategy to reflect the new information\n4. Do not change anything unnecessarily - just adapt what needs to change based on the prompt{{/if}}\n5. Return the complete updated task\n6. VERY IMPORTANT: Preserve all subtasks marked as \"done\" or \"completed\" - do not modify their content\n7. For tasks with completed subtasks, build upon what has already been done rather than rewriting everything\n8. If an existing completed subtask needs to be changed/undone based on the new context, DO NOT modify it directly\n9. Instead, add a new subtask that clearly indicates what needs to be changed or replaced\n10. Use the existence of completed subtasks as an opportunity to make new subtasks more specific and targeted\n11. Ensure any new subtasks have unique IDs that don't conflict with existing ones\n12. CRITICAL: For subtask IDs, use ONLY numeric values (1, 2, 3, etc.) NOT strings (\"1\", \"2\", \"3\")\n13. CRITICAL: Subtask IDs should start from 1 and increment sequentially (1, 2, 3...) - do NOT use parent task ID as prefix{{#if useResearch}}\n14. Include links to documentation or resources where helpful\n15. Focus on practical, implementable solutions using current technologies{{/if}}\n\nThe changes described in the prompt should be thoughtfully applied to make the task more accurate and actionable.",
			"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before updating the task:\n\n1. Use the Glob tool to explore the project structure (e.g., \"**/*.js\", \"**/*.json\", \"**/README.md\")\n2. Use the Grep tool to search for existing implementations, patterns, and technologies\n3. Use the Read tool to examine relevant files and understand current implementation\n4. Analyze how the task changes relate to the existing codebase\n\nBased on your analysis:\n- Update task details to reference specific files, functions, or patterns from the codebase\n- Ensure implementation details align with the project's current architecture\n- Include specific code examples or file references where appropriate\n- Consider how changes impact existing components\n\nProject Root: {{projectRoot}}\n\n{{/if}}Here is the task to update{{#if useResearch}} with research-backed information{{/if}}:\n{{{taskJson}}}\n\nPlease {{#if useResearch}}research and {{/if}}update this task based on the following {{#if useResearch}}context:\n{{updatePrompt}}\n\nIncorporate current best practices, latest stable versions, and proven approaches.{{/if}}{{#if (not useResearch)}}new context:\n{{updatePrompt}}{{/if}}\n\nIMPORTANT: {{#if useResearch}}Preserve any subtasks marked as \"done\" or \"completed\".{{/if}}{{#if (not useResearch)}}In the task JSON above, any subtasks with \"status\": \"done\" or \"status\": \"completed\" should be preserved exactly as is. Build your changes around these completed items.{{/if}}\n{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}\n{{/if}}\n\nReturn the complete updated task{{#if useResearch}} with research-backed improvements{{/if}}.\n\nIMPORTANT: Your response must be a JSON object with a single property named \"task\" containing the updated task object."
		},
		"append": {
			"condition": "appendMode === true",
			"system": "You are an AI assistant helping to append additional information to a software development task. You will be provided with the task's existing details, context, and a user request string.\n\nYour Goal: Based *only* on the user's request and all the provided context (including existing details if relevant to the request), GENERATE the new text content that should be added to the task's details.\nFocus *only* on generating the substance of the update.\n\nOutput Requirements:\n1. Return *only* the newly generated text content as a plain string. Do NOT return a JSON object or any other structured data.\n2. Your string response should NOT include any of the task's original details, unless the user's request explicitly asks to rephrase, summarize, or directly modify existing text.\n3. Do NOT include any timestamps, XML-like tags, markdown, or any other special formatting in your string response.\n4. Ensure the generated text is concise yet complete for the update based on the user request. Avoid conversational fillers or explanations about what you are doing (e.g., do not start with \"Okay, here's the update...\").",
			"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before generating the task update:\n\n1. Use the Glob tool to explore the project structure (e.g., \"**/*.js\", \"**/*.json\", \"**/README.md\")\n2. Use the Grep tool to search for existing implementations, patterns, and technologies\n3. Use the Read tool to examine relevant files and understand current implementation\n4. Analyze the current codebase to inform your update\n\nBased on your analysis:\n- Include specific file references, code patterns, or implementation details\n- Ensure suggestions align with the project's current architecture\n- Reference existing components or patterns when relevant\n\nProject Root: {{projectRoot}}\n\n{{/if}}Task Context:\n\nTask: {{{json task}}}\nCurrent Task Details (for context only):\n{{currentDetails}}\n\nUser Request: \"{{updatePrompt}}\"\n\nBased on the User Request and all the Task Context (including current task details provided above), what is the new information or text that should be appended to this task's details? Return this new text as a plain string.\n{{#if gatheredContext}}\n\n# Additional Project Context\n\n{{gatheredContext}}\n{{/if}}"
		}
	}
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/common/errors/task-master-error.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Base error class for Task Master operations
 * Provides comprehensive error handling with metadata, context, and serialization support
 */

/**
 * Error codes used throughout the Task Master system
 */
export const ERROR_CODES = {
	// File system errors
	FILE_NOT_FOUND: 'FILE_NOT_FOUND',
	FILE_READ_ERROR: 'FILE_READ_ERROR',
	FILE_WRITE_ERROR: 'FILE_WRITE_ERROR',

	// Parsing errors
	PARSE_ERROR: 'PARSE_ERROR',
	JSON_PARSE_ERROR: 'JSON_PARSE_ERROR',
	YAML_PARSE_ERROR: 'YAML_PARSE_ERROR',

	// Validation errors
	VALIDATION_ERROR: 'VALIDATION_ERROR',
	SCHEMA_VALIDATION_ERROR: 'SCHEMA_VALIDATION_ERROR',
	TYPE_VALIDATION_ERROR: 'TYPE_VALIDATION_ERROR',

	// API and network errors
	API_ERROR: 'API_ERROR',
	NETWORK_ERROR: 'NETWORK_ERROR',
	AUTHENTICATION_ERROR: 'AUTHENTICATION_ERROR',
	AUTHORIZATION_ERROR: 'AUTHORIZATION_ERROR',

	// Task management errors
	TASK_NOT_FOUND: 'TASK_NOT_FOUND',
	TASK_DEPENDENCY_ERROR: 'TASK_DEPENDENCY_ERROR',
	TASK_STATUS_ERROR: 'TASK_STATUS_ERROR',

	// Storage errors
	STORAGE_ERROR: 'STORAGE_ERROR',
	DATABASE_ERROR: 'DATABASE_ERROR',

	// Configuration errors
	CONFIG_ERROR: 'CONFIG_ERROR',
	MISSING_CONFIGURATION: 'MISSING_CONFIGURATION',
	INVALID_CONFIGURATION: 'INVALID_CONFIGURATION',

	// Provider errors
	PROVIDER_ERROR: 'PROVIDER_ERROR',
	PROVIDER_NOT_FOUND: 'PROVIDER_NOT_FOUND',
	PROVIDER_INITIALIZATION_ERROR: 'PROVIDER_INITIALIZATION_ERROR',

	// Generic errors
	INTERNAL_ERROR: 'INTERNAL_ERROR',
	INVALID_INPUT: 'INVALID_INPUT',
	NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
	UNKNOWN_ERROR: 'UNKNOWN_ERROR',
	NOT_FOUND: 'NOT_FOUND',

	// Context errors
	NO_BRIEF_SELECTED: 'NO_BRIEF_SELECTED'
} as const;

export type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];

/**
 * Error context interface for additional error metadata
 */
export interface ErrorContext {
	/** Additional details about the error */
	details?: any;
	/** Error timestamp */
	timestamp?: Date;
	/** Operation that failed */
	operation?: string;
	/** Resource identifier related to the error */
	resource?: string;
	/** Stack of operations leading to the error */
	operationStack?: string[];
	/** User-safe message for display */
	userMessage?: string;
	/** Internal error identifier for debugging */
	errorId?: string;
	/** Additional metadata */
	metadata?: Record<string, any>;
	/** Allow additional properties for flexibility */
	[key: string]: any;
}

/**
 * Serializable error representation
 */
export interface SerializableError {
	name: string;
	message: string;
	code: string;
	context: ErrorContext;
	stack?: string;
	cause?: SerializableError;
}

/**
 * Base error class for all Task Master operations
 *
 * Provides comprehensive error handling with:
 * - Error codes for programmatic handling
 * - Rich context and metadata support
 * - Error chaining with cause property
 * - Serialization for logging and transport
 * - Sanitization for user-facing messages
 *
 * @example
 * ```typescript
 * try {
 *   // Some operation that might fail
 *   throw new TaskMasterError(
 *     'Failed to parse task file',
 *     ERROR_CODES.PARSE_ERROR,
 *     {
 *       details: { filename: 'tasks.json', line: 42 },
 *       operation: 'parseTaskFile',
 *       userMessage: 'There was an error reading your task file'
 *     }
 *   );
 * } catch (error) {
 *   console.error(error.toJSON());
 *   throw new TaskMasterError(
 *     'Operation failed',
 *     ERROR_CODES.INTERNAL_ERROR,
 *     { operation: 'processTask' },
 *     error
 *   );
 * }
 * ```
 */
export class TaskMasterError extends Error {
	/** Error code for programmatic handling */
	public readonly code: string;

	/** Rich context and metadata */
	public readonly context: ErrorContext;

	/** Original error that caused this error (for error chaining) */
	public readonly cause?: Error;

	/** Timestamp when error was created */
	public readonly timestamp: Date;

	/**
	 * Create a new TaskMasterError
	 *
	 * @param message - Human-readable error message
	 * @param code - Error code from ERROR_CODES
	 * @param context - Additional error context and metadata
	 * @param cause - Original error that caused this error (for chaining)
	 */
	constructor(
		message: string,
		code: string = ERROR_CODES.UNKNOWN_ERROR,
		context: ErrorContext = {},
		cause?: Error
	) {
		super(message);

		// Set error name
		this.name = 'TaskMasterError';

		// Set properties
		this.code = code;
		this.cause = cause;
		this.timestamp = new Date();

		// Merge context with defaults
		this.context = {
			timestamp: this.timestamp,
			...context
		};

		// Fix prototype chain for proper instanceof checks
		Object.setPrototypeOf(this, TaskMasterError.prototype);

		// Maintain proper stack trace
		if (Error.captureStackTrace) {
			Error.captureStackTrace(this, TaskMasterError);
		}

		// If we have a cause error, append its stack trace
		if (cause?.stack) {
			this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
		}
	}

	/**
	 * Get a user-friendly error message
	 * Falls back to the main message if no user message is provided
	 */
	public getUserMessage(): string {
		return this.context.userMessage || this.message;
	}

	/**
	 * Get sanitized error details safe for user display
	 * Removes sensitive information and internal details
	 */
	public getSanitizedDetails(): Record<string, any> {
		const { details, resource, operation } = this.context;

		return {
			code: this.code,
			message: this.getUserMessage(),
			...(resource && { resource }),
			...(operation && { operation }),
			...(details &&
				typeof details === 'object' &&
				!this.containsSensitiveInfo(details) && { details })
		};
	}

	/**
	 * Check if error details contain potentially sensitive information
	 */
	private containsSensitiveInfo(obj: any): boolean {
		if (typeof obj !== 'object' || obj === null) return false;

		const sensitiveKeys = [
			'password',
			'token',
			'key',
			'secret',
			'auth',
			'credential'
		];
		const objString = JSON.stringify(obj).toLowerCase();

		return sensitiveKeys.some((key) => objString.includes(key));
	}

	/**
	 * Convert error to JSON for serialization
	 * Includes all error information for logging and debugging
	 */
	public toJSON(): SerializableError {
		const result: SerializableError = {
			name: this.name,
			message: this.message,
			code: this.code,
			context: this.context,
			stack: this.stack
		};

		// Include serialized cause if present
		if (this.cause) {
			if (this.cause instanceof TaskMasterError) {
				result.cause = this.cause.toJSON();
			} else {
				result.cause = {
					name: this.cause.name,
					message: this.cause.message,
					code: ERROR_CODES.UNKNOWN_ERROR,
					context: {},
					stack: this.cause.stack
				};
			}
		}

		return result;
	}

	/**
	 * Convert error to string representation
	 * Provides formatted output for logging and debugging
	 */
	public toString(): string {
		let result = `${this.name}[${this.code}]: ${this.message}`;

		if (this.context.operation) {
			result += ` (operation: ${this.context.operation})`;
		}

		if (this.context.resource) {
			result += ` (resource: ${this.context.resource})`;
		}

		if (this.cause) {
			result += `\nCaused by: ${this.cause.toString()}`;
		}

		return result;
	}

	/**
	 * Check if this error is of a specific code
	 */
	public is(code: string): boolean {
		return this.code === code;
	}

	/**
	 * Check if this error or any error in its cause chain is of a specific code
	 */
	public hasCode(code: string): boolean {
		if (this.is(code)) return true;

		if (this.cause instanceof TaskMasterError) {
			return this.cause.hasCode(code);
		}

		return false;
	}

	/**
	 * Create a new error with additional context
	 */
	public withContext(
		additionalContext: Partial<ErrorContext>
	): TaskMasterError {
		return new TaskMasterError(
			this.message,
			this.code,
			{ ...this.context, ...additionalContext },
			this.cause
		);
	}

	/**
	 * Create a new error wrapping this one as the cause
	 */
	public wrap(
		message: string,
		code: string = ERROR_CODES.INTERNAL_ERROR,
		context: ErrorContext = {}
	): TaskMasterError {
		return new TaskMasterError(message, code, context, this);
	}
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/git/services/scope-detector.test.ts:
--------------------------------------------------------------------------------

```typescript
import { beforeEach, describe, expect, it } from 'vitest';
import { ScopeDetector } from './scope-detector.js';

describe('ScopeDetector', () => {
	let scopeDetector: ScopeDetector;

	beforeEach(() => {
		scopeDetector = new ScopeDetector();
	});

	describe('detectScope', () => {
		it('should detect cli scope from CLI file changes', () => {
			const files = ['packages/cli/src/commands/start.ts'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('cli');
		});

		it('should detect core scope from core package changes', () => {
			const files = ['packages/tm-core/src/workflow/orchestrator.ts'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('core');
		});

		it('should detect test scope from test file changes', () => {
			const files = ['packages/tm-core/src/workflow/orchestrator.test.ts'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('test');
		});

		it('should detect docs scope from documentation changes', () => {
			const files = ['README.md', 'docs/guide.md'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('docs');
		});

		it('should detect config scope from configuration changes', () => {
			const files = ['tsconfig.json'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('config');
		});

		it('should detect workflow scope from workflow files', () => {
			const files = ['packages/tm-core/src/workflow/types.ts'];
			const scope = scopeDetector.detectScope(files);

			// Files within packages get the package scope (more specific than feature scope)
			expect(scope).toBe('core');
		});

		it('should detect git scope from git adapter files', () => {
			const files = ['packages/tm-core/src/git/git-adapter.ts'];
			const scope = scopeDetector.detectScope(files);

			// Files within packages get the package scope (more specific than feature scope)
			expect(scope).toBe('core');
		});

		it('should detect storage scope from storage files', () => {
			const files = ['packages/tm-core/src/storage/state-manager.ts'];
			const scope = scopeDetector.detectScope(files);

			// Files within packages get the package scope (more specific than feature scope)
			expect(scope).toBe('core');
		});

		it('should use most relevant scope when multiple files', () => {
			const files = [
				'packages/cli/src/commands/start.ts',
				'packages/cli/src/commands/stop.ts',
				'packages/tm-core/src/types.ts'
			];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('cli');
		});

		it('should handle mixed scopes by choosing highest priority', () => {
			const files = [
				'README.md',
				'packages/tm-core/src/workflow/orchestrator.ts'
			];
			const scope = scopeDetector.detectScope(files);

			// Core is higher priority than docs
			expect(scope).toBe('core');
		});

		it('should handle empty file list gracefully', () => {
			const files: string[] = [];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('repo');
		});

		it('should detect mcp scope from MCP server files', () => {
			const files = ['packages/mcp-server/src/tools.ts'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('mcp');
		});

		it('should detect auth scope from authentication files', () => {
			const files = ['packages/tm-core/src/auth/auth-manager.ts'];
			const scope = scopeDetector.detectScope(files);

			// Files within packages get the package scope (more specific than feature scope)
			expect(scope).toBe('core');
		});

		it('should detect deps scope from dependency changes', () => {
			const files = ['pnpm-lock.yaml'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('deps');
		});
	});

	describe('detectScopeWithCustomRules', () => {
		it('should use custom scope mapping rules', () => {
			const customRules: Record<string, number> = {
				custom: 100
			};

			const customDetector = new ScopeDetector(
				{
					'custom/**': 'custom'
				},
				customRules
			);

			const files = ['custom/file.ts'];
			const scope = customDetector.detectScope(files);

			expect(scope).toBe('custom');
		});

		it('should override default priorities with custom priorities', () => {
			const customPriorities: Record<string, number> = {
				docs: 100, // Make docs highest priority
				core: 10
			};

			const customDetector = new ScopeDetector(undefined, customPriorities);

			const files = [
				'README.md',
				'packages/tm-core/src/workflow/orchestrator.ts'
			];
			const scope = customDetector.detectScope(files);

			expect(scope).toBe('docs');
		});
	});

	describe('getAllMatchingScopes', () => {
		it('should return all matching scopes for files', () => {
			const files = [
				'packages/cli/src/commands/start.ts',
				'packages/tm-core/src/workflow/orchestrator.ts',
				'README.md'
			];

			const scopes = scopeDetector.getAllMatchingScopes(files);

			expect(scopes).toContain('cli');
			expect(scopes).toContain('core');
			expect(scopes).toContain('docs');
			expect(scopes).toHaveLength(3);
		});

		it('should return unique scopes only', () => {
			const files = [
				'packages/cli/src/commands/start.ts',
				'packages/cli/src/commands/stop.ts'
			];

			const scopes = scopeDetector.getAllMatchingScopes(files);

			expect(scopes).toEqual(['cli']);
		});

		it('should return empty array for files with no matches', () => {
			const files = ['unknown/path/file.ts'];
			const scopes = scopeDetector.getAllMatchingScopes(files);

			expect(scopes).toEqual([]);
		});
	});

	describe('getScopePriority', () => {
		it('should return priority for known scope', () => {
			const priority = scopeDetector.getScopePriority('core');

			expect(priority).toBeGreaterThan(0);
		});

		it('should return 0 for unknown scope', () => {
			const priority = scopeDetector.getScopePriority('nonexistent');

			expect(priority).toBe(0);
		});

		it('should prioritize core > cli > test > docs', () => {
			const corePriority = scopeDetector.getScopePriority('core');
			const cliPriority = scopeDetector.getScopePriority('cli');
			const testPriority = scopeDetector.getScopePriority('test');
			const docsPriority = scopeDetector.getScopePriority('docs');

			expect(corePriority).toBeGreaterThan(cliPriority);
			expect(cliPriority).toBeGreaterThan(testPriority);
			expect(testPriority).toBeGreaterThan(docsPriority);
		});
	});

	describe('edge cases', () => {
		it('should handle Windows paths', () => {
			const files = ['packages\\cli\\src\\commands\\start.ts'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('cli');
		});

		it('should handle absolute paths', () => {
			const files = [
				'/home/user/project/packages/tm-core/src/workflow/orchestrator.ts'
			];
			const scope = scopeDetector.detectScope(files);

			// Absolute paths won't match package patterns
			expect(scope).toBe('workflow');
		});

		it('should handle paths with special characters', () => {
			const files = ['packages/tm-core/src/workflow/[email protected]'];
			const scope = scopeDetector.detectScope(files);

			// Files within packages get the package scope
			expect(scope).toBe('core');
		});

		it('should handle very long file paths', () => {
			const files = [
				'packages/tm-core/src/deeply/nested/directory/structure/with/many/levels/file.ts'
			];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('core');
		});

		it('should handle files in root directory', () => {
			const files = ['file.ts'];
			const scope = scopeDetector.detectScope(files);

			expect(scope).toBe('repo');
		});
	});

	describe('getMatchingScope', () => {
		it('should return matching scope for single file', () => {
			const scope = scopeDetector.getMatchingScope('packages/cli/src/index.ts');

			expect(scope).toBe('cli');
		});

		it('should return null for non-matching file', () => {
			const scope = scopeDetector.getMatchingScope('unknown/file.ts');

			expect(scope).toBeNull();
		});

		it('should match test files', () => {
			const scope = scopeDetector.getMatchingScope(
				'src/components/button.test.tsx'
			);

			expect(scope).toBe('test');
		});
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/tasks/services/tag.service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview TagService - Business logic for tag management
 * Handles tag creation, deletion, renaming, and copying
 */

import type { IStorage } from '../../../common/interfaces/storage.interface.js';
import type { TagInfo } from '../../../common/interfaces/storage.interface.js';
import { TaskMasterError, ERROR_CODES } from '../../../common/errors/task-master-error.js';

/**
 * Options for creating a new tag
 */
export interface CreateTagOptions {
	/** Copy tasks from current tag */
	copyFromCurrent?: boolean;
	/** Copy tasks from specific tag */
	copyFromTag?: string;
	/** Tag description */
	description?: string;
	/** Create from git branch name */
	fromBranch?: boolean;
}

/**
 * Options for deleting a tag
 * Note: Confirmation prompts are a CLI presentation concern
 * and are not handled by TagService (business logic layer)
 */
export interface DeleteTagOptions {
	// Currently no options - interface kept for future extensibility
}

/**
 * Options for copying a tag
 */
export interface CopyTagOptions {
	// Currently no options - interface kept for future extensibility
}

/**
 * Reserved tag names that cannot be used
 * Only 'master' is reserved as it's the system default tag
 * Users can use 'main' or 'default' if desired
 */
const RESERVED_TAG_NAMES = ['master'];

/**
 * Maximum length for tag names (prevents filesystem/UI issues)
 */
const MAX_TAG_NAME_LENGTH = 50;

/**
 * TagService - Handles tag management business logic
 * Validates operations and delegates to storage layer
 */
export class TagService {
	constructor(private storage: IStorage) {}

	/**
	 * Validate tag name format and restrictions
	 * @throws {TaskMasterError} if validation fails
	 */
	private validateTagName(name: string, context = 'Tag name'): void {
		if (!name || typeof name !== 'string') {
			throw new TaskMasterError(
				`${context} is required and must be a string`,
				ERROR_CODES.VALIDATION_ERROR
			);
		}

		// Check length
		if (name.length > MAX_TAG_NAME_LENGTH) {
			throw new TaskMasterError(
				`${context} must be ${MAX_TAG_NAME_LENGTH} characters or less`,
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: name, maxLength: MAX_TAG_NAME_LENGTH }
			);
		}

		// Check format: alphanumeric, hyphens, underscores only
		if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
			throw new TaskMasterError(
				`${context} can only contain letters, numbers, hyphens, and underscores`,
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: name }
			);
		}

		// Check reserved names
		if (RESERVED_TAG_NAMES.includes(name.toLowerCase())) {
			throw new TaskMasterError(
				`"${name}" is a reserved tag name`,
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: name, reserved: true }
			);
		}
	}

	/**
	 * Check if storage supports tag mutation operations
	 * @throws {TaskMasterError} if operation not supported
	 */
	private checkTagMutationSupport(operation: string): void {
		const storageType = this.storage.getStorageType();

		if (storageType === 'api') {
			throw new TaskMasterError(
				`${operation} is not supported with API storage. Use the web interface at Hamster Studio.`,
				ERROR_CODES.NOT_IMPLEMENTED,
				{ storageType: 'api', operation }
			);
		}
	}

	/**
	 * Create a new tag
	 * For API storage: throws error (client should redirect to web UI)
	 * For file storage: creates tag with optional task copying
	 */
	async createTag(
		name: string,
		options: CreateTagOptions = {}
	): Promise<TagInfo> {
		// Validate tag name
		this.validateTagName(name);

		// Check if tag already exists
		const allTags = await this.storage.getAllTags();
		if (allTags.includes(name)) {
			throw new TaskMasterError(
				`Tag "${name}" already exists`,
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: name }
			);
		}

		// Validate copyFromTag if provided
		if (options.copyFromTag && !allTags.includes(options.copyFromTag)) {
			throw new TaskMasterError(
				`Cannot copy from missing tag "${options.copyFromTag}"`,
				ERROR_CODES.NOT_FOUND,
				{ tagName: options.copyFromTag }
			);
		}

		// For API storage, we can't create tags via CLI
		// The client (CLI/bridge) should handle redirecting to web UI
		this.checkTagMutationSupport('Tag creation');

		// Determine which tag to copy from
		let copyFrom: string | undefined;
		if (options.copyFromTag) {
			copyFrom = options.copyFromTag;
		} else if (options.copyFromCurrent) {
			const result = await this.storage.getTagsWithStats();
			copyFrom = result.currentTag || undefined;
		}

		// Delegate to storage layer
		await this.storage.createTag(name, {
			copyFrom,
			description: options.description
		});

		// Return tag info
		const tagInfo: TagInfo = {
			name,
			taskCount: 0,
			completedTasks: 0,
			isCurrent: false,
			statusBreakdown: {},
			description: options.description || `Tag created on ${new Date().toLocaleDateString()}`
		};

		return tagInfo;
	}

	/**
	 * Delete an existing tag
	 * Cannot delete master tag
	 * For API storage: throws error (client should redirect to web UI)
	 */
	async deleteTag(
		name: string,
		_options: DeleteTagOptions = {}
	): Promise<void> {
		// Validate tag name
		this.validateTagName(name);

		// Cannot delete master tag
		if (name === 'master') {
			throw new TaskMasterError(
				'Cannot delete the "master" tag',
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: name, protected: true }
			);
		}

		// For API storage, we can't delete tags via CLI
		this.checkTagMutationSupport('Tag deletion');

		// Check if tag exists
		const allTags = await this.storage.getAllTags();
		if (!allTags.includes(name)) {
			throw new TaskMasterError(
				`Tag "${name}" does not exist`,
				ERROR_CODES.NOT_FOUND,
				{ tagName: name }
			);
		}

		// Delegate to storage
		await this.storage.deleteTag(name);
	}

	/**
	 * Rename an existing tag
	 * Cannot rename master tag
	 * For API storage: throws error (client should redirect to web UI)
	 */
	async renameTag(oldName: string, newName: string): Promise<void> {
		// Validate both names
		this.validateTagName(oldName, 'Old tag name');
		this.validateTagName(newName, 'New tag name');

		// Cannot rename master tag
		if (oldName === 'master') {
			throw new TaskMasterError(
				'Cannot rename the "master" tag',
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: oldName, protected: true }
			);
		}

		// For API storage, we can't rename tags via CLI
		this.checkTagMutationSupport('Tag renaming');

		// Check if old tag exists
		const allTags = await this.storage.getAllTags();
		if (!allTags.includes(oldName)) {
			throw new TaskMasterError(
				`Tag "${oldName}" does not exist`,
				ERROR_CODES.NOT_FOUND,
				{ tagName: oldName }
			);
		}

		// Check if new name already exists
		if (allTags.includes(newName)) {
			throw new TaskMasterError(
				`Tag "${newName}" already exists`,
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: newName }
			);
		}

		// Delegate to storage
		await this.storage.renameTag(oldName, newName);
	}

	/**
	 * Copy an existing tag to create a new tag with the same tasks
	 * For API storage: throws error (client should show alternative)
	 */
	async copyTag(
		sourceName: string,
		targetName: string,
		_options: CopyTagOptions = {}
	): Promise<void> {
		// Validate both names
		this.validateTagName(sourceName, 'Source tag name');
		this.validateTagName(targetName, 'Target tag name');

		// For API storage, we can't copy tags via CLI
		this.checkTagMutationSupport('Tag copying');

		// Check if source tag exists
		const allTags = await this.storage.getAllTags();
		if (!allTags.includes(sourceName)) {
			throw new TaskMasterError(
				`Source tag "${sourceName}" does not exist`,
				ERROR_CODES.NOT_FOUND,
				{ tagName: sourceName }
			);
		}

		// Check if target name already exists
		if (allTags.includes(targetName)) {
			throw new TaskMasterError(
				`Target tag "${targetName}" already exists`,
				ERROR_CODES.VALIDATION_ERROR,
				{ tagName: targetName }
			);
		}

		// Delegate to storage
		await this.storage.copyTag(sourceName, targetName);
	}

	/**
	 * Get all tags with statistics
	 * Works with both file and API storage
	 */
	async getTagsWithStats() {
		return await this.storage.getTagsWithStats();
	}
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/runtime-state-manager.service.spec.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Unit tests for RuntimeStateManager service
 */

import fs from 'node:fs/promises';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
import { RuntimeStateManager } from './runtime-state-manager.service.js';

vi.mock('node:fs', () => ({
	promises: {
		readFile: vi.fn(),
		writeFile: vi.fn(),
		mkdir: vi.fn(),
		unlink: vi.fn()
	}
}));

describe('RuntimeStateManager', () => {
	let stateManager: RuntimeStateManager;
	const testProjectRoot = '/test/project';

	beforeEach(() => {
		stateManager = new RuntimeStateManager(testProjectRoot);
		vi.clearAllMocks();
		// Clear environment variables
		delete process.env.TASKMASTER_TAG;
	});

	afterEach(() => {
		vi.restoreAllMocks();
		delete process.env.TASKMASTER_TAG;
	});

	describe('loadState', () => {
		it('should load state from file', async () => {
			const mockState = {
				activeTag: 'feature-branch',
				lastUpdated: '2024-01-01T00:00:00.000Z',
				metadata: { test: 'data' }
			};

			vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockState));

			const state = await stateManager.loadState();

			expect(fs.readFile).toHaveBeenCalledWith(
				'/test/project/.taskmaster/state.json',
				'utf-8'
			);
			expect(state.currentTag).toBe('feature-branch');
			expect(state.metadata).toEqual({ test: 'data' });
		});

		it('should override with environment variable if set', async () => {
			const mockState = { activeTag: 'file-tag' };
			vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockState));

			process.env.TASKMASTER_TAG = 'env-tag';

			const state = await stateManager.loadState();

			expect(state.currentTag).toBe('env-tag');
		});

		it('should use default state when file does not exist', async () => {
			const error = new Error('File not found') as any;
			error.code = 'ENOENT';
			vi.mocked(fs.readFile).mockRejectedValue(error);

			const state = await stateManager.loadState();

			expect(state.currentTag).toBe(DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG);
		});

		it('should use environment variable when file does not exist', async () => {
			const error = new Error('File not found') as any;
			error.code = 'ENOENT';
			vi.mocked(fs.readFile).mockRejectedValue(error);

			process.env.TASKMASTER_TAG = 'env-tag';

			const state = await stateManager.loadState();

			expect(state.currentTag).toBe('env-tag');
		});

		it('should handle file read errors gracefully', async () => {
			vi.mocked(fs.readFile).mockRejectedValue(new Error('Permission denied'));

			const state = await stateManager.loadState();

			expect(state.currentTag).toBe(DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG);
		});

		it('should handle invalid JSON gracefully', async () => {
			vi.mocked(fs.readFile).mockResolvedValue('invalid json');

			// Mock console.warn to avoid noise in tests
			const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

			const state = await stateManager.loadState();

			expect(state.currentTag).toBe(DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG);
			expect(warnSpy).toHaveBeenCalled();

			warnSpy.mockRestore();
		});
	});

	describe('saveState', () => {
		it('should save state to file with timestamp', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			// Set a specific state
			await stateManager.setCurrentTag('test-tag');

			// Verify mkdir was called
			expect(fs.mkdir).toHaveBeenCalledWith('/test/project/.taskmaster', {
				recursive: true
			});

			// Verify writeFile was called with correct data
			expect(fs.writeFile).toHaveBeenCalledWith(
				'/test/project/.taskmaster/state.json',
				expect.stringContaining('"activeTag":"test-tag"'),
				'utf-8'
			);

			// Verify timestamp is included
			expect(fs.writeFile).toHaveBeenCalledWith(
				expect.any(String),
				expect.stringContaining('"lastUpdated"'),
				'utf-8'
			);
		});

		it('should throw TaskMasterError on save failure', async () => {
			vi.mocked(fs.mkdir).mockRejectedValue(new Error('Disk full'));

			await expect(stateManager.saveState()).rejects.toThrow(
				'Failed to save runtime state'
			);
		});

		it('should format JSON with proper indentation', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await stateManager.saveState();

			const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
			const jsonContent = writeCall[1] as string;

			// Check for 2-space indentation
			expect(jsonContent).toMatch(/\n  /);
		});
	});

	describe('getActiveTag', () => {
		it('should return current active tag', () => {
			const tag = stateManager.getCurrentTag();
			expect(tag).toBe(DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG);
		});

		it('should return updated tag after setActiveTag', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await stateManager.setCurrentTag('new-tag');

			expect(stateManager.getCurrentTag()).toBe('new-tag');
		});
	});

	describe('setActiveTag', () => {
		it('should update active tag and save state', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await stateManager.setCurrentTag('feature-xyz');

			expect(stateManager.getCurrentTag()).toBe('feature-xyz');
			expect(fs.writeFile).toHaveBeenCalled();
		});
	});

	describe('getState', () => {
		it('should return copy of current state', () => {
			const state1 = stateManager.getState();
			const state2 = stateManager.getState();

			expect(state1).not.toBe(state2); // Different instances
			expect(state1).toEqual(state2); // Same content
			expect(state1.currentTag).toBe(DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG);
		});
	});

	describe('updateMetadata', () => {
		it('should update metadata and save state', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await stateManager.updateMetadata({ key1: 'value1' });

			const state = stateManager.getState();
			expect(state.metadata).toEqual({ key1: 'value1' });
			expect(fs.writeFile).toHaveBeenCalled();
		});

		it('should merge metadata with existing values', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await stateManager.updateMetadata({ key1: 'value1' });
			await stateManager.updateMetadata({ key2: 'value2' });

			const state = stateManager.getState();
			expect(state.metadata).toEqual({
				key1: 'value1',
				key2: 'value2'
			});
		});

		it('should override existing metadata values', async () => {
			vi.mocked(fs.mkdir).mockResolvedValue(undefined);
			vi.mocked(fs.writeFile).mockResolvedValue(undefined);

			await stateManager.updateMetadata({ key1: 'value1' });
			await stateManager.updateMetadata({ key1: 'value2' });

			const state = stateManager.getState();
			expect(state.metadata).toEqual({ key1: 'value2' });
		});
	});

	describe('clearState', () => {
		it('should delete state file and reset to defaults', async () => {
			vi.mocked(fs.unlink).mockResolvedValue(undefined);

			await stateManager.clearState();

			expect(fs.unlink).toHaveBeenCalledWith(
				'/test/project/.taskmaster/state.json'
			);
			expect(stateManager.getCurrentTag()).toBe(
				DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG
			);
			expect(stateManager.getState().metadata).toBeUndefined();
		});

		it('should ignore ENOENT errors when file does not exist', async () => {
			const error = new Error('File not found') as any;
			error.code = 'ENOENT';
			vi.mocked(fs.unlink).mockRejectedValue(error);

			await expect(stateManager.clearState()).resolves.not.toThrow();
			expect(stateManager.getCurrentTag()).toBe(
				DEFAULT_CONFIG_VALUES.TAGS.DEFAULT_TAG
			);
		});

		it('should throw other errors', async () => {
			vi.mocked(fs.unlink).mockRejectedValue(new Error('Permission denied'));

			await expect(stateManager.clearState()).rejects.toThrow(
				'Permission denied'
			);
		});
	});
});

```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/task-manager/generate-task-files.test.js:
--------------------------------------------------------------------------------

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

// Mock the dependencies before importing the module under test
jest.unstable_mockModule('fs', () => ({
	default: {
		existsSync: jest.fn(),
		mkdirSync: jest.fn(),
		readdirSync: jest.fn(),
		unlinkSync: jest.fn(),
		writeFileSync: jest.fn()
	},
	existsSync: jest.fn(),
	mkdirSync: jest.fn(),
	readdirSync: jest.fn(),
	unlinkSync: jest.fn(),
	writeFileSync: jest.fn()
}));

jest.unstable_mockModule('path', () => ({
	default: {
		join: jest.fn((...args) => args.join('/')),
		dirname: jest.fn((p) => p.split('/').slice(0, -1).join('/'))
	},
	join: jest.fn((...args) => args.join('/')),
	dirname: jest.fn((p) => p.split('/').slice(0, -1).join('/'))
}));

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),
	findTaskById: jest.fn((tasks, id) =>
		tasks.find((t) => t.id === parseInt(id))
	),
	findProjectRoot: jest.fn(() => '/mock/project/root'),
	resolveEnvVariable: jest.fn((varName) => `mock_${varName}`),
	ensureTagMetadata: jest.fn()
}));

jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
	formatDependenciesWithStatus: jest.fn(),
	displayBanner: jest.fn(),
	displayTaskList: jest.fn(),
	startLoadingIndicator: jest.fn(() => ({ stop: jest.fn() })),
	stopLoadingIndicator: jest.fn(),
	createProgressBar: jest.fn(() => ' MOCK_PROGRESS_BAR '),
	getStatusWithColor: jest.fn((status) => status),
	getComplexityWithColor: jest.fn((score) => `Score: ${score}`)
}));

jest.unstable_mockModule(
	'../../../../../scripts/modules/dependency-manager.js',
	() => ({
		validateAndFixDependencies: jest.fn(),
		validateTaskDependencies: jest.fn()
	})
);

jest.unstable_mockModule(
	'../../../../../scripts/modules/config-manager.js',
	() => ({
		getDebugFlag: jest.fn(() => false),
		getProjectName: jest.fn(() => 'Test Project')
	})
);

// Import the mocked modules
const { readJSON, writeJSON, log, findProjectRoot, ensureTagMetadata } =
	await import('../../../../../scripts/modules/utils.js');
const { formatDependenciesWithStatus } = await import(
	'../../../../../scripts/modules/ui.js'
);
const { validateAndFixDependencies } = await import(
	'../../../../../scripts/modules/dependency-manager.js'
);

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

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

describe('generateTaskFiles', () => {
	// Sample task data for testing - updated to tagged format
	const sampleTasksData = {
		master: {
			tasks: [
				{
					id: 1,
					title: 'Task 1',
					description: 'First task description',
					status: 'pending',
					dependencies: [],
					priority: 'high',
					details: 'Detailed information for task 1',
					testStrategy: 'Test strategy for task 1'
				},
				{
					id: 2,
					title: 'Task 2',
					description: 'Second task description',
					status: 'pending',
					dependencies: [1],
					priority: 'medium',
					details: 'Detailed information for task 2',
					testStrategy: 'Test strategy for task 2'
				},
				{
					id: 3,
					title: 'Task with Subtasks',
					description: 'Task with subtasks description',
					status: 'pending',
					dependencies: [1, 2],
					priority: 'high',
					details: 'Detailed information for task 3',
					testStrategy: 'Test strategy for task 3',
					subtasks: [
						{
							id: 1,
							title: 'Subtask 1',
							description: 'First subtask',
							status: 'pending',
							dependencies: [],
							details: 'Details for subtask 1'
						},
						{
							id: 2,
							title: 'Subtask 2',
							description: 'Second subtask',
							status: 'pending',
							dependencies: [1],
							details: 'Details for subtask 2'
						}
					]
				}
			],
			metadata: {
				projectName: 'Test Project',
				created: '2024-01-01T00:00:00.000Z',
				updated: '2024-01-01T00:00:00.000Z'
			}
		}
	};

	beforeEach(() => {
		jest.clearAllMocks();
		// Mock readJSON to return the full tagged structure
		readJSON.mockImplementation((tasksPath, projectRoot, tag) => {
			if (tag && sampleTasksData[tag]) {
				return {
					...sampleTasksData[tag],
					tag,
					_rawTaggedData: sampleTasksData
				};
			}
			// Default to master if no tag or tag not found
			return {
				...sampleTasksData.master,
				tag: 'master',
				_rawTaggedData: sampleTasksData
			};
		});
	});

	test('should generate task files from tasks.json - working test', async () => {
		// Set up mocks for this specific test
		fs.existsSync.mockReturnValue(true);

		// Call the function
		const tasksPath = 'tasks/tasks.json';
		const outputDir = 'tasks';

		await generateTaskFiles(tasksPath, outputDir, {
			tag: 'master',
			mcpLog: { info: jest.fn() }
		});

		// Verify the data was read with new signature, defaulting to master
		expect(readJSON).toHaveBeenCalledWith(tasksPath, undefined, 'master');

		// Verify dependencies were validated with the raw tagged data
		expect(validateAndFixDependencies).toHaveBeenCalledWith(
			sampleTasksData,
			tasksPath,
			undefined,
			'master'
		);

		// Verify files were written for each task in the master tag
		expect(fs.writeFileSync).toHaveBeenCalledTimes(3);

		// Verify specific file paths
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			'tasks/task_001.txt',
			expect.any(String)
		);
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			'tasks/task_002.txt',
			expect.any(String)
		);
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			'tasks/task_003.txt',
			expect.any(String)
		);
	});

	test('should format dependencies with status indicators', async () => {
		// Set up mocks
		fs.existsSync.mockReturnValue(true);
		formatDependenciesWithStatus.mockReturnValue(
			'✅ Task 1 (done), ⏱️ Task 2 (pending)'
		);

		// Call the function
		await generateTaskFiles('tasks/tasks.json', 'tasks', {
			tag: 'master',
			mcpLog: { info: jest.fn() }
		});

		// Verify formatDependenciesWithStatus was called for tasks with dependencies
		// It will be called multiple times, once for each task that has dependencies.
		expect(formatDependenciesWithStatus).toHaveBeenCalled();
	});

	test('should handle tasks with no subtasks', async () => {
		// Create data with tasks that have no subtasks - updated to tagged format
		const tasksWithoutSubtasks = {
			master: {
				tasks: [
					{
						id: 1,
						title: 'Simple Task',
						description: 'A simple task without subtasks',
						status: 'pending',
						dependencies: [],
						priority: 'medium',
						details: 'Simple task details',
						testStrategy: 'Simple test strategy'
					}
				],
				metadata: {
					projectName: 'Test Project',
					created: '2024-01-01T00:00:00.000Z',
					updated: '2024-01-01T00:00:00.000Z'
				}
			}
		};

		// Update the mock for this specific test case
		readJSON.mockImplementation((tasksPath, projectRoot, tag) => {
			return {
				...tasksWithoutSubtasks.master,
				tag: 'master',
				_rawTaggedData: tasksWithoutSubtasks
			};
		});

		fs.existsSync.mockReturnValue(true);

		// Call the function
		await generateTaskFiles('tasks/tasks.json', 'tasks', {
			tag: 'master',
			mcpLog: { info: jest.fn() }
		});

		// Verify the file was written
		expect(fs.writeFileSync).toHaveBeenCalledTimes(1);
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			'tasks/task_001.txt',
			expect.any(String)
		);
	});

	test('should validate dependencies before generating files', async () => {
		// Set up mocks
		fs.existsSync.mockReturnValue(true);

		// Call the function
		await generateTaskFiles('tasks/tasks.json', 'tasks', {
			tag: 'master',
			mcpLog: { info: jest.fn() }
		});

		// Verify validateAndFixDependencies was called with the raw tagged data
		expect(validateAndFixDependencies).toHaveBeenCalledWith(
			sampleTasksData,
			'tasks/tasks.json',
			undefined,
			'master'
		);
	});
});

```

--------------------------------------------------------------------------------
/apps/cli/src/commands/models/setup.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Main setup orchestration for interactive model configuration
 */

import chalk from 'chalk';
import {
	getConfig,
	getModelConfiguration,
	setModel,
	writeConfig
} from '../../lib/model-management.js';
import {
	customProviderConfigs,
	handleCustomProvider
} from './custom-providers.js';
import {
	buildPromptChoices,
	displaySetupIntro,
	promptForModel
} from './prompts.js';
import type { CurrentModels, CustomProviderId, ModelRole } from './types.js';

/**
 * Check if a value is a custom provider ID
 */
function isCustomProviderId(value: unknown): value is CustomProviderId {
	if (typeof value !== 'string') return false;
	return Object.values(customProviderConfigs).some(
		(config) => config.id === value
	);
}

/**
 * Handle setting a model for a specific role
 */
async function handleSetModel(
	role: ModelRole,
	selectedValue: string | { id: string; provider: string } | null,
	currentModel: {
		modelId?: string | null;
		provider?: string | null;
		baseURL?: string | null;
	} | null,
	projectRoot: string
): Promise<{ success: boolean; modified: boolean }> {
	const currentModelId = currentModel?.modelId ?? null;
	const currentProvider = currentModel?.provider ?? null;
	const currentBaseURL = currentModel?.baseURL ?? null;
	// Handle cancellation
	if (selectedValue === '__CANCEL__') {
		console.log(
			chalk.yellow(`\nSetup canceled during ${role} model selection.`)
		);
		return { success: false, modified: false };
	}

	// Handle no change
	if (selectedValue === '__NO_CHANGE__') {
		console.log(chalk.gray(`No change selected for ${role} model.`));
		return { success: true, modified: false };
	}

	let modelIdToSet: string | null = null;
	let providerHint: string | null = null;
	let baseURL: string | null = null;

	// Handle custom providers
	if (isCustomProviderId(selectedValue)) {
		const result = await handleCustomProvider(
			selectedValue,
			role,
			currentModel
		);
		if (!result.success) {
			return { success: false, modified: false };
		}
		if (!result.modelId) {
			return { success: true, modified: false };
		}
		modelIdToSet = result.modelId;
		providerHint = result.provider;
		baseURL = result.baseURL || null;
	}
	// Handle standard model selection
	else if (
		selectedValue &&
		typeof selectedValue === 'object' &&
		'id' in selectedValue
	) {
		modelIdToSet = selectedValue.id;
		providerHint = selectedValue.provider;
	}
	// Handle disabling fallback
	else if (selectedValue === null && role === 'fallback') {
		modelIdToSet = null;
		providerHint = null;
	}
	// Unknown selection
	else if (selectedValue) {
		console.error(
			chalk.red(
				`Internal Error: Unexpected selection value for ${role}: ${JSON.stringify(selectedValue)}`
			)
		);
		return { success: false, modified: false };
	}

	// Check if there's actually a change to make
	if (
		modelIdToSet === currentModelId &&
		(providerHint ?? null) === currentProvider &&
		(baseURL ?? null) === currentBaseURL
	) {
		return { success: true, modified: false };
	}

	// Set the model
	if (modelIdToSet) {
		const result = await setModel(role, modelIdToSet, {
			projectRoot,
			providerHint: providerHint || undefined,
			baseURL: baseURL || undefined
		});

		if (result.success) {
			console.log(
				chalk.blue(
					`Set ${role} model: ${result.data?.provider} / ${result.data?.modelId}`
				)
			);
			if (result.data?.warning) {
				console.log(chalk.yellow(result.data?.warning));
			}
			return { success: true, modified: true };
		} else {
			console.error(
				chalk.red(
					`Error setting ${role} model: ${result.error?.message || 'Unknown'}`
				)
			);
			return { success: false, modified: false };
		}
	}
	// Disable fallback model
	else if (role === 'fallback') {
		const currentCfg = getConfig(projectRoot);
		if (currentCfg?.models?.fallback?.modelId) {
			currentCfg.models.fallback = {
				...currentCfg.models.fallback,
				provider: undefined,
				modelId: undefined
			};
			if (writeConfig(currentCfg, projectRoot)) {
				console.log(chalk.blue('Fallback model disabled.'));
				return { success: true, modified: true };
			} else {
				console.error(
					chalk.red('Failed to disable fallback model in config file.')
				);
				return { success: false, modified: false };
			}
		} else {
			console.log(chalk.blue('Fallback model was already disabled.'));
			return { success: true, modified: false };
		}
	}

	return { success: true, modified: false };
}

/**
 * Run interactive model setup
 */
export async function runInteractiveSetup(
	projectRoot: string
): Promise<boolean> {
	if (!projectRoot) {
		console.error(
			chalk.red(
				'Error: Could not determine project root for interactive setup.'
			)
		);
		process.exit(1);
	}

	// Get current configuration
	const currentConfigResult = await getModelConfiguration({ projectRoot });
	const currentModels: CurrentModels =
		currentConfigResult.success && currentConfigResult.data
			? {
					main: currentConfigResult.data.activeModels.main
						? {
								modelId: currentConfigResult.data.activeModels.main.modelId,
								provider: currentConfigResult.data.activeModels.main.provider,
								baseURL: currentConfigResult.data.activeModels.main.baseURL
							}
						: null,
					research: currentConfigResult.data.activeModels.research
						? {
								modelId: currentConfigResult.data.activeModels.research.modelId,
								provider:
									currentConfigResult.data.activeModels.research.provider,
								baseURL: currentConfigResult.data.activeModels.research.baseURL
							}
						: null,
					fallback: currentConfigResult.data.activeModels.fallback
						? {
								modelId: currentConfigResult.data.activeModels.fallback.modelId,
								provider:
									currentConfigResult.data.activeModels.fallback.provider,
								baseURL: currentConfigResult.data.activeModels.fallback.baseURL
							}
						: null
				}
			: { main: null, research: null, fallback: null };

	// Handle config load failure gracefully
	if (
		!currentConfigResult.success &&
		currentConfigResult.error?.code !== 'CONFIG_MISSING'
	) {
		console.warn(
			chalk.yellow(
				`Warning: Could not load current model configuration: ${currentConfigResult.error?.message || 'Unknown error'}. Proceeding with defaults.`
			)
		);
	}

	// Build prompt data
	const mainPromptData = buildPromptChoices('main', currentModels);
	const researchPromptData = buildPromptChoices('research', currentModels);
	const fallbackPromptData = buildPromptChoices(
		'fallback',
		currentModels,
		true
	);

	// Display intro
	displaySetupIntro();

	// Prompt for main model
	const mainModel = await promptForModel('main', mainPromptData);
	if (mainModel === '__CANCEL__') {
		return false;
	}

	// Prompt for research model
	const researchModel = await promptForModel('research', researchPromptData);
	if (researchModel === '__CANCEL__') {
		return false;
	}

	// Prompt for fallback model
	const fallbackModel = await promptForModel('fallback', fallbackPromptData);
	if (fallbackModel === '__CANCEL__') {
		return false;
	}

	// Process all model selections
	let setupSuccess = true;
	let setupConfigModified = false;

	const mainResult = await handleSetModel(
		'main',
		mainModel,
		currentModels.main,
		projectRoot
	);
	if (!mainResult.success) setupSuccess = false;
	if (mainResult.modified) setupConfigModified = true;

	const researchResult = await handleSetModel(
		'research',
		researchModel,
		currentModels.research,
		projectRoot
	);
	if (!researchResult.success) setupSuccess = false;
	if (researchResult.modified) setupConfigModified = true;

	const fallbackResult = await handleSetModel(
		'fallback',
		fallbackModel,
		currentModels.fallback,
		projectRoot
	);
	if (!fallbackResult.success) setupSuccess = false;
	if (fallbackResult.modified) setupConfigModified = true;

	// Display final result
	if (setupSuccess && setupConfigModified) {
		console.log(chalk.green.bold('\nModel setup complete!'));
	} else if (setupSuccess && !setupConfigModified) {
		console.log(chalk.yellow('\nNo changes made to model configuration.'));
	} else {
		console.error(
			chalk.red(
				'\nErrors occurred during model selection. Please review and try again.'
			)
		);
	}

	return setupSuccess;
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/tasks/services/task-execution-service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview TaskExecutionService for handling task execution business logic
 * Extracted from CLI start command to be reusable across CLI and extension
 */

import type { Task } from '../../../common/types/index.js';
import type { TaskService } from './task-service.js';

export interface StartTaskOptions {
	subtaskId?: string;
	dryRun?: boolean;
	updateStatus?: boolean;
	force?: boolean;
	silent?: boolean;
}

export interface StartTaskResult {
	task: Task | null;
	found: boolean;
	started: boolean;
	subtaskId?: string;
	subtask?: any;
	error?: string;
	executionOutput?: string;
	/** Command to execute (for CLI to run directly) */
	command?: {
		executable: string;
		args: string[];
		cwd: string;
	};
}

export interface ConflictCheckResult {
	canProceed: boolean;
	conflictingTasks: Task[];
	reason?: string;
}

/**
 * TaskExecutionService handles the business logic for starting and executing tasks
 */
export class TaskExecutionService {
	constructor(private taskService: TaskService) {}

	/**
	 * Start working on a task with comprehensive business logic
	 */
	async startTask(
		taskId: string,
		options: StartTaskOptions = {}
	): Promise<StartTaskResult> {
		try {
			// Handle subtask IDs by extracting parent task ID
			const { parentId, subtaskId } = this.parseTaskId(taskId);

			// Check for in-progress task conflicts
			if (!options.force) {
				const conflictCheck = await this.checkInProgressConflicts(taskId);
				if (!conflictCheck.canProceed) {
					return {
						task: null,
						found: false,
						started: false,
						error: `Conflicting tasks in progress: ${conflictCheck.conflictingTasks.map((t) => `#${t.id}: ${t.title}`).join(', ')}`
					};
				}
			}

			// Get the actual task (parent task if dealing with subtask)
			const task = await this.taskService.getTask(parentId);
			if (!task) {
				return {
					task: null,
					found: false,
					started: false,
					error: `Task ${parentId} not found`
				};
			}

			// Find the specific subtask if provided
			let subtask = undefined;
			if (subtaskId && task.subtasks) {
				subtask = task.subtasks.find((st) => String(st.id) === subtaskId);
			}

			// Update task status to in-progress if not disabled
			if (options.updateStatus && !options.dryRun) {
				try {
					await this.taskService.updateTaskStatus(parentId, 'in-progress');
				} catch (error) {
					// Log but don't fail - status update is not critical
					console.warn(
						`Could not update task status: ${error instanceof Error ? error.message : String(error)}`
					);
				}
			}

			// Prepare execution command instead of executing directly
			let started = false;
			let executionOutput = 'Task ready to execute';
			let command = undefined;

			if (!options.dryRun) {
				// Prepare the command for execution by the CLI
				command = await this.prepareExecutionCommand(task, subtask);
				started = !!command; // Command prepared successfully
				executionOutput = command
					? `Command prepared: ${command.executable} ${command.args.join(' ')}`
					: 'Failed to prepare execution command';
			} else {
				// For dry-run, just show that we would execute
				started = true;
				executionOutput = 'Dry run - task would be executed';
				// Also prepare command for dry run display
				command = await this.prepareExecutionCommand(task, subtask);
			}

			return {
				task,
				found: true,
				started,
				subtaskId,
				subtask,
				executionOutput,
				command: command || undefined
			};
		} catch (error) {
			return {
				task: null,
				found: false,
				started: false,
				error: error instanceof Error ? error.message : String(error)
			};
		}
	}

	/**
	 * Check for existing in-progress tasks and determine conflicts
	 */
	async checkInProgressConflicts(
		targetTaskId: string
	): Promise<ConflictCheckResult> {
		const allTasks = await this.taskService.getTaskList();
		const inProgressTasks = allTasks.tasks.filter(
			(task) => task.status === 'in-progress'
		);

		// If the target task is already in-progress, that's fine
		const targetTaskInProgress = inProgressTasks.find(
			(task) => task.id === targetTaskId
		);
		if (targetTaskInProgress) {
			return { canProceed: true, conflictingTasks: [] };
		}

		// Check if target is a subtask and its parent is in-progress
		const isSubtask = targetTaskId.includes('.');
		if (isSubtask) {
			const parentTaskId = targetTaskId.split('.')[0];
			const parentInProgress = inProgressTasks.find(
				(task) => task.id === parentTaskId
			);
			if (parentInProgress) {
				return { canProceed: true, conflictingTasks: [] }; // Allow subtasks when parent is in-progress
			}
		}

		// Check if other unrelated tasks are in-progress
		const conflictingTasks = inProgressTasks.filter((task) => {
			if (task.id === targetTaskId) return false;

			// If target is a subtask, exclude its parent from conflicts
			if (isSubtask) {
				const parentTaskId = targetTaskId.split('.')[0];
				if (task.id === parentTaskId) return false;
			}

			// If the in-progress task is a subtask of our target parent, exclude it
			if (task.id.toString().includes('.')) {
				const taskParentId = task.id.toString().split('.')[0];
				if (isSubtask && taskParentId === targetTaskId.split('.')[0]) {
					return false;
				}
			}

			return true;
		});

		if (conflictingTasks.length > 0) {
			return {
				canProceed: false,
				conflictingTasks,
				reason: 'Other tasks are already in progress'
			};
		}

		return { canProceed: true, conflictingTasks: [] };
	}

	/**
	 * Get the next available task to start
	 */
	async getNextAvailableTask(): Promise<string | null> {
		const nextTask = await this.taskService.getNextTask();
		return nextTask?.id || null;
	}

	/**
	 * Parse a task ID to determine if it's a subtask and extract components
	 */
	private parseTaskId(taskId: string): {
		parentId: string;
		subtaskId?: string;
	} {
		if (taskId.includes('.')) {
			const [parentId, subtaskId] = taskId.split('.');
			return { parentId, subtaskId };
		}
		return { parentId: taskId };
	}

	/**
	 * Check if a task can be started (no conflicts)
	 */
	async canStartTask(taskId: string, force = false): Promise<boolean> {
		if (force) return true;

		const conflictCheck = await this.checkInProgressConflicts(taskId);
		return conflictCheck.canProceed;
	}

	/**
	 * Prepare execution command for the CLI to run
	 */
	private async prepareExecutionCommand(
		task: Task,
		subtask?: any
	): Promise<{ executable: string; args: string[]; cwd: string } | null> {
		try {
			// Format the task into a prompt
			const taskPrompt = this.formatTaskPrompt(task, subtask);

			// Use claude command - could be extended for other executors
			const executable = 'claude';
			const args = [taskPrompt];
			const cwd = process.cwd(); // or could get from project root

			return { executable, args, cwd };
		} catch (error) {
			console.warn(
				`Failed to prepare execution command: ${error instanceof Error ? error.message : String(error)}`
			);
			return null;
		}
	}

	/**
	 * Format task into a prompt suitable for execution
	 */
	private formatTaskPrompt(task: Task, subtask?: any): string {
		const workItem = subtask || task;
		const itemType = subtask ? 'Subtask' : 'Task';
		const itemId = subtask ? `${task.id}.${subtask.id}` : task.id;

		let prompt = `${itemType} #${itemId}: ${workItem.title}\n\n`;

		if (workItem.description) {
			prompt += `Description:\n${workItem.description}\n\n`;
		}

		if (workItem.details) {
			prompt += `Implementation Details:\n${workItem.details}\n\n`;
		}

		if (workItem.testStrategy) {
			prompt += `Test Strategy:\n${workItem.testStrategy}\n\n`;
		}

		if (task.dependencies && task.dependencies.length > 0) {
			prompt += `Dependencies: ${task.dependencies.join(', ')}\n\n`;
		}

		prompt += `Please help me implement this ${itemType.toLowerCase()}.`;

		return prompt;
	}

	/**
	 * Get task with subtask resolution
	 */
	async getTaskWithSubtask(
		taskId: string
	): Promise<{ task: Task | null; subtask?: any; subtaskId?: string }> {
		const { parentId, subtaskId } = this.parseTaskId(taskId);

		const task = await this.taskService.getTask(parentId);
		if (!task) {
			return { task: null };
		}

		if (subtaskId && task.subtasks) {
			const subtask = task.subtasks.find((st) => String(st.id) === subtaskId);
			return { task, subtask, subtaskId };
		}

		return { task };
	}
}

```

--------------------------------------------------------------------------------
/packages/tm-core/tests/unit/base-provider.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Unit tests for BaseProvider abstract class
 */

import { beforeEach, describe, expect, it } from 'vitest';
import {
	ERROR_CODES,
	TaskMasterError
} from '../../src/errors/task-master-error';
import { MockProvider } from '../mocks/mock-provider';

describe('BaseProvider', () => {
	describe('constructor', () => {
		it('should require an API key', () => {
			expect(() => {
				new MockProvider({ apiKey: '' });
			}).toThrow(TaskMasterError);
		});

		it('should initialize with provided API key and model', () => {
			const provider = new MockProvider({
				apiKey: 'test-key',
				model: 'mock-model-v2'
			});

			expect(provider.getModel()).toBe('mock-model-v2');
		});

		it('should use default model if not provided', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });
			expect(provider.getModel()).toBe('mock-model-v1');
		});
	});

	describe('generateCompletion', () => {
		let provider: MockProvider;

		beforeEach(() => {
			provider = new MockProvider({ apiKey: 'test-key' });
		});

		it('should successfully generate a completion', async () => {
			const response = await provider.generateCompletion('Test prompt');

			expect(response).toMatchObject({
				content: 'Mock response to: Test prompt',
				provider: 'mock',
				model: 'mock-model-v1',
				inputTokens: expect.any(Number),
				outputTokens: expect.any(Number),
				totalTokens: expect.any(Number),
				duration: expect.any(Number),
				timestamp: expect.any(String)
			});
		});

		it('should validate empty prompts', async () => {
			await expect(provider.generateCompletion('')).rejects.toThrow(
				'Prompt must be a non-empty string'
			);
		});

		it('should validate prompt type', async () => {
			await expect(provider.generateCompletion(null as any)).rejects.toThrow(
				'Prompt must be a non-empty string'
			);
		});

		it('should validate temperature range', async () => {
			await expect(
				provider.generateCompletion('Test', { temperature: 3 })
			).rejects.toThrow('Temperature must be between 0 and 2');
		});

		it('should validate maxTokens range', async () => {
			await expect(
				provider.generateCompletion('Test', { maxTokens: 0 })
			).rejects.toThrow('Max tokens must be between 1 and 100000');
		});

		it('should validate topP range', async () => {
			await expect(
				provider.generateCompletion('Test', { topP: 1.5 })
			).rejects.toThrow('Top-p must be between 0 and 1');
		});
	});

	describe('retry logic', () => {
		it('should retry on rate limit errors', async () => {
			const provider = new MockProvider({
				apiKey: 'test-key',
				failAfterAttempts: 2,
				simulateRateLimit: true,
				responseDelay: 10
			});

			const response = await provider.generateCompletion('Test prompt');

			expect(response.content).toBe('Mock response to: Test prompt');
			expect(provider.getAttemptCount()).toBe(3); // 2 failures + 1 success
		});

		it('should retry on timeout errors', async () => {
			const provider = new MockProvider({
				apiKey: 'test-key',
				failAfterAttempts: 1,
				simulateTimeout: true
			});

			const response = await provider.generateCompletion('Test prompt');

			expect(response.content).toBe('Mock response to: Test prompt');
			expect(provider.getAttemptCount()).toBe(2); // 1 failure + 1 success
		});

		it('should fail after max retries', async () => {
			const provider = new MockProvider({
				apiKey: 'test-key',
				shouldFail: true
			});

			await expect(provider.generateCompletion('Test prompt')).rejects.toThrow(
				'mock provider error'
			);
		});

		it('should calculate exponential backoff delays', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });

			// Access protected method through type assertion
			const calculateDelay = (provider as any).calculateBackoffDelay.bind(
				provider
			);

			const delay1 = calculateDelay(1);
			const delay2 = calculateDelay(2);
			const delay3 = calculateDelay(3);

			// Check exponential growth (with jitter, so use ranges)
			expect(delay1).toBeGreaterThanOrEqual(900);
			expect(delay1).toBeLessThanOrEqual(1100);

			expect(delay2).toBeGreaterThanOrEqual(1800);
			expect(delay2).toBeLessThanOrEqual(2200);

			expect(delay3).toBeGreaterThanOrEqual(3600);
			expect(delay3).toBeLessThanOrEqual(4400);
		});
	});

	describe('error handling', () => {
		it('should wrap provider errors properly', async () => {
			const provider = new MockProvider({
				apiKey: 'test-key',
				shouldFail: true
			});

			try {
				await provider.generateCompletion('Test prompt');
				expect.fail('Should have thrown an error');
			} catch (error) {
				expect(error).toBeInstanceOf(TaskMasterError);
				const tmError = error as TaskMasterError;
				expect(tmError.code).toBe(ERROR_CODES.PROVIDER_ERROR);
				expect(tmError.context.operation).toBe('generateCompletion');
				expect(tmError.context.resource).toBe('mock');
			}
		});

		it('should identify rate limit errors correctly', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });
			const isRateLimitError = (provider as any).isRateLimitError.bind(
				provider
			);

			expect(isRateLimitError(new Error('Rate limit exceeded'))).toBe(true);
			expect(isRateLimitError(new Error('Too many requests'))).toBe(true);
			expect(isRateLimitError(new Error('Status: 429'))).toBe(true);
			expect(isRateLimitError(new Error('Some other error'))).toBe(false);
		});

		it('should identify timeout errors correctly', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });
			const isTimeoutError = (provider as any).isTimeoutError.bind(provider);

			expect(isTimeoutError(new Error('Request timeout'))).toBe(true);
			expect(isTimeoutError(new Error('Operation timed out'))).toBe(true);
			expect(isTimeoutError(new Error('ECONNRESET'))).toBe(true);
			expect(isTimeoutError(new Error('Some other error'))).toBe(false);
		});

		it('should identify network errors correctly', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });
			const isNetworkError = (provider as any).isNetworkError.bind(provider);

			expect(isNetworkError(new Error('Network error'))).toBe(true);
			expect(isNetworkError(new Error('ENOTFOUND'))).toBe(true);
			expect(isNetworkError(new Error('ECONNREFUSED'))).toBe(true);
			expect(isNetworkError(new Error('Some other error'))).toBe(false);
		});
	});

	describe('model management', () => {
		it('should get and set model', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });

			expect(provider.getModel()).toBe('mock-model-v1');

			provider.setModel('mock-model-v2');
			expect(provider.getModel()).toBe('mock-model-v2');
		});
	});

	describe('provider information', () => {
		it('should return provider info', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });
			const info = provider.getProviderInfo();

			expect(info.name).toBe('mock');
			expect(info.displayName).toBe('Mock Provider');
			expect(info.requiresApiKey).toBe(true);
			expect(info.models).toHaveLength(2);
		});

		it('should return available models', () => {
			const provider = new MockProvider({ apiKey: 'test-key' });
			const models = provider.getAvailableModels();

			expect(models).toHaveLength(2);
			expect(models[0].id).toBe('mock-model-v1');
			expect(models[1].id).toBe('mock-model-v2');
		});

		it('should validate credentials', async () => {
			const validProvider = new MockProvider({ apiKey: 'valid-key' });
			const invalidProvider = new MockProvider({ apiKey: 'invalid-key' });

			expect(await validProvider.validateCredentials()).toBe(true);
			expect(await invalidProvider.validateCredentials()).toBe(false);
		});
	});

	describe('template method pattern', () => {
		it('should follow the template method flow', async () => {
			const provider = new MockProvider({
				apiKey: 'test-key',
				responseDelay: 50
			});

			const startTime = Date.now();
			const response = await provider.generateCompletion('Test prompt', {
				temperature: 0.5,
				maxTokens: 100
			});
			const endTime = Date.now();

			// Verify the response was processed through the template
			expect(response.content).toBeDefined();
			expect(response.duration).toBeGreaterThanOrEqual(50);
			expect(response.duration).toBeLessThanOrEqual(endTime - startTime + 10);
			expect(response.timestamp).toBeDefined();
			expect(response.provider).toBe('mock');
		});
	});
});

```
Page 18/50FirstPrevNextLast