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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/tests/integration/profiles/amp-init-functionality.test.js:
--------------------------------------------------------------------------------

```javascript
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { getRulesProfile } from '../../../src/utils/rule-transformer.js';
import { convertAllRulesToProfileRules } from '../../../src/utils/rule-transformer.js';

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

describe('Amp Profile Init Functionality', () => {
	let tempDir;
	let ampProfile;

	beforeEach(() => {
		// Create temporary directory for testing
		tempDir = fs.mkdtempSync(path.join(__dirname, 'temp-amp-'));

		// Get the Amp profile
		ampProfile = getRulesProfile('amp');
	});

	afterEach(() => {
		// Clean up temporary directory
		if (fs.existsSync(tempDir)) {
			fs.rmSync(tempDir, { recursive: true, force: true });
		}
	});

	describe('Profile Configuration', () => {
		test('should have correct profile metadata', () => {
			expect(ampProfile).toBeDefined();
			expect(ampProfile.profileName).toBe('amp');
			expect(ampProfile.displayName).toBe('Amp');
			expect(ampProfile.profileDir).toBe('.vscode');
			expect(ampProfile.rulesDir).toBe('.');
			expect(ampProfile.mcpConfig).toBe(true);
			expect(ampProfile.mcpConfigName).toBe('settings.json');
			expect(ampProfile.mcpConfigPath).toBe('.vscode/settings.json');
			expect(ampProfile.includeDefaultRules).toBe(false);
		});

		test('should have correct file mapping', () => {
			expect(ampProfile.fileMap).toBeDefined();
			expect(ampProfile.fileMap['AGENTS.md']).toBe('.taskmaster/AGENT.md');
		});

		test('should have lifecycle functions', () => {
			expect(typeof ampProfile.onAddRulesProfile).toBe('function');
			expect(typeof ampProfile.onRemoveRulesProfile).toBe('function');
			expect(typeof ampProfile.onPostConvertRulesProfile).toBe('function');
		});
	});

	describe('AGENT.md Handling', () => {
		test('should create AGENT.md with import when none exists', () => {
			// Create mock AGENTS.md source
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });
			fs.writeFileSync(
				path.join(assetsDir, 'AGENTS.md'),
				'Task Master instructions'
			);

			// Call onAddRulesProfile
			ampProfile.onAddRulesProfile(tempDir, assetsDir);

			// Check that AGENT.md was created with import
			const agentFile = path.join(tempDir, 'AGENT.md');
			expect(fs.existsSync(agentFile)).toBe(true);

			const content = fs.readFileSync(agentFile, 'utf8');
			expect(content).toContain('# Amp Instructions');
			expect(content).toContain('## Task Master AI Instructions');
			expect(content).toContain('@./.taskmaster/AGENT.md');

			// Check that .taskmaster/AGENT.md was created
			const taskMasterAgent = path.join(tempDir, '.taskmaster', 'AGENT.md');
			expect(fs.existsSync(taskMasterAgent)).toBe(true);
		});

		test('should append import to existing AGENT.md', () => {
			// Create existing AGENT.md
			const existingContent =
				'# My Existing Amp Instructions\n\nSome content here.';
			fs.writeFileSync(path.join(tempDir, 'AGENT.md'), existingContent);

			// Create mock AGENTS.md source
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });
			fs.writeFileSync(
				path.join(assetsDir, 'AGENTS.md'),
				'Task Master instructions'
			);

			// Call onAddRulesProfile
			ampProfile.onAddRulesProfile(tempDir, assetsDir);

			// Check that import was appended
			const agentFile = path.join(tempDir, 'AGENT.md');
			const content = fs.readFileSync(agentFile, 'utf8');
			expect(content).toContain('# My Existing Amp Instructions');
			expect(content).toContain('Some content here.');
			expect(content).toContain('## Task Master AI Instructions');
			expect(content).toContain('@./.taskmaster/AGENT.md');
		});

		test('should not duplicate import if already exists', () => {
			// Create AGENT.md with existing import
			const existingContent =
				"# My Amp Instructions\n\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n@./.taskmaster/AGENT.md";
			fs.writeFileSync(path.join(tempDir, 'AGENT.md'), existingContent);

			// Create mock AGENTS.md source
			const assetsDir = path.join(tempDir, 'assets');
			fs.mkdirSync(assetsDir, { recursive: true });
			fs.writeFileSync(
				path.join(assetsDir, 'AGENTS.md'),
				'Task Master instructions'
			);

			// Call onAddRulesProfile
			ampProfile.onAddRulesProfile(tempDir, assetsDir);

			// Check that import was not duplicated
			const agentFile = path.join(tempDir, 'AGENT.md');
			const content = fs.readFileSync(agentFile, 'utf8');
			const importCount = (content.match(/@\.\/.taskmaster\/AGENT\.md/g) || [])
				.length;
			expect(importCount).toBe(1);
		});
	});

	describe('MCP Configuration', () => {
		test('should rename mcpServers to amp.mcpServers', () => {
			// Create .vscode directory and settings.json with mcpServers
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });

			const initialConfig = {
				mcpServers: {
					'task-master-ai': {
						command: 'npx',
						args: ['-y', 'task-master-ai']
					}
				}
			};

			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				JSON.stringify(initialConfig, null, '\t')
			);

			// Call onPostConvertRulesProfile (which should transform mcpServers to amp.mcpServers)
			ampProfile.onPostConvertRulesProfile(
				tempDir,
				path.join(tempDir, 'assets')
			);

			// Check that mcpServers was renamed to amp.mcpServers
			const settingsFile = path.join(vscodeDirPath, 'settings.json');
			const content = fs.readFileSync(settingsFile, 'utf8');
			const config = JSON.parse(content);

			expect(config.mcpServers).toBeUndefined();
			expect(config['amp.mcpServers']).toBeDefined();
			expect(config['amp.mcpServers']['task-master-ai']).toBeDefined();
		});

		test('should not rename if amp.mcpServers already exists', () => {
			// Create .vscode directory and settings.json with both mcpServers and amp.mcpServers
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });

			const initialConfig = {
				mcpServers: {
					'some-other-server': {
						command: 'other-command'
					}
				},
				'amp.mcpServers': {
					'task-master-ai': {
						command: 'npx',
						args: ['-y', 'task-master-ai']
					}
				}
			};

			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				JSON.stringify(initialConfig, null, '\t')
			);

			// Call onAddRulesProfile
			ampProfile.onAddRulesProfile(tempDir, path.join(tempDir, 'assets'));

			// Check that both sections remain unchanged
			const settingsFile = path.join(vscodeDirPath, 'settings.json');
			const content = fs.readFileSync(settingsFile, 'utf8');
			const config = JSON.parse(content);

			expect(config.mcpServers).toBeDefined();
			expect(config.mcpServers['some-other-server']).toBeDefined();
			expect(config['amp.mcpServers']).toBeDefined();
			expect(config['amp.mcpServers']['task-master-ai']).toBeDefined();
		});
	});

	describe('Removal Functionality', () => {
		test('should remove AGENT.md import and clean up files', () => {
			// Setup: Create AGENT.md with import and .taskmaster/AGENT.md
			const agentContent =
				"# My Amp Instructions\n\nSome content.\n\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n@./.taskmaster/AGENT.md\n";
			fs.writeFileSync(path.join(tempDir, 'AGENT.md'), agentContent);

			fs.mkdirSync(path.join(tempDir, '.taskmaster'), { recursive: true });
			fs.writeFileSync(
				path.join(tempDir, '.taskmaster', 'AGENT.md'),
				'Task Master instructions'
			);

			// Call onRemoveRulesProfile
			ampProfile.onRemoveRulesProfile(tempDir);

			// Check that .taskmaster/AGENT.md was removed
			expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
				false
			);

			// Check that import was removed from AGENT.md
			const remainingContent = fs.readFileSync(
				path.join(tempDir, 'AGENT.md'),
				'utf8'
			);
			expect(remainingContent).not.toContain('## Task Master AI Instructions');
			expect(remainingContent).not.toContain('@./.taskmaster/AGENT.md');
			expect(remainingContent).toContain('# My Amp Instructions');
			expect(remainingContent).toContain('Some content.');
		});

		test('should remove empty AGENT.md if only contained import', () => {
			// Setup: Create AGENT.md with only import
			const agentContent =
				"# Amp Instructions\n\n## Task Master AI Instructions\n**Import Task Master's development workflow commands and guidelines, treat as if import is in the main AGENT.md file.**\n@./.taskmaster/AGENT.md";
			fs.writeFileSync(path.join(tempDir, 'AGENT.md'), agentContent);

			fs.mkdirSync(path.join(tempDir, '.taskmaster'), { recursive: true });
			fs.writeFileSync(
				path.join(tempDir, '.taskmaster', 'AGENT.md'),
				'Task Master instructions'
			);

			// Call onRemoveRulesProfile
			ampProfile.onRemoveRulesProfile(tempDir);

			// Check that AGENT.md was removed
			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(false);
		});

		test('should remove amp.mcpServers section from settings.json', () => {
			// Setup: Create .vscode/settings.json with amp.mcpServers and other settings
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });

			const initialConfig = {
				'amp.mcpServers': {
					'task-master-ai': {
						command: 'npx',
						args: ['-y', 'task-master-ai']
					}
				},
				'other.setting': 'value'
			};

			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				JSON.stringify(initialConfig, null, '\t')
			);

			// Call onRemoveRulesProfile
			ampProfile.onRemoveRulesProfile(tempDir);

			// Check that amp.mcpServers was removed but other settings remain
			const settingsFile = path.join(vscodeDirPath, 'settings.json');
			expect(fs.existsSync(settingsFile)).toBe(true);

			const content = fs.readFileSync(settingsFile, 'utf8');
			const config = JSON.parse(content);

			expect(config['amp.mcpServers']).toBeUndefined();
			expect(config['other.setting']).toBe('value');
		});

		test('should remove settings.json and .vscode directory if empty after removal', () => {
			// Setup: Create .vscode/settings.json with only amp.mcpServers
			const vscodeDirPath = path.join(tempDir, '.vscode');
			fs.mkdirSync(vscodeDirPath, { recursive: true });

			const initialConfig = {
				'amp.mcpServers': {
					'task-master-ai': {
						command: 'npx',
						args: ['-y', 'task-master-ai']
					}
				}
			};

			fs.writeFileSync(
				path.join(vscodeDirPath, 'settings.json'),
				JSON.stringify(initialConfig, null, '\t')
			);

			// Call onRemoveRulesProfile
			ampProfile.onRemoveRulesProfile(tempDir);

			// Check that settings.json and .vscode directory were removed
			expect(fs.existsSync(path.join(vscodeDirPath, 'settings.json'))).toBe(
				false
			);
			expect(fs.existsSync(vscodeDirPath)).toBe(false);
		});
	});

	describe('Full Integration', () => {
		test('should work with convertAllRulesToProfileRules', () => {
			// This test ensures the profile works with the full rule transformer
			const result = convertAllRulesToProfileRules(tempDir, ampProfile);

			expect(result.success).toBeGreaterThan(0);
			expect(result.failed).toBe(0);

			// Check that .taskmaster/AGENT.md was created
			expect(fs.existsSync(path.join(tempDir, '.taskmaster', 'AGENT.md'))).toBe(
				true
			);

			// Check that AGENT.md was created with import
			expect(fs.existsSync(path.join(tempDir, 'AGENT.md'))).toBe(true);
			const agentContent = fs.readFileSync(
				path.join(tempDir, 'AGENT.md'),
				'utf8'
			);
			expect(agentContent).toContain('@./.taskmaster/AGENT.md');
		});
	});
});

```

--------------------------------------------------------------------------------
/apps/extension/src/webview/components/TaskMasterKanban.tsx:
--------------------------------------------------------------------------------

```typescript
/**
 * Main Kanban Board Component
 */

import React, { useState, useCallback, useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { RefreshCw } from 'lucide-react';
import {
	type DragEndEvent,
	KanbanBoard,
	KanbanCards,
	KanbanHeader,
	KanbanProvider
} from '@/components/ui/shadcn-io/kanban';
import { TaskCard } from './TaskCard';
import { TaskEditModal } from './TaskEditModal';
import { PollingStatus } from './PollingStatus';
import { TagDropdown } from './TagDropdown';
import { EmptyState } from './EmptyState';
import { useVSCodeContext } from '../contexts/VSCodeContext';
import {
	useTasks,
	useUpdateTaskStatus,
	useUpdateTask,
	taskKeys
} from '../hooks/useTaskQueries';
import { kanbanStatuses, HEADER_HEIGHT } from '../constants';
import type { TaskMasterTask, TaskUpdates } from '../types';

export const TaskMasterKanban: React.FC = () => {
	const { state, dispatch, sendMessage, availableHeight } = useVSCodeContext();
	const queryClient = useQueryClient();
	const {
		error: legacyError,
		editingTask,
		polling,
		currentTag,
		availableTags
	} = state;
	const [activeTask, setActiveTask] = useState<TaskMasterTask | null>(null);
	const [isRefreshing, setIsRefreshing] = useState(false);

	// Use React Query to fetch tasks
	const {
		data: serverTasks = [],
		isLoading,
		error,
		isFetching,
		isSuccess
	} = useTasks({ tag: currentTag });
	const updateTaskStatus = useUpdateTaskStatus();
	const updateTask = useUpdateTask();

	// Debug logging
	console.log('🔍 TaskMasterKanban Query State:', {
		isLoading,
		isFetching,
		isSuccess,
		tasksCount: serverTasks?.length,
		error
	});

	// Temporary state only for active drag operations
	const [tempReorderedTasks, setTempReorderedTasks] = useState<
		TaskMasterTask[] | null
	>(null);

	// Use temp tasks only if actively set, otherwise use server tasks
	const tasks = tempReorderedTasks ?? serverTasks;

	// Calculate header height for proper kanban board sizing
	const kanbanHeight = availableHeight - HEADER_HEIGHT;

	// Group tasks by status
	const tasksByStatus = kanbanStatuses.reduce(
		(acc, status) => {
			acc[status.id] = tasks.filter((task) => task.status === status.id);
			return acc;
		},
		{} as Record<string, TaskMasterTask[]>
	);

	// Debug logging
	console.log('TaskMasterKanban render:', {
		tasksCount: tasks.length,
		currentTag,
		tasksByStatus: Object.entries(tasksByStatus).map(([status, tasks]) => ({
			status,
			count: tasks.length,
			taskIds: tasks.map((t) => t.id)
		})),
		allTaskIds: tasks.map((t) => ({ id: t.id, title: t.title }))
	});

	// Handle task update
	const handleUpdateTask = async (taskId: string, updates: TaskUpdates) => {
		console.log(`🔄 Updating task ${taskId} content:`, updates);

		try {
			await updateTask.mutateAsync({
				taskId,
				updates,
				options: { append: false, research: false }
			});

			console.log(`✅ Task ${taskId} content updated successfully`);

			// Close the edit modal
			dispatch({
				type: 'SET_EDITING_TASK',
				payload: { taskId: null }
			});
		} catch (error) {
			console.error(`❌ Failed to update task ${taskId}:`, error);
			dispatch({
				type: 'SET_ERROR',
				payload: `Failed to update task: ${error}`
			});
		}
	};

	// Handle drag start
	const handleDragStart = useCallback(
		(event: DragEndEvent) => {
			const taskId = event.active.id as string;
			const task = tasks.find((t) => t.id === taskId);
			if (task) {
				setActiveTask(task);
			}
		},
		[tasks]
	);

	// Handle drag cancel
	const handleDragCancel = useCallback(() => {
		setActiveTask(null);
		// Clear any temporary state
		setTempReorderedTasks(null);
	}, []);

	// Handle drag end
	const handleDragEnd = useCallback(
		async (event: DragEndEvent) => {
			const { active, over } = event;

			// Reset active task
			setActiveTask(null);

			if (!over || active.id === over.id) {
				// Clear any temp state if drag was cancelled
				setTempReorderedTasks(null);
				return;
			}

			const taskId = active.id as string;
			const newStatus = over.id as TaskMasterTask['status'];

			// Find the task
			const task = tasks.find((t) => t.id === taskId);
			if (!task || task.status === newStatus) {
				// Clear temp state if no change needed
				setTempReorderedTasks(null);
				return;
			}

			// Create the optimistically reordered tasks
			const reorderedTasks = tasks.map((t) =>
				t.id === taskId ? { ...t, status: newStatus } : t
			);

			// Set temporary state to show immediate visual feedback
			setTempReorderedTasks(reorderedTasks);

			try {
				// Update on server - React Query will handle optimistic updates
				await updateTaskStatus.mutateAsync({ taskId, newStatus });
				// Clear temp state after mutation starts successfully
				setTempReorderedTasks(null);
			} catch (error) {
				// On error, clear temp state - React Query will revert optimistic update
				setTempReorderedTasks(null);
				dispatch({
					type: 'SET_ERROR',
					payload: `Failed to update task status: ${error}`
				});
			}
		},
		[tasks, updateTaskStatus, dispatch]
	);

	// Handle retry connection
	const handleRetry = useCallback(() => {
		sendMessage({ type: 'retryConnection' });
	}, [sendMessage]);

	// Handle refresh
	const handleRefresh = useCallback(async () => {
		setIsRefreshing(true);
		try {
			// Invalidate all task queries
			await queryClient.invalidateQueries({ queryKey: taskKeys.all });
		} finally {
			// Reset after a short delay to show the animation
			setTimeout(() => setIsRefreshing(false), 500);
		}
	}, [queryClient]);

	// Handle tag switching
	const handleTagSwitch = useCallback(
		async (tagName: string) => {
			console.log('Switching to tag:', tagName);
			await sendMessage({ type: 'switchTag', data: { tagName } });
			dispatch({
				type: 'SET_TAG_DATA',
				payload: { currentTag: tagName, availableTags }
			});
		},
		[sendMessage, dispatch, availableTags]
	);

	// Use React Query loading state
	const displayError = error
		? error instanceof Error
			? error.message
			: String(error)
		: legacyError;

	if (isLoading) {
		return (
			<div
				className="flex items-center justify-center"
				style={{ height: `${kanbanHeight}px` }}
			>
				<div className="text-center">
					<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-vscode-foreground mx-auto mb-4" />
					<p className="text-sm text-vscode-foreground/70">Loading tasks...</p>
				</div>
			</div>
		);
	}

	if (displayError) {
		return (
			<div className="bg-red-500/10 border border-red-500/30 rounded-lg p-4 m-4">
				<p className="text-red-400 text-sm">Error: {displayError}</p>
				<button
					onClick={() => dispatch({ type: 'CLEAR_ERROR' })}
					className="mt-2 text-sm text-red-400 hover:text-red-300 underline"
				>
					Dismiss
				</button>
			</div>
		);
	}

	return (
		<>
			<div className="flex flex-col" style={{ height: `${availableHeight}px` }}>
				<div className="flex-shrink-0 p-4 bg-vscode-sidebar-background border-b border-vscode-border">
					<div className="flex items-center justify-between">
						<h1 className="text-lg font-semibold text-vscode-foreground">
							TaskMaster Kanban
						</h1>
						<div className="flex items-center gap-4">
							<TagDropdown
								currentTag={currentTag}
								availableTags={availableTags}
								onTagSwitch={handleTagSwitch}
								sendMessage={sendMessage}
								dispatch={dispatch}
							/>
							<button
								onClick={handleRefresh}
								disabled={isRefreshing}
								className="p-1.5 rounded hover:bg-vscode-button-hoverBackground transition-colors"
								title="Refresh tasks"
							>
								<RefreshCw
									className={`w-4 h-4 text-vscode-foreground/70 ${isRefreshing ? 'animate-spin' : ''}`}
								/>
							</button>
							<PollingStatus polling={polling} onRetry={handleRetry} />
							<div className="flex items-center gap-2">
								<div
									className={`w-2 h-2 rounded-full ${state.isConnected ? 'bg-green-400' : 'bg-red-400'}`}
								/>
								<span className="text-xs text-vscode-foreground/70">
									{state.connectionStatus}
								</span>
							</div>
							<button
								onClick={() => dispatch({ type: 'NAVIGATE_TO_CONFIG' })}
								className="p-1.5 rounded hover:bg-vscode-button-hoverBackground transition-colors"
								title="TaskMaster Configuration"
							>
								<svg
									className="w-4 h-4 text-vscode-foreground/70"
									fill="none"
									stroke="currentColor"
									viewBox="0 0 24 24"
								>
									<path
										strokeLinecap="round"
										strokeLinejoin="round"
										strokeWidth={2}
										d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
									/>
									<path
										strokeLinecap="round"
										strokeLinejoin="round"
										strokeWidth={2}
										d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
									/>
								</svg>
							</button>
						</div>
					</div>
				</div>

				<div
					className="flex-1 px-4 py-4 overflow-hidden"
					style={{ height: `${kanbanHeight}px` }}
				>
					{tasks.length === 0 ? (
						<EmptyState currentTag={currentTag} />
					) : (
						<KanbanProvider
							onDragStart={handleDragStart}
							onDragEnd={handleDragEnd}
							onDragCancel={handleDragCancel}
							className="kanban-container w-full h-full overflow-x-auto overflow-y-hidden"
							dragOverlay={
								activeTask ? <TaskCard task={activeTask} dragging /> : null
							}
						>
							<div className="flex gap-4 h-full min-w-fit">
								{kanbanStatuses.map((status) => {
									const statusTasks = tasksByStatus[status.id] || [];
									const hasScrollbar = statusTasks.length > 4;

									return (
										<KanbanBoard
											key={status.id}
											id={status.id}
											className={`
												w-80 flex flex-col
												border border-vscode-border/30
												rounded-lg
												bg-vscode-sidebar-background/50
											`}
										>
											<KanbanHeader
												name={`${status.title} (${statusTasks.length})`}
												color={status.color}
												className="px-3 py-3 text-sm font-medium flex-shrink-0 border-b border-vscode-border/30"
											/>
											<div
												className={`
													flex flex-col gap-2 
													overflow-y-auto overflow-x-hidden
													p-2
													scrollbar-thin scrollbar-track-transparent
													${hasScrollbar ? 'pr-1' : ''}
												`}
												style={{
													maxHeight: `${kanbanHeight - 80}px`
												}}
											>
												<KanbanCards>
													{statusTasks.map((task) => (
														<TaskCard
															key={task.id}
															task={task}
															onViewDetails={(taskId) => {
																console.log(
																	'🔍 Navigating to task details:',
																	taskId
																);
																dispatch({
																	type: 'NAVIGATE_TO_TASK',
																	payload: taskId
																});
															}}
														/>
													))}
												</KanbanCards>
											</div>
										</KanbanBoard>
									);
								})}
							</div>
						</KanbanProvider>
					)}
				</div>
			</div>

			{/* Task Edit Modal */}
			{editingTask?.taskId && editingTask.editData && (
				<TaskEditModal
					task={editingTask.editData}
					onSave={handleUpdateTask}
					onCancel={() => {
						dispatch({
							type: 'SET_EDITING_TASK',
							payload: { taskId: null }
						});
					}}
				/>
			)}
		</>
	);
};

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/auth/services/oauth-service.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * OAuth 2.0 Authorization Code Flow service
 */

import crypto from 'crypto';
import http from 'http';
import os from 'os';
import { URL } from 'url';
import { Session } from '@supabase/supabase-js';
import { TASKMASTER_VERSION } from '../../../common/constants/index.js';
import { getLogger } from '../../../common/logger/index.js';
import { SupabaseAuthClient } from '../../integration/clients/supabase-client.js';
import { getAuthConfig } from '../config.js';
import { ContextStore } from '../services/context-store.js';
import {
	AuthConfig,
	AuthCredentials,
	AuthenticationError,
	CliData,
	OAuthFlowOptions
} from '../types.js';

export class OAuthService {
	private logger = getLogger('OAuthService');
	private contextStore: ContextStore;
	private supabaseClient: SupabaseAuthClient;
	private baseUrl: string;
	private authorizationUrl: string | null = null;
	private originalState: string | null = null;
	private authorizationReady: Promise<void> | null = null;
	private resolveAuthorizationReady: (() => void) | null = null;

	constructor(
		contextStore: ContextStore,
		supabaseClient: SupabaseAuthClient,
		config: Partial<AuthConfig> = {}
	) {
		this.contextStore = contextStore;
		this.supabaseClient = supabaseClient;
		const authConfig = getAuthConfig(config);
		this.baseUrl = authConfig.baseUrl;
	}

	/**
	 * Start OAuth 2.0 Authorization Code Flow with browser handling
	 */
	async authenticate(options: OAuthFlowOptions = {}): Promise<AuthCredentials> {
		const {
			openBrowser,
			timeout = 300000, // 5 minutes default
			onAuthUrl,
			onWaitingForAuth,
			onSuccess,
			onError
		} = options;

		try {
			// Start the OAuth flow (starts local server)
			const authPromise = this.startFlow(timeout);

			// Wait for server to be ready and URL to be generated
			if (this.authorizationReady) {
				await this.authorizationReady;
			}

			// Get the authorization URL
			const authUrl = this.getAuthorizationUrl();

			if (!authUrl) {
				throw new AuthenticationError(
					'Failed to generate authorization URL',
					'URL_GENERATION_FAILED'
				);
			}

			// Notify about the auth URL
			if (onAuthUrl) {
				onAuthUrl(authUrl);
			}

			// Open browser if callback provided
			if (openBrowser) {
				try {
					await openBrowser(authUrl);
					this.logger.debug('Browser opened successfully with URL:', authUrl);
				} catch (error) {
					// Log the error but don't throw - user can still manually open the URL
					this.logger.warn('Failed to open browser automatically:', error);
				}
			}

			// Notify that we're waiting for authentication
			if (onWaitingForAuth) {
				onWaitingForAuth();
			}

			// Wait for authentication to complete
			const credentials = await authPromise;

			// Notify success
			if (onSuccess) {
				onSuccess(credentials);
			}

			return credentials;
		} catch (error) {
			const authError =
				error instanceof AuthenticationError
					? error
					: new AuthenticationError(
							`OAuth authentication failed: ${(error as Error).message}`,
							'OAUTH_FAILED',
							error
						);

			// Notify error
			if (onError) {
				onError(authError);
			}

			throw authError;
		}
	}

	/**
	 * Start the OAuth flow (internal implementation)
	 */
	private async startFlow(timeout: number = 300000): Promise<AuthCredentials> {
		const state = this.generateState();

		// Store the original state for verification
		this.originalState = state;

		// Create a promise that will resolve when the server is ready
		this.authorizationReady = new Promise<void>((resolve) => {
			this.resolveAuthorizationReady = resolve;
		});

		return new Promise((resolve, reject) => {
			let timeoutId: NodeJS.Timeout;
			// Create local HTTP server for OAuth callback
			const server = http.createServer();

			// Start server on localhost only, bind to port 0 for automatic port assignment
			server.listen(0, '127.0.0.1', () => {
				const address = server.address();
				if (!address || typeof address === 'string') {
					reject(new Error('Failed to get server address'));
					return;
				}
				const port = address.port;
				const callbackUrl = `http://localhost:${port}/callback`;

				// Set up request handler after we know the port
				server.on('request', async (req, res) => {
					const url = new URL(req.url!, `http://127.0.0.1:${port}`);

					if (url.pathname === '/callback') {
						await this.handleCallback(
							url,
							res,
							server,
							resolve,
							reject,
							timeoutId
						);
					} else {
						// Handle other paths (favicon, etc.)
						res.writeHead(404);
						res.end();
					}
				});

				// Prepare CLI data object (server handles OAuth/PKCE)
				const cliData: CliData = {
					callback: callbackUrl,
					state: state,
					name: 'Task Master CLI',
					version: this.getCliVersion(),
					device: os.hostname(),
					user: os.userInfo().username,
					platform: os.platform(),
					timestamp: Date.now()
				};

				// Build authorization URL for CLI-specific sign-in page
				const authUrl = new URL(`${this.baseUrl}/auth/cli/sign-in`);

				// Encode CLI data as base64
				const cliParam = Buffer.from(JSON.stringify(cliData)).toString(
					'base64'
				);

				// Set the single CLI parameter with all encoded data
				authUrl.searchParams.append('cli', cliParam);

				// Store auth URL for browser opening
				this.authorizationUrl = authUrl.toString();

				this.logger.info(
					`OAuth session started - ${cliData.name} v${cliData.version} on port ${port}`
				);
				this.logger.debug('CLI data:', cliData);

				// Signal that the server is ready and URL is available
				if (this.resolveAuthorizationReady) {
					this.resolveAuthorizationReady();
					this.resolveAuthorizationReady = null;
				}
			});

			// Set timeout for authentication
			timeoutId = setTimeout(() => {
				if (server.listening) {
					server.close();
					// Clean up the readiness promise if still pending
					if (this.resolveAuthorizationReady) {
						this.resolveAuthorizationReady();
						this.resolveAuthorizationReady = null;
					}
					reject(
						new AuthenticationError('Authentication timeout', 'AUTH_TIMEOUT')
					);
				}
			}, timeout);
		});
	}

	/**
	 * Handle OAuth callback
	 */
	private async handleCallback(
		url: URL,
		res: http.ServerResponse,
		server: http.Server,
		resolve: (value: AuthCredentials) => void,
		reject: (error: any) => void,
		timeoutId?: NodeJS.Timeout
	): Promise<void> {
		// Server now returns tokens directly instead of code
		const type = url.searchParams.get('type');
		const returnedState = url.searchParams.get('state');
		const accessToken = url.searchParams.get('access_token');
		const refreshToken = url.searchParams.get('refresh_token');
		const expiresIn = url.searchParams.get('expires_in');
		const error = url.searchParams.get('error');
		const errorDescription = url.searchParams.get('error_description');

		// Server handles displaying success/failure, just close connection
		res.writeHead(200);
		res.end();

		if (error) {
			if (server.listening) {
				server.close();
			}
			reject(
				new AuthenticationError(
					errorDescription || error || 'Authentication failed',
					'OAUTH_ERROR'
				)
			);
			return;
		}

		// Verify state parameter for CSRF protection
		if (returnedState !== this.originalState) {
			if (server.listening) {
				server.close();
			}
			reject(
				new AuthenticationError('Invalid state parameter', 'INVALID_STATE')
			);
			return;
		}

		// Handle authorization code for PKCE flow
		const code = url.searchParams.get('code');
		this.logger.info(`Code: ${code}, type: ${type}`);
		if (code && type === 'pkce_callback') {
			try {
				this.logger.info('Received authorization code for PKCE flow');

				const session = await this.supabaseClient.exchangeCodeForSession(code);

				// Save user info to context store
				this.contextStore.saveContext({
					userId: session.user.id,
					email: session.user.email
				});

				// Calculate expiration - can be overridden with TM_TOKEN_EXPIRY_MINUTES
				let expiresAt: string | undefined;
				const tokenExpiryMinutes = process.env.TM_TOKEN_EXPIRY_MINUTES;
				if (tokenExpiryMinutes) {
					const minutes = parseInt(tokenExpiryMinutes);
					expiresAt = new Date(Date.now() + minutes * 60 * 1000).toISOString();
					this.logger.warn(`Token expiry overridden to ${minutes} minute(s)`);
				} else {
					expiresAt = session.expires_at
						? new Date(session.expires_at * 1000).toISOString()
						: undefined;
				}

				// Return credentials for backward compatibility
				const authData: AuthCredentials = {
					token: session.access_token,
					refreshToken: session.refresh_token,
					userId: session.user.id,
					email: session.user.email,
					expiresAt,
					tokenType: 'standard',
					savedAt: new Date().toISOString()
				};

				if (server.listening) {
					server.close();
				}
				// Clear timeout since authentication succeeded
				if (timeoutId) {
					clearTimeout(timeoutId);
				}
				resolve(authData);
				return;
			} catch (error) {
				if (server.listening) {
					server.close();
				}
				reject(error);
				return;
			}
		}

		// Handle direct token response from server (legacy flow)
		if (
			accessToken &&
			(type === 'oauth_success' || type === 'session_transfer')
		) {
			try {
				this.logger.info(
					`\n\n==============================================\n Received tokens via ${type}\n==============================================\n`
				);

				// Create a session with the tokens and set it in Supabase client
				// This automatically saves the session to session.json via SupabaseSessionStorage
				const session: Session = {
					access_token: accessToken,
					refresh_token: refreshToken || '',
					expires_in: expiresIn ? parseInt(expiresIn) : 0,
					token_type: 'bearer',
					user: null as any // Will be populated by setSession
				};

				// Set the session in Supabase client
				await this.supabaseClient.setSession(session);

				// Get user info from the session
				const user = await this.supabaseClient.getUser();

				// Save user info to context store
				this.contextStore.saveContext({
					userId: user?.id || 'unknown',
					email: user?.email
				});

				// Calculate expiration time - can be overridden with TM_TOKEN_EXPIRY_MINUTES
				let expiresAt: string | undefined;
				const tokenExpiryMinutes = process.env.TM_TOKEN_EXPIRY_MINUTES;
				if (tokenExpiryMinutes) {
					const minutes = parseInt(tokenExpiryMinutes);
					expiresAt = new Date(Date.now() + minutes * 60 * 1000).toISOString();
					this.logger.warn(`Token expiry overridden to ${minutes} minute(s)`);
				} else {
					expiresAt = expiresIn
						? new Date(Date.now() + parseInt(expiresIn) * 1000).toISOString()
						: undefined;
				}

				// Return credentials for backward compatibility
				const authData: AuthCredentials = {
					token: accessToken,
					refreshToken: refreshToken || undefined,
					userId: user?.id || 'unknown',
					email: user?.email,
					expiresAt,
					tokenType: 'standard',
					savedAt: new Date().toISOString()
				};

				if (server.listening) {
					server.close();
				}
				// Clear timeout since authentication succeeded
				if (timeoutId) {
					clearTimeout(timeoutId);
				}
				resolve(authData);
			} catch (error) {
				if (server.listening) {
					server.close();
				}
				reject(error);
			}
		} else {
			if (server.listening) {
				server.close();
			}
			reject(new AuthenticationError('No access token received', 'NO_TOKEN'));
		}
	}

	/**
	 * Generate state for OAuth flow
	 */
	private generateState(): string {
		return crypto.randomBytes(32).toString('base64url');
	}

	/**
	 * Get CLI version from centralized constants
	 */
	private getCliVersion(): string {
		return TASKMASTER_VERSION;
	}

	/**
	 * Get the authorization URL (for browser opening)
	 */
	getAuthorizationUrl(): string | null {
		return this.authorizationUrl;
	}
}

```

--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/managers/config-manager.spec.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Integration tests for ConfigManager
 * Tests the orchestration of all configuration services
 */

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_CONFIG_VALUES } from '../../../common/interfaces/configuration.interface.js';
import { ConfigLoader } from '../services/config-loader.service.js';
import { ConfigMerger } from '../services/config-merger.service.js';
import { ConfigPersistence } from '../services/config-persistence.service.js';
import { EnvironmentConfigProvider } from '../services/environment-config-provider.service.js';
import { RuntimeStateManager } from '../services/runtime-state-manager.service.js';
import { ConfigManager } from './config-manager.js';

// Mock all services
vi.mock('../services/config-loader.service.js');
vi.mock('../services/config-merger.service.js');
vi.mock('../services/runtime-state-manager.service.js');
vi.mock('../services/config-persistence.service.js');
vi.mock('../services/environment-config-provider.service.js');

describe('ConfigManager', () => {
	let manager: ConfigManager;
	const testProjectRoot = '/test/project';
	const originalEnv = { ...process.env };

	beforeEach(async () => {
		vi.clearAllMocks();

		// Clear environment variables
		Object.keys(process.env).forEach((key) => {
			if (key.startsWith('TASKMASTER_')) {
				delete process.env[key];
			}
		});

		// Setup default mock behaviors
		vi.mocked(ConfigLoader).mockImplementation(
			() =>
				({
					getDefaultConfig: vi.fn().mockReturnValue({
						models: { main: 'default-model', fallback: 'fallback-model' },
						storage: { type: 'file' },
						version: '1.0.0'
					}),
					loadLocalConfig: vi.fn().mockResolvedValue(null),
					loadGlobalConfig: vi.fn().mockResolvedValue(null),
					hasLocalConfig: vi.fn().mockResolvedValue(false),
					hasGlobalConfig: vi.fn().mockResolvedValue(false)
				}) as any
		);

		vi.mocked(ConfigMerger).mockImplementation(
			() =>
				({
					addSource: vi.fn(),
					clearSources: vi.fn(),
					merge: vi.fn().mockReturnValue({
						models: { main: 'merged-model', fallback: 'fallback-model' },
						storage: { type: 'file' }
					}),
					getSources: vi.fn().mockReturnValue([]),
					hasSource: vi.fn().mockReturnValue(false),
					removeSource: vi.fn().mockReturnValue(false)
				}) as any
		);

		vi.mocked(RuntimeStateManager).mockImplementation(
			() =>
				({
					loadState: vi.fn().mockResolvedValue({ activeTag: 'master' }),
					saveState: vi.fn().mockResolvedValue(undefined),
					getCurrentTag: vi.fn().mockReturnValue('master'),
					setCurrentTag: vi.fn().mockResolvedValue(undefined),
					getState: vi.fn().mockReturnValue({ activeTag: 'master' }),
					updateMetadata: vi.fn().mockResolvedValue(undefined),
					clearState: vi.fn().mockResolvedValue(undefined)
				}) as any
		);

		vi.mocked(ConfigPersistence).mockImplementation(
			() =>
				({
					saveConfig: vi.fn().mockResolvedValue(undefined),
					configExists: vi.fn().mockResolvedValue(false),
					deleteConfig: vi.fn().mockResolvedValue(undefined),
					getBackups: vi.fn().mockResolvedValue([]),
					restoreFromBackup: vi.fn().mockResolvedValue(undefined)
				}) as any
		);

		vi.mocked(EnvironmentConfigProvider).mockImplementation(
			() =>
				({
					loadConfig: vi.fn().mockReturnValue({}),
					getRuntimeState: vi.fn().mockReturnValue({}),
					hasEnvVar: vi.fn().mockReturnValue(false),
					getAllTaskmasterEnvVars: vi.fn().mockReturnValue({}),
					addMapping: vi.fn(),
					getMappings: vi.fn().mockReturnValue([])
				}) as any
		);

		// Since constructor is private, we need to use the factory method
		// But for testing, we'll create a test instance using create()
		manager = await ConfigManager.create(testProjectRoot);
	});

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

	describe('creation', () => {
		it('should initialize all services when created', () => {
			// Services should have been initialized during beforeEach
			expect(ConfigLoader).toHaveBeenCalledWith(testProjectRoot);
			expect(ConfigMerger).toHaveBeenCalled();
			expect(RuntimeStateManager).toHaveBeenCalledWith(testProjectRoot);
			expect(ConfigPersistence).toHaveBeenCalledWith(testProjectRoot);
			expect(EnvironmentConfigProvider).toHaveBeenCalled();
		});
	});

	describe('create (factory method)', () => {
		it('should create and initialize manager', async () => {
			const createdManager = await ConfigManager.create(testProjectRoot);

			expect(createdManager).toBeInstanceOf(ConfigManager);
			expect(createdManager.getConfig()).toBeDefined();
		});
	});

	describe('initialization (via create)', () => {
		it('should load and merge all configuration sources', () => {
			// Manager was created in beforeEach, so initialization already happened
			const loader = (manager as any).loader;
			const merger = (manager as any).merger;
			const stateManager = (manager as any).stateManager;
			const envProvider = (manager as any).envProvider;

			// Verify loading sequence
			expect(merger.clearSources).toHaveBeenCalled();
			expect(loader.getDefaultConfig).toHaveBeenCalled();
			expect(loader.loadGlobalConfig).toHaveBeenCalled();
			expect(loader.loadLocalConfig).toHaveBeenCalled();
			expect(envProvider.loadConfig).toHaveBeenCalled();
			expect(merger.merge).toHaveBeenCalled();
			expect(stateManager.loadState).toHaveBeenCalled();
		});

		it('should add sources with correct precedence during creation', () => {
			const merger = (manager as any).merger;

			// Check that sources were added with correct precedence
			expect(merger.addSource).toHaveBeenCalledWith(
				expect.objectContaining({
					name: 'defaults',
					precedence: 0
				})
			);

			// Note: local and env sources may not be added if they don't exist
			// The mock setup determines what gets called
		});
	});

	describe('configuration access', () => {
		// Manager is already initialized in the main beforeEach

		it('should return merged configuration', () => {
			const config = manager.getConfig();
			expect(config).toEqual({
				models: { main: 'merged-model', fallback: 'fallback-model' },
				storage: { type: 'file' }
			});
		});

		it('should return storage configuration', () => {
			const storage = manager.getStorageConfig();
			expect(storage).toEqual({ type: 'file' });
		});

		it('should return API storage configuration when configured', async () => {
			// Create a new instance with API storage config
			vi.mocked(ConfigMerger).mockImplementationOnce(
				() =>
					({
						addSource: vi.fn(),
						clearSources: vi.fn(),
						merge: vi.fn().mockReturnValue({
							storage: {
								type: 'api',
								apiEndpoint: 'https://api.example.com',
								apiAccessToken: 'token123'
							}
						}),
						getSources: vi.fn().mockReturnValue([]),
						hasSource: vi.fn().mockReturnValue(false),
						removeSource: vi.fn().mockReturnValue(false)
					}) as any
			);

			const apiManager = await ConfigManager.create(testProjectRoot);

			const storage = apiManager.getStorageConfig();
			expect(storage).toEqual({
				type: 'api',
				apiEndpoint: 'https://api.example.com',
				apiAccessToken: 'token123'
			});
		});

		it('should return model configuration', () => {
			const models = manager.getModelConfig();
			expect(models).toEqual({
				main: 'merged-model',
				fallback: 'fallback-model'
			});
		});

		it('should return default models when not configured', () => {
			// Update the mock for current instance
			const merger = (manager as any).merger;
			merger.merge.mockReturnValue({});
			// Force re-merge
			(manager as any).config = merger.merge();

			const models = manager.getModelConfig();
			expect(models).toEqual({
				main: DEFAULT_CONFIG_VALUES.MODELS.MAIN,
				fallback: DEFAULT_CONFIG_VALUES.MODELS.FALLBACK
			});
		});

		it('should return response language', () => {
			const language = manager.getResponseLanguage();
			expect(language).toBe('English');
		});

		it('should return custom response language', () => {
			// Update config for current instance
			(manager as any).config = {
				custom: { responseLanguage: 'Spanish' }
			};

			const language = manager.getResponseLanguage();
			expect(language).toBe('Spanish');
		});

		it('should return project root', () => {
			expect(manager.getProjectRoot()).toBe(testProjectRoot);
		});

		it('should check if API is explicitly configured', () => {
			expect(manager.isApiExplicitlyConfigured()).toBe(false);
		});

		it('should detect when API is explicitly configured', () => {
			// Update config for current instance
			(manager as any).config = {
				storage: {
					type: 'api',
					apiEndpoint: 'https://api.example.com',
					apiAccessToken: 'token'
				}
			};

			expect(manager.isApiExplicitlyConfigured()).toBe(true);
		});
	});

	describe('runtime state', () => {
		// Manager is already initialized in the main beforeEach

		it('should get active tag from state manager', () => {
			const tag = manager.getActiveTag();
			expect(tag).toBe('master');
		});

		it('should set active tag through state manager', async () => {
			await manager.setActiveTag('feature-branch');

			const stateManager = (manager as any).stateManager;
			expect(stateManager.setCurrentTag).toHaveBeenCalledWith('feature-branch');
		});
	});

	describe('configuration updates', () => {
		// Manager is already initialized in the main beforeEach

		it('should update configuration and save', async () => {
			const updates = {
				models: { main: 'new-model', fallback: 'fallback-model' }
			};
			await manager.updateConfig(updates);

			const persistence = (manager as any).persistence;
			expect(persistence.saveConfig).toHaveBeenCalled();
		});

		it('should re-initialize after update to maintain precedence', async () => {
			const merger = (manager as any).merger;
			merger.clearSources.mockClear();

			await manager.updateConfig({ custom: { test: 'value' } });

			expect(merger.clearSources).toHaveBeenCalled();
		});

		it('should set response language', async () => {
			await manager.setResponseLanguage('French');

			const persistence = (manager as any).persistence;
			expect(persistence.saveConfig).toHaveBeenCalledWith(
				expect.objectContaining({
					custom: { responseLanguage: 'French' }
				})
			);
		});

		it('should save configuration with options', async () => {
			await manager.saveConfig();

			const persistence = (manager as any).persistence;
			expect(persistence.saveConfig).toHaveBeenCalledWith(expect.any(Object), {
				createBackup: true,
				atomic: true
			});
		});
	});

	describe('utilities', () => {
		// Manager is already initialized in the main beforeEach

		it('should reset configuration to defaults', async () => {
			await manager.reset();

			const persistence = (manager as any).persistence;
			const stateManager = (manager as any).stateManager;

			expect(persistence.deleteConfig).toHaveBeenCalled();
			expect(stateManager.clearState).toHaveBeenCalled();
		});

		it('should re-initialize after reset', async () => {
			const merger = (manager as any).merger;
			merger.clearSources.mockClear();

			await manager.reset();

			expect(merger.clearSources).toHaveBeenCalled();
		});

		it('should get configuration sources for debugging', () => {
			const merger = (manager as any).merger;
			const mockSources = [{ name: 'test', config: {}, precedence: 1 }];
			merger.getSources.mockReturnValue(mockSources);

			const sources = manager.getConfigSources();

			expect(sources).toEqual(mockSources);
		});
	});

	describe('error handling', () => {
		it('should handle missing services gracefully', async () => {
			// Even if a service fails, manager should still work
			const loader = (manager as any).loader;
			loader.loadLocalConfig.mockRejectedValue(new Error('File error'));

			// Creating a new manager should not throw even if service fails
			await expect(
				ConfigManager.create(testProjectRoot)
			).resolves.not.toThrow();
		});
	});
});

```

--------------------------------------------------------------------------------
/apps/extension/src/utils/notificationPreferences.ts:
--------------------------------------------------------------------------------

```typescript
import * as vscode from 'vscode';
import { ErrorCategory, ErrorSeverity, NotificationType } from './errorHandler';
import { logger } from './logger';

export interface NotificationPreferences {
	// Global notification toggles
	enableToastNotifications: boolean;
	enableVSCodeNotifications: boolean;
	enableConsoleLogging: boolean;

	// Toast notification settings
	toastDuration: {
		info: number;
		warning: number;
		error: number;
	};

	// Category-based preferences
	categoryPreferences: Record<
		ErrorCategory,
		{
			showToUser: boolean;
			notificationType: NotificationType;
			logToConsole: boolean;
		}
	>;

	// Severity-based preferences
	severityPreferences: Record<
		ErrorSeverity,
		{
			showToUser: boolean;
			notificationType: NotificationType;
			minToastDuration: number;
		}
	>;

	// Advanced settings
	maxToastCount: number;
	enableErrorTracking: boolean;
	enableDetailedErrorInfo: boolean;
}

export class NotificationPreferencesManager {
	private static instance: NotificationPreferencesManager | null = null;
	private readonly configSection = 'taskMasterKanban';

	private constructor() {}

	static getInstance(): NotificationPreferencesManager {
		if (!NotificationPreferencesManager.instance) {
			NotificationPreferencesManager.instance =
				new NotificationPreferencesManager();
		}
		return NotificationPreferencesManager.instance;
	}

	/**
	 * Get current notification preferences from VS Code settings
	 */
	getPreferences(): NotificationPreferences {
		const config = vscode.workspace.getConfiguration(this.configSection);

		return {
			enableToastNotifications: config.get('notifications.enableToast', true),
			enableVSCodeNotifications: config.get('notifications.enableVSCode', true),
			enableConsoleLogging: config.get('notifications.enableConsole', true),

			toastDuration: {
				info: config.get('notifications.toastDuration.info', 5000),
				warning: config.get('notifications.toastDuration.warning', 7000),
				error: config.get('notifications.toastDuration.error', 10000)
			},

			categoryPreferences: this.getCategoryPreferences(config),
			severityPreferences: this.getSeverityPreferences(config),

			maxToastCount: config.get('notifications.maxToastCount', 5),
			enableErrorTracking: config.get(
				'notifications.enableErrorTracking',
				true
			),
			enableDetailedErrorInfo: config.get(
				'notifications.enableDetailedErrorInfo',
				false
			)
		};
	}

	/**
	 * Update notification preferences in VS Code settings
	 */
	async updatePreferences(
		preferences: Partial<NotificationPreferences>
	): Promise<void> {
		const config = vscode.workspace.getConfiguration(this.configSection);

		if (preferences.enableToastNotifications !== undefined) {
			await config.update(
				'notifications.enableToast',
				preferences.enableToastNotifications,
				vscode.ConfigurationTarget.Global
			);
		}

		if (preferences.enableVSCodeNotifications !== undefined) {
			await config.update(
				'notifications.enableVSCode',
				preferences.enableVSCodeNotifications,
				vscode.ConfigurationTarget.Global
			);
		}

		if (preferences.enableConsoleLogging !== undefined) {
			await config.update(
				'notifications.enableConsole',
				preferences.enableConsoleLogging,
				vscode.ConfigurationTarget.Global
			);
		}

		if (preferences.toastDuration) {
			await config.update(
				'notifications.toastDuration',
				preferences.toastDuration,
				vscode.ConfigurationTarget.Global
			);
		}

		if (preferences.maxToastCount !== undefined) {
			await config.update(
				'notifications.maxToastCount',
				preferences.maxToastCount,
				vscode.ConfigurationTarget.Global
			);
		}

		if (preferences.enableErrorTracking !== undefined) {
			await config.update(
				'notifications.enableErrorTracking',
				preferences.enableErrorTracking,
				vscode.ConfigurationTarget.Global
			);
		}

		if (preferences.enableDetailedErrorInfo !== undefined) {
			await config.update(
				'notifications.enableDetailedErrorInfo',
				preferences.enableDetailedErrorInfo,
				vscode.ConfigurationTarget.Global
			);
		}
	}

	/**
	 * Check if notifications should be shown for a specific error category and severity
	 */
	shouldShowNotification(
		category: ErrorCategory,
		severity: ErrorSeverity
	): boolean {
		const preferences = this.getPreferences();

		// Check global toggles first
		if (
			!preferences.enableToastNotifications &&
			!preferences.enableVSCodeNotifications
		) {
			return false;
		}

		// Check category preferences
		const categoryPref = preferences.categoryPreferences[category];
		if (categoryPref && !categoryPref.showToUser) {
			return false;
		}

		// Check severity preferences
		const severityPref = preferences.severityPreferences[severity];
		if (severityPref && !severityPref.showToUser) {
			return false;
		}

		return true;
	}

	/**
	 * Get the appropriate notification type for an error
	 */
	getNotificationType(
		category: ErrorCategory,
		severity: ErrorSeverity
	): NotificationType {
		const preferences = this.getPreferences();

		// Check category preference first
		const categoryPref = preferences.categoryPreferences[category];
		if (categoryPref) {
			return categoryPref.notificationType;
		}

		// Fall back to severity preference
		const severityPref = preferences.severityPreferences[severity];
		if (severityPref) {
			return severityPref.notificationType;
		}

		// Default fallback
		return this.getDefaultNotificationType(severity);
	}

	/**
	 * Get toast duration for a specific severity
	 */
	getToastDuration(severity: ErrorSeverity): number {
		const preferences = this.getPreferences();

		switch (severity) {
			case ErrorSeverity.LOW:
				return preferences.toastDuration.info;
			case ErrorSeverity.MEDIUM:
				return preferences.toastDuration.warning;
			case ErrorSeverity.HIGH:
			case ErrorSeverity.CRITICAL:
				return preferences.toastDuration.error;
			default:
				return preferences.toastDuration.warning;
		}
	}

	/**
	 * Reset preferences to defaults
	 */
	async resetToDefaults(): Promise<void> {
		const config = vscode.workspace.getConfiguration(this.configSection);

		// Reset all notification settings
		await config.update(
			'notifications',
			undefined,
			vscode.ConfigurationTarget.Global
		);

		logger.log('Task Master Kanban notification preferences reset to defaults');
	}

	/**
	 * Get category-based preferences with defaults
	 */
	private getCategoryPreferences(config: vscode.WorkspaceConfiguration): Record<
		ErrorCategory,
		{
			showToUser: boolean;
			notificationType: NotificationType;
			logToConsole: boolean;
		}
	> {
		const defaults = {
			[ErrorCategory.MCP_CONNECTION]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.CONFIGURATION]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_WARNING,
				logToConsole: true
			},
			[ErrorCategory.TASK_LOADING]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_WARNING,
				logToConsole: true
			},
			[ErrorCategory.UI_RENDERING]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_INFO,
				logToConsole: false
			},
			[ErrorCategory.VALIDATION]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_WARNING,
				logToConsole: true
			},
			[ErrorCategory.NETWORK]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_WARNING,
				logToConsole: true
			},
			[ErrorCategory.INTERNAL]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.TASK_MASTER_API]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_ERROR,
				logToConsole: true
			},
			[ErrorCategory.DATA_VALIDATION]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_WARNING,
				logToConsole: true
			},
			[ErrorCategory.DATA_PARSING]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.TASK_DATA_CORRUPTION]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.VSCODE_API]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.WEBVIEW]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_WARNING,
				logToConsole: true
			},
			[ErrorCategory.EXTENSION_HOST]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.USER_INTERACTION]: {
				showToUser: false,
				notificationType: NotificationType.CONSOLE_ONLY,
				logToConsole: true
			},
			[ErrorCategory.DRAG_DROP]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_INFO,
				logToConsole: false
			},
			[ErrorCategory.COMPONENT_RENDER]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_WARNING,
				logToConsole: true
			},
			[ErrorCategory.PERMISSION]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.FILE_SYSTEM]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				logToConsole: true
			},
			[ErrorCategory.UNKNOWN]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_WARNING,
				logToConsole: true
			}
		};

		// Allow user overrides from settings
		const userPreferences = config.get('notifications.categoryPreferences', {});
		return { ...defaults, ...userPreferences };
	}

	/**
	 * Get severity-based preferences with defaults
	 */
	private getSeverityPreferences(config: vscode.WorkspaceConfiguration): Record<
		ErrorSeverity,
		{
			showToUser: boolean;
			notificationType: NotificationType;
			minToastDuration: number;
		}
	> {
		const defaults = {
			[ErrorSeverity.LOW]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_INFO,
				minToastDuration: 3000
			},
			[ErrorSeverity.MEDIUM]: {
				showToUser: true,
				notificationType: NotificationType.TOAST_WARNING,
				minToastDuration: 5000
			},
			[ErrorSeverity.HIGH]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_WARNING,
				minToastDuration: 7000
			},
			[ErrorSeverity.CRITICAL]: {
				showToUser: true,
				notificationType: NotificationType.VSCODE_ERROR,
				minToastDuration: 10000
			}
		};

		// Allow user overrides from settings
		const userPreferences = config.get('notifications.severityPreferences', {});
		return { ...defaults, ...userPreferences };
	}

	/**
	 * Get default notification type for severity
	 */
	private getDefaultNotificationType(
		severity: ErrorSeverity
	): NotificationType {
		switch (severity) {
			case ErrorSeverity.LOW:
				return NotificationType.TOAST_INFO;
			case ErrorSeverity.MEDIUM:
				return NotificationType.TOAST_WARNING;
			case ErrorSeverity.HIGH:
				return NotificationType.VSCODE_WARNING;
			case ErrorSeverity.CRITICAL:
				return NotificationType.VSCODE_ERROR;
			default:
				return NotificationType.CONSOLE_ONLY;
		}
	}
}

// Export convenience functions
export function getNotificationPreferences(): NotificationPreferences {
	return NotificationPreferencesManager.getInstance().getPreferences();
}

export function updateNotificationPreferences(
	preferences: Partial<NotificationPreferences>
): Promise<void> {
	return NotificationPreferencesManager.getInstance().updatePreferences(
		preferences
	);
}

export function shouldShowNotification(
	category: ErrorCategory,
	severity: ErrorSeverity
): boolean {
	return NotificationPreferencesManager.getInstance().shouldShowNotification(
		category,
		severity
	);
}

export function getNotificationType(
	category: ErrorCategory,
	severity: ErrorSeverity
): NotificationType {
	return NotificationPreferencesManager.getInstance().getNotificationType(
		category,
		severity
	);
}

export function getToastDuration(severity: ErrorSeverity): number {
	return NotificationPreferencesManager.getInstance().getToastDuration(
		severity
	);
}

```

--------------------------------------------------------------------------------
/apps/extension/src/utils/task-master-api/transformers/task-transformer.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Task Transformer
 * Handles transformation and validation of MCP responses to internal format
 */

import type { ExtensionLogger } from '../../logger';
import { MCPTaskResponse, type TaskMasterTask } from '../types';

export class TaskTransformer {
	constructor(private logger: ExtensionLogger) {}

	/**
	 * Transform MCP tasks response to internal format
	 */
	transformMCPTasksResponse(mcpResponse: any): TaskMasterTask[] {
		const transformStartTime = Date.now();

		try {
			// Validate response structure
			const validationResult = this.validateMCPResponse(mcpResponse);
			if (!validationResult.isValid) {
				this.logger.warn(
					'MCP response validation failed:',
					validationResult.errors
				);
				return [];
			}

			// Handle different response structures
			let tasks = [];
			if (Array.isArray(mcpResponse)) {
				tasks = mcpResponse;
			} else if (mcpResponse.data) {
				if (Array.isArray(mcpResponse.data)) {
					tasks = mcpResponse.data;
				} else if (
					mcpResponse.data.tasks &&
					Array.isArray(mcpResponse.data.tasks)
				) {
					tasks = mcpResponse.data.tasks;
				}
			} else if (mcpResponse.tasks && Array.isArray(mcpResponse.tasks)) {
				tasks = mcpResponse.tasks;
			}

			this.logger.log(`Transforming ${tasks.length} tasks from MCP response`, {
				responseStructure: {
					isArray: Array.isArray(mcpResponse),
					hasData: !!mcpResponse.data,
					dataIsArray: Array.isArray(mcpResponse.data),
					hasDataTasks: !!mcpResponse.data?.tasks,
					hasTasks: !!mcpResponse.tasks
				}
			});

			const transformedTasks: TaskMasterTask[] = [];
			const transformationErrors: Array<{
				taskId: any;
				error: string;
				task: any;
			}> = [];

			for (let i = 0; i < tasks.length; i++) {
				try {
					const task = tasks[i];
					const transformedTask = this.transformSingleTask(task, i);
					if (transformedTask) {
						transformedTasks.push(transformedTask);
					}
				} catch (error) {
					const errorMsg =
						error instanceof Error
							? error.message
							: 'Unknown transformation error';
					transformationErrors.push({
						taskId: tasks[i]?.id || `unknown_${i}`,
						error: errorMsg,
						task: tasks[i]
					});
					this.logger.error(
						`Failed to transform task at index ${i}:`,
						errorMsg,
						tasks[i]
					);
				}
			}

			// Log transformation summary
			const transformDuration = Date.now() - transformStartTime;
			this.logger.log(`Transformation completed in ${transformDuration}ms`, {
				totalTasks: tasks.length,
				successfulTransformations: transformedTasks.length,
				errors: transformationErrors.length,
				errorSummary: transformationErrors.map((e) => ({
					id: e.taskId,
					error: e.error
				}))
			});

			return transformedTasks;
		} catch (error) {
			this.logger.error(
				'Critical error during response transformation:',
				error
			);
			return [];
		}
	}

	/**
	 * Validate MCP response structure
	 */
	private validateMCPResponse(mcpResponse: any): {
		isValid: boolean;
		errors: string[];
	} {
		const errors: string[] = [];

		if (!mcpResponse) {
			errors.push('Response is null or undefined');
			return { isValid: false, errors };
		}

		// Arrays are valid responses
		if (Array.isArray(mcpResponse)) {
			return { isValid: true, errors };
		}

		if (typeof mcpResponse !== 'object') {
			errors.push('Response is not an object or array');
			return { isValid: false, errors };
		}

		if (mcpResponse.error) {
			errors.push(`MCP error: ${mcpResponse.error}`);
		}

		// Check for valid task structure
		const hasValidTasksStructure =
			(mcpResponse.data && Array.isArray(mcpResponse.data)) ||
			(mcpResponse.data?.tasks && Array.isArray(mcpResponse.data.tasks)) ||
			(mcpResponse.tasks && Array.isArray(mcpResponse.tasks));

		if (!hasValidTasksStructure && !mcpResponse.error) {
			errors.push('Response does not contain a valid tasks array structure');
		}

		return { isValid: errors.length === 0, errors };
	}

	/**
	 * Transform a single task with validation
	 */
	private transformSingleTask(task: any, index: number): TaskMasterTask | null {
		if (!task || typeof task !== 'object') {
			this.logger.warn(`Task at index ${index} is not a valid object:`, task);
			return null;
		}

		try {
			// Validate required fields
			const taskId = this.validateAndNormalizeId(task.id, index);
			const title =
				this.validateAndNormalizeString(
					task.title,
					'Untitled Task',
					`title for task ${taskId}`
				) || 'Untitled Task';
			const description =
				this.validateAndNormalizeString(
					task.description,
					'',
					`description for task ${taskId}`
				) || '';

			// Normalize and validate status/priority
			const status = this.normalizeStatus(task.status);
			const priority = this.normalizePriority(task.priority);

			// Handle optional fields
			const details = this.validateAndNormalizeString(
				task.details,
				undefined,
				`details for task ${taskId}`
			);
			const testStrategy = this.validateAndNormalizeString(
				task.testStrategy,
				undefined,
				`testStrategy for task ${taskId}`
			);

			// Handle complexity score
			const complexityScore =
				typeof task.complexityScore === 'number'
					? task.complexityScore
					: undefined;

			// Transform dependencies
			const dependencies = this.transformDependencies(
				task.dependencies,
				taskId
			);

			// Transform subtasks
			const subtasks = this.transformSubtasks(task.subtasks, taskId);

			const transformedTask: TaskMasterTask = {
				id: taskId,
				title,
				description,
				status,
				priority,
				details,
				testStrategy,
				complexityScore,
				dependencies,
				subtasks
			};

			// Log successful transformation for complex tasks
			if (
				(subtasks && subtasks.length > 0) ||
				dependencies.length > 0 ||
				complexityScore !== undefined
			) {
				this.logger.debug(`Successfully transformed complex task ${taskId}:`, {
					subtaskCount: subtasks?.length ?? 0,
					dependencyCount: dependencies.length,
					status,
					priority,
					complexityScore
				});
			}

			return transformedTask;
		} catch (error) {
			this.logger.error(
				`Error transforming task at index ${index}:`,
				error,
				task
			);
			return null;
		}
	}

	private validateAndNormalizeId(id: any, fallbackIndex: number): string {
		if (id === null || id === undefined) {
			const generatedId = `generated_${fallbackIndex}_${Date.now()}`;
			this.logger.warn(`Task missing ID, generated: ${generatedId}`);
			return generatedId;
		}

		const stringId = String(id).trim();
		if (stringId === '') {
			const generatedId = `empty_${fallbackIndex}_${Date.now()}`;
			this.logger.warn(`Task has empty ID, generated: ${generatedId}`);
			return generatedId;
		}

		return stringId;
	}

	private validateAndNormalizeString(
		value: any,
		defaultValue: string | undefined,
		fieldName: string
	): string | undefined {
		if (value === null || value === undefined) {
			return defaultValue;
		}

		if (typeof value !== 'string') {
			this.logger.warn(`${fieldName} is not a string, converting:`, value);
			return String(value).trim() || defaultValue;
		}

		const trimmed = value.trim();
		if (trimmed === '' && defaultValue !== undefined) {
			return defaultValue;
		}

		return trimmed || defaultValue;
	}

	private transformDependencies(dependencies: any, taskId: string): string[] {
		if (!dependencies) {
			return [];
		}

		if (!Array.isArray(dependencies)) {
			this.logger.warn(
				`Dependencies for task ${taskId} is not an array:`,
				dependencies
			);
			return [];
		}

		const validDependencies: string[] = [];
		for (let i = 0; i < dependencies.length; i++) {
			const dep = dependencies[i];
			if (dep === null || dep === undefined) {
				this.logger.warn(`Null dependency at index ${i} for task ${taskId}`);
				continue;
			}

			const stringDep = String(dep).trim();
			if (stringDep === '') {
				this.logger.warn(`Empty dependency at index ${i} for task ${taskId}`);
				continue;
			}

			// Check for self-dependency
			if (stringDep === taskId) {
				this.logger.warn(
					`Self-dependency detected for task ${taskId}, skipping`
				);
				continue;
			}

			validDependencies.push(stringDep);
		}

		return validDependencies;
	}

	private transformSubtasks(
		subtasks: any,
		parentTaskId: string
	): TaskMasterTask['subtasks'] {
		if (!subtasks) {
			return [];
		}

		if (!Array.isArray(subtasks)) {
			this.logger.warn(
				`Subtasks for task ${parentTaskId} is not an array:`,
				subtasks
			);
			return [];
		}

		const validSubtasks = [];
		for (let i = 0; i < subtasks.length; i++) {
			try {
				const subtask = subtasks[i];
				if (!subtask || typeof subtask !== 'object') {
					this.logger.warn(
						`Invalid subtask at index ${i} for task ${parentTaskId}:`,
						subtask
					);
					continue;
				}

				const transformedSubtask = {
					id: typeof subtask.id === 'number' ? subtask.id : i + 1,
					title:
						this.validateAndNormalizeString(
							subtask.title,
							`Subtask ${i + 1}`,
							`subtask title for parent ${parentTaskId}`
						) || `Subtask ${i + 1}`,
					description: this.validateAndNormalizeString(
						subtask.description,
						undefined,
						`subtask description for parent ${parentTaskId}`
					),
					status:
						this.validateAndNormalizeString(
							subtask.status,
							'pending',
							`subtask status for parent ${parentTaskId}`
						) || 'pending',
					details: this.validateAndNormalizeString(
						subtask.details,
						undefined,
						`subtask details for parent ${parentTaskId}`
					),
					testStrategy: this.validateAndNormalizeString(
						subtask.testStrategy,
						undefined,
						`subtask testStrategy for parent ${parentTaskId}`
					),
					dependencies: subtask.dependencies || []
				};

				validSubtasks.push(transformedSubtask);
			} catch (error) {
				this.logger.error(
					`Error transforming subtask at index ${i} for task ${parentTaskId}:`,
					error
				);
			}
		}

		return validSubtasks;
	}

	private normalizeStatus(status: string): TaskMasterTask['status'] {
		const original = status;
		const normalized = status?.toLowerCase()?.trim() || 'pending';

		const statusMap: Record<string, TaskMasterTask['status']> = {
			pending: 'pending',
			'in-progress': 'in-progress',
			in_progress: 'in-progress',
			inprogress: 'in-progress',
			progress: 'in-progress',
			working: 'in-progress',
			active: 'in-progress',
			review: 'review',
			reviewing: 'review',
			'in-review': 'review',
			in_review: 'review',
			done: 'done',
			completed: 'done',
			complete: 'done',
			finished: 'done',
			closed: 'done',
			resolved: 'done',
			blocked: 'deferred',
			block: 'deferred',
			stuck: 'deferred',
			waiting: 'deferred',
			cancelled: 'cancelled',
			canceled: 'cancelled',
			cancel: 'cancelled',
			abandoned: 'cancelled',
			deferred: 'deferred',
			defer: 'deferred',
			postponed: 'deferred',
			later: 'deferred'
		};

		const result = statusMap[normalized] || 'pending';

		if (original && original !== result) {
			this.logger.debug(`Normalized status '${original}' -> '${result}'`);
		}

		return result;
	}

	private normalizePriority(priority: string): TaskMasterTask['priority'] {
		const original = priority;
		const normalized = priority?.toLowerCase()?.trim() || 'medium';

		let result: TaskMasterTask['priority'] = 'medium';

		if (
			normalized.includes('high') ||
			normalized.includes('urgent') ||
			normalized.includes('critical') ||
			normalized.includes('important') ||
			normalized === 'h' ||
			normalized === '3'
		) {
			result = 'high';
		} else if (
			normalized.includes('low') ||
			normalized.includes('minor') ||
			normalized.includes('trivial') ||
			normalized === 'l' ||
			normalized === '1'
		) {
			result = 'low';
		} else if (
			normalized.includes('medium') ||
			normalized.includes('normal') ||
			normalized.includes('standard') ||
			normalized === 'm' ||
			normalized === '2'
		) {
			result = 'medium';
		}

		if (original && original !== result) {
			this.logger.debug(`Normalized priority '${original}' -> '${result}'`);
		}

		return result;
	}
}

```

--------------------------------------------------------------------------------
/tests/unit/ai-providers/provider-registry.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Tests for ProviderRegistry - Singleton for managing AI providers
 *
 * This test suite covers:
 * 1. Singleton pattern behavior
 * 2. Provider registration and validation
 * 3. Provider retrieval and management
 * 4. Provider unregistration
 * 5. Registry reset (for testing)
 * 6. Interface validation for registered providers
 */

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

// Import ProviderRegistry
const { default: ProviderRegistry } = await import(
	'../../../src/provider-registry/index.js'
);

// Mock provider classes for testing
class MockValidProvider {
	constructor() {
		this.name = 'MockValidProvider';
	}

	generateText() {
		return Promise.resolve({ text: 'mock text' });
	}
	streamText() {
		return Promise.resolve('mock stream');
	}
	generateObject() {
		return Promise.resolve({ object: {} });
	}
	getRequiredApiKeyName() {
		return 'MOCK_API_KEY';
	}
}

class MockInvalidProvider {
	constructor() {
		this.name = 'MockInvalidProvider';
	}
	// Missing required methods: generateText, streamText, generateObject
}

describe('ProviderRegistry', () => {
	let registry;

	beforeEach(() => {
		// Get a fresh instance and reset it
		registry = ProviderRegistry.getInstance();
		registry.reset();
	});

	afterEach(() => {
		// Clean up after each test
		registry.reset();
	});

	describe('Singleton Pattern', () => {
		test('getInstance returns the same instance', () => {
			const instance1 = ProviderRegistry.getInstance();
			const instance2 = ProviderRegistry.getInstance();

			expect(instance1).toBe(instance2);
			expect(instance1).toBe(registry);
		});

		test('multiple calls to getInstance return same instance', () => {
			const instances = Array.from({ length: 5 }, () =>
				ProviderRegistry.getInstance()
			);

			instances.forEach((instance) => {
				expect(instance).toBe(registry);
			});
		});
	});

	describe('Initialization', () => {
		test('registry is not auto-initialized when mocked', () => {
			// When mocked, the auto-initialization at import may not occur
			expect(registry._initialized).toBe(false);
		});

		test('initialize sets initialized flag', () => {
			expect(registry._initialized).toBe(false);

			const result = registry.initialize();

			expect(registry._initialized).toBe(true);
			expect(result).toBe(registry);
		});

		test('initialize can be called multiple times safely', () => {
			// First call initializes
			registry.initialize();
			expect(registry._initialized).toBe(true);

			// Second call should not throw
			expect(() => registry.initialize()).not.toThrow();
		});

		test('initialize returns self for chaining', () => {
			const result = registry.initialize();
			expect(result).toBe(registry);
		});
	});

	describe('Provider Registration', () => {
		test('registerProvider adds valid provider successfully', () => {
			const mockProvider = new MockValidProvider();
			const options = { priority: 'high' };

			const result = registry.registerProvider('mock', mockProvider, options);

			expect(result).toBe(registry); // Should return self for chaining
			expect(registry.hasProvider('mock')).toBe(true);
		});

		test('registerProvider validates provider name', () => {
			const mockProvider = new MockValidProvider();

			// Test empty string
			expect(() => registry.registerProvider('', mockProvider)).toThrow(
				'Provider name must be a non-empty string'
			);

			// Test null
			expect(() => registry.registerProvider(null, mockProvider)).toThrow(
				'Provider name must be a non-empty string'
			);

			// Test non-string
			expect(() => registry.registerProvider(123, mockProvider)).toThrow(
				'Provider name must be a non-empty string'
			);
		});

		test('registerProvider validates provider instance', () => {
			expect(() => registry.registerProvider('mock', null)).toThrow(
				'Provider instance is required'
			);

			expect(() => registry.registerProvider('mock', undefined)).toThrow(
				'Provider instance is required'
			);
		});

		test('registerProvider validates provider interface', () => {
			const invalidProvider = new MockInvalidProvider();

			expect(() => registry.registerProvider('mock', invalidProvider)).toThrow(
				'Provider must implement BaseAIProvider interface'
			);
		});

		test('registerProvider stores provider with metadata', () => {
			const mockProvider = new MockValidProvider();
			const options = { priority: 'high', custom: 'value' };
			const beforeRegistration = new Date();

			registry.registerProvider('mock', mockProvider, options);

			const storedEntry = registry._providers.get('mock');
			expect(storedEntry.instance).toBe(mockProvider);
			expect(storedEntry.options).toEqual(options);
			expect(storedEntry.registeredAt).toBeInstanceOf(Date);
			expect(storedEntry.registeredAt.getTime()).toBeGreaterThanOrEqual(
				beforeRegistration.getTime()
			);
		});

		test('registerProvider can overwrite existing providers', () => {
			const provider1 = new MockValidProvider();
			const provider2 = new MockValidProvider();

			registry.registerProvider('mock', provider1);
			expect(registry.getProvider('mock')).toBe(provider1);

			registry.registerProvider('mock', provider2);
			expect(registry.getProvider('mock')).toBe(provider2);
		});

		test('registerProvider handles missing options', () => {
			const mockProvider = new MockValidProvider();

			registry.registerProvider('mock', mockProvider);

			const storedEntry = registry._providers.get('mock');
			expect(storedEntry.options).toEqual({});
		});
	});

	describe('Provider Retrieval', () => {
		beforeEach(() => {
			const mockProvider = new MockValidProvider();
			registry.registerProvider('mock', mockProvider, { test: 'value' });
		});

		test('hasProvider returns correct boolean values', () => {
			expect(registry.hasProvider('mock')).toBe(true);
			expect(registry.hasProvider('nonexistent')).toBe(false);
			expect(registry.hasProvider('')).toBe(false);
			expect(registry.hasProvider(null)).toBe(false);
		});

		test('getProvider returns correct provider instance', () => {
			const provider = registry.getProvider('mock');
			expect(provider).toBeInstanceOf(MockValidProvider);
			expect(provider.name).toBe('MockValidProvider');
		});

		test('getProvider returns null for nonexistent provider', () => {
			expect(registry.getProvider('nonexistent')).toBe(null);
			expect(registry.getProvider('')).toBe(null);
			expect(registry.getProvider(null)).toBe(null);
		});

		test('getAllProviders returns copy of providers map', () => {
			const mockProvider2 = new MockValidProvider();
			registry.registerProvider('mock2', mockProvider2);

			const allProviders = registry.getAllProviders();

			expect(allProviders).toBeInstanceOf(Map);
			expect(allProviders.size).toBe(2);
			expect(allProviders.has('mock')).toBe(true);
			expect(allProviders.has('mock2')).toBe(true);

			// Should be a copy, not the original
			expect(allProviders).not.toBe(registry._providers);
		});

		test('getAllProviders returns empty map when no providers', () => {
			registry.reset();

			const allProviders = registry.getAllProviders();

			expect(allProviders).toBeInstanceOf(Map);
			expect(allProviders.size).toBe(0);
		});
	});

	describe('Provider Unregistration', () => {
		beforeEach(() => {
			const mockProvider = new MockValidProvider();
			registry.registerProvider('mock', mockProvider);
		});

		test('unregisterProvider removes existing provider', () => {
			expect(registry.hasProvider('mock')).toBe(true);

			const result = registry.unregisterProvider('mock');

			expect(result).toBe(true);
			expect(registry.hasProvider('mock')).toBe(false);
		});

		test('unregisterProvider returns false for nonexistent provider', () => {
			const result = registry.unregisterProvider('nonexistent');

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

		test('unregisterProvider handles edge cases', () => {
			expect(registry.unregisterProvider('')).toBe(false);
			expect(registry.unregisterProvider(null)).toBe(false);
			expect(registry.unregisterProvider(undefined)).toBe(false);
		});
	});

	describe('Registry Reset', () => {
		beforeEach(() => {
			const mockProvider = new MockValidProvider();
			registry.registerProvider('mock', mockProvider);
			registry.initialize();
		});

		test('reset clears all providers', () => {
			expect(registry.hasProvider('mock')).toBe(true);
			expect(registry._initialized).toBe(true);

			registry.reset();

			expect(registry.hasProvider('mock')).toBe(false);
			expect(registry._providers.size).toBe(0);
		});

		test('reset clears initialization flag', () => {
			expect(registry._initialized).toBe(true);

			registry.reset();

			expect(registry._initialized).toBe(false);
		});

		// No log assertion for reset, just call reset
		test('reset can be called without error', () => {
			expect(() => registry.reset()).not.toThrow();
		});

		test('reset allows re-initialization', () => {
			registry.reset();
			expect(registry._initialized).toBe(false);

			registry.initialize();
			expect(registry._initialized).toBe(true);
		});
	});

	describe('Interface Validation', () => {
		test('validates generateText method exists', () => {
			const providerWithoutGenerateText = {
				streamText: jest.fn(),
				generateObject: jest.fn()
			};

			expect(() =>
				registry.registerProvider('invalid', providerWithoutGenerateText)
			).toThrow('Provider must implement BaseAIProvider interface');
		});

		test('validates streamText method exists', () => {
			const providerWithoutStreamText = {
				generateText: jest.fn(),
				generateObject: jest.fn()
			};

			expect(() =>
				registry.registerProvider('invalid', providerWithoutStreamText)
			).toThrow('Provider must implement BaseAIProvider interface');
		});

		test('validates generateObject method exists', () => {
			const providerWithoutGenerateObject = {
				generateText: jest.fn(),
				streamText: jest.fn()
			};

			expect(() =>
				registry.registerProvider('invalid', providerWithoutGenerateObject)
			).toThrow('Provider must implement BaseAIProvider interface');
		});

		test('validates methods are functions', () => {
			const providerWithNonFunctionMethods = {
				generateText: 'not a function',
				streamText: jest.fn(),
				generateObject: jest.fn()
			};

			expect(() =>
				registry.registerProvider('invalid', providerWithNonFunctionMethods)
			).toThrow('Provider must implement BaseAIProvider interface');
		});

		test('accepts provider with all required methods', () => {
			const validProvider = {
				generateText: jest.fn(),
				streamText: jest.fn(),
				generateObject: jest.fn()
			};

			expect(() =>
				registry.registerProvider('valid', validProvider)
			).not.toThrow();
		});
	});

	describe('Edge Cases and Error Handling', () => {
		test('handles provider registration after reset', () => {
			const mockProvider = new MockValidProvider();
			registry.registerProvider('mock', mockProvider);
			expect(registry.hasProvider('mock')).toBe(true);

			registry.reset();
			expect(registry.hasProvider('mock')).toBe(false);

			registry.registerProvider('mock', mockProvider);
			expect(registry.hasProvider('mock')).toBe(true);
		});

		test('handles multiple registrations and unregistrations', () => {
			const provider1 = new MockValidProvider();
			const provider2 = new MockValidProvider();

			registry.registerProvider('provider1', provider1);
			registry.registerProvider('provider2', provider2);

			expect(registry.getAllProviders().size).toBe(2);

			registry.unregisterProvider('provider1');
			expect(registry.hasProvider('provider1')).toBe(false);
			expect(registry.hasProvider('provider2')).toBe(true);

			registry.unregisterProvider('provider2');
			expect(registry.getAllProviders().size).toBe(0);
		});

		test('maintains provider isolation', () => {
			const provider1 = new MockValidProvider();
			const provider2 = new MockValidProvider();

			registry.registerProvider('provider1', provider1);
			registry.registerProvider('provider2', provider2);

			const retrieved1 = registry.getProvider('provider1');
			const retrieved2 = registry.getProvider('provider2');

			expect(retrieved1).toBe(provider1);
			expect(retrieved2).toBe(provider2);
			expect(retrieved1).not.toBe(retrieved2);
		});
	});
});

```

--------------------------------------------------------------------------------
/apps/cli/src/commands/list.command.ts:
--------------------------------------------------------------------------------

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

import {
	OUTPUT_FORMATS,
	type OutputFormat,
	STATUS_ICONS,
	TASK_STATUSES,
	type Task,
	type TaskStatus,
	type TmCore,
	createTmCore
} from '@tm/core';
import type { StorageType } from '@tm/core';
import chalk from 'chalk';
import { Command } from 'commander';
import {
	type NextTaskInfo,
	calculateDependencyStatistics,
	calculateSubtaskStatistics,
	calculateTaskStatistics,
	displayDashboards,
	displayRecommendedNextTask,
	displaySuggestedNextSteps,
	getPriorityBreakdown,
	getTaskDescription
} from '../ui/index.js';
import { displayCommandHeader } from '../utils/display-helpers.js';
import { displayError } from '../utils/error-handler.js';
import { getProjectRoot } from '../utils/project-root.js';
import { isTaskComplete } from '../utils/task-status.js';
import * as ui from '../utils/ui.js';

/**
 * Options interface for the list command
 */
export interface ListCommandOptions {
	status?: string;
	tag?: string;
	withSubtasks?: boolean;
	format?: OutputFormat;
	json?: boolean;
	silent?: boolean;
	project?: string;
}

/**
 * Result type from list command
 */
export interface ListTasksResult {
	tasks: Task[];
	total: number;
	filtered: number;
	tag?: string;
	storageType: Exclude<StorageType, 'auto'>;
}

/**
 * ListTasksCommand extending Commander's Command class
 * This is a thin presentation layer over @tm/core
 */
export class ListTasksCommand extends Command {
	private tmCore?: TmCore;
	private lastResult?: ListTasksResult;

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

		// Configure the command
		this.description('List tasks with optional filtering')
			.alias('ls')
			.option('-s, --status <status>', 'Filter by status (comma-separated)')
			.option('-t, --tag <tag>', 'Filter by tag')
			.option('--with-subtasks', 'Include subtasks in the output')
			.option(
				'-f, --format <format>',
				'Output format (text, json, compact)',
				'text'
			)
			.option('--json', 'Output in JSON format (shorthand for --format json)')
			.option('--silent', 'Suppress output (useful for programmatic usage)')
			.option(
				'-p, --project <path>',
				'Project root directory (auto-detected if not provided)'
			)
			.action(async (options: ListCommandOptions) => {
				await this.executeCommand(options);
			});
	}

	/**
	 * Execute the list command
	 */
	private async executeCommand(options: ListCommandOptions): Promise<void> {
		try {
			// Validate options
			if (!this.validateOptions(options)) {
				process.exit(1);
			}

			// Initialize tm-core (project root auto-detected if not provided)
			await this.initializeCore(getProjectRoot(options.project));

			// Get tasks from core
			const result = await this.getTasks(options);

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

			// Display results
			if (!options.silent) {
				this.displayResults(result, options);
			}
		} catch (error: any) {
			displayError(error);
		}
	}

	/**
	 * Validate command options
	 */
	private validateOptions(options: ListCommandOptions): boolean {
		// Validate format
		if (
			options.format &&
			!OUTPUT_FORMATS.includes(options.format as OutputFormat)
		) {
			console.error(chalk.red(`Invalid format: ${options.format}`));
			console.error(chalk.gray(`Valid formats: ${OUTPUT_FORMATS.join(', ')}`));
			return false;
		}

		// Validate status
		if (options.status) {
			const statuses = options.status.split(',').map((s: string) => s.trim());

			for (const status of statuses) {
				if (status !== 'all' && !TASK_STATUSES.includes(status as TaskStatus)) {
					console.error(chalk.red(`Invalid status: ${status}`));
					console.error(
						chalk.gray(`Valid statuses: ${TASK_STATUSES.join(', ')}`)
					);
					return false;
				}
			}
		}

		return true;
	}

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

	/**
	 * Get tasks from tm-core
	 */
	private async getTasks(
		options: ListCommandOptions
	): Promise<ListTasksResult> {
		if (!this.tmCore) {
			throw new Error('TmCore not initialized');
		}

		// Build filter
		const filter =
			options.status && options.status !== 'all'
				? {
						status: options.status
							.split(',')
							.map((s: string) => s.trim() as TaskStatus)
					}
				: undefined;

		// Call tm-core
		const result = await this.tmCore.tasks.list({
			tag: options.tag,
			filter,
			includeSubtasks: options.withSubtasks
		});

		return result as ListTasksResult;
	}

	/**
	 * Display results based on format
	 */
	private displayResults(
		result: ListTasksResult,
		options: ListCommandOptions
	): void {
		// If --json flag is set, override format to 'json'
		const format = (
			options.json ? 'json' : options.format || 'text'
		) as OutputFormat;

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

			case 'compact':
				this.displayCompact(result.tasks, options.withSubtasks);
				break;

			case 'text':
			default:
				this.displayText(result, options.withSubtasks);
				break;
		}
	}

	/**
	 * Display in JSON format
	 */
	private displayJson(data: ListTasksResult): void {
		console.log(
			JSON.stringify(
				{
					tasks: data.tasks,
					metadata: {
						total: data.total,
						filtered: data.filtered,
						tag: data.tag,
						storageType: data.storageType
					}
				},
				null,
				2
			)
		);
	}

	/**
	 * Display in compact format
	 */
	private displayCompact(tasks: Task[], withSubtasks?: boolean): void {
		tasks.forEach((task) => {
			const icon = STATUS_ICONS[task.status];
			console.log(`${chalk.cyan(task.id)} ${icon} ${task.title}`);

			if (withSubtasks && task.subtasks?.length) {
				task.subtasks.forEach((subtask) => {
					const subIcon = STATUS_ICONS[subtask.status];
					console.log(
						`  ${chalk.gray(String(subtask.id))} ${subIcon} ${chalk.gray(subtask.title)}`
					);
				});
			}
		});
	}

	/**
	 * Display in text format with tables
	 */
	private displayText(data: ListTasksResult, withSubtasks?: boolean): void {
		const { tasks, tag, storageType } = data;

		// Display header using utility function
		displayCommandHeader(this.tmCore, {
			tag: tag || 'master',
			storageType
		});

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

		// Calculate statistics
		const taskStats = calculateTaskStatistics(tasks);
		const subtaskStats = calculateSubtaskStatistics(tasks);
		const depStats = calculateDependencyStatistics(tasks);
		const priorityBreakdown = getPriorityBreakdown(tasks);

		// Find next task following the same logic as findNextTask
		const nextTaskInfo = this.findNextTask(tasks);

		// Get the full task object with complexity data already included
		const nextTask = nextTaskInfo
			? tasks.find((t) => String(t.id) === String(nextTaskInfo.id))
			: undefined;

		// Display dashboard boxes (nextTask already has complexity from storage enrichment)
		displayDashboards(
			taskStats,
			subtaskStats,
			priorityBreakdown,
			depStats,
			nextTask
		);

		// Task table
		console.log(
			ui.createTaskTable(tasks, {
				showSubtasks: withSubtasks,
				showDependencies: true,
				showComplexity: true // Enable complexity column
			})
		);

		// Display recommended next task section immediately after table
		if (nextTask) {
			const description = getTaskDescription(nextTask);

			displayRecommendedNextTask({
				id: nextTask.id,
				title: nextTask.title,
				priority: nextTask.priority,
				status: nextTask.status,
				dependencies: nextTask.dependencies,
				description,
				complexity: nextTask.complexity as number | undefined
			});
		} else {
			displayRecommendedNextTask(undefined);
		}

		// Display suggested next steps at the end
		displaySuggestedNextSteps();
	}

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

	/**
	 * Find the next task to work on
	 * Implements the same logic as scripts/modules/task-manager/find-next-task.js
	 */
	private findNextTask(tasks: Task[]): NextTaskInfo | undefined {
		const priorityValues: Record<string, number> = {
			critical: 4,
			high: 3,
			medium: 2,
			low: 1
		};

		// Build set of completed task IDs (including subtasks)
		const completedIds = new Set<string>();
		tasks.forEach((t) => {
			if (isTaskComplete(t.status)) {
				completedIds.add(String(t.id));
			}
			if (t.subtasks) {
				t.subtasks.forEach((st) => {
					if (isTaskComplete(st.status as TaskStatus)) {
						completedIds.add(`${t.id}.${st.id}`);
					}
				});
			}
		});

		// First, look for eligible subtasks in in-progress parent tasks
		const candidateSubtasks: NextTaskInfo[] = [];

		tasks
			.filter(
				(t) => t.status === 'in-progress' && t.subtasks && t.subtasks.length > 0
			)
			.forEach((parent) => {
				parent.subtasks!.forEach((st) => {
					const stStatus = (st.status || 'pending').toLowerCase();
					if (stStatus !== 'pending' && stStatus !== 'in-progress') return;

					// Check if dependencies are satisfied
					const fullDeps =
						st.dependencies?.map((d) => {
							// Handle both numeric and string IDs
							if (typeof d === 'string' && d.includes('.')) {
								return d;
							}
							return `${parent.id}.${d}`;
						}) ?? [];

					const depsSatisfied =
						fullDeps.length === 0 ||
						fullDeps.every((depId) => completedIds.has(String(depId)));

					if (depsSatisfied) {
						candidateSubtasks.push({
							id: `${parent.id}.${st.id}`,
							title: st.title || `Subtask ${st.id}`,
							priority: st.priority || parent.priority || 'medium',
							dependencies: fullDeps.map((d) => String(d))
						});
					}
				});
			});

		if (candidateSubtasks.length > 0) {
			// Sort by priority, then by dependencies count, then by ID
			candidateSubtasks.sort((a, b) => {
				const pa = priorityValues[a.priority || 'medium'] ?? 2;
				const pb = priorityValues[b.priority || 'medium'] ?? 2;
				if (pb !== pa) return pb - pa;

				const depCountA = a.dependencies?.length || 0;
				const depCountB = b.dependencies?.length || 0;
				if (depCountA !== depCountB) return depCountA - depCountB;

				return String(a.id).localeCompare(String(b.id));
			});
			return candidateSubtasks[0];
		}

		// Fall back to finding eligible top-level tasks
		const eligibleTasks = tasks.filter((task) => {
			// Skip non-eligible statuses
			const status = (task.status || 'pending').toLowerCase();
			if (status !== 'pending' && status !== 'in-progress') return false;

			// Check dependencies
			const deps = task.dependencies || [];
			const depsSatisfied =
				deps.length === 0 ||
				deps.every((depId) => completedIds.has(String(depId)));

			return depsSatisfied;
		});

		if (eligibleTasks.length === 0) return undefined;

		// Sort eligible tasks
		eligibleTasks.sort((a, b) => {
			// Priority (higher first)
			const pa = priorityValues[a.priority || 'medium'] ?? 2;
			const pb = priorityValues[b.priority || 'medium'] ?? 2;
			if (pb !== pa) return pb - pa;

			// Dependencies count (fewer first)
			const depCountA = a.dependencies?.length || 0;
			const depCountB = b.dependencies?.length || 0;
			if (depCountA !== depCountB) return depCountA - depCountB;

			// ID (lower first)
			return Number(a.id) - Number(b.id);
		});

		const nextTask = eligibleTasks[0];
		return {
			id: nextTask.id,
			title: nextTask.title,
			priority: nextTask.priority,
			dependencies: nextTask.dependencies?.map((d) => String(d))
		};
	}

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

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

	/**
	 * Register this command on an existing program
	 */
	static register(program: Command, name?: string): ListTasksCommand {
		const listCommand = new ListTasksCommand(name);
		program.addCommand(listCommand);
		return listCommand;
	}
}

```

--------------------------------------------------------------------------------
/apps/cli/tests/integration/commands/autopilot/workflow.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Integration tests for autopilot workflow commands
 */

import type { WorkflowState } from '@tm/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

// Track file system state in memory - must be in vi.hoisted() for mock access
const {
	mockFileSystem,
	pathExistsFn,
	readJSONFn,
	writeJSONFn,
	ensureDirFn,
	removeFn
} = vi.hoisted(() => {
	const mockFileSystem = new Map<string, string>();

	return {
		mockFileSystem,
		pathExistsFn: vi.fn((path: string) =>
			Promise.resolve(mockFileSystem.has(path))
		),
		readJSONFn: vi.fn((path: string) => {
			const data = mockFileSystem.get(path);
			return data
				? Promise.resolve(JSON.parse(data))
				: Promise.reject(new Error('File not found'));
		}),
		writeJSONFn: vi.fn((path: string, data: any) => {
			mockFileSystem.set(path, JSON.stringify(data));
			return Promise.resolve();
		}),
		ensureDirFn: vi.fn(() => Promise.resolve()),
		removeFn: vi.fn((path: string) => {
			mockFileSystem.delete(path);
			return Promise.resolve();
		})
	};
});

// Mock fs-extra before any imports
vi.mock('fs-extra', () => ({
	default: {
		pathExists: pathExistsFn,
		readJSON: readJSONFn,
		writeJSON: writeJSONFn,
		ensureDir: ensureDirFn,
		remove: removeFn
	}
}));

vi.mock('@tm/core', () => ({
	WorkflowOrchestrator: vi.fn().mockImplementation((context) => ({
		getCurrentPhase: vi.fn().mockReturnValue('SUBTASK_LOOP'),
		getCurrentTDDPhase: vi.fn().mockReturnValue('RED'),
		getContext: vi.fn().mockReturnValue(context),
		transition: vi.fn(),
		restoreState: vi.fn(),
		getState: vi.fn().mockReturnValue({ phase: 'SUBTASK_LOOP', context }),
		enableAutoPersist: vi.fn(),
		canResumeFromState: vi.fn().mockReturnValue(true),
		getCurrentSubtask: vi.fn().mockReturnValue({
			id: '1',
			title: 'Test Subtask',
			status: 'pending',
			attempts: 0
		}),
		getProgress: vi.fn().mockReturnValue({
			completed: 0,
			total: 3,
			current: 1,
			percentage: 0
		}),
		canProceed: vi.fn().mockReturnValue(false)
	})),
	GitAdapter: vi.fn().mockImplementation(() => ({
		ensureGitRepository: vi.fn().mockResolvedValue(undefined),
		ensureCleanWorkingTree: vi.fn().mockResolvedValue(undefined),
		createAndCheckoutBranch: vi.fn().mockResolvedValue(undefined),
		hasStagedChanges: vi.fn().mockResolvedValue(true),
		getStatus: vi.fn().mockResolvedValue({
			staged: ['file1.ts'],
			modified: ['file2.ts']
		}),
		createCommit: vi.fn().mockResolvedValue(undefined),
		getLastCommit: vi.fn().mockResolvedValue({
			hash: 'abc123def456',
			message: 'test commit'
		}),
		stageFiles: vi.fn().mockResolvedValue(undefined)
	})),
	CommitMessageGenerator: vi.fn().mockImplementation(() => ({
		generateMessage: vi.fn().mockReturnValue('feat: test commit message')
	})),
	createTaskMasterCore: vi.fn().mockResolvedValue({
		getTaskWithSubtask: vi.fn().mockResolvedValue({
			task: {
				id: '1',
				title: 'Test Task',
				subtasks: [
					{ id: '1', title: 'Subtask 1', status: 'pending' },
					{ id: '2', title: 'Subtask 2', status: 'pending' },
					{ id: '3', title: 'Subtask 3', status: 'pending' }
				],
				tag: 'test'
			}
		}),
		close: vi.fn().mockResolvedValue(undefined)
	})
}));

// Import after mocks are set up
import { Command } from 'commander';
import { AutopilotCommand } from '../../../../src/commands/autopilot/index.js';

describe('Autopilot Workflow Integration Tests', () => {
	const projectRoot = '/test/project';
	let program: Command;

	beforeEach(() => {
		mockFileSystem.clear();

		// Clear mock call history
		pathExistsFn.mockClear();
		readJSONFn.mockClear();
		writeJSONFn.mockClear();
		ensureDirFn.mockClear();
		removeFn.mockClear();

		program = new Command();
		AutopilotCommand.register(program);

		// Use exitOverride to handle Commander exits in tests
		program.exitOverride();
	});

	afterEach(() => {
		mockFileSystem.clear();
		vi.restoreAllMocks();
	});

	describe('start command', () => {
		it('should initialize workflow and create branch', async () => {
			const consoleLogSpy = vi
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			await program.parseAsync([
				'node',
				'test',
				'autopilot',
				'start',
				'1',
				'--project-root',
				projectRoot,
				'--json'
			]);

			// Verify writeJSON was called with state
			expect(writeJSONFn).toHaveBeenCalledWith(
				expect.stringContaining('workflow-state.json'),
				expect.objectContaining({
					phase: expect.any(String),
					context: expect.any(Object)
				}),
				expect.any(Object)
			);

			consoleLogSpy.mockRestore();
		});

		it('should reject invalid task ID', async () => {
			const consoleErrorSpy = vi
				.spyOn(console, 'error')
				.mockImplementation(() => {});

			await expect(
				program.parseAsync([
					'node',
					'test',
					'autopilot',
					'start',
					'invalid',
					'--project-root',
					projectRoot,
					'--json'
				])
			).rejects.toMatchObject({ exitCode: 1 });

			consoleErrorSpy.mockRestore();
		});

		it('should reject starting when workflow exists without force', async () => {
			// Create existing state
			const mockState: WorkflowState = {
				phase: 'SUBTASK_LOOP',
				context: {
					taskId: '1',
					subtasks: [],
					currentSubtaskIndex: 0,
					errors: [],
					metadata: {}
				}
			};

			mockFileSystem.set(
				`${projectRoot}/.taskmaster/workflow-state.json`,
				JSON.stringify(mockState)
			);

			const consoleErrorSpy = vi
				.spyOn(console, 'error')
				.mockImplementation(() => {});

			await expect(
				program.parseAsync([
					'node',
					'test',
					'autopilot',
					'start',
					'1',
					'--project-root',
					projectRoot,
					'--json'
				])
			).rejects.toMatchObject({ exitCode: 1 });

			consoleErrorSpy.mockRestore();
		});
	});

	describe('resume command', () => {
		beforeEach(() => {
			// Create saved state
			const mockState: WorkflowState = {
				phase: 'SUBTASK_LOOP',
				context: {
					taskId: '1',
					subtasks: [
						{
							id: '1',
							title: 'Test Subtask',
							status: 'pending',
							attempts: 0
						}
					],
					currentSubtaskIndex: 0,
					currentTDDPhase: 'RED',
					branchName: 'task-1',
					errors: [],
					metadata: {}
				}
			};

			mockFileSystem.set(
				`${projectRoot}/.taskmaster/workflow-state.json`,
				JSON.stringify(mockState)
			);
		});

		it('should restore workflow from saved state', async () => {
			const consoleLogSpy = vi
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			await program.parseAsync([
				'node',
				'test',
				'autopilot',
				'resume',
				'--project-root',
				projectRoot,
				'--json'
			]);

			expect(consoleLogSpy).toHaveBeenCalled();
			const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
			expect(output.success).toBe(true);
			expect(output.taskId).toBe('1');

			consoleLogSpy.mockRestore();
		});

		it('should error when no state exists', async () => {
			mockFileSystem.clear();

			const consoleErrorSpy = vi
				.spyOn(console, 'error')
				.mockImplementation(() => {});

			await expect(
				program.parseAsync([
					'node',
					'test',
					'autopilot',
					'resume',
					'--project-root',
					projectRoot,
					'--json'
				])
			).rejects.toMatchObject({ exitCode: 1 });

			consoleErrorSpy.mockRestore();
		});
	});

	describe('next command', () => {
		beforeEach(() => {
			const mockState: WorkflowState = {
				phase: 'SUBTASK_LOOP',
				context: {
					taskId: '1',
					subtasks: [
						{
							id: '1',
							title: 'Test Subtask',
							status: 'pending',
							attempts: 0
						}
					],
					currentSubtaskIndex: 0,
					currentTDDPhase: 'RED',
					branchName: 'task-1',
					errors: [],
					metadata: {}
				}
			};

			mockFileSystem.set(
				`${projectRoot}/.taskmaster/workflow-state.json`,
				JSON.stringify(mockState)
			);
		});

		it('should return next action in JSON format', async () => {
			const consoleLogSpy = vi
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			await program.parseAsync([
				'node',
				'test',
				'autopilot',
				'next',
				'--project-root',
				projectRoot,
				'--json'
			]);

			expect(consoleLogSpy).toHaveBeenCalled();
			const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
			expect(output.action).toBe('generate_test');
			expect(output.phase).toBe('SUBTASK_LOOP');
			expect(output.tddPhase).toBe('RED');

			consoleLogSpy.mockRestore();
		});
	});

	describe('status command', () => {
		beforeEach(() => {
			const mockState: WorkflowState = {
				phase: 'SUBTASK_LOOP',
				context: {
					taskId: '1',
					subtasks: [
						{ id: '1', title: 'Subtask 1', status: 'completed', attempts: 1 },
						{ id: '2', title: 'Subtask 2', status: 'pending', attempts: 0 },
						{ id: '3', title: 'Subtask 3', status: 'pending', attempts: 0 }
					],
					currentSubtaskIndex: 1,
					currentTDDPhase: 'RED',
					branchName: 'task-1',
					errors: [],
					metadata: {}
				}
			};

			mockFileSystem.set(
				`${projectRoot}/.taskmaster/workflow-state.json`,
				JSON.stringify(mockState)
			);
		});

		it('should display workflow progress', async () => {
			const consoleLogSpy = vi
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			await program.parseAsync([
				'node',
				'test',
				'autopilot',
				'status',
				'--project-root',
				projectRoot,
				'--json'
			]);

			expect(consoleLogSpy).toHaveBeenCalled();
			const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
			expect(output.taskId).toBe('1');
			expect(output.phase).toBe('SUBTASK_LOOP');
			expect(output.progress).toBeDefined();
			expect(output.subtasks).toHaveLength(3);

			consoleLogSpy.mockRestore();
		});
	});

	describe('complete command', () => {
		beforeEach(() => {
			const mockState: WorkflowState = {
				phase: 'SUBTASK_LOOP',
				context: {
					taskId: '1',
					subtasks: [
						{
							id: '1',
							title: 'Test Subtask',
							status: 'in-progress',
							attempts: 0
						}
					],
					currentSubtaskIndex: 0,
					currentTDDPhase: 'RED',
					branchName: 'task-1',
					errors: [],
					metadata: {}
				}
			};

			mockFileSystem.set(
				`${projectRoot}/.taskmaster/workflow-state.json`,
				JSON.stringify(mockState)
			);
		});

		it('should validate RED phase has failures', async () => {
			const consoleErrorSpy = vi
				.spyOn(console, 'error')
				.mockImplementation(() => {});

			await expect(
				program.parseAsync([
					'node',
					'test',
					'autopilot',
					'complete',
					'--project-root',
					projectRoot,
					'--results',
					'{"total":10,"passed":10,"failed":0,"skipped":0}',
					'--json'
				])
			).rejects.toMatchObject({ exitCode: 1 });

			consoleErrorSpy.mockRestore();
		});

		it('should complete RED phase with failures', async () => {
			const consoleLogSpy = vi
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			await program.parseAsync([
				'node',
				'test',
				'autopilot',
				'complete',
				'--project-root',
				projectRoot,
				'--results',
				'{"total":10,"passed":9,"failed":1,"skipped":0}',
				'--json'
			]);

			expect(consoleLogSpy).toHaveBeenCalled();
			const output = JSON.parse(consoleLogSpy.mock.calls[0][0]);
			expect(output.success).toBe(true);
			expect(output.nextPhase).toBe('GREEN');

			consoleLogSpy.mockRestore();
		});
	});

	describe('abort command', () => {
		beforeEach(() => {
			const mockState: WorkflowState = {
				phase: 'SUBTASK_LOOP',
				context: {
					taskId: '1',
					subtasks: [
						{
							id: '1',
							title: 'Test Subtask',
							status: 'pending',
							attempts: 0
						}
					],
					currentSubtaskIndex: 0,
					currentTDDPhase: 'RED',
					branchName: 'task-1',
					errors: [],
					metadata: {}
				}
			};

			mockFileSystem.set(
				`${projectRoot}/.taskmaster/workflow-state.json`,
				JSON.stringify(mockState)
			);
		});

		it('should abort workflow and delete state', async () => {
			const consoleLogSpy = vi
				.spyOn(console, 'log')
				.mockImplementation(() => {});

			await program.parseAsync([
				'node',
				'test',
				'autopilot',
				'abort',
				'--project-root',
				projectRoot,
				'--force',
				'--json'
			]);

			// Verify remove was called
			expect(removeFn).toHaveBeenCalledWith(
				expect.stringContaining('workflow-state.json')
			);

			consoleLogSpy.mockRestore();
		});
	});
});

```

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

```javascript
/**
 * Tests for the add-task.js module
 */
import { jest } from '@jest/globals';
import { hasCodebaseAnalysis } from '../../../../../scripts/modules/config-manager.js';

// Mock the dependencies before importing the module under test
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
	readJSON: jest.fn(),
	writeJSON: jest.fn(),
	log: jest.fn(),
	CONFIG: {
		model: 'mock-claude-model',
		maxTokens: 4000,
		temperature: 0.7,
		debug: false
	},
	sanitizePrompt: jest.fn((prompt) => prompt),
	truncate: jest.fn((text) => text),
	isSilentMode: jest.fn(() => false),
	findTaskById: jest.fn((tasks, id) => {
		if (!tasks) return null;
		const allTasks = [];
		const queue = [...tasks];
		while (queue.length > 0) {
			const task = queue.shift();
			allTasks.push(task);
			if (task.subtasks) {
				queue.push(...task.subtasks);
			}
		}
		return allTasks.find((task) => String(task.id) === String(id));
	}),
	getCurrentTag: jest.fn(() => 'master'),
	ensureTagMetadata: jest.fn((tagObj) => tagObj),
	flattenTasksWithSubtasks: jest.fn((tasks) => {
		const allTasks = [];
		const queue = [...(tasks || [])];
		while (queue.length > 0) {
			const task = queue.shift();
			allTasks.push(task);
			if (task.subtasks) {
				for (const subtask of task.subtasks) {
					queue.push({ ...subtask, id: `${task.id}.${subtask.id}` });
				}
			}
		}
		return allTasks;
	}),
	markMigrationForNotice: jest.fn(),
	performCompleteTagMigration: jest.fn(),
	setTasksForTag: jest.fn(),
	getTasksForTag: jest.fn((data, tag) => data[tag]?.tasks || [])
}));

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

jest.unstable_mockModule(
	'../../../../../scripts/modules/ai-services-unified.js',
	() => ({
		generateObjectService: jest.fn().mockResolvedValue({
			mainResult: {
				object: {
					title: 'Task from prompt: Create a new authentication system',
					description:
						'Task generated from: Create a new authentication system',
					details:
						'Implementation details for task generated from prompt: Create a new authentication system',
					testStrategy: 'Write unit tests to verify functionality',
					dependencies: []
				}
			},
			telemetryData: {
				timestamp: new Date().toISOString(),
				userId: '1234567890',
				commandName: 'add-task',
				modelUsed: 'claude-3-5-sonnet',
				providerName: 'anthropic',
				inputTokens: 1000,
				outputTokens: 500,
				totalTokens: 1500,
				totalCost: 0.012414,
				currency: 'USD'
			}
		})
	})
);

jest.unstable_mockModule(
	'../../../../../scripts/modules/config-manager.js',
	() => ({
		getDefaultPriority: jest.fn(() => 'medium'),
		hasCodebaseAnalysis: jest.fn(() => false)
	})
);

jest.unstable_mockModule(
	'../../../../../scripts/modules/utils/contextGatherer.js',
	() => ({
		default: jest.fn().mockImplementation(() => ({
			gather: jest.fn().mockResolvedValue({
				contextSummary: 'Mock context summary',
				allRelatedTaskIds: [],
				graphVisualization: 'Mock graph'
			})
		}))
	})
);

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

jest.unstable_mockModule(
	'../../../../../scripts/modules/prompt-manager.js',
	() => ({
		getPromptManager: jest.fn().mockReturnValue({
			loadPrompt: jest.fn().mockResolvedValue({
				systemPrompt: 'Mocked system prompt',
				userPrompt: 'Mocked user prompt'
			})
		})
	})
);

// Mock external UI libraries
jest.unstable_mockModule('chalk', () => ({
	default: {
		white: { bold: jest.fn((text) => text) },
		cyan: Object.assign(
			jest.fn((text) => text),
			{
				bold: jest.fn((text) => text)
			}
		),
		green: jest.fn((text) => text),
		yellow: jest.fn((text) => text),
		bold: jest.fn((text) => text)
	}
}));

jest.unstable_mockModule('boxen', () => ({
	default: jest.fn((text) => text)
}));

jest.unstable_mockModule('cli-table3', () => ({
	default: jest.fn().mockImplementation(() => ({
		push: jest.fn(),
		toString: jest.fn(() => 'mocked table')
	}))
}));

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

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

const generateTaskFiles = (
	await import(
		'../../../../../scripts/modules/task-manager/generate-task-files.js'
	)
).default;

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

describe('addTask', () => {
	const sampleTasks = {
		master: {
			tasks: [
				{
					id: 1,
					title: 'Task 1',
					description: 'First task',
					status: 'pending',
					dependencies: []
				},
				{
					id: 2,
					title: 'Task 2',
					description: 'Second task',
					status: 'pending',
					dependencies: []
				},
				{
					id: 3,
					title: 'Task 3',
					description: 'Third task',
					status: 'pending',
					dependencies: [1]
				}
			]
		}
	};

	// Create a helper function for consistent mcpLog mock
	const createMcpLogMock = () => ({
		info: jest.fn(),
		warn: jest.fn(),
		error: jest.fn(),
		debug: jest.fn(),
		success: jest.fn()
	});

	beforeEach(() => {
		jest.clearAllMocks();
		readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks)));

		// Mock console.log to avoid output during tests
		jest.spyOn(console, 'log').mockImplementation(() => {});
	});

	afterEach(() => {
		console.log.mockRestore();
	});

	test('should add a new task using AI', async () => {
		// Arrange
		const prompt = 'Create a new authentication system';
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act
		const result = await addTask(
			'tasks/tasks.json',
			prompt,
			[],
			'medium',
			context,
			'json'
		);

		// Assert
		expect(readJSON).toHaveBeenCalledWith(
			'tasks/tasks.json',
			'/mock/project/root',
			'master'
		);
		expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
		expect(writeJSON).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.objectContaining({
				master: expect.objectContaining({
					tasks: expect.arrayContaining([
						expect.objectContaining({
							id: 4, // Next ID after existing tasks
							title: expect.stringContaining(
								'Create a new authentication system'
							),
							status: 'pending'
						})
					])
				})
			}),
			'/mock/project/root', // projectRoot parameter
			'master' // tag parameter
		);
		expect(result).toEqual(
			expect.objectContaining({
				newTaskId: 4,
				telemetryData: expect.any(Object)
			})
		);
	});

	test('should validate dependencies when adding a task', async () => {
		// Arrange
		const prompt = 'Create a new authentication system';
		const validDependencies = [1, 2]; // These exist in sampleTasks
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act
		const result = await addTask(
			'tasks/tasks.json',
			prompt,
			validDependencies,
			'medium',
			context,
			'json'
		);

		// Assert
		expect(writeJSON).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.objectContaining({
				master: expect.objectContaining({
					tasks: expect.arrayContaining([
						expect.objectContaining({
							id: 4,
							dependencies: validDependencies
						})
					])
				})
			}),
			'/mock/project/root', // projectRoot parameter
			'master' // tag parameter
		);
	});

	test('should filter out invalid dependencies', async () => {
		// Arrange
		const prompt = 'Create a new authentication system';
		const invalidDependencies = [999]; // Non-existent task ID
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act
		const result = await addTask(
			'tasks/tasks.json',
			prompt,
			invalidDependencies,
			'medium',
			context,
			'json'
		);

		// Assert
		expect(writeJSON).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.objectContaining({
				master: expect.objectContaining({
					tasks: expect.arrayContaining([
						expect.objectContaining({
							id: 4,
							dependencies: [] // Invalid dependencies should be filtered out
						})
					])
				})
			}),
			'/mock/project/root', // projectRoot parameter
			'master' // tag parameter
		);
		expect(context.mcpLog.warn).toHaveBeenCalledWith(
			expect.stringContaining(
				'The following dependencies do not exist or are invalid: 999'
			)
		);
	});

	test('should use specified priority', async () => {
		// Arrange
		const prompt = 'Create a new authentication system';
		const priority = 'high';
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act
		await addTask('tasks/tasks.json', prompt, [], priority, context, 'json');

		// Assert
		expect(writeJSON).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.objectContaining({
				master: expect.objectContaining({
					tasks: expect.arrayContaining([
						expect.objectContaining({
							priority: priority
						})
					])
				})
			}),
			'/mock/project/root', // projectRoot parameter
			'master' // tag parameter
		);
	});

	test('should handle empty tasks file', async () => {
		// Arrange
		readJSON.mockReturnValue({ master: { tasks: [] } });
		const prompt = 'Create a new authentication system';
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act
		const result = await addTask(
			'tasks/tasks.json',
			prompt,
			[],
			'medium',
			context,
			'json'
		);

		// Assert
		expect(result.newTaskId).toBe(1); // First task should have ID 1
		expect(writeJSON).toHaveBeenCalledWith(
			'tasks/tasks.json',
			expect.objectContaining({
				master: expect.objectContaining({
					tasks: expect.arrayContaining([
						expect.objectContaining({
							id: 1
						})
					])
				})
			}),
			'/mock/project/root', // projectRoot parameter
			'master' // tag parameter
		);
	});

	test('should handle missing tasks file', async () => {
		// Arrange
		readJSON.mockReturnValue(null);
		const prompt = 'Create a new authentication system';
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act
		const result = await addTask(
			'tasks/tasks.json',
			prompt,
			[],
			'medium',
			context,
			'json'
		);

		// Assert
		expect(result.newTaskId).toBe(1); // First task should have ID 1
		expect(writeJSON).toHaveBeenCalledTimes(1); // Should create file and add task in one go.
	});

	test('should handle AI service errors', async () => {
		// Arrange
		generateObjectService.mockRejectedValueOnce(new Error('AI service failed'));
		const prompt = 'Create a new authentication system';
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act & Assert
		await expect(
			addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json')
		).rejects.toThrow('AI service failed');
	});

	test('should handle file read errors', async () => {
		// Arrange
		readJSON.mockImplementation(() => {
			throw new Error('File read failed');
		});
		const prompt = 'Create a new authentication system';
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act & Assert
		await expect(
			addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json')
		).rejects.toThrow('File read failed');
	});

	test('should handle file write errors', async () => {
		// Arrange
		writeJSON.mockImplementation(() => {
			throw new Error('File write failed');
		});
		const prompt = 'Create a new authentication system';
		const context = {
			mcpLog: createMcpLogMock(),
			projectRoot: '/mock/project/root',
			tag: 'master'
		};

		// Act & Assert
		await expect(
			addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json')
		).rejects.toThrow('File write failed');
	});
});

```

--------------------------------------------------------------------------------
/tests/unit/scripts/modules/dependency-manager/cross-tag-dependencies.test.js:
--------------------------------------------------------------------------------

```javascript
import { jest } from '@jest/globals';
import {
	validateCrossTagMove,
	findCrossTagDependencies,
	getDependentTaskIds,
	validateSubtaskMove,
	canMoveWithDependencies
} from '../../../../../scripts/modules/dependency-manager.js';

describe('Cross-Tag Dependency Validation', () => {
	describe('validateCrossTagMove', () => {
		const mockAllTasks = [
			{ id: 1, tag: 'backlog', dependencies: [2], title: 'Task 1' },
			{ id: 2, tag: 'backlog', dependencies: [], title: 'Task 2' },
			{ id: 3, tag: 'in-progress', dependencies: [1], title: 'Task 3' },
			{ id: 4, tag: 'done', dependencies: [], title: 'Task 4' }
		];

		it('should allow move when no dependencies exist', () => {
			const task = { id: 2, dependencies: [], title: 'Task 2' };
			const result = validateCrossTagMove(
				task,
				'backlog',
				'in-progress',
				mockAllTasks
			);

			expect(result.canMove).toBe(true);
			expect(result.conflicts).toHaveLength(0);
		});

		it('should block move when cross-tag dependencies exist', () => {
			const task = { id: 1, dependencies: [2], title: 'Task 1' };
			const result = validateCrossTagMove(
				task,
				'backlog',
				'in-progress',
				mockAllTasks
			);

			expect(result.canMove).toBe(false);
			expect(result.conflicts).toHaveLength(1);
			expect(result.conflicts[0]).toMatchObject({
				taskId: 1,
				dependencyId: 2,
				dependencyTag: 'backlog'
			});
		});

		it('should allow move when dependencies are in target tag', () => {
			const task = { id: 3, dependencies: [1], title: 'Task 3' };
			// Move both task 1 and task 3 to in-progress, then move task 1 to done
			const updatedTasks = mockAllTasks.map((t) => {
				if (t.id === 1) return { ...t, tag: 'in-progress' };
				if (t.id === 3) return { ...t, tag: 'in-progress' };
				return t;
			});
			// Now move task 1 to done
			const updatedTasks2 = updatedTasks.map((t) =>
				t.id === 1 ? { ...t, tag: 'done' } : t
			);
			const result = validateCrossTagMove(
				task,
				'in-progress',
				'done',
				updatedTasks2
			);

			expect(result.canMove).toBe(true);
			expect(result.conflicts).toHaveLength(0);
		});

		it('should handle multiple dependencies correctly', () => {
			const task = { id: 5, dependencies: [1, 3], title: 'Task 5' };
			const result = validateCrossTagMove(
				task,
				'backlog',
				'done',
				mockAllTasks
			);

			expect(result.canMove).toBe(false);
			expect(result.conflicts).toHaveLength(2);
			expect(result.conflicts[0].dependencyId).toBe(1);
			expect(result.conflicts[1].dependencyId).toBe(3);
		});

		it('should throw error for invalid task parameter', () => {
			expect(() =>
				validateCrossTagMove(null, 'backlog', 'in-progress', mockAllTasks)
			).toThrow('Task parameter must be a valid object');
		});

		it('should throw error for invalid source tag', () => {
			const task = { id: 1, dependencies: [], title: 'Task 1' };
			expect(() =>
				validateCrossTagMove(task, '', 'in-progress', mockAllTasks)
			).toThrow('Source tag must be a valid string');
		});

		it('should throw error for invalid target tag', () => {
			const task = { id: 1, dependencies: [], title: 'Task 1' };
			expect(() =>
				validateCrossTagMove(task, 'backlog', null, mockAllTasks)
			).toThrow('Target tag must be a valid string');
		});

		it('should throw error for invalid allTasks parameter', () => {
			const task = { id: 1, dependencies: [], title: 'Task 1' };
			expect(() =>
				validateCrossTagMove(task, 'backlog', 'in-progress', 'not-an-array')
			).toThrow('All tasks parameter must be an array');
		});
	});

	describe('findCrossTagDependencies', () => {
		const mockAllTasks = [
			{ id: 1, tag: 'backlog', dependencies: [2], title: 'Task 1' },
			{ id: 2, tag: 'backlog', dependencies: [], title: 'Task 2' },
			{ id: 3, tag: 'in-progress', dependencies: [1], title: 'Task 3' },
			{ id: 4, tag: 'done', dependencies: [], title: 'Task 4' }
		];

		it('should find cross-tag dependencies for multiple tasks', () => {
			const sourceTasks = [
				{ id: 1, dependencies: [2], title: 'Task 1' },
				{ id: 3, dependencies: [1], title: 'Task 3' }
			];
			const conflicts = findCrossTagDependencies(
				sourceTasks,
				'backlog',
				'done',
				mockAllTasks
			);

			expect(conflicts).toHaveLength(2);
			expect(conflicts[0].taskId).toBe(1);
			expect(conflicts[0].dependencyId).toBe(2);
			expect(conflicts[1].taskId).toBe(3);
			expect(conflicts[1].dependencyId).toBe(1);
		});

		it('should return empty array when no cross-tag dependencies exist', () => {
			const sourceTasks = [
				{ id: 2, dependencies: [], title: 'Task 2' },
				{ id: 4, dependencies: [], title: 'Task 4' }
			];
			const conflicts = findCrossTagDependencies(
				sourceTasks,
				'backlog',
				'done',
				mockAllTasks
			);

			expect(conflicts).toHaveLength(0);
		});

		it('should handle tasks without dependencies', () => {
			const sourceTasks = [{ id: 2, dependencies: [], title: 'Task 2' }];
			const conflicts = findCrossTagDependencies(
				sourceTasks,
				'backlog',
				'done',
				mockAllTasks
			);

			expect(conflicts).toHaveLength(0);
		});

		it('should throw error for invalid sourceTasks parameter', () => {
			expect(() =>
				findCrossTagDependencies(
					'not-an-array',
					'backlog',
					'done',
					mockAllTasks
				)
			).toThrow('Source tasks parameter must be an array');
		});

		it('should throw error for invalid source tag', () => {
			const sourceTasks = [{ id: 1, dependencies: [], title: 'Task 1' }];
			expect(() =>
				findCrossTagDependencies(sourceTasks, '', 'done', mockAllTasks)
			).toThrow('Source tag must be a valid string');
		});

		it('should throw error for invalid target tag', () => {
			const sourceTasks = [{ id: 1, dependencies: [], title: 'Task 1' }];
			expect(() =>
				findCrossTagDependencies(sourceTasks, 'backlog', null, mockAllTasks)
			).toThrow('Target tag must be a valid string');
		});

		it('should throw error for invalid allTasks parameter', () => {
			const sourceTasks = [{ id: 1, dependencies: [], title: 'Task 1' }];
			expect(() =>
				findCrossTagDependencies(sourceTasks, 'backlog', 'done', 'not-an-array')
			).toThrow('All tasks parameter must be an array');
		});
	});

	describe('getDependentTaskIds', () => {
		const mockAllTasks = [
			{ id: 1, tag: 'backlog', dependencies: [2], title: 'Task 1' },
			{ id: 2, tag: 'backlog', dependencies: [], title: 'Task 2' },
			{ id: 3, tag: 'in-progress', dependencies: [1], title: 'Task 3' },
			{ id: 4, tag: 'done', dependencies: [], title: 'Task 4' }
		];

		it('should return dependent task IDs', () => {
			const sourceTasks = [{ id: 1, dependencies: [2], title: 'Task 1' }];
			const crossTagDependencies = [
				{ taskId: 1, dependencyId: 2, dependencyTag: 'backlog' }
			];
			const dependentIds = getDependentTaskIds(
				sourceTasks,
				crossTagDependencies,
				mockAllTasks
			);

			expect(dependentIds).toContain(2);
			// The function also finds tasks that depend on the source task, so we expect more than just the dependency
			expect(dependentIds.length).toBeGreaterThan(0);
		});

		it('should handle multiple dependencies with recursive resolution', () => {
			const sourceTasks = [{ id: 5, dependencies: [1, 3], title: 'Task 5' }];
			const crossTagDependencies = [
				{ taskId: 5, dependencyId: 1, dependencyTag: 'backlog' },
				{ taskId: 5, dependencyId: 3, dependencyTag: 'in-progress' }
			];
			const dependentIds = getDependentTaskIds(
				sourceTasks,
				crossTagDependencies,
				mockAllTasks
			);

			// Should find all dependencies recursively:
			// Task 5 → [1, 3], Task 1 → [2], so total is [1, 2, 3]
			expect(dependentIds).toContain(1);
			expect(dependentIds).toContain(2); // Task 1's dependency
			expect(dependentIds).toContain(3);
			expect(dependentIds).toHaveLength(3);
		});

		it('should return empty array when no dependencies', () => {
			const sourceTasks = [{ id: 2, dependencies: [], title: 'Task 2' }];
			const crossTagDependencies = [];
			const dependentIds = getDependentTaskIds(
				sourceTasks,
				crossTagDependencies,
				mockAllTasks
			);

			// The function finds tasks that depend on source tasks, so even with no cross-tag dependencies,
			// it might find tasks that depend on the source task
			expect(Array.isArray(dependentIds)).toBe(true);
		});

		it('should throw error for invalid sourceTasks parameter', () => {
			const crossTagDependencies = [];
			expect(() =>
				getDependentTaskIds('not-an-array', crossTagDependencies, mockAllTasks)
			).toThrow('Source tasks parameter must be an array');
		});

		it('should throw error for invalid crossTagDependencies parameter', () => {
			const sourceTasks = [{ id: 1, dependencies: [], title: 'Task 1' }];
			expect(() =>
				getDependentTaskIds(sourceTasks, 'not-an-array', mockAllTasks)
			).toThrow('Cross tag dependencies parameter must be an array');
		});

		it('should throw error for invalid allTasks parameter', () => {
			const sourceTasks = [{ id: 1, dependencies: [], title: 'Task 1' }];
			const crossTagDependencies = [];
			expect(() =>
				getDependentTaskIds(sourceTasks, crossTagDependencies, 'not-an-array')
			).toThrow('All tasks parameter must be an array');
		});
	});

	describe('validateSubtaskMove', () => {
		it('should throw error for subtask movement', () => {
			expect(() =>
				validateSubtaskMove('1.2', 'backlog', 'in-progress')
			).toThrow('Cannot move subtask 1.2 directly between tags');
		});

		it('should allow regular task movement', () => {
			expect(() =>
				validateSubtaskMove('1', 'backlog', 'in-progress')
			).not.toThrow();
		});

		it('should throw error for invalid taskId parameter', () => {
			expect(() => validateSubtaskMove(null, 'backlog', 'in-progress')).toThrow(
				'Task ID must be a valid string'
			);
		});

		it('should throw error for invalid source tag', () => {
			expect(() => validateSubtaskMove('1', '', 'in-progress')).toThrow(
				'Source tag must be a valid string'
			);
		});

		it('should throw error for invalid target tag', () => {
			expect(() => validateSubtaskMove('1', 'backlog', null)).toThrow(
				'Target tag must be a valid string'
			);
		});
	});

	describe('canMoveWithDependencies', () => {
		const mockAllTasks = [
			{ id: 1, tag: 'backlog', dependencies: [2], title: 'Task 1' },
			{ id: 2, tag: 'backlog', dependencies: [], title: 'Task 2' },
			{ id: 3, tag: 'in-progress', dependencies: [1], title: 'Task 3' },
			{ id: 4, tag: 'done', dependencies: [], title: 'Task 4' }
		];

		it('should return canMove: true when no conflicts exist', () => {
			const result = canMoveWithDependencies(
				'2',
				'backlog',
				'in-progress',
				mockAllTasks
			);

			expect(result.canMove).toBe(true);
			expect(result.dependentTaskIds).toHaveLength(0);
			expect(result.conflicts).toHaveLength(0);
		});

		it('should return canMove: false when conflicts exist', () => {
			const result = canMoveWithDependencies(
				'1',
				'backlog',
				'in-progress',
				mockAllTasks
			);

			expect(result.canMove).toBe(false);
			expect(result.dependentTaskIds).toContain(2);
			expect(result.conflicts).toHaveLength(1);
		});

		it('should return canMove: false when task not found', () => {
			const result = canMoveWithDependencies(
				'999',
				'backlog',
				'in-progress',
				mockAllTasks
			);

			expect(result.canMove).toBe(false);
			expect(result.error).toBe('Task not found');
		});

		it('should handle string task IDs', () => {
			const result = canMoveWithDependencies(
				'2',
				'backlog',
				'in-progress',
				mockAllTasks
			);

			expect(result.canMove).toBe(true);
		});

		it('should throw error for invalid taskId parameter', () => {
			expect(() =>
				canMoveWithDependencies(null, 'backlog', 'in-progress', mockAllTasks)
			).toThrow('Task ID must be a valid string');
		});

		it('should throw error for invalid source tag', () => {
			expect(() =>
				canMoveWithDependencies('1', '', 'in-progress', mockAllTasks)
			).toThrow('Source tag must be a valid string');
		});

		it('should throw error for invalid target tag', () => {
			expect(() =>
				canMoveWithDependencies('1', 'backlog', null, mockAllTasks)
			).toThrow('Target tag must be a valid string');
		});

		it('should throw error for invalid allTasks parameter', () => {
			expect(() =>
				canMoveWithDependencies('1', 'backlog', 'in-progress', 'not-an-array')
			).toThrow('All tasks parameter must be an array');
		});
	});
});

```
Page 24/50FirstPrevNextLast