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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/apps/extension/docs/extension-development-guide.md:
--------------------------------------------------------------------------------

```markdown
# VS Code Extension Development Guide

## 📁 File Structure Overview

This VS Code extension uses a **3-file packaging system** to avoid dependency conflicts during publishing:

```
apps/extension/
├── package.json           # Development configuration
├── package.publish.json   # Clean publishing configuration  
├── package.mjs           # Build script for packaging
├── .vscodeignore         # Files to exclude from extension package
└── vsix-build/           # Generated clean package directory
```

## 📋 File Purposes

### `package.json` (Development)
- **Purpose**: Development environment with all build tools
- **Contains**: 
  - All `devDependencies` needed for building
  - Development scripts (`build`, `watch`, `lint`, etc.)
  - Development package name: `"taskr"`
- **Used for**: Local development, building, testing

### `package.publish.json` (Publishing)
- **Purpose**: Clean distribution version for VS Code Marketplace
- **Contains**:
  - **No devDependencies** (avoids dependency conflicts)
  - Publishing metadata (`keywords`, `repository`, `categories`)
  - Marketplace package name: `"taskr-kanban"`
  - VS Code extension configuration
- **Used for**: Final extension packaging

### `package.mjs` (Build Script)
- **Purpose**: Creates clean package for distribution
- **Process**:
  1. Builds the extension (`build:js` + `build:css`)
  2. Creates clean `vsix-build/` directory
  3. Copies only essential files (no source code)
  4. Renames `package.publish.json` → `package.json`
  5. Ready for `vsce package`

## 🚀 Development Workflow

### Local Development
```bash
# Install dependencies
npm install

# Start development with hot reload
npm run watch

# Run just JavaScript build
npm run build:js

# Run just CSS build  
npm run build:css

# Full production build
npm run build

# Type checking
npm run check-types

# Linting
npm run lint
```

### Testing in VS Code
1. Press `F5` in VS Code to launch Extension Development Host
2. Test your extension functionality in the new window
3. Use `Developer: Reload Window` to reload after changes

## 📦 Production Packaging

### Step 1: Build Clean Package
```bash
npm run package
```
This creates `vsix-build/` with clean distribution files.

### Step 2: Create VSIX
```bash
cd vsix-build
npx vsce package --no-dependencies
```
Creates: `taskr-kanban-1.0.1.vsix`

### Alternative: One Command
```bash
npm run package && cd vsix-build && npx vsce package --no-dependencies
```

## 🔄 Keeping Files in Sync

### Critical Fields to Sync Between Files

When updating extension metadata, ensure these fields match between `package.json` and `package.publish.json`:

#### Version & Identity
```json
{
  "version": "1.0.1",                    // ⚠️ MUST MATCH
  "publisher": "Hamster",        // ⚠️ MUST MATCH  
  "displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH
  "description": "A visual Kanban board...",  // ⚠️ MUST MATCH
}
```

#### VS Code Configuration
```json
{
  "engines": { "vscode": "^1.101.0" },   // ⚠️ MUST MATCH
  "categories": [...],                    // ⚠️ MUST MATCH
  "activationEvents": [...],              // ⚠️ MUST MATCH
  "main": "./dist/extension.js",          // ⚠️ MUST MATCH
  "contributes": { ... }                  // ⚠️ MUST MATCH EXACTLY
}
```

### Key Differences (Should NOT Match)
```json
// package.json (dev)
{
  "name": "taskr",                       // ✅ Short dev name
  "devDependencies": { ... },            // ✅ Only in dev file
  "scripts": { ... }                     // ✅ Build scripts
}

// package.publish.json (publishing)
{
  "name": "taskr-kanban",               // ✅ Marketplace name
  "keywords": [...],                     // ✅ Only in publish file
  "repository": "https://github.com/...", // ✅ Only in publish file
  // NO devDependencies                  // ✅ Clean for publishing
  // NO build scripts                    // ✅ Not needed in package
}
```

## 🤖 Automated Release Process

### Changesets Workflow
This extension uses [Changesets](https://github.com/changesets/changesets) for automated version management and publishing.

#### Adding 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

#### Automated Publishing
The automation workflow runs on pushes to `main`:

1. **Version Workflow** (`.github/workflows/version.yml`):
   - Detects when changesets exist
   - Creates a "Version Packages" PR with updated versions and CHANGELOG
   - When the PR is merged, automatically publishes the extension

2. **Release Process** (`scripts/release.sh`):
   - Builds the extension using the 3-file packaging system
   - Creates VSIX package
   - Publishes to VS Code Marketplace (if `VSCE_PAT` is set)
   - Publishes to Open VSX Registry (if `OVSX_PAT` is set)
   - Creates git tags for the extension version

#### Required Secrets
For automated publishing, these secrets must be set in the repository:

- `VSCE_PAT`: Personal Access Token for VS Code Marketplace
- `OVSX_PAT`: Personal Access Token for Open VSX Registry
- `GITHUB_TOKEN`: Automatically provided by GitHub Actions

#### Manual Release
If needed, you can manually trigger a release:

```bash
# From project root
./scripts/release.sh
```

### Extension Tagging
The extension uses a separate tagging strategy from the main package:

- **Extension tags**: `[email protected]`
- **Main package tags**: `[email protected]`

This allows independent versioning and prevents conflicts in the monorepo.

## 🔍 Troubleshooting

### Dependency Conflicts
**Problem**: `vsce package` fails with missing dependencies
**Solution**: Use the 3-file system - never run `vsce package` from root

### Build Failures
**Problem**: Extension not working after build
**Check**:
1. All files copied to `vsix-build/dist/`
2. `package.publish.json` has correct `main` field
3. VS Code engine version compatibility

### Sync Issues
**Problem**: Extension works locally but fails when packaged
**Check**: Ensure critical fields are synced between package files

### Changeset Issues
**Problem**: Version workflow not triggering
**Check**: 
1. Changeset files exist in `.changeset/`
2. Package name in changeset matches `package.publish.json`
3. Changes are pushed to `main` branch

**Problem**: Publishing fails
**Check**:
1. Required secrets are set in repository settings
2. `package.publish.json` has correct repository URL
3. Build process completes successfully

## 📝 Version Release Checklist

### Manual Releases
1. **Create changeset**: `npx changeset add`
2. **Update critical fields** in both `package.json` and `package.publish.json`
3. **Test locally** with `F5` in VS Code
4. **Commit and push** to trigger automated workflow

### Automated Releases (Recommended)
1. **Create changeset**: `npx changeset add`
2. **Push to feature branch** and create PR
3. **Merge PR** - this triggers version PR creation
4. **Review and merge version PR** - this triggers automated publishing

## 🎯 Why This System?

- **Avoids dependency conflicts**: VS Code doesn't see dev dependencies
- **Clean distribution**: Only essential files in final package
- **Faster packaging**: No dependency resolution during `vsce package`
- **Maintainable**: Clear separation of dev vs. production configs
- **Reliable**: Consistent, conflict-free packaging process
- **Automated**: Changesets handle versioning and publishing automatically
- **Traceable**: Clear changelog and git tags for every release

---

**Remember**: Always use `npx changeset add` for changes, then push to trigger automated releases! 🚀 

```

--------------------------------------------------------------------------------
/apps/cli/src/utils/ui.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview UI utilities for Task Master CLI
 * Provides formatting, display, and visual components for the command line interface
 */

import chalk from 'chalk';
import boxen from 'boxen';
import Table from 'cli-table3';
import type { Task, TaskStatus, TaskPriority } from '@tm/core/types';

/**
 * Get colored status display with ASCII icons (matches scripts/modules/ui.js style)
 */
export function getStatusWithColor(
	status: TaskStatus,
	forTable: boolean = false
): string {
	const statusConfig = {
		done: {
			color: chalk.green,
			icon: '✓',
			tableIcon: '✓'
		},
		pending: {
			color: chalk.yellow,
			icon: '○',
			tableIcon: '○'
		},
		'in-progress': {
			color: chalk.hex('#FFA500'),
			icon: '▶',
			tableIcon: '▶'
		},
		deferred: {
			color: chalk.gray,
			icon: 'x',
			tableIcon: 'x'
		},
		review: {
			color: chalk.magenta,
			icon: '?',
			tableIcon: '?'
		},
		cancelled: {
			color: chalk.gray,
			icon: 'x',
			tableIcon: 'x'
		},
		blocked: {
			color: chalk.red,
			icon: '!',
			tableIcon: '!'
		},
		completed: {
			color: chalk.green,
			icon: '✓',
			tableIcon: '✓'
		}
	};

	const config = statusConfig[status] || {
		color: chalk.red,
		icon: 'X',
		tableIcon: 'X'
	};

	const icon = forTable ? config.tableIcon : config.icon;
	return config.color(`${icon} ${status}`);
}

/**
 * Get colored priority display
 */
export function getPriorityWithColor(priority: TaskPriority): string {
	const priorityColors: Record<TaskPriority, (text: string) => string> = {
		critical: chalk.red.bold,
		high: chalk.red,
		medium: chalk.yellow,
		low: chalk.gray
	};

	const colorFn = priorityColors[priority] || chalk.white;
	return colorFn(priority);
}

/**
 * Get colored complexity display
 */
export function getComplexityWithColor(complexity: number | string): string {
	const score =
		typeof complexity === 'string' ? parseInt(complexity, 10) : complexity;

	if (isNaN(score)) {
		return chalk.gray('N/A');
	}

	if (score >= 8) {
		return chalk.red.bold(`${score} (High)`);
	} else if (score >= 5) {
		return chalk.yellow(`${score} (Medium)`);
	} else {
		return chalk.green(`${score} (Low)`);
	}
}

/**
 * Truncate text to specified length
 */
export function truncate(text: string, maxLength: number): string {
	if (text.length <= maxLength) {
		return text;
	}
	return text.substring(0, maxLength - 3) + '...';
}

/**
 * Create a progress bar
 */
export function createProgressBar(
	completed: number,
	total: number,
	width: number = 30
): string {
	if (total === 0) {
		return chalk.gray('No tasks');
	}

	const percentage = Math.round((completed / total) * 100);
	const filled = Math.round((completed / total) * width);
	const empty = width - filled;

	const bar = chalk.green('█').repeat(filled) + chalk.gray('░').repeat(empty);

	return `${bar} ${chalk.cyan(`${percentage}%`)} (${completed}/${total})`;
}

/**
 * Display a fancy banner
 */
export function displayBanner(title: string = 'Task Master'): void {
	console.log(
		boxen(chalk.white.bold(title), {
			padding: 1,
			margin: { top: 1, bottom: 1 },
			borderStyle: 'round',
			borderColor: 'blue',
			textAlignment: 'center'
		})
	);
}

/**
 * Display an error message (matches scripts/modules/ui.js style)
 */
export function displayError(message: string, details?: string): void {
	console.error(
		boxen(
			chalk.red.bold('X Error: ') +
				chalk.white(message) +
				(details ? '\n\n' + chalk.gray(details) : ''),
			{
				padding: 1,
				borderStyle: 'round',
				borderColor: 'red'
			}
		)
	);
}

/**
 * Display a success message
 */
export function displaySuccess(message: string): void {
	console.log(
		boxen(
			chalk.green.bold(String.fromCharCode(8730) + ' ') + chalk.white(message),
			{
				padding: 1,
				borderStyle: 'round',
				borderColor: 'green'
			}
		)
	);
}

/**
 * Display a warning message
 */
export function displayWarning(message: string): void {
	console.log(
		boxen(chalk.yellow.bold('⚠ ') + chalk.white(message), {
			padding: 1,
			borderStyle: 'round',
			borderColor: 'yellow'
		})
	);
}

/**
 * Display info message
 */
export function displayInfo(message: string): void {
	console.log(
		boxen(chalk.blue.bold('i ') + chalk.white(message), {
			padding: 1,
			borderStyle: 'round',
			borderColor: 'blue'
		})
	);
}

/**
 * Format dependencies with their status
 */
export function formatDependenciesWithStatus(
	dependencies: string[] | number[],
	tasks: Task[]
): string {
	if (!dependencies || dependencies.length === 0) {
		return chalk.gray('none');
	}

	const taskMap = new Map(tasks.map((t) => [t.id.toString(), t]));

	return dependencies
		.map((depId) => {
			const task = taskMap.get(depId.toString());
			if (!task) {
				return chalk.red(`${depId} (not found)`);
			}

			const statusIcon =
				task.status === 'done'
					? '✓'
					: task.status === 'in-progress'
						? '►'
						: '○';

			return `${depId}${statusIcon}`;
		})
		.join(', ');
}

/**
 * Create a task table for display
 */
export function createTaskTable(
	tasks: Task[],
	options?: {
		showSubtasks?: boolean;
		showComplexity?: boolean;
		showDependencies?: boolean;
	}
): string {
	const {
		showSubtasks = false,
		showComplexity = false,
		showDependencies = true
	} = options || {};

	// Calculate dynamic column widths based on terminal width
	const terminalWidth = process.stdout.columns * 0.9 || 100;
	// Adjust column widths to better match the original layout
	const baseColWidths = showComplexity
		? [
				Math.floor(terminalWidth * 0.06),
				Math.floor(terminalWidth * 0.4),
				Math.floor(terminalWidth * 0.15),
				Math.floor(terminalWidth * 0.12),
				Math.floor(terminalWidth * 0.2),
				Math.floor(terminalWidth * 0.12)
			] // ID, Title, Status, Priority, Dependencies, Complexity
		: [
				Math.floor(terminalWidth * 0.08),
				Math.floor(terminalWidth * 0.4),
				Math.floor(terminalWidth * 0.18),
				Math.floor(terminalWidth * 0.12),
				Math.floor(terminalWidth * 0.2)
			]; // ID, Title, Status, Priority, Dependencies

	const headers = [
		chalk.blue.bold('ID'),
		chalk.blue.bold('Title'),
		chalk.blue.bold('Status'),
		chalk.blue.bold('Priority')
	];
	const colWidths = baseColWidths.slice(0, 4);

	if (showDependencies) {
		headers.push(chalk.blue.bold('Dependencies'));
		colWidths.push(baseColWidths[4]);
	}

	if (showComplexity) {
		headers.push(chalk.blue.bold('Complexity'));
		colWidths.push(baseColWidths[5] || 12);
	}

	const table = new Table({
		head: headers,
		style: { head: [], border: [] },
		colWidths,
		wordWrap: true
	});

	tasks.forEach((task) => {
		const row: string[] = [
			chalk.cyan(task.id.toString()),
			truncate(task.title, colWidths[1] - 3),
			getStatusWithColor(task.status, true), // Use table version
			getPriorityWithColor(task.priority)
		];

		if (showDependencies) {
			// For table display, show simple format without status icons
			if (!task.dependencies || task.dependencies.length === 0) {
				row.push(chalk.gray('None'));
			} else {
				row.push(
					chalk.cyan(task.dependencies.map((d) => String(d)).join(', '))
				);
			}
		}

		if (showComplexity) {
			// Show N/A if no complexity score
			row.push(chalk.gray('N/A'));
		}

		table.push(row);

		// Add subtasks if requested
		if (showSubtasks && task.subtasks && task.subtasks.length > 0) {
			task.subtasks.forEach((subtask) => {
				const subRow: string[] = [
					chalk.gray(` └─ ${subtask.id}`),
					chalk.gray(truncate(subtask.title, colWidths[1] - 6)),
					chalk.gray(getStatusWithColor(subtask.status, true)),
					chalk.gray(subtask.priority || 'medium')
				];

				if (showDependencies) {
					subRow.push(
						chalk.gray(
							subtask.dependencies && subtask.dependencies.length > 0
								? subtask.dependencies.map((dep) => String(dep)).join(', ')
								: 'None'
						)
					);
				}

				if (showComplexity) {
					subRow.push(chalk.gray('--'));
				}

				table.push(subRow);
			});
		}
	});

	return table.toString();
}

```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/move-task-cross-tag.test.js:
--------------------------------------------------------------------------------

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

// Mock the utils functions
const mockFindTasksPath = jest
	.fn()
	.mockReturnValue('/test/path/.taskmaster/tasks/tasks.json');
jest.mock('../../../../mcp-server/src/core/utils/path-utils.js', () => ({
	findTasksPath: mockFindTasksPath
}));

const mockEnableSilentMode = jest.fn();
const mockDisableSilentMode = jest.fn();
const mockReadJSON = jest.fn();
const mockWriteJSON = jest.fn();
jest.mock('../../../../scripts/modules/utils.js', () => ({
	enableSilentMode: mockEnableSilentMode,
	disableSilentMode: mockDisableSilentMode,
	readJSON: mockReadJSON,
	writeJSON: mockWriteJSON
}));

// Import the direct function after setting up mocks
import { moveTaskCrossTagDirect } from '../../../../mcp-server/src/core/direct-functions/move-task-cross-tag.js';

describe('MCP Cross-Tag Move Direct Function', () => {
	const mockLog = {
		info: jest.fn(),
		error: jest.fn(),
		warn: jest.fn()
	};

	beforeEach(() => {
		jest.clearAllMocks();
	});

	describe('Mock Verification', () => {
		it('should verify that mocks are working', () => {
			// Test that findTasksPath mock is working
			expect(mockFindTasksPath()).toBe(
				'/test/path/.taskmaster/tasks/tasks.json'
			);

			// Test that readJSON mock is working
			mockReadJSON.mockReturnValue('test');
			expect(mockReadJSON()).toBe('test');
		});
	});

	describe('Parameter Validation', () => {
		it('should return error when source IDs are missing', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_SOURCE_IDS');
			expect(result.error.message).toBe('Source IDs are required');
		});

		it('should return error when source tag is missing', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1,2',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_SOURCE_TAG');
			expect(result.error.message).toBe(
				'Source tag is required for cross-tag moves'
			);
		});

		it('should return error when target tag is missing', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1,2',
					sourceTag: 'backlog',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_TARGET_TAG');
			expect(result.error.message).toBe(
				'Target tag is required for cross-tag moves'
			);
		});

		it('should return error when source and target tags are the same', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1,2',
					sourceTag: 'backlog',
					targetTag: 'backlog',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('SAME_SOURCE_TARGET_TAG');
			expect(result.error.message).toBe(
				'Source and target tags are the same ("backlog")'
			);
			expect(result.error.suggestions).toHaveLength(3);
		});
	});

	describe('Error Code Mapping', () => {
		it('should map tag not found errors correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'invalid',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
			expect(result.error.message).toBe(
				'Source tag "invalid" not found or invalid'
			);
			expect(result.error.suggestions).toHaveLength(3);
		});

		it('should map missing project root errors correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress'
					// Missing projectRoot
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_PROJECT_ROOT');
			expect(result.error.message).toBe(
				'Project root is required if tasksJsonPath is not provided'
			);
		});
	});

	describe('Move Options Handling', () => {
		it('should handle move options correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					withDependencies: true,
					ignoreDependencies: false,
					projectRoot: '/test'
				},
				mockLog
			);

			// The function should fail due to missing tag, but options should be processed
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
		});
	});

	describe('Function Call Flow', () => {
		it('should call findTasksPath when projectRoot is provided', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			// The function should fail due to tag validation before reaching path resolution
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');

			// Since the function fails early, findTasksPath is not called
			expect(mockFindTasksPath).toHaveBeenCalledTimes(0);
		});

		it('should enable and disable silent mode during execution', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			// The function should fail due to tag validation before reaching silent mode calls
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');

			// Since the function fails early, silent mode is not called
			expect(mockEnableSilentMode).toHaveBeenCalledTimes(0);
			expect(mockDisableSilentMode).toHaveBeenCalledTimes(0);
		});

		it('should parse source IDs correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1, 2, 3', // With spaces
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					projectRoot: '/test'
				},
				mockLog
			);

			// Should fail due to tag validation, but ID parsing should work
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
		});

		it('should handle move options correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress',
					withDependencies: true,
					ignoreDependencies: false,
					projectRoot: '/test'
				},
				mockLog
			);

			// Should fail due to tag validation, but option processing should work
			expect(result.success).toBe(false);
			expect(result.error.code).toBe('TAG_OR_TASK_NOT_FOUND');
		});
	});

	describe('Error Handling', () => {
		it('should handle missing project root correctly', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'in-progress'
					// Missing projectRoot
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('MISSING_PROJECT_ROOT');
			expect(result.error.message).toBe(
				'Project root is required if tasksJsonPath is not provided'
			);
		});

		it('should handle same source and target tags', async () => {
			const result = await moveTaskCrossTagDirect(
				{
					sourceIds: '1',
					sourceTag: 'backlog',
					targetTag: 'backlog',
					projectRoot: '/test'
				},
				mockLog
			);

			expect(result.success).toBe(false);
			expect(result.error.code).toBe('SAME_SOURCE_TARGET_TAG');
			expect(result.error.message).toBe(
				'Source and target tags are the same ("backlog")'
			);
			expect(result.error.suggestions).toHaveLength(3);
		});
	});
});

```

--------------------------------------------------------------------------------
/scripts/modules/task-manager/migrate.js:
--------------------------------------------------------------------------------

```javascript
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { createLogWrapper } from '../../../mcp-server/src/tools/utils.js';
import { findProjectRoot } from '../utils.js';
import {
	LEGACY_CONFIG_FILE,
	TASKMASTER_CONFIG_FILE
} from '../../../src/constants/paths.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Create a simple log wrapper for CLI use
const log = createLogWrapper({
	info: (msg) => console.log(chalk.blue('ℹ'), msg),
	warn: (msg) => console.log(chalk.yellow('⚠'), msg),
	error: (msg) => console.error(chalk.red('✗'), msg),
	success: (msg) => console.log(chalk.green('✓'), msg)
});

/**
 * Main migration function
 * @param {Object} options - Migration options
 */
export async function migrateProject(options = {}) {
	const projectRoot = findProjectRoot() || process.cwd();

	log.info(`Starting migration in: ${projectRoot}`);

	// Check if .taskmaster directory already exists
	const taskmasterDir = path.join(projectRoot, '.taskmaster');
	if (fs.existsSync(taskmasterDir) && !options.force) {
		log.warn(
			'.taskmaster directory already exists. Use --force to overwrite or skip migration.'
		);
		return;
	}

	// Analyze what needs to be migrated
	const migrationPlan = analyzeMigrationNeeds(projectRoot);

	if (migrationPlan.length === 0) {
		log.info(
			'No files to migrate. Project may already be using the new structure.'
		);
		return;
	}

	// Show migration plan
	log.info('Migration plan:');
	for (const item of migrationPlan) {
		const action = options.dryRun ? 'Would move' : 'Will move';
		log.info(`  ${action}: ${item.from} → ${item.to}`);
	}

	if (options.dryRun) {
		log.info(
			'Dry run complete. Use --dry-run=false to perform actual migration.'
		);
		return;
	}

	// Confirm migration
	if (!options.yes) {
		const readline = await import('readline');
		const rl = readline.createInterface({
			input: process.stdin,
			output: process.stdout
		});

		const answer = await new Promise((resolve) => {
			rl.question('Proceed with migration? (y/N): ', resolve);
		});
		rl.close();

		if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
			log.info('Migration cancelled.');
			return;
		}
	}

	// Perform migration
	try {
		await performMigration(projectRoot, migrationPlan, options);
		log.success('Migration completed successfully!');
		log.info('You can now use the new .taskmaster directory structure.');
		if (!options.cleanup) {
			log.info(
				'Old files were preserved. Use --cleanup to remove them after verification.'
			);
		}
	} catch (error) {
		log.error(`Migration failed: ${error.message}`);
		throw error;
	}
}

/**
 * Analyze what files need to be migrated
 * @param {string} projectRoot - Project root directory
 * @returns {Array} Migration plan items
 */
function analyzeMigrationNeeds(projectRoot) {
	const migrationPlan = [];

	// Check for tasks directory
	const tasksDir = path.join(projectRoot, 'tasks');
	if (fs.existsSync(tasksDir)) {
		const tasksFiles = fs.readdirSync(tasksDir);
		for (const file of tasksFiles) {
			migrationPlan.push({
				from: path.join('tasks', file),
				to: path.join('.taskmaster', 'tasks', file),
				type: 'task'
			});
		}
	}

	// Check for scripts directory files
	const scriptsDir = path.join(projectRoot, 'scripts');
	if (fs.existsSync(scriptsDir)) {
		const scriptsFiles = fs.readdirSync(scriptsDir);
		for (const file of scriptsFiles) {
			const filePath = path.join(scriptsDir, file);
			if (fs.statSync(filePath).isFile()) {
				// Categorize files more intelligently
				let destination;
				const lowerFile = file.toLowerCase();

				if (
					lowerFile.includes('example') ||
					lowerFile.includes('template') ||
					lowerFile.includes('boilerplate') ||
					lowerFile.includes('sample')
				) {
					// Template/example files go to templates (including example_prd.txt)
					destination = path.join('.taskmaster', 'templates', file);
				} else if (
					lowerFile.includes('complexity') &&
					lowerFile.includes('report') &&
					lowerFile.endsWith('.json')
				) {
					// Only actual complexity reports go to reports
					destination = path.join('.taskmaster', 'reports', file);
				} else if (
					lowerFile.includes('prd') ||
					lowerFile.endsWith('.md') ||
					lowerFile.endsWith('.txt')
				) {
					// Documentation files go to docs (but not examples or reports)
					destination = path.join('.taskmaster', 'docs', file);
				} else {
					// Other files stay in scripts or get skipped - don't force everything into templates
					log.warn(
						`Skipping migration of '${file}' - uncertain categorization. You may need to move this manually.`
					);
					continue;
				}

				migrationPlan.push({
					from: path.join('scripts', file),
					to: destination,
					type: 'script'
				});
			}
		}
	}

	// Check for .taskmasterconfig
	const oldConfig = path.join(projectRoot, LEGACY_CONFIG_FILE);
	if (fs.existsSync(oldConfig)) {
		migrationPlan.push({
			from: LEGACY_CONFIG_FILE,
			to: TASKMASTER_CONFIG_FILE,
			type: 'config'
		});
	}

	return migrationPlan;
}

/**
 * Perform the actual migration
 * @param {string} projectRoot - Project root directory
 * @param {Array} migrationPlan - List of files to migrate
 * @param {Object} options - Migration options
 */
async function performMigration(projectRoot, migrationPlan, options) {
	// Create .taskmaster directory
	const taskmasterDir = path.join(projectRoot, '.taskmaster');
	if (!fs.existsSync(taskmasterDir)) {
		fs.mkdirSync(taskmasterDir, { recursive: true });
	}

	// Group migration items by destination directory to create only needed subdirs
	const neededDirs = new Set();
	for (const item of migrationPlan) {
		const destDir = path.dirname(item.to);
		neededDirs.add(destDir);
	}

	// Create only the directories we actually need
	for (const dir of neededDirs) {
		const fullDirPath = path.join(projectRoot, dir);
		if (!fs.existsSync(fullDirPath)) {
			fs.mkdirSync(fullDirPath, { recursive: true });
			log.info(`Created directory: ${dir}`);
		}
	}

	// Create backup if requested
	if (options.backup) {
		const backupDir = path.join(projectRoot, '.taskmaster-migration-backup');
		log.info(`Creating backup in: ${backupDir}`);
		if (fs.existsSync(backupDir)) {
			fs.rmSync(backupDir, { recursive: true, force: true });
		}
		fs.mkdirSync(backupDir, { recursive: true });
	}

	// Migrate files
	for (const item of migrationPlan) {
		const fromPath = path.join(projectRoot, item.from);
		const toPath = path.join(projectRoot, item.to);

		if (!fs.existsSync(fromPath)) {
			log.warn(`Source file not found: ${item.from}`);
			continue;
		}

		// Create backup if requested
		if (options.backup) {
			const backupPath = path.join(
				projectRoot,
				'.taskmaster-migration-backup',
				item.from
			);
			const backupDir = path.dirname(backupPath);
			if (!fs.existsSync(backupDir)) {
				fs.mkdirSync(backupDir, { recursive: true });
			}
			fs.copyFileSync(fromPath, backupPath);
		}

		// Ensure destination directory exists
		const toDir = path.dirname(toPath);
		if (!fs.existsSync(toDir)) {
			fs.mkdirSync(toDir, { recursive: true });
		}

		// Copy file
		fs.copyFileSync(fromPath, toPath);
		log.info(`Migrated: ${item.from} → ${item.to}`);

		// Remove original if cleanup is requested
		if (options.cleanup) {
			fs.unlinkSync(fromPath);
		}
	}

	// Clean up empty directories if cleanup is requested
	if (options.cleanup) {
		const dirsToCheck = ['tasks', 'scripts'];
		for (const dir of dirsToCheck) {
			const dirPath = path.join(projectRoot, dir);
			if (fs.existsSync(dirPath)) {
				try {
					const files = fs.readdirSync(dirPath);
					if (files.length === 0) {
						fs.rmdirSync(dirPath);
						log.info(`Removed empty directory: ${dir}`);
					}
				} catch (error) {
					// Directory not empty or other error, skip
				}
			}
		}
	}
}

export default { migrateProject };

```

--------------------------------------------------------------------------------
/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 check-types`

#### 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! 🚀

```

--------------------------------------------------------------------------------
/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 a complete valid JSON object representing the 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 only the updated task as a valid JSON object{{#if useResearch}} with research-backed improvements{{/if}}."
		},
		"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 ONLY this new text as a plain string.\n{{#if gatheredContext}}\n\n# Additional Project Context\n\n{{gatheredContext}}\n{{/if}}"
		}
	}
}

```

--------------------------------------------------------------------------------
/src/prompts/parse-prd.json:
--------------------------------------------------------------------------------

```json
{
	"id": "parse-prd",
	"version": "1.0.0",
	"description": "Parse a Product Requirements Document into structured tasks",
	"metadata": {
		"author": "system",
		"created": "2024-01-01T00:00:00Z",
		"updated": "2024-01-01T00:00:00Z",
		"tags": ["prd", "parsing", "initialization"]
	},
	"parameters": {
		"numTasks": {
			"type": "number",
			"required": true,
			"description": "Target number of tasks to generate"
		},
		"nextId": {
			"type": "number",
			"required": true,
			"description": "Starting ID for tasks"
		},
		"research": {
			"type": "boolean",
			"default": false,
			"description": "Enable research mode for latest best practices"
		},
		"prdContent": {
			"type": "string",
			"required": true,
			"description": "Content of the PRD file"
		},
		"prdPath": {
			"type": "string",
			"required": true,
			"description": "Path to the PRD file"
		},
		"defaultTaskPriority": {
			"type": "string",
			"required": false,
			"default": "medium",
			"enum": ["high", "medium", "low"],
			"description": "Default priority for generated tasks"
		},
		"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 specialized in analyzing Product Requirements Documents (PRDs) and generating a structured, logically ordered, dependency-aware and sequenced list of development tasks in JSON format.{{#if research}}\nBefore breaking down the PRD into tasks, you will:\n1. Research and analyze the latest technologies, libraries, frameworks, and best practices that would be appropriate for this project\n2. Identify any potential technical challenges, security concerns, or scalability issues not explicitly mentioned in the PRD without discarding any explicit requirements or going overboard with complexity -- always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches\n3. Consider current industry standards and evolving trends relevant to this project (this step aims to solve LLM hallucinations and out of date information due to training data cutoff dates)\n4. Evaluate alternative implementation approaches and recommend the most efficient path\n5. Include specific library versions, helpful APIs, and concrete implementation guidance based on your research\n6. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches\n\nYour task breakdown should incorporate this research, resulting in more detailed implementation guidance, more accurate dependency mapping, and more precise technology recommendations than would be possible from the PRD text alone, while maintaining all explicit requirements and best practices and all details and nuances of the PRD.{{/if}}\n\nAnalyze the provided PRD content and generate {{#if (gt numTasks 0)}}approximately {{numTasks}}{{else}}an appropriate number of{{/if}} top-level development tasks. If the complexity or the level of detail of the PRD is high, generate more tasks relative to the complexity of the PRD\nEach task should represent a logical unit of work needed to implement the requirements and focus on the most direct and effective way to implement the requirements without unnecessary complexity or overengineering. Include pseudo-code, implementation details, and test strategy for each task. Find the most up to date information to implement each task.\nAssign sequential IDs starting from {{nextId}}. Infer title, description, details, and test strategy for each task based *only* on the PRD content.\nSet status to 'pending', dependencies to an empty array [], and priority to '{{defaultTaskPriority}}' initially for all tasks.\nRespond ONLY with a valid JSON object containing a single key \"tasks\", where the value is an array of task objects adhering to the provided Zod schema. Do not include any explanation or markdown formatting.\n\nEach task should follow this JSON structure:\n{\n\t\"id\": number,\n\t\"title\": string,\n\t\"description\": string,\n\t\"status\": \"pending\",\n\t\"dependencies\": number[] (IDs of tasks this depends on),\n\t\"priority\": \"high\" | \"medium\" | \"low\",\n\t\"details\": string (implementation details),\n\t\"testStrategy\": string (validation approach)\n}\n\nGuidelines:\n1. {{#if (gt numTasks 0)}}Unless complexity warrants otherwise{{else}}Depending on the complexity{{/if}}, create {{#if (gt numTasks 0)}}exactly {{numTasks}}{{else}}an appropriate number of{{/if}} tasks, numbered sequentially starting from {{nextId}}\n2. Each task should be atomic and focused on a single responsibility following the most up to date best practices and standards\n3. Order tasks logically - consider dependencies and implementation sequence\n4. Early tasks should focus on setup, core functionality first, then advanced features\n5. Include clear validation/testing approach for each task\n6. Set appropriate dependency IDs (a task can only depend on tasks with lower IDs, potentially including existing tasks with IDs less than {{nextId}} if applicable)\n7. Assign priority (high/medium/low) based on criticality and dependency order\n8. Include detailed implementation guidance in the \"details\" field{{#if research}}, with specific libraries and version recommendations based on your research{{/if}}\n9. If the PRD contains specific requirements for libraries, database schemas, frameworks, tech stacks, or any other implementation details, STRICTLY ADHERE to these requirements in your task breakdown and do not discard them under any circumstance\n10. Focus on filling in any gaps left by the PRD or areas that aren't fully specified, while preserving all explicit requirements\n11. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches{{#if research}}\n12. For each task, include specific, actionable guidance based on current industry standards and best practices discovered through research{{/if}}",
			"user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before generating tasks:\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 key files like package.json, README.md, and main entry points\n4. Analyze the current state of implementation to understand what already exists\n\nBased on your analysis:\n- Identify what components/features are already implemented\n- Understand the technology stack, frameworks, and patterns in use\n- Generate tasks that build upon the existing codebase rather than duplicating work\n- Ensure tasks align with the project's current architecture and conventions\n\nProject Root: {{projectRoot}}\n\n{{/if}}Here's the Product Requirements Document (PRD) to break down into {{#if (gt numTasks 0)}}approximately {{numTasks}}{{else}}an appropriate number of{{/if}} tasks, starting IDs from {{nextId}}:{{#if research}}\n\nRemember to thoroughly research current best practices and technologies before task breakdown to provide specific, actionable implementation details.{{/if}}\n\n{{prdContent}}\n\n\n\t\tReturn your response in this format:\n{\n    \"tasks\": [\n        {\n            \"id\": 1,\n            \"title\": \"Setup Project Repository\",\n            \"description\": \"...\",\n            ...\n        },\n        ...\n    ],\n    \"metadata\": {\n        \"projectName\": \"PRD Implementation\",\n        \"totalTasks\": {{#if (gt numTasks 0)}}{{numTasks}}{{else}}{number of tasks}{{/if}},\n        \"sourceFile\": \"{{prdPath}}\",\n        \"generatedAt\": \"YYYY-MM-DD\"\n    }\n}"
		}
	}
}

```

--------------------------------------------------------------------------------
/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)
			);
		});
	});
});

```

--------------------------------------------------------------------------------
/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/services/organization.service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Organization and Brief management service
 * Handles fetching and managing organizations and briefs from the API
 */

import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '../types/database.types.js';
import { TaskMasterError, ERROR_CODES } from '../errors/task-master-error.js';
import { getLogger } from '../logger/index.js';

/**
 * Organization data structure
 */
export interface Organization {
	id: string;
	name: string;
	slug: string;
}

/**
 * Brief data structure
 */
export interface Brief {
	id: string;
	accountId: string;
	documentId: string;
	status: string;
	createdAt: string;
	updatedAt: string;
}

/**
 * Task data structure from the remote database
 */
export interface RemoteTask {
	id: string;
	briefId: string;
	documentId: string;
	position: number | null;
	subtaskPosition: number | null;
	status: string;
	createdAt: string;
	updatedAt: string;
	// Document details from join
	document?: {
		id: string;
		document_name: string;
		title: string;
		description: string;
	};
}

/**
 * Service for managing organizations and briefs
 */
export class OrganizationService {
	private logger = getLogger('OrganizationService');

	constructor(private supabaseClient: SupabaseClient<Database>) {}

	/**
	 * Get all organizations for the authenticated user
	 */
	async getOrganizations(): Promise<Organization[]> {
		try {
			// The user is already authenticated via the Authorization header
			// Query the user_accounts view/table (filtered by RLS for current user)
			const { data, error } = await this.supabaseClient
				.from('user_accounts')
				.select(`
					id,
					name,
					slug
				`);

			if (error) {
				throw new TaskMasterError(
					`Failed to fetch organizations: ${error.message}`,
					ERROR_CODES.API_ERROR,
					{ operation: 'getOrganizations' },
					error
				);
			}

			if (!data || data.length === 0) {
				this.logger.debug('No organizations found for user');
				return [];
			}

			// Map to our Organization interface
			return data.map((org) => ({
				id: org.id ?? '',
				name: org.name ?? '',
				slug: org.slug ?? org.id ?? '' // Use ID as fallback if slug is null
			}));
		} catch (error) {
			if (error instanceof TaskMasterError) {
				throw error;
			}
			throw new TaskMasterError(
				'Failed to fetch organizations',
				ERROR_CODES.API_ERROR,
				{ operation: 'getOrganizations' },
				error as Error
			);
		}
	}

	/**
	 * Get a specific organization by ID
	 */
	async getOrganization(orgId: string): Promise<Organization | null> {
		try {
			const { data, error } = await this.supabaseClient
				.from('accounts')
				.select(`
					id,
					name,
					slug
				`)
				.eq('id', orgId)
				.single();

			if (error) {
				if (error.code === 'PGRST116') {
					// No rows found
					return null;
				}
				throw new TaskMasterError(
					`Failed to fetch organization: ${error.message}`,
					ERROR_CODES.API_ERROR,
					{ operation: 'getOrganization', orgId },
					error
				);
			}

			if (!data) {
				return null;
			}

			const accountData =
				data as Database['public']['Tables']['accounts']['Row'];
			return {
				id: accountData.id,
				name: accountData.name,
				slug: accountData.slug || accountData.id
			};
		} catch (error) {
			if (error instanceof TaskMasterError) {
				throw error;
			}
			throw new TaskMasterError(
				'Failed to fetch organization',
				ERROR_CODES.API_ERROR,
				{ operation: 'getOrganization', orgId },
				error as Error
			);
		}
	}

	/**
	 * Get all briefs for a specific organization
	 */
	async getBriefs(orgId: string): Promise<Brief[]> {
		try {
			const { data, error } = await this.supabaseClient
				.from('brief')
				.select(`
					id,
					account_id,
					document_id,
					status,
					created_at,
					updated_at
				`)
				.eq('account_id', orgId);

			if (error) {
				throw new TaskMasterError(
					`Failed to fetch briefs: ${error.message}`,
					ERROR_CODES.API_ERROR,
					{ operation: 'getBriefs', orgId },
					error
				);
			}

			if (!data || data.length === 0) {
				this.logger.debug(`No briefs found for organization ${orgId}`);
				return [];
			}

			// Map to our Brief interface
			return data.map((brief: any) => ({
				id: brief.id,
				accountId: brief.account_id,
				documentId: brief.document_id,
				status: brief.status,
				createdAt: brief.created_at,
				updatedAt: brief.updated_at
			}));
		} catch (error) {
			if (error instanceof TaskMasterError) {
				throw error;
			}
			throw new TaskMasterError(
				'Failed to fetch briefs',
				ERROR_CODES.API_ERROR,
				{ operation: 'getBriefs', orgId },
				error as Error
			);
		}
	}

	/**
	 * Get a specific brief by ID
	 */
	async getBrief(briefId: string): Promise<Brief | null> {
		try {
			const { data, error } = await this.supabaseClient
				.from('brief')
				.select(`
					id,
					account_id,
					document_id,
					status,
					created_at,
					updated_at
				`)
				.eq('id', briefId)
				.single();

			if (error) {
				if (error.code === 'PGRST116') {
					// No rows found
					return null;
				}
				throw new TaskMasterError(
					`Failed to fetch brief: ${error.message}`,
					ERROR_CODES.API_ERROR,
					{ operation: 'getBrief', briefId },
					error
				);
			}

			if (!data) {
				return null;
			}

			const briefData = data as any;
			return {
				id: briefData.id,
				accountId: briefData.account_id,
				documentId: briefData.document_id,
				status: briefData.status,
				createdAt: briefData.created_at,
				updatedAt: briefData.updated_at
			};
		} catch (error) {
			if (error instanceof TaskMasterError) {
				throw error;
			}
			throw new TaskMasterError(
				'Failed to fetch brief',
				ERROR_CODES.API_ERROR,
				{ operation: 'getBrief', briefId },
				error as Error
			);
		}
	}

	/**
	 * Validate that a user has access to an organization
	 */
	async validateOrgAccess(orgId: string): Promise<boolean> {
		try {
			const org = await this.getOrganization(orgId);
			return org !== null;
		} catch (error) {
			this.logger.error(`Failed to validate org access: ${error}`);
			return false;
		}
	}

	/**
	 * Validate that a user has access to a brief
	 */
	async validateBriefAccess(briefId: string): Promise<boolean> {
		try {
			const brief = await this.getBrief(briefId);
			return brief !== null;
		} catch (error) {
			this.logger.error(`Failed to validate brief access: ${error}`);
			return false;
		}
	}

	/**
	 * Get all tasks for a specific brief
	 */
	async getTasks(briefId: string): Promise<RemoteTask[]> {
		try {
			const { data, error } = await this.supabaseClient
				.from('tasks')
				.select(`
					*,
					document:document_id (
						id,
						document_name,
						title,
						description
					)
				`)
				.eq('brief_id', briefId)
				.order('position', { ascending: true })
				.order('subtask_position', { ascending: true })
				.order('created_at', { ascending: true });

			if (error) {
				throw new TaskMasterError(
					`Failed to fetch tasks: ${error.message}`,
					ERROR_CODES.API_ERROR,
					{ operation: 'getTasks', briefId },
					error
				);
			}

			if (!data || data.length === 0) {
				this.logger.debug(`No tasks found for brief ${briefId}`);
				return [];
			}

			// Map to our RemoteTask interface
			return data.map((task: any) => ({
				id: task.id,
				briefId: task.brief_id,
				documentId: task.document_id,
				position: task.position,
				subtaskPosition: task.subtask_position,
				status: task.status,
				createdAt: task.created_at,
				updatedAt: task.updated_at,
				document: task.document
					? {
							id: task.document.id,
							document_name: task.document.document_name,
							title: task.document.title,
							description: task.document.description
						}
					: undefined
			}));
		} catch (error) {
			if (error instanceof TaskMasterError) {
				throw error;
			}
			throw new TaskMasterError(
				'Failed to fetch tasks',
				ERROR_CODES.API_ERROR,
				{ operation: 'getTasks', briefId },
				error as Error
			);
		}
	}
}

```

--------------------------------------------------------------------------------
/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.25.4",
	"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",
		"check-types": "tsc --noEmit"
	},
	"dependencies": {
		"task-master-ai": "0.27.3"
	},
	"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.7.3"
	},
	"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
};

```

--------------------------------------------------------------------------------
/packages/tm-core/src/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'
} 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/config/services/runtime-state-manager.service.spec.ts:
--------------------------------------------------------------------------------

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

import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { promises as fs } from 'node:fs';
import { RuntimeStateManager } from './runtime-state-manager.service.js';
import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.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.activeTag).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.activeTag).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.activeTag).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.activeTag).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.activeTag).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.activeTag).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.setActiveTag('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.getActiveTag();
			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.setActiveTag('new-tag');

			expect(stateManager.getActiveTag()).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.setActiveTag('feature-xyz');

			expect(stateManager.getActiveTag()).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.activeTag).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.getActiveTag()).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.getActiveTag()).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'
			);
		});
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/src/interfaces/storage.interface.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Storage interface definitions for the tm-core package
 * This file defines the contract for all storage implementations
 */

import type { Task, TaskMetadata, TaskStatus } from '../types/index.js';

/**
 * Result type for updateTaskStatus operations
 */
export interface UpdateStatusResult {
	success: boolean;
	oldStatus: TaskStatus;
	newStatus: TaskStatus;
	taskId: string;
}

/**
 * Interface for storage operations on tasks
 * All storage implementations must implement this interface
 */
export interface IStorage {
	/**
	 * Load all tasks from storage, optionally filtered by tag
	 * @param tag - Optional tag to filter tasks by
	 * @returns Promise that resolves to an array of tasks
	 */
	loadTasks(tag?: string): Promise<Task[]>;

	/**
	 * Load a single task by ID
	 * @param taskId - ID of the task to load
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves to the task or null if not found
	 */
	loadTask(taskId: string, tag?: string): Promise<Task | null>;

	/**
	 * Save tasks to storage, replacing existing tasks
	 * @param tasks - Array of tasks to save
	 * @param tag - Optional tag context for the tasks
	 * @returns Promise that resolves when save is complete
	 */
	saveTasks(tasks: Task[], tag?: string): Promise<void>;

	/**
	 * Append new tasks to existing storage without replacing
	 * @param tasks - Array of tasks to append
	 * @param tag - Optional tag context for the tasks
	 * @returns Promise that resolves when append is complete
	 */
	appendTasks(tasks: Task[], tag?: string): Promise<void>;

	/**
	 * Update a specific task by ID
	 * @param taskId - ID of the task to update
	 * @param updates - Partial task object with fields to update
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves when update is complete
	 */
	updateTask(
		taskId: string,
		updates: Partial<Task>,
		tag?: string
	): Promise<void>;

	/**
	 * Update task or subtask status by ID
	 * @param taskId - ID of the task or subtask (e.g., "1" or "1.2")
	 * @param newStatus - New status to set
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves to update result with old and new status
	 */
	updateTaskStatus(
		taskId: string,
		newStatus: TaskStatus,
		tag?: string
	): Promise<UpdateStatusResult>;

	/**
	 * Delete a task by ID
	 * @param taskId - ID of the task to delete
	 * @param tag - Optional tag context for the task
	 * @returns Promise that resolves when deletion is complete
	 */
	deleteTask(taskId: string, tag?: string): Promise<void>;

	/**
	 * Check if tasks exist in storage for the given tag
	 * @param tag - Optional tag to check existence for
	 * @returns Promise that resolves to boolean indicating existence
	 */
	exists(tag?: string): Promise<boolean>;

	/**
	 * Load metadata about the task collection
	 * @param tag - Optional tag to get metadata for
	 * @returns Promise that resolves to task metadata
	 */
	loadMetadata(tag?: string): Promise<TaskMetadata | null>;

	/**
	 * Save metadata about the task collection
	 * @param metadata - Metadata object to save
	 * @param tag - Optional tag context for the metadata
	 * @returns Promise that resolves when save is complete
	 */
	saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;

	/**
	 * Get all available tags in storage
	 * @returns Promise that resolves to array of available tags
	 */
	getAllTags(): Promise<string[]>;

	/**
	 * Delete all tasks and metadata for a specific tag
	 * @param tag - Tag to delete
	 * @returns Promise that resolves when deletion is complete
	 */
	deleteTag(tag: string): Promise<void>;

	/**
	 * Rename a tag (move all tasks from old tag to new tag)
	 * @param oldTag - Current tag name
	 * @param newTag - New tag name
	 * @returns Promise that resolves when rename is complete
	 */
	renameTag(oldTag: string, newTag: string): Promise<void>;

	/**
	 * Copy all tasks from one tag to another
	 * @param sourceTag - Source tag to copy from
	 * @param targetTag - Target tag to copy to
	 * @returns Promise that resolves when copy is complete
	 */
	copyTag(sourceTag: string, targetTag: string): Promise<void>;

	/**
	 * Initialize storage (create necessary directories, files, etc.)
	 * @returns Promise that resolves when initialization is complete
	 */
	initialize(): Promise<void>;

	/**
	 * Clean up and close storage connections
	 * @returns Promise that resolves when cleanup is complete
	 */
	close(): Promise<void>;

	/**
	 * Get storage statistics (file sizes, task counts, etc.)
	 * @returns Promise that resolves to storage statistics
	 */
	getStats(): Promise<StorageStats>;
}

/**
 * Storage statistics interface
 */
export interface StorageStats {
	/** Total number of tasks across all tags */
	totalTasks: number;
	/** Total number of tags */
	totalTags: number;
	/** Storage size in bytes */
	storageSize: number;
	/** Last modified timestamp */
	lastModified: string;
	/** Available tags with task counts */
	tagStats: Array<{
		tag: string;
		taskCount: number;
		lastModified: string;
	}>;
}

/**
 * Configuration options for storage implementations
 */
export interface StorageConfig {
	/** Base path for storage */
	basePath: string;
	/** Enable backup creation */
	enableBackup?: boolean;
	/** Maximum number of backups to keep */
	maxBackups?: number;
	/** Enable compression for storage */
	enableCompression?: boolean;
	/** File encoding (default: utf8) */
	encoding?: BufferEncoding;
	/** Enable atomic writes */
	atomicWrites?: boolean;
}

/**
 * Base abstract class for storage implementations
 * Provides common functionality and enforces the interface
 */
export abstract class BaseStorage implements IStorage {
	protected config: StorageConfig;

	constructor(config: StorageConfig) {
		this.config = config;
	}

	// Abstract methods that must be implemented by concrete classes
	abstract loadTasks(tag?: string): Promise<Task[]>;
	abstract loadTask(taskId: string, tag?: string): Promise<Task | null>;
	abstract saveTasks(tasks: Task[], tag?: string): Promise<void>;
	abstract appendTasks(tasks: Task[], tag?: string): Promise<void>;
	abstract updateTask(
		taskId: string,
		updates: Partial<Task>,
		tag?: string
	): Promise<void>;
	abstract updateTaskStatus(
		taskId: string,
		newStatus: TaskStatus,
		tag?: string
	): Promise<UpdateStatusResult>;
	abstract deleteTask(taskId: string, tag?: string): Promise<void>;
	abstract exists(tag?: string): Promise<boolean>;
	abstract loadMetadata(tag?: string): Promise<TaskMetadata | null>;
	abstract saveMetadata(metadata: TaskMetadata, tag?: string): Promise<void>;
	abstract getAllTags(): Promise<string[]>;
	abstract deleteTag(tag: string): Promise<void>;
	abstract renameTag(oldTag: string, newTag: string): Promise<void>;
	abstract copyTag(sourceTag: string, targetTag: string): Promise<void>;
	abstract initialize(): Promise<void>;
	abstract close(): Promise<void>;
	abstract getStats(): Promise<StorageStats>;

	/**
	 * Utility method to generate backup filename
	 * @param originalPath - Original file path
	 * @returns Backup file path with timestamp
	 */
	protected generateBackupPath(originalPath: string): string {
		const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
		const parts = originalPath.split('.');
		const extension = parts.pop();
		const baseName = parts.join('.');
		return `${baseName}.backup.${timestamp}.${extension}`;
	}

	/**
	 * Utility method to validate task data before storage operations
	 * @param task - Task to validate
	 * @throws Error if task is invalid
	 */
	protected validateTask(task: Task): void {
		if (!task.id) {
			throw new Error('Task ID is required');
		}
		if (!task.title) {
			throw new Error('Task title is required');
		}
		if (!task.description) {
			throw new Error('Task description is required');
		}
		if (!task.status) {
			throw new Error('Task status is required');
		}
	}

	/**
	 * Utility method to sanitize tag names for file system safety
	 * @param tag - Tag name to sanitize
	 * @returns Sanitized tag name
	 */
	protected sanitizeTag(tag: string): string {
		return tag.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
	}
}

```

--------------------------------------------------------------------------------
/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/show.command.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview ShowCommand using Commander's native class pattern
 * Extends Commander.Command for better integration with the framework
 */

import { Command } from 'commander';
import chalk from 'chalk';
import boxen from 'boxen';
import { createTaskMasterCore, type Task, type TaskMasterCore } from '@tm/core';
import type { StorageType } from '@tm/core/types';
import * as ui from '../utils/ui.js';
import { displayTaskDetails } from '../ui/components/task-detail.component.js';

/**
 * Options interface for the show command
 */
export interface ShowCommandOptions {
	id?: string;
	status?: string;
	format?: 'text' | 'json';
	silent?: boolean;
	project?: string;
}

/**
 * Result type from show command
 */
export interface ShowTaskResult {
	task: Task | null;
	found: boolean;
	storageType: Exclude<StorageType, 'auto'>;
}

/**
 * Result type for multiple tasks
 */
export interface ShowMultipleTasksResult {
	tasks: Task[];
	notFound: string[];
	storageType: Exclude<StorageType, 'auto'>;
}

/**
 * ShowCommand extending Commander's Command class
 * This is a thin presentation layer over @tm/core
 */
export class ShowCommand extends Command {
	private tmCore?: TaskMasterCore;
	private lastResult?: ShowTaskResult | ShowMultipleTasksResult;

	constructor(name?: string) {
		super(name || 'show');

		// Configure the command
		this.description('Display detailed information about one or more tasks')
			.argument('[id]', 'Task ID(s) to show (comma-separated for multiple)')
			.option(
				'-i, --id <id>',
				'Task ID(s) to show (comma-separated for multiple)'
			)
			.option('-s, --status <status>', 'Filter subtasks by status')
			.option('-f, --format <format>', 'Output format (text, json)', 'text')
			.option('--silent', 'Suppress output (useful for programmatic usage)')
			.option('-p, --project <path>', 'Project root directory', process.cwd())
			.action(
				async (taskId: string | undefined, options: ShowCommandOptions) => {
					await this.executeCommand(taskId, options);
				}
			);
	}

	/**
	 * Execute the show command
	 */
	private async executeCommand(
		taskId: string | undefined,
		options: ShowCommandOptions
	): Promise<void> {
		try {
			// Validate options
			if (!this.validateOptions(options)) {
				process.exit(1);
			}

			// Initialize tm-core
			await this.initializeCore(options.project || process.cwd());

			// Get the task ID from argument or option
			const idArg = taskId || options.id;
			if (!idArg) {
				console.error(chalk.red('Error: Please provide a task ID'));
				process.exit(1);
			}

			// Check if multiple IDs are provided (comma-separated)
			const taskIds = idArg
				.split(',')
				.map((id) => id.trim())
				.filter((id) => id.length > 0);

			// Get tasks from core
			const result =
				taskIds.length > 1
					? await this.getMultipleTasks(taskIds, options)
					: await this.getSingleTask(taskIds[0], options);

			// Store result for programmatic access
			this.setLastResult(result);

			// Display results
			if (!options.silent) {
				this.displayResults(result, options);
			}
		} catch (error: any) {
			const msg = error?.getSanitizedDetails?.() ?? {
				message: error?.message ?? String(error)
			};
			console.error(chalk.red(`Error: ${msg.message || 'Unexpected error'}`));
			if (error.stack && process.env.DEBUG) {
				console.error(chalk.gray(error.stack));
			}
			process.exit(1);
		}
	}

	/**
	 * Validate command options
	 */
	private validateOptions(options: ShowCommandOptions): boolean {
		// Validate format
		if (options.format && !['text', 'json'].includes(options.format)) {
			console.error(chalk.red(`Invalid format: ${options.format}`));
			console.error(chalk.gray(`Valid formats: text, json`));
			return false;
		}

		return true;
	}

	/**
	 * Initialize TaskMasterCore
	 */
	private async initializeCore(projectRoot: string): Promise<void> {
		if (!this.tmCore) {
			this.tmCore = await createTaskMasterCore({ projectPath: projectRoot });
		}
	}

	/**
	 * Get a single task from tm-core
	 */
	private async getSingleTask(
		taskId: string,
		_options: ShowCommandOptions
	): Promise<ShowTaskResult> {
		if (!this.tmCore) {
			throw new Error('TaskMasterCore not initialized');
		}

		// Get the task
		const task = await this.tmCore.getTask(taskId);

		// Get storage type
		const storageType = this.tmCore.getStorageType();

		return {
			task,
			found: task !== null,
			storageType: storageType as Exclude<StorageType, 'auto'>
		};
	}

	/**
	 * Get multiple tasks from tm-core
	 */
	private async getMultipleTasks(
		taskIds: string[],
		_options: ShowCommandOptions
	): Promise<ShowMultipleTasksResult> {
		if (!this.tmCore) {
			throw new Error('TaskMasterCore not initialized');
		}

		const tasks: Task[] = [];
		const notFound: string[] = [];

		// Get each task individually
		for (const taskId of taskIds) {
			const task = await this.tmCore.getTask(taskId);
			if (task) {
				tasks.push(task);
			} else {
				notFound.push(taskId);
			}
		}

		// Get storage type
		const storageType = this.tmCore.getStorageType();

		return {
			tasks,
			notFound,
			storageType: storageType as Exclude<StorageType, 'auto'>
		};
	}

	/**
	 * Display results based on format
	 */
	private displayResults(
		result: ShowTaskResult | ShowMultipleTasksResult,
		options: ShowCommandOptions
	): void {
		const format = options.format || 'text';

		switch (format) {
			case 'json':
				this.displayJson(result);
				break;

			case 'text':
			default:
				if ('task' in result) {
					// Single task result
					this.displaySingleTask(result, options);
				} else {
					// Multiple tasks result
					this.displayMultipleTasks(result, options);
				}
				break;
		}
	}

	/**
	 * Display in JSON format
	 */
	private displayJson(result: ShowTaskResult | ShowMultipleTasksResult): void {
		console.log(JSON.stringify(result, null, 2));
	}

	/**
	 * Display a single task in text format
	 */
	private displaySingleTask(
		result: ShowTaskResult,
		options: ShowCommandOptions
	): void {
		if (!result.found || !result.task) {
			console.log(
				boxen(chalk.yellow(`Task not found!`), {
					padding: { top: 0, bottom: 0, left: 1, right: 1 },
					borderColor: 'yellow',
					borderStyle: 'round',
					margin: { top: 1 }
				})
			);
			return;
		}

		// Use the global task details display function
		displayTaskDetails(result.task, {
			statusFilter: options.status,
			showSuggestedActions: true
		});
	}

	/**
	 * Display multiple tasks in text format
	 */
	private displayMultipleTasks(
		result: ShowMultipleTasksResult,
		_options: ShowCommandOptions
	): void {
		// Header
		ui.displayBanner(`Tasks (${result.tasks.length} found)`);

		if (result.notFound.length > 0) {
			console.log(chalk.yellow(`\n⚠ Not found: ${result.notFound.join(', ')}`));
		}

		if (result.tasks.length === 0) {
			ui.displayWarning('No tasks found matching the criteria.');
			return;
		}

		// Task table
		console.log(chalk.blue.bold(`\n📋 Tasks:\n`));
		console.log(
			ui.createTaskTable(result.tasks, {
				showSubtasks: true,
				showDependencies: true
			})
		);

		console.log(`\n${chalk.gray('Storage: ' + result.storageType)}`);
	}

	/**
	 * Set the last result for programmatic access
	 */
	private setLastResult(
		result: ShowTaskResult | ShowMultipleTasksResult
	): void {
		this.lastResult = result;
	}

	/**
	 * Get the last result (for programmatic usage)
	 */
	getLastResult(): ShowTaskResult | ShowMultipleTasksResult | undefined {
		return this.lastResult;
	}

	/**
	 * Clean up resources
	 */
	async cleanup(): Promise<void> {
		if (this.tmCore) {
			await this.tmCore.close();
			this.tmCore = undefined;
		}
	}

	/**
	 * Static method to register this command on an existing program
	 * This is for gradual migration - allows commands.js to use this
	 */
	static registerOn(program: Command): Command {
		const showCommand = new ShowCommand();
		program.addCommand(showCommand);
		return showCommand;
	}

	/**
	 * Alternative registration that returns the command for chaining
	 * Can also configure the command name if needed
	 */
	static register(program: Command, name?: string): ShowCommand {
		const showCommand = new ShowCommand(name);
		program.addCommand(showCommand);
		return showCommand;
	}
}

```
Page 14/38FirstPrevNextLast