#
tokens: 48351/50000 12/975 files (page 23/50)
lines: off (toggle) GitHub
raw markdown copy
This is page 23 of 50. Use http://codebase.md/eyaltoledano/claude-task-master?lines=false&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/unit/init.test.js:
--------------------------------------------------------------------------------

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

// Mock external modules
jest.mock('child_process', () => ({
	execSync: jest.fn()
}));

jest.mock('readline', () => ({
	createInterface: jest.fn(() => ({
		question: jest.fn(),
		close: jest.fn()
	}))
}));

// Mock figlet for banner display
jest.mock('figlet', () => ({
	default: {
		textSync: jest.fn(() => 'Task Master')
	}
}));

// Mock console methods
jest.mock('console', () => ({
	log: jest.fn(),
	info: jest.fn(),
	warn: jest.fn(),
	error: jest.fn(),
	clear: jest.fn()
}));

describe('Windsurf Rules File Handling', () => {
	let tempDir;

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

		// Create a temporary directory for testing
		tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));

		// Spy on fs methods
		jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
		jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
			if (filePath.toString().includes('.windsurfrules')) {
				return 'Existing windsurf rules content';
			}
			return '{}';
		});
		jest.spyOn(fs, 'existsSync').mockImplementation((filePath) => {
			// Mock specific file existence checks
			if (filePath.toString().includes('package.json')) {
				return true;
			}
			return false;
		});
		jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
		jest.spyOn(fs, 'copyFileSync').mockImplementation(() => {});
	});

	afterEach(() => {
		// Clean up the temporary directory
		try {
			fs.rmSync(tempDir, { recursive: true, force: true });
		} catch (err) {
			console.error(`Error cleaning up: ${err.message}`);
		}
	});

	// Test function that simulates the behavior of .windsurfrules handling
	function mockCopyTemplateFile(templateName, targetPath) {
		if (templateName === 'windsurfrules') {
			const filename = path.basename(targetPath);

			if (filename === '.windsurfrules') {
				if (fs.existsSync(targetPath)) {
					// Should append content when file exists
					const existingContent = fs.readFileSync(targetPath, 'utf8');
					const updatedContent =
						existingContent.trim() +
						'\n\n# Added by Claude Task Master - Development Workflow Rules\n\n' +
						'New content';
					fs.writeFileSync(targetPath, updatedContent);
					return;
				}
			}

			// If file doesn't exist, create it normally
			fs.writeFileSync(targetPath, 'New content');
		}
	}

	test('creates .windsurfrules when it does not exist', () => {
		// Arrange
		const targetPath = path.join(tempDir, '.windsurfrules');

		// Act
		mockCopyTemplateFile('windsurfrules', targetPath);

		// Assert
		expect(fs.writeFileSync).toHaveBeenCalledWith(targetPath, 'New content');
	});

	test('appends content to existing .windsurfrules', () => {
		// Arrange
		const targetPath = path.join(tempDir, '.windsurfrules');
		const existingContent = 'Existing windsurf rules content';

		// Override the existsSync mock just for this test
		fs.existsSync.mockReturnValueOnce(true); // Target file exists
		fs.readFileSync.mockReturnValueOnce(existingContent);

		// Act
		mockCopyTemplateFile('windsurfrules', targetPath);

		// Assert
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			targetPath,
			expect.stringContaining(existingContent)
		);
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			targetPath,
			expect.stringContaining('Added by Claude Task Master')
		);
	});

	test('includes .windsurfrules in project structure creation', () => {
		// This test verifies the expected behavior by using a mock implementation
		// that represents how createProjectStructure should work

		// Mock implementation of createProjectStructure
		function mockCreateProjectStructure(projectName) {
			// Copy template files including .windsurfrules
			mockCopyTemplateFile(
				'windsurfrules',
				path.join(tempDir, '.windsurfrules')
			);
		}

		// Act - call our mock implementation
		mockCreateProjectStructure('test-project');

		// Assert - verify that .windsurfrules was created
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			path.join(tempDir, '.windsurfrules'),
			expect.any(String)
		);
	});
});

// New test suite for MCP Configuration Handling
describe('MCP Configuration Handling', () => {
	let tempDir;

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

		// Create a temporary directory for testing
		tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));

		// Spy on fs methods
		jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
		jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
			if (filePath.toString().includes('mcp.json')) {
				return JSON.stringify({
					mcpServers: {
						'existing-server': {
							command: 'node',
							args: ['server.js']
						}
					}
				});
			}
			return '{}';
		});
		jest.spyOn(fs, 'existsSync').mockImplementation((filePath) => {
			// Return true for specific paths to test different scenarios
			if (filePath.toString().includes('package.json')) {
				return true;
			}
			// Default to false for other paths
			return false;
		});
		jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
		jest.spyOn(fs, 'copyFileSync').mockImplementation(() => {});
	});

	afterEach(() => {
		// Clean up the temporary directory
		try {
			fs.rmSync(tempDir, { recursive: true, force: true });
		} catch (err) {
			console.error(`Error cleaning up: ${err.message}`);
		}
	});

	// Test function that simulates the behavior of setupMCPConfiguration
	function mockSetupMCPConfiguration(targetDir, projectName) {
		const mcpDirPath = path.join(targetDir, '.cursor');
		const mcpJsonPath = path.join(mcpDirPath, 'mcp.json');

		// Create .cursor directory if it doesn't exist
		if (!fs.existsSync(mcpDirPath)) {
			fs.mkdirSync(mcpDirPath, { recursive: true });
		}

		// New MCP config to be added - references the installed package
		const newMCPServer = {
			'task-master-ai': {
				command: 'npx',
				args: ['task-master-ai', 'mcp-server']
			}
		};

		// Check if mcp.json already exists
		if (fs.existsSync(mcpJsonPath)) {
			try {
				// Read existing config
				const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));

				// Initialize mcpServers if it doesn't exist
				if (!mcpConfig.mcpServers) {
					mcpConfig.mcpServers = {};
				}

				// Add the taskmaster-ai server if it doesn't exist
				if (!mcpConfig.mcpServers['task-master-ai']) {
					mcpConfig.mcpServers['task-master-ai'] =
						newMCPServer['task-master-ai'];
				}

				// Write the updated configuration
				fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 4));
			} catch (error) {
				// Create new configuration on error
				const newMCPConfig = {
					mcpServers: newMCPServer
				};

				fs.writeFileSync(mcpJsonPath, JSON.stringify(newMCPConfig, null, 4));
			}
		} else {
			// If mcp.json doesn't exist, create it
			const newMCPConfig = {
				mcpServers: newMCPServer
			};

			fs.writeFileSync(mcpJsonPath, JSON.stringify(newMCPConfig, null, 4));
		}
	}

	test('creates mcp.json when it does not exist', () => {
		// Arrange
		const mcpJsonPath = path.join(tempDir, '.cursor', 'mcp.json');

		// Act
		mockSetupMCPConfiguration(tempDir, 'test-project');

		// Assert
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			mcpJsonPath,
			expect.stringContaining('task-master-ai')
		);

		// Should create a proper structure with mcpServers key
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			mcpJsonPath,
			expect.stringContaining('mcpServers')
		);

		// Should reference npx command
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			mcpJsonPath,
			expect.stringContaining('npx')
		);
	});

	test('updates existing mcp.json by adding new server', () => {
		// Arrange
		const mcpJsonPath = path.join(tempDir, '.cursor', 'mcp.json');

		// Override the existsSync mock to simulate mcp.json exists
		fs.existsSync.mockImplementation((filePath) => {
			if (filePath.toString().includes('mcp.json')) {
				return true;
			}
			return false;
		});

		// Act
		mockSetupMCPConfiguration(tempDir, 'test-project');

		// Assert
		// Should preserve existing server
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			mcpJsonPath,
			expect.stringContaining('existing-server')
		);

		// Should add our new server
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			mcpJsonPath,
			expect.stringContaining('task-master-ai')
		);
	});

	test('handles JSON parsing errors by creating new mcp.json', () => {
		// Arrange
		const mcpJsonPath = path.join(tempDir, '.cursor', 'mcp.json');

		// Override existsSync to say mcp.json exists
		fs.existsSync.mockImplementation((filePath) => {
			if (filePath.toString().includes('mcp.json')) {
				return true;
			}
			return false;
		});

		// But make readFileSync return invalid JSON
		fs.readFileSync.mockImplementation((filePath) => {
			if (filePath.toString().includes('mcp.json')) {
				return '{invalid json';
			}
			return '{}';
		});

		// Act
		mockSetupMCPConfiguration(tempDir, 'test-project');

		// Assert
		// Should create a new valid JSON file with our server
		expect(fs.writeFileSync).toHaveBeenCalledWith(
			mcpJsonPath,
			expect.stringContaining('task-master-ai')
		);
	});

	test('does not modify existing server configuration if it already exists', () => {
		// Arrange
		const mcpJsonPath = path.join(tempDir, '.cursor', 'mcp.json');

		// Override existsSync to say mcp.json exists
		fs.existsSync.mockImplementation((filePath) => {
			if (filePath.toString().includes('mcp.json')) {
				return true;
			}
			return false;
		});

		// Return JSON that already has task-master-ai
		fs.readFileSync.mockImplementation((filePath) => {
			if (filePath.toString().includes('mcp.json')) {
				return JSON.stringify({
					mcpServers: {
						'existing-server': {
							command: 'node',
							args: ['server.js']
						},
						'task-master-ai': {
							command: 'custom',
							args: ['custom-args']
						}
					}
				});
			}
			return '{}';
		});

		// Spy to check what's written
		const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync');

		// Act
		mockSetupMCPConfiguration(tempDir, 'test-project');

		// Assert
		// Verify the written data contains the original taskmaster configuration
		const dataWritten = JSON.parse(writeFileSyncSpy.mock.calls[0][1]);
		expect(dataWritten.mcpServers['task-master-ai'].command).toBe('custom');
		expect(dataWritten.mcpServers['task-master-ai'].args).toContain(
			'custom-args'
		);
	});

	test('creates the .cursor directory if it doesnt exist', () => {
		// Arrange
		const cursorDirPath = path.join(tempDir, '.cursor');

		// Make sure it looks like the directory doesn't exist
		fs.existsSync.mockReturnValue(false);

		// Act
		mockSetupMCPConfiguration(tempDir, 'test-project');

		// Assert
		expect(fs.mkdirSync).toHaveBeenCalledWith(cursorDirPath, {
			recursive: true
		});
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/tests/integration/list-tasks.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview End-to-end integration test for listTasks functionality
 */

import { promises as fs } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
	type Task,
	type TaskMasterCore,
	type TaskStatus,
	createTaskMasterCore
} from '../../src/index';

describe('TaskMasterCore - listTasks E2E', () => {
	let tmpDir: string;
	let tmCore: TaskMasterCore;

	// Sample tasks data
	const sampleTasks: Task[] = [
		{
			id: '1',
			title: 'Setup project',
			description: 'Initialize the project structure',
			status: 'done',
			priority: 'high',
			dependencies: [],
			details: 'Create all necessary directories and config files',
			testStrategy: 'Manual verification',
			subtasks: [
				{
					id: 1,
					parentId: '1',
					title: 'Create directories',
					description: 'Create project directories',
					status: 'done',
					priority: 'high',
					dependencies: [],
					details: 'Create src, tests, docs directories',
					testStrategy: 'Check directories exist'
				},
				{
					id: 2,
					parentId: '1',
					title: 'Initialize package.json',
					description: 'Create package.json file',
					status: 'done',
					priority: 'high',
					dependencies: [],
					details: 'Run npm init',
					testStrategy: 'Verify package.json exists'
				}
			],
			tags: ['setup', 'infrastructure']
		},
		{
			id: '2',
			title: 'Implement core features',
			description: 'Build the main functionality',
			status: 'in-progress',
			priority: 'high',
			dependencies: ['1'],
			details: 'Implement all core business logic',
			testStrategy: 'Unit tests for all features',
			subtasks: [],
			tags: ['feature', 'core'],
			assignee: 'developer1'
		},
		{
			id: '3',
			title: 'Write documentation',
			description: 'Create user and developer docs',
			status: 'pending',
			priority: 'medium',
			dependencies: ['2'],
			details: 'Write comprehensive documentation',
			testStrategy: 'Review by team',
			subtasks: [],
			tags: ['documentation'],
			complexity: 'simple'
		},
		{
			id: '4',
			title: 'Performance optimization',
			description: 'Optimize for speed and efficiency',
			status: 'blocked',
			priority: 'low',
			dependencies: ['2'],
			details: 'Profile and optimize bottlenecks',
			testStrategy: 'Performance benchmarks',
			subtasks: [],
			assignee: 'developer2',
			complexity: 'complex'
		},
		{
			id: '5',
			title: 'Security audit',
			description: 'Review security vulnerabilities',
			status: 'deferred',
			priority: 'critical',
			dependencies: [],
			details: 'Complete security assessment',
			testStrategy: 'Security scanning tools',
			subtasks: [],
			tags: ['security', 'audit']
		}
	];

	beforeEach(async () => {
		// Create temp directory for testing
		tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'tm-core-test-'));

		// Create .taskmaster/tasks directory
		const tasksDir = path.join(tmpDir, '.taskmaster', 'tasks');
		await fs.mkdir(tasksDir, { recursive: true });

		// Write sample tasks.json
		const tasksFile = path.join(tasksDir, 'tasks.json');
		const tasksData = {
			tasks: sampleTasks,
			metadata: {
				version: '1.0.0',
				lastModified: new Date().toISOString(),
				taskCount: sampleTasks.length,
				completedCount: 1
			}
		};
		await fs.writeFile(tasksFile, JSON.stringify(tasksData, null, 2));

		// Create TaskMasterCore instance
		tmCore = createTaskMasterCore(tmpDir);
		await tmCore.initialize();
	});

	afterEach(async () => {
		// Cleanup
		if (tmCore) {
			await tmCore.close();
		}

		// Remove temp directory
		await fs.rm(tmpDir, { recursive: true, force: true });
	});

	describe('Basic listing', () => {
		it('should list all tasks', async () => {
			const result = await tmCore.listTasks();

			expect(result.tasks).toHaveLength(5);
			expect(result.total).toBe(5);
			expect(result.filtered).toBe(5);
			expect(result.tag).toBeUndefined();
		});

		it('should include subtasks by default', async () => {
			const result = await tmCore.listTasks();
			const setupTask = result.tasks.find((t) => t.id === '1');

			expect(setupTask?.subtasks).toHaveLength(2);
			expect(setupTask?.subtasks[0].title).toBe('Create directories');
		});

		it('should exclude subtasks when requested', async () => {
			const result = await tmCore.listTasks({ includeSubtasks: false });
			const setupTask = result.tasks.find((t) => t.id === '1');

			expect(setupTask?.subtasks).toHaveLength(0);
		});
	});

	describe('Filtering', () => {
		it('should filter by status', async () => {
			const result = await tmCore.listTasks({
				filter: { status: 'done' }
			});

			expect(result.filtered).toBe(1);
			expect(result.tasks[0].id).toBe('1');
		});

		it('should filter by multiple statuses', async () => {
			const result = await tmCore.listTasks({
				filter: { status: ['done', 'in-progress'] }
			});

			expect(result.filtered).toBe(2);
			const ids = result.tasks.map((t) => t.id);
			expect(ids).toContain('1');
			expect(ids).toContain('2');
		});

		it('should filter by priority', async () => {
			const result = await tmCore.listTasks({
				filter: { priority: 'high' }
			});

			expect(result.filtered).toBe(2);
		});

		it('should filter by tags', async () => {
			const result = await tmCore.listTasks({
				filter: { tags: ['setup'] }
			});

			expect(result.filtered).toBe(1);
			expect(result.tasks[0].id).toBe('1');
		});

		it('should filter by assignee', async () => {
			const result = await tmCore.listTasks({
				filter: { assignee: 'developer1' }
			});

			expect(result.filtered).toBe(1);
			expect(result.tasks[0].id).toBe('2');
		});

		it('should filter by complexity', async () => {
			const result = await tmCore.listTasks({
				filter: { complexity: 'complex' }
			});

			expect(result.filtered).toBe(1);
			expect(result.tasks[0].id).toBe('4');
		});

		it('should filter by search term', async () => {
			const result = await tmCore.listTasks({
				filter: { search: 'documentation' }
			});

			expect(result.filtered).toBe(1);
			expect(result.tasks[0].id).toBe('3');
		});

		it('should filter by hasSubtasks', async () => {
			const withSubtasks = await tmCore.listTasks({
				filter: { hasSubtasks: true }
			});

			expect(withSubtasks.filtered).toBe(1);
			expect(withSubtasks.tasks[0].id).toBe('1');

			const withoutSubtasks = await tmCore.listTasks({
				filter: { hasSubtasks: false }
			});

			expect(withoutSubtasks.filtered).toBe(4);
		});

		it('should handle combined filters', async () => {
			const result = await tmCore.listTasks({
				filter: {
					priority: ['high', 'critical'],
					status: ['pending', 'deferred']
				}
			});

			expect(result.filtered).toBe(1);
			expect(result.tasks[0].id).toBe('5'); // Critical priority, deferred status
		});
	});

	describe('Helper methods', () => {
		it('should get task by ID', async () => {
			const task = await tmCore.getTask('2');

			expect(task).not.toBeNull();
			expect(task?.title).toBe('Implement core features');
		});

		it('should return null for non-existent task', async () => {
			const task = await tmCore.getTask('999');

			expect(task).toBeNull();
		});

		it('should get tasks by status', async () => {
			const pendingTasks = await tmCore.getTasksByStatus('pending');

			expect(pendingTasks).toHaveLength(1);
			expect(pendingTasks[0].id).toBe('3');

			const multipleTasks = await tmCore.getTasksByStatus(['done', 'blocked']);

			expect(multipleTasks).toHaveLength(2);
		});

		it('should get task statistics', async () => {
			const stats = await tmCore.getTaskStats();

			expect(stats.total).toBe(5);
			expect(stats.byStatus.done).toBe(1);
			expect(stats.byStatus['in-progress']).toBe(1);
			expect(stats.byStatus.pending).toBe(1);
			expect(stats.byStatus.blocked).toBe(1);
			expect(stats.byStatus.deferred).toBe(1);
			expect(stats.byStatus.cancelled).toBe(0);
			expect(stats.byStatus.review).toBe(0);
			expect(stats.withSubtasks).toBe(1);
			expect(stats.blocked).toBe(1);
		});
	});

	describe('Error handling', () => {
		it('should handle missing tasks file gracefully', async () => {
			// Create new instance with empty directory
			const emptyDir = await fs.mkdtemp(path.join(os.tmpdir(), 'tm-empty-'));
			const emptyCore = createTaskMasterCore(emptyDir);

			try {
				const result = await emptyCore.listTasks();

				expect(result.tasks).toHaveLength(0);
				expect(result.total).toBe(0);
				expect(result.filtered).toBe(0);
			} finally {
				await emptyCore.close();
				await fs.rm(emptyDir, { recursive: true, force: true });
			}
		});

		it('should validate task entities', async () => {
			// Write invalid task data
			const invalidDir = await fs.mkdtemp(
				path.join(os.tmpdir(), 'tm-invalid-')
			);
			const tasksDir = path.join(invalidDir, '.taskmaster', 'tasks');
			await fs.mkdir(tasksDir, { recursive: true });

			const invalidData = {
				tasks: [
					{
						id: '', // Invalid: empty ID
						title: 'Test',
						description: 'Test',
						status: 'done',
						priority: 'high',
						dependencies: [],
						details: 'Test',
						testStrategy: 'Test',
						subtasks: []
					}
				],
				metadata: {
					version: '1.0.0',
					lastModified: new Date().toISOString(),
					taskCount: 1,
					completedCount: 0
				}
			};

			await fs.writeFile(
				path.join(tasksDir, 'tasks.json'),
				JSON.stringify(invalidData)
			);

			const invalidCore = createTaskMasterCore(invalidDir);

			try {
				await expect(invalidCore.listTasks()).rejects.toThrow();
			} finally {
				await invalidCore.close();
				await fs.rm(invalidDir, { recursive: true, force: true });
			}
		});
	});

	describe('Tags support', () => {
		beforeEach(async () => {
			// Create tasks for a different tag
			const taggedTasks = [
				{
					id: 'tag-1',
					title: 'Tagged task',
					description: 'Task with tag',
					status: 'pending' as TaskStatus,
					priority: 'medium' as const,
					dependencies: [],
					details: 'Tagged task details',
					testStrategy: 'Test',
					subtasks: []
				}
			];

			const tagFile = path.join(
				tmpDir,
				'.taskmaster',
				'tasks',
				'feature-branch.json'
			);
			await fs.writeFile(
				tagFile,
				JSON.stringify({
					tasks: taggedTasks,
					metadata: {
						version: '1.0.0',
						lastModified: new Date().toISOString(),
						taskCount: 1,
						completedCount: 0
					}
				})
			);
		});

		it('should list tasks for specific tag', async () => {
			const result = await tmCore.listTasks({ tag: 'feature-branch' });

			expect(result.tasks).toHaveLength(1);
			expect(result.tasks[0].id).toBe('tag-1');
			expect(result.tag).toBe('feature-branch');
		});

		it('should list default tasks when no tag specified', async () => {
			const result = await tmCore.listTasks();

			expect(result.tasks).toHaveLength(5);
			expect(result.tasks[0].id).toBe('1');
		});
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/tests/integration/auth-token-refresh.test.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * @fileoverview Integration tests for JWT token auto-refresh functionality
 *
 * These tests verify that expired tokens are automatically refreshed
 * when making API calls through AuthManager.
 */

import fs from 'fs';
import os from 'os';
import path from 'path';
import type { Session } from '@supabase/supabase-js';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AuthManager } from '../../src/modules/auth/managers/auth-manager.js';
import { CredentialStore } from '../../src/modules/auth/services/credential-store.js';
import type { AuthCredentials } from '../../src/modules/auth/types.js';

describe('AuthManager - Token Auto-Refresh Integration', () => {
	let authManager: AuthManager;
	let credentialStore: CredentialStore;
	let tmpDir: string;
	let authFile: string;

	// Mock Supabase session that will be returned on refresh
	const mockRefreshedSession: Session = {
		access_token: 'new-access-token-xyz',
		refresh_token: 'new-refresh-token-xyz',
		token_type: 'bearer',
		expires_at: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
		expires_in: 3600,
		user: {
			id: 'test-user-id',
			email: '[email protected]',
			aud: 'authenticated',
			role: 'authenticated',
			app_metadata: {},
			user_metadata: {},
			created_at: new Date().toISOString()
		}
	};

	beforeEach(() => {
		// Reset singletons
		AuthManager.resetInstance();
		CredentialStore.resetInstance();

		// Create temporary directory for test isolation
		tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tm-auth-integration-'));
		authFile = path.join(tmpDir, 'auth.json');

		// Initialize AuthManager with test config (this will create CredentialStore internally)
		authManager = AuthManager.getInstance({
			configDir: tmpDir,
			configFile: authFile
		});

		// Get the CredentialStore instance that AuthManager created
		credentialStore = CredentialStore.getInstance();
		credentialStore.clearCredentials();
	});

	afterEach(() => {
		// Clean up
		try {
			credentialStore.clearCredentials();
		} catch {
			// Ignore cleanup errors
		}
		AuthManager.resetInstance();
		CredentialStore.resetInstance();
		vi.restoreAllMocks();

		// Remove temporary directory
		if (tmpDir && fs.existsSync(tmpDir)) {
			fs.rmSync(tmpDir, { recursive: true, force: true });
		}
	});

	describe('Expired Token Detection', () => {
		it('should return expired token for Supabase to refresh', () => {
			// Set up expired credentials
			const expiredCredentials: AuthCredentials = {
				token: 'expired-token',
				refreshToken: 'valid-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() - 60000).toISOString(), // 1 minute ago
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(expiredCredentials);

			authManager = AuthManager.getInstance();

			// Get credentials returns them even if expired
			const credentials = authManager.getCredentials();

			expect(credentials).not.toBeNull();
			expect(credentials?.token).toBe('expired-token');
			expect(credentials?.refreshToken).toBe('valid-refresh-token');
		});

		it('should return valid token', () => {
			// Set up valid credentials
			const validCredentials: AuthCredentials = {
				token: 'valid-token',
				refreshToken: 'valid-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() + 3600000).toISOString(), // 1 hour from now
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(validCredentials);

			authManager = AuthManager.getInstance();

			const credentials = authManager.getCredentials();

			expect(credentials?.token).toBe('valid-token');
		});
	});

	describe('Token Refresh Flow', () => {
		it('should manually refresh expired token and save new credentials', async () => {
			const expiredCredentials: AuthCredentials = {
				token: 'old-token',
				refreshToken: 'old-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() - 60000).toISOString(),
				savedAt: new Date(Date.now() - 3600000).toISOString(),
				selectedContext: {
					orgId: 'test-org',
					briefId: 'test-brief',
					updatedAt: new Date().toISOString()
				}
			};

			credentialStore.saveCredentials(expiredCredentials);

			authManager = AuthManager.getInstance();

			vi.spyOn(
				authManager['supabaseClient'],
				'refreshSession'
			).mockResolvedValue(mockRefreshedSession);

			// Explicitly call refreshToken() method
			const refreshedCredentials = await authManager.refreshToken();

			expect(refreshedCredentials).not.toBeNull();
			expect(refreshedCredentials.token).toBe('new-access-token-xyz');
			expect(refreshedCredentials.refreshToken).toBe('new-refresh-token-xyz');

			// Verify context was preserved
			expect(refreshedCredentials.selectedContext?.orgId).toBe('test-org');
			expect(refreshedCredentials.selectedContext?.briefId).toBe('test-brief');

			// Verify new expiration is in the future
			const newExpiry = new Date(refreshedCredentials.expiresAt!).getTime();
			const now = Date.now();
			expect(newExpiry).toBeGreaterThan(now);
		});

		it('should throw error if manual refresh fails', async () => {
			const expiredCredentials: AuthCredentials = {
				token: 'expired-token',
				refreshToken: 'invalid-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() - 60000).toISOString(),
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(expiredCredentials);

			authManager = AuthManager.getInstance();

			// Mock refresh to fail
			vi.spyOn(
				authManager['supabaseClient'],
				'refreshSession'
			).mockRejectedValue(new Error('Refresh token expired'));

			// Explicit refreshToken() call should throw
			await expect(authManager.refreshToken()).rejects.toThrow();
		});

		it('should return expired credentials even without refresh token', () => {
			const expiredCredentials: AuthCredentials = {
				token: 'expired-token',
				// No refresh token
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() - 60000).toISOString(),
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(expiredCredentials);

			authManager = AuthManager.getInstance();

			const credentials = authManager.getCredentials();

			// Credentials are returned even without refresh token
			expect(credentials).not.toBeNull();
			expect(credentials?.token).toBe('expired-token');
			expect(credentials?.refreshToken).toBeUndefined();
		});

		it('should return null if credentials missing expiresAt', () => {
			const credentialsWithoutExpiry: AuthCredentials = {
				token: 'test-token',
				refreshToken: 'refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				// Missing expiresAt - invalid token
				savedAt: new Date().toISOString()
			} as any;

			credentialStore.saveCredentials(credentialsWithoutExpiry);

			authManager = AuthManager.getInstance();

			const credentials = authManager.getCredentials();

			// Tokens without valid expiration are considered invalid
			expect(credentials).toBeNull();
		});
	});

	describe('Clock Skew Tolerance', () => {
		it('should return credentials within 30-second expiry window', () => {
			// Token expires in 15 seconds (within 30-second buffer)
			// Supabase will handle refresh automatically
			const almostExpiredCredentials: AuthCredentials = {
				token: 'almost-expired-token',
				refreshToken: 'valid-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() + 15000).toISOString(), // 15 seconds from now
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(almostExpiredCredentials);

			authManager = AuthManager.getInstance();

			const credentials = authManager.getCredentials();

			// Credentials are returned (Supabase handles auto-refresh in background)
			expect(credentials).not.toBeNull();
			expect(credentials?.token).toBe('almost-expired-token');
			expect(credentials?.refreshToken).toBe('valid-refresh-token');
		});

		it('should return valid token well before expiry', () => {
			// Token expires in 5 minutes
			const validCredentials: AuthCredentials = {
				token: 'valid-token',
				refreshToken: 'valid-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() + 300000).toISOString(), // 5 minutes
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(validCredentials);

			authManager = AuthManager.getInstance();

			const credentials = authManager.getCredentials();

			// Valid credentials are returned as-is
			expect(credentials).not.toBeNull();
			expect(credentials?.token).toBe('valid-token');
			expect(credentials?.refreshToken).toBe('valid-refresh-token');
		});
	});

	describe('Synchronous vs Async Methods', () => {
		it('getCredentials should return expired credentials', () => {
			const expiredCredentials: AuthCredentials = {
				token: 'expired-token',
				refreshToken: 'valid-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() - 60000).toISOString(),
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(expiredCredentials);

			authManager = AuthManager.getInstance();

			// Returns credentials even if expired - Supabase will handle refresh
			const credentials = authManager.getCredentials();

			expect(credentials).not.toBeNull();
			expect(credentials?.token).toBe('expired-token');
			expect(credentials?.refreshToken).toBe('valid-refresh-token');
		});
	});

	describe('Multiple Concurrent Calls', () => {
		it('should handle concurrent getCredentials calls gracefully', () => {
			const expiredCredentials: AuthCredentials = {
				token: 'expired-token',
				refreshToken: 'valid-refresh-token',
				userId: 'test-user-id',
				email: '[email protected]',
				expiresAt: new Date(Date.now() - 60000).toISOString(),
				savedAt: new Date().toISOString()
			};

			credentialStore.saveCredentials(expiredCredentials);

			authManager = AuthManager.getInstance();

			// Make multiple concurrent calls (synchronous now)
			const creds1 = authManager.getCredentials();
			const creds2 = authManager.getCredentials();
			const creds3 = authManager.getCredentials();

			// All should get the same credentials (even if expired)
			expect(creds1?.token).toBe('expired-token');
			expect(creds2?.token).toBe('expired-token');
			expect(creds3?.token).toBe('expired-token');

			// All include refresh token for Supabase to use
			expect(creds1?.refreshToken).toBe('valid-refresh-token');
			expect(creds2?.refreshToken).toBe('valid-refresh-token');
			expect(creds3?.refreshToken).toBe('valid-refresh-token');
		});
	});
});

```

--------------------------------------------------------------------------------
/apps/extension/src/components/TaskDetails/TaskMetadataSidebar.tsx:
--------------------------------------------------------------------------------

```typescript
import type React from 'react';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Loader2, Play } from 'lucide-react';
import { PriorityBadge } from './PriorityBadge';
import type { TaskMasterTask } from '../../webview/types';
import { useVSCodeContext } from '../../webview/contexts/VSCodeContext';

interface TaskMetadataSidebarProps {
	currentTask: TaskMasterTask;
	tasks: TaskMasterTask[];
	complexity: any;
	isSubtask: boolean;
	onStatusChange: (status: TaskMasterTask['status']) => void;
	onDependencyClick: (depId: string) => void;
	isRegenerating?: boolean;
	isAppending?: boolean;
}

export const TaskMetadataSidebar: React.FC<TaskMetadataSidebarProps> = ({
	currentTask,
	tasks,
	complexity,
	isSubtask,
	onStatusChange,
	onDependencyClick,
	isRegenerating = false,
	isAppending = false
}) => {
	const { sendMessage } = useVSCodeContext();
	const [isLoadingComplexity, setIsLoadingComplexity] = useState(false);
	const [mcpComplexityScore, setMcpComplexityScore] = useState<
		number | undefined
	>(undefined);
	const [isStartingTask, setIsStartingTask] = useState(false);

	// Get complexity score from task
	const currentComplexityScore = complexity?.score;

	// Display logic - use MCP score if available, otherwise use current score
	const displayComplexityScore =
		mcpComplexityScore !== undefined
			? mcpComplexityScore
			: currentComplexityScore;

	// Fetch complexity from MCP when needed
	const fetchComplexityFromMCP = async (force = false) => {
		if (!currentTask || (!force && currentComplexityScore !== undefined)) {
			return;
		}
		setIsLoadingComplexity(true);
		try {
			const complexityResult = await sendMessage({
				type: 'mcpRequest',
				tool: 'complexity_report',
				params: {}
			});
			if (complexityResult?.data?.report?.complexityAnalysis) {
				const taskComplexity =
					complexityResult.data.report.complexityAnalysis.tasks?.find(
						(t: any) => t.id === currentTask.id
					);
				if (taskComplexity) {
					setMcpComplexityScore(taskComplexity.complexityScore);
				}
			}
		} catch (error) {
			console.error('Failed to fetch complexity from MCP:', error);
		} finally {
			setIsLoadingComplexity(false);
		}
	};

	// Handle running complexity analysis for a task
	const handleRunComplexityAnalysis = async () => {
		if (!currentTask) {
			return;
		}
		setIsLoadingComplexity(true);
		try {
			// Run complexity analysis on this specific task
			await sendMessage({
				type: 'mcpRequest',
				tool: 'analyze_project_complexity',
				params: {
					ids: currentTask.id.toString(),
					research: false
				}
			});
			// After analysis, fetch the updated complexity report
			setTimeout(() => {
				fetchComplexityFromMCP(true);
			}, 1000);
		} catch (error) {
			console.error('Failed to run complexity analysis:', error);
		} finally {
			setIsLoadingComplexity(false);
		}
	};

	// Handle starting a task
	const handleStartTask = async () => {
		if (!currentTask || isStartingTask) {
			return;
		}

		setIsStartingTask(true);

		try {
			// Send message to extension to open terminal
			const result = await sendMessage({
				type: 'openTerminal',
				data: {
					taskId: currentTask.id,
					taskTitle: currentTask.title
				}
			});

			// Handle the response
			if (result && !result.success) {
				console.error('Terminal execution failed:', result.error);
				// The extension will show VS Code error notification and webview toast
			} else if (result && result.success) {
				console.log('Terminal started successfully:', result.terminalName);
			}
		} catch (error) {
			console.error('Failed to start task:', error);
			// This handles network/communication errors
		} finally {
			// Reset loading state
			setIsStartingTask(false);
		}
	};

	// Effect to handle complexity on task change
	useEffect(() => {
		if (currentTask?.id) {
			setMcpComplexityScore(undefined);
			if (currentComplexityScore === undefined) {
				fetchComplexityFromMCP();
			}
		}
	}, [currentTask?.id, currentComplexityScore]);

	return (
		<div className="md:col-span-1 border-l border-textSeparator-foreground">
			<div className="p-6">
				<div className="space-y-6">
					<div>
						<h3 className="text-sm font-medium text-vscode-foreground/70 mb-3">
							Properties
						</h3>
					</div>

					<div className="space-y-4">
						{/* Status */}
						<div className="flex items-center justify-between">
							<span className="text-sm text-vscode-foreground/70">Status</span>
							<select
								value={currentTask.status}
								onChange={(e) =>
									onStatusChange(e.target.value as TaskMasterTask['status'])
								}
								className="border rounded-md px-3 py-1 text-sm font-medium focus:ring-1 focus:border-vscode-focusBorder focus:ring-vscode-focusBorder"
								style={{
									backgroundColor:
										currentTask.status === 'pending'
											? 'rgba(156, 163, 175, 0.2)'
											: currentTask.status === 'in-progress'
												? 'rgba(245, 158, 11, 0.2)'
												: currentTask.status === 'review'
													? 'rgba(59, 130, 246, 0.2)'
													: currentTask.status === 'done'
														? 'rgba(34, 197, 94, 0.2)'
														: currentTask.status === 'deferred'
															? 'rgba(239, 68, 68, 0.2)'
															: 'var(--vscode-input-background)',
									color:
										currentTask.status === 'pending'
											? 'var(--vscode-foreground)'
											: currentTask.status === 'in-progress'
												? '#d97706'
												: currentTask.status === 'review'
													? '#2563eb'
													: currentTask.status === 'done'
														? '#16a34a'
														: currentTask.status === 'deferred'
															? '#dc2626'
															: 'var(--vscode-foreground)',
									borderColor:
										currentTask.status === 'pending'
											? 'rgba(156, 163, 175, 0.4)'
											: currentTask.status === 'in-progress'
												? 'rgba(245, 158, 11, 0.4)'
												: currentTask.status === 'review'
													? 'rgba(59, 130, 246, 0.4)'
													: currentTask.status === 'done'
														? 'rgba(34, 197, 94, 0.4)'
														: currentTask.status === 'deferred'
															? 'rgba(239, 68, 68, 0.4)'
															: 'var(--vscode-input-border)'
								}}
							>
								<option value="pending">To do</option>
								<option value="in-progress">In Progress</option>
								<option value="review">Review</option>
								<option value="done">Done</option>
								<option value="deferred">Deferred</option>
							</select>
						</div>

						{/* Priority */}
						<div className="flex items-center justify-between">
							<span className="text-sm text-muted-foreground">Priority</span>
							<PriorityBadge priority={currentTask.priority} />
						</div>

						{/* Complexity Score */}
						<div className="space-y-2">
							<label className="text-sm font-medium text-[var(--vscode-foreground)]">
								Complexity Score
							</label>
							{isLoadingComplexity ? (
								<div className="flex items-center gap-2">
									<Loader2 className="w-4 h-4 animate-spin text-[var(--vscode-descriptionForeground)]" />
									<span className="text-sm text-[var(--vscode-descriptionForeground)]">
										Loading...
									</span>
								</div>
							) : displayComplexityScore !== undefined ? (
								<div className="flex items-center gap-2">
									<span className="text-sm font-medium text-[var(--vscode-foreground)]">
										{displayComplexityScore}/10
									</span>
									<div
										className={`flex-1 rounded-full h-2 ${
											displayComplexityScore >= 7
												? 'bg-red-500/20'
												: displayComplexityScore >= 4
													? 'bg-yellow-500/20'
													: 'bg-green-500/20'
										}`}
									>
										<div
											className={`h-2 rounded-full transition-all duration-300 ${
												displayComplexityScore >= 7
													? 'bg-red-500'
													: displayComplexityScore >= 4
														? 'bg-yellow-500'
														: 'bg-green-500'
											}`}
											style={{
												width: `${(displayComplexityScore || 0) * 10}%`
											}}
										/>
									</div>
								</div>
							) : currentTask?.status === 'done' ||
								currentTask?.status === 'deferred' ||
								currentTask?.status === 'review' ? (
								<div className="text-sm text-[var(--vscode-descriptionForeground)]">
									N/A
								</div>
							) : (
								<>
									<div className="text-sm text-[var(--vscode-descriptionForeground)]">
										No complexity score available
									</div>
									<div className="mt-3">
										<Button
											onClick={() => handleRunComplexityAnalysis()}
											variant="outline"
											size="sm"
											className="text-xs"
											disabled={isRegenerating || isAppending}
										>
											Run Complexity Analysis
										</Button>
									</div>
								</>
							)}
						</div>
					</div>
					<div className="border-b border-textSeparator-foreground" />

					{/* Dependencies */}
					{currentTask.dependencies && currentTask.dependencies.length > 0 && (
						<div>
							<h4 className="text-sm font-medium text-vscode-foreground/70 mb-3">
								Dependencies
							</h4>
							<div className="space-y-2">
								{currentTask.dependencies.map((depId) => {
									// Convert both to string for comparison since depId might be string or number
									const depTask = tasks.find(
										(t) => String(t.id) === String(depId)
									);
									const fullTitle = `Task ${depId}: ${depTask?.title || 'Unknown Task'}`;
									const truncatedTitle =
										fullTitle.length > 40
											? fullTitle.substring(0, 37) + '...'
											: fullTitle;
									return (
										<div
											key={depId}
											className="text-sm text-link cursor-pointer hover:text-link-hover"
											onClick={() => onDependencyClick(depId)}
											title={fullTitle}
										>
											{truncatedTitle}
										</div>
									);
								})}
							</div>
						</div>
					)}

					{/* Divider after Dependencies */}
					{currentTask.dependencies && currentTask.dependencies.length > 0 && (
						<div className="border-b border-textSeparator-foreground" />
					)}

					{/* Start Task Button */}
					<div className="mt-4">
						<Button
							onClick={handleStartTask}
							variant="default"
							size="sm"
							className="w-full text-xs"
							disabled={
								isRegenerating ||
								isAppending ||
								isStartingTask ||
								currentTask?.status === 'done' ||
								currentTask?.status === 'in-progress'
							}
						>
							{isStartingTask ? (
								<Loader2 className="w-4 h-4 mr-2 animate-spin" />
							) : (
								<Play className="w-4 h-4 mr-2" />
							)}
							{isStartingTask ? 'Starting...' : 'Start Task'}
						</Button>
					</div>
				</div>
			</div>
		</div>
	);
};

```

--------------------------------------------------------------------------------
/scripts/modules/utils/fuzzyTaskSearch.js:
--------------------------------------------------------------------------------

```javascript
/**
 * fuzzyTaskSearch.js
 * Reusable fuzzy search utility for finding relevant tasks based on semantic similarity
 */

import Fuse from 'fuse.js';

/**
 * Configuration for different search contexts
 */
const SEARCH_CONFIGS = {
	research: {
		threshold: 0.5, // More lenient for research (broader context)
		limit: 20,
		keys: [
			{ name: 'title', weight: 2.0 },
			{ name: 'description', weight: 1.0 },
			{ name: 'details', weight: 0.5 },
			{ name: 'dependencyTitles', weight: 0.5 }
		]
	},
	addTask: {
		threshold: 0.4, // Stricter for add-task (more precise context)
		limit: 15,
		keys: [
			{ name: 'title', weight: 2.0 },
			{ name: 'description', weight: 1.5 },
			{ name: 'details', weight: 0.8 },
			{ name: 'dependencyTitles', weight: 0.5 }
		]
	},
	default: {
		threshold: 0.4,
		limit: 15,
		keys: [
			{ name: 'title', weight: 2.0 },
			{ name: 'description', weight: 1.5 },
			{ name: 'details', weight: 1.0 },
			{ name: 'dependencyTitles', weight: 0.5 }
		]
	}
};

/**
 * Purpose categories for pattern-based task matching
 */
const PURPOSE_CATEGORIES = [
	{ pattern: /(command|cli|flag)/i, label: 'CLI commands' },
	{ pattern: /(task|subtask|add)/i, label: 'Task management' },
	{ pattern: /(dependency|depend)/i, label: 'Dependency handling' },
	{ pattern: /(AI|model|prompt|research)/i, label: 'AI integration' },
	{ pattern: /(UI|display|show|interface)/i, label: 'User interface' },
	{ pattern: /(schedule|time|cron)/i, label: 'Scheduling' },
	{ pattern: /(config|setting|option)/i, label: 'Configuration' },
	{ pattern: /(test|testing|spec)/i, label: 'Testing' },
	{ pattern: /(auth|login|user)/i, label: 'Authentication' },
	{ pattern: /(database|db|data)/i, label: 'Data management' },
	{ pattern: /(api|endpoint|route)/i, label: 'API development' },
	{ pattern: /(deploy|build|release)/i, label: 'Deployment' },
	{ pattern: /(security|auth|login|user)/i, label: 'Security' },
	{ pattern: /.*/, label: 'Other' }
];

/**
 * Relevance score thresholds
 */
const RELEVANCE_THRESHOLDS = {
	high: 0.25,
	medium: 0.4,
	low: 0.6
};

/**
 * Fuzzy search utility class for finding relevant tasks
 */
export class FuzzyTaskSearch {
	constructor(tasks, searchType = 'default') {
		this.tasks = tasks;
		this.config = SEARCH_CONFIGS[searchType] || SEARCH_CONFIGS.default;
		this.searchableTasks = this._prepareSearchableTasks(tasks);
		this.fuse = new Fuse(this.searchableTasks, {
			includeScore: true,
			threshold: this.config.threshold,
			keys: this.config.keys,
			shouldSort: true,
			useExtendedSearch: true,
			limit: this.config.limit
		});
	}

	/**
	 * Prepare tasks for searching by expanding dependency titles
	 * @param {Array} tasks - Array of task objects
	 * @returns {Array} Tasks with expanded dependency information
	 */
	_prepareSearchableTasks(tasks) {
		return tasks.map((task) => {
			// Get titles of this task's dependencies if they exist
			const dependencyTitles =
				task.dependencies?.length > 0
					? task.dependencies
							.map((depId) => {
								const depTask = tasks.find((t) => t.id === depId);
								return depTask ? depTask.title : '';
							})
							.filter((title) => title)
							.join(' ')
					: '';

			return {
				...task,
				dependencyTitles
			};
		});
	}

	/**
	 * Extract significant words from a prompt
	 * @param {string} prompt - The search prompt
	 * @returns {Array<string>} Array of significant words
	 */
	_extractPromptWords(prompt) {
		return prompt
			.toLowerCase()
			.replace(/[^\w\s-]/g, ' ') // Replace non-alphanumeric chars with spaces
			.split(/\s+/)
			.filter((word) => word.length > 3); // Words at least 4 chars
	}

	/**
	 * Find tasks related to a prompt using fuzzy search
	 * @param {string} prompt - The search prompt
	 * @param {Object} options - Search options
	 * @param {number} [options.maxResults=8] - Maximum number of results to return
	 * @param {boolean} [options.includeRecent=true] - Include recent tasks in results
	 * @param {boolean} [options.includeCategoryMatches=true] - Include category-based matches
	 * @returns {Object} Search results with relevance breakdown
	 */
	findRelevantTasks(prompt, options = {}) {
		const {
			maxResults = 8,
			includeRecent = true,
			includeCategoryMatches = true
		} = options;

		// Extract significant words from prompt
		const promptWords = this._extractPromptWords(prompt);

		// Perform fuzzy search with full prompt
		const fuzzyResults = this.fuse.search(prompt);

		// Also search for each significant word to catch different aspects
		let wordResults = [];
		for (const word of promptWords) {
			if (word.length > 5) {
				// Only use significant words
				const results = this.fuse.search(word);
				if (results.length > 0) {
					wordResults.push(...results);
				}
			}
		}

		// Merge and deduplicate results
		const mergedResults = [...fuzzyResults];

		// Add word results that aren't already in fuzzyResults
		for (const wordResult of wordResults) {
			if (!mergedResults.some((r) => r.item.id === wordResult.item.id)) {
				mergedResults.push(wordResult);
			}
		}

		// Group search results by relevance
		const highRelevance = mergedResults
			.filter((result) => result.score < RELEVANCE_THRESHOLDS.high)
			.map((result) => ({ ...result.item, score: result.score }));

		const mediumRelevance = mergedResults
			.filter(
				(result) =>
					result.score >= RELEVANCE_THRESHOLDS.high &&
					result.score < RELEVANCE_THRESHOLDS.medium
			)
			.map((result) => ({ ...result.item, score: result.score }));

		const lowRelevance = mergedResults
			.filter(
				(result) =>
					result.score >= RELEVANCE_THRESHOLDS.medium &&
					result.score < RELEVANCE_THRESHOLDS.low
			)
			.map((result) => ({ ...result.item, score: result.score }));

		// Get recent tasks (newest first) if requested
		const recentTasks = includeRecent
			? [...this.tasks].sort((a, b) => b.id - a.id).slice(0, 5)
			: [];

		// Find category-based matches if requested
		let categoryTasks = [];
		let promptCategory = null;
		if (includeCategoryMatches) {
			promptCategory = PURPOSE_CATEGORIES.find((cat) =>
				cat.pattern.test(prompt)
			);
			categoryTasks = promptCategory
				? this.tasks
						.filter(
							(t) =>
								promptCategory.pattern.test(t.title) ||
								promptCategory.pattern.test(t.description) ||
								(t.details && promptCategory.pattern.test(t.details))
						)
						.slice(0, 3)
				: [];
		}

		// Combine all relevant tasks, prioritizing by relevance
		const allRelevantTasks = [...highRelevance];

		// Add medium relevance if not already included
		for (const task of mediumRelevance) {
			if (!allRelevantTasks.some((t) => t.id === task.id)) {
				allRelevantTasks.push(task);
			}
		}

		// Add low relevance if not already included
		for (const task of lowRelevance) {
			if (!allRelevantTasks.some((t) => t.id === task.id)) {
				allRelevantTasks.push(task);
			}
		}

		// Add category tasks if not already included
		for (const task of categoryTasks) {
			if (!allRelevantTasks.some((t) => t.id === task.id)) {
				allRelevantTasks.push(task);
			}
		}

		// Add recent tasks if not already included
		for (const task of recentTasks) {
			if (!allRelevantTasks.some((t) => t.id === task.id)) {
				allRelevantTasks.push(task);
			}
		}

		// Get top N results for final output
		const finalResults = allRelevantTasks.slice(0, maxResults);

		return {
			results: finalResults,
			breakdown: {
				highRelevance,
				mediumRelevance,
				lowRelevance,
				categoryTasks,
				recentTasks,
				promptCategory,
				promptWords
			},
			metadata: {
				totalSearched: this.tasks.length,
				fuzzyMatches: fuzzyResults.length,
				wordMatches: wordResults.length,
				finalCount: finalResults.length
			}
		};
	}

	/**
	 * Get task IDs from search results
	 * @param {Object} searchResults - Results from findRelevantTasks
	 * @returns {Array<string>} Array of task ID strings
	 */
	getTaskIds(searchResults) {
		return searchResults.results.map((task) => task.id.toString());
	}

	/**
	 * Get task IDs including subtasks from search results
	 * @param {Object} searchResults - Results from findRelevantTasks
	 * @param {boolean} [includeSubtasks=false] - Whether to include subtask IDs
	 * @returns {Array<string>} Array of task and subtask ID strings
	 */
	getTaskIdsWithSubtasks(searchResults, includeSubtasks = false) {
		const taskIds = [];

		for (const task of searchResults.results) {
			taskIds.push(task.id.toString());

			if (includeSubtasks && task.subtasks && task.subtasks.length > 0) {
				for (const subtask of task.subtasks) {
					taskIds.push(`${task.id}.${subtask.id}`);
				}
			}
		}

		return taskIds;
	}

	/**
	 * Format search results for display
	 * @param {Object} searchResults - Results from findRelevantTasks
	 * @param {Object} options - Formatting options
	 * @returns {string} Formatted search results summary
	 */
	formatSearchSummary(searchResults, options = {}) {
		const { includeScores = false, includeBreakdown = false } = options;
		const { results, breakdown, metadata } = searchResults;

		let summary = `Found ${results.length} relevant tasks from ${metadata.totalSearched} total tasks`;

		if (includeBreakdown && breakdown) {
			const parts = [];
			if (breakdown.highRelevance.length > 0)
				parts.push(`${breakdown.highRelevance.length} high relevance`);
			if (breakdown.mediumRelevance.length > 0)
				parts.push(`${breakdown.mediumRelevance.length} medium relevance`);
			if (breakdown.lowRelevance.length > 0)
				parts.push(`${breakdown.lowRelevance.length} low relevance`);
			if (breakdown.categoryTasks.length > 0)
				parts.push(`${breakdown.categoryTasks.length} category matches`);

			if (parts.length > 0) {
				summary += ` (${parts.join(', ')})`;
			}

			if (breakdown.promptCategory) {
				summary += `\nCategory detected: ${breakdown.promptCategory.label}`;
			}
		}

		return summary;
	}
}

/**
 * Factory function to create a fuzzy search instance
 * @param {Array} tasks - Array of task objects
 * @param {string} [searchType='default'] - Type of search configuration to use
 * @returns {FuzzyTaskSearch} Fuzzy search instance
 */
export function createFuzzyTaskSearch(tasks, searchType = 'default') {
	return new FuzzyTaskSearch(tasks, searchType);
}

/**
 * Quick utility function to find relevant task IDs for a prompt
 * @param {Array} tasks - Array of task objects
 * @param {string} prompt - Search prompt
 * @param {Object} options - Search options
 * @returns {Array<string>} Array of relevant task ID strings
 */
export function findRelevantTaskIds(tasks, prompt, options = {}) {
	const {
		searchType = 'default',
		maxResults = 8,
		includeSubtasks = false
	} = options;

	const fuzzySearch = new FuzzyTaskSearch(tasks, searchType);
	const results = fuzzySearch.findRelevantTasks(prompt, { maxResults });

	return includeSubtasks
		? fuzzySearch.getTaskIdsWithSubtasks(results, true)
		: fuzzySearch.getTaskIds(results);
}

export default FuzzyTaskSearch;

```

--------------------------------------------------------------------------------
/src/ai-providers/base-provider.js:
--------------------------------------------------------------------------------

```javascript
import {
	generateObject,
	generateText,
	streamText,
	streamObject,
	zodSchema,
	JSONParseError,
	NoObjectGeneratedError
} from 'ai';
import { jsonrepair } from 'jsonrepair';
import { log, findProjectRoot } from '../../scripts/modules/utils.js';
import { isProxyEnabled } from '../../scripts/modules/config-manager.js';
import { EnvHttpProxyAgent } from 'undici';

/**
 * Base class for all AI providers
 */
export class BaseAIProvider {
	constructor() {
		if (this.constructor === BaseAIProvider) {
			throw new Error('BaseAIProvider cannot be instantiated directly');
		}

		// Each provider must set their name
		this.name = this.constructor.name;

		// Cache proxy agent to avoid creating multiple instances
		this._proxyAgent = null;

		/**
		 * Whether this provider needs explicit schema in JSON mode
		 * Can be overridden by subclasses
		 * @type {boolean}
		 */
		this.needsExplicitJsonSchema = false;

		/**
		 * Whether this provider supports temperature parameter
		 * Can be overridden by subclasses
		 * @type {boolean}
		 */
		this.supportsTemperature = true;
	}

	/**
	 * Validates authentication parameters - can be overridden by providers
	 * @param {object} params - Parameters to validate
	 */
	validateAuth(params) {
		// Default: require API key (most providers need this)
		if (!params.apiKey) {
			throw new Error(`${this.name} API key is required`);
		}
	}

	/**
	 * Creates a custom fetch function with proxy support.
	 * Only enables proxy when TASKMASTER_ENABLE_PROXY environment variable is set to 'true'
	 * or enableProxy is set to true in config.json.
	 * Automatically reads http_proxy/https_proxy environment variables when enabled.
	 * @returns {Function} Custom fetch function with proxy support, or undefined if proxy is disabled
	 */
	createProxyFetch() {
		// Cache project root to avoid repeated lookups
		if (!this._projectRoot) {
			this._projectRoot = findProjectRoot();
		}
		const projectRoot = this._projectRoot;

		if (!isProxyEnabled(null, projectRoot)) {
			// Return undefined to use default fetch without proxy
			return undefined;
		}

		// Proxy is enabled, create and return proxy fetch
		if (!this._proxyAgent) {
			this._proxyAgent = new EnvHttpProxyAgent();
		}
		return (url, options = {}) => {
			return fetch(url, {
				...options,
				dispatcher: this._proxyAgent
			});
		};
	}

	/**
	 * Validates common parameters across all methods
	 * @param {object} params - Parameters to validate
	 */
	validateParams(params) {
		// Validate authentication (can be overridden by providers)
		this.validateAuth(params);

		// Validate required model ID
		if (!params.modelId) {
			throw new Error(`${this.name} Model ID is required`);
		}

		// Validate optional parameters
		this.validateOptionalParams(params);
	}

	/**
	 * Validates optional parameters like temperature and maxTokens
	 * @param {object} params - Parameters to validate
	 */
	validateOptionalParams(params) {
		if (
			params.temperature !== undefined &&
			(params.temperature < 0 || params.temperature > 1)
		) {
			throw new Error('Temperature must be between 0 and 1');
		}
		if (params.maxTokens !== undefined) {
			const maxTokens = Number(params.maxTokens);
			if (!Number.isFinite(maxTokens) || maxTokens <= 0) {
				throw new Error('maxTokens must be a finite number greater than 0');
			}
		}
	}

	/**
	 * Validates message array structure
	 */
	validateMessages(messages) {
		if (!messages || !Array.isArray(messages) || messages.length === 0) {
			throw new Error('Invalid or empty messages array provided');
		}

		for (const msg of messages) {
			if (!msg.role || !msg.content) {
				throw new Error(
					'Invalid message format. Each message must have role and content'
				);
			}
		}
	}

	/**
	 * Common error handler
	 */
	handleError(operation, error) {
		const errorMessage = error.message || 'Unknown error occurred';
		log('error', `${this.name} ${operation} failed: ${errorMessage}`, {
			error
		});
		throw new Error(
			`${this.name} API error during ${operation}: ${errorMessage}`
		);
	}

	/**
	 * Creates and returns a client instance for the provider
	 * @abstract
	 */
	getClient(params) {
		throw new Error('getClient must be implemented by provider');
	}

	/**
	 * Returns if the API key is required
	 * @abstract
	 * @returns {boolean} if the API key is required, defaults to true
	 */
	isRequiredApiKey() {
		return true;
	}

	/**
	 * Returns the required API key environment variable name
	 * @abstract
	 * @returns {string|null} The environment variable name, or null if no API key is required
	 */
	getRequiredApiKeyName() {
		throw new Error('getRequiredApiKeyName must be implemented by provider');
	}

	/**
	 * Prepares token limit parameter based on model requirements
	 * @param {string} modelId - The model ID
	 * @param {number} maxTokens - The maximum tokens value
	 * @returns {object} Object with either maxTokens or max_completion_tokens
	 */
	prepareTokenParam(modelId, maxTokens) {
		if (maxTokens === undefined) {
			return {};
		}

		// Ensure maxTokens is an integer
		const tokenValue = Math.floor(Number(maxTokens));

		return { maxOutputTokens: tokenValue };
	}

	/**
	 * Generates text using the provider's model
	 */
	async generateText(params) {
		try {
			this.validateParams(params);
			this.validateMessages(params.messages);

			log(
				'debug',
				`Generating ${this.name} text with model: ${params.modelId}`
			);

			const client = await this.getClient(params);
			const result = await generateText({
				model: client(params.modelId),
				messages: params.messages,
				...this.prepareTokenParam(params.modelId, params.maxTokens),
				...(this.supportsTemperature && params.temperature !== undefined
					? { temperature: params.temperature }
					: {})
			});

			log(
				'debug',
				`${this.name} generateText completed successfully for model: ${params.modelId}`
			);

			const inputTokens =
				result.usage?.inputTokens ?? result.usage?.promptTokens ?? 0;
			const outputTokens =
				result.usage?.outputTokens ?? result.usage?.completionTokens ?? 0;
			const totalTokens =
				result.usage?.totalTokens ?? inputTokens + outputTokens;

			return {
				text: result.text,
				usage: {
					inputTokens,
					outputTokens,
					totalTokens
				}
			};
		} catch (error) {
			this.handleError('text generation', error);
		}
	}

	/**
	 * Streams text using the provider's model
	 */
	async streamText(params) {
		try {
			this.validateParams(params);
			this.validateMessages(params.messages);

			log('debug', `Streaming ${this.name} text with model: ${params.modelId}`);

			const client = await this.getClient(params);
			const stream = await streamText({
				model: client(params.modelId),
				messages: params.messages,
				...this.prepareTokenParam(params.modelId, params.maxTokens),
				...(this.supportsTemperature && params.temperature !== undefined
					? { temperature: params.temperature }
					: {})
			});

			log(
				'debug',
				`${this.name} streamText initiated successfully for model: ${params.modelId}`
			);

			return stream;
		} catch (error) {
			this.handleError('text streaming', error);
		}
	}

	/**
	 * Streams a structured object using the provider's model
	 */
	async streamObject(params) {
		try {
			this.validateParams(params);
			this.validateMessages(params.messages);

			if (!params.schema) {
				throw new Error('Schema is required for object streaming');
			}

			log(
				'debug',
				`Streaming ${this.name} object with model: ${params.modelId}`
			);

			const client = await this.getClient(params);
			const result = await streamObject({
				model: client(params.modelId),
				messages: params.messages,
				schema: zodSchema(params.schema),
				mode: params.mode || 'auto',
				maxOutputTokens: params.maxTokens,
				...(this.supportsTemperature && params.temperature !== undefined
					? { temperature: params.temperature }
					: {})
			});

			log(
				'debug',
				`${this.name} streamObject initiated successfully for model: ${params.modelId}`
			);

			// Return the stream result directly
			// The stream result contains partialObjectStream and other properties
			return result;
		} catch (error) {
			this.handleError('object streaming', error);
		}
	}

	/**
	 * Generates a structured object using the provider's model
	 */
	async generateObject(params) {
		try {
			this.validateParams(params);
			this.validateMessages(params.messages);

			if (!params.schema) {
				throw new Error('Schema is required for object generation');
			}
			if (!params.objectName) {
				throw new Error('Object name is required for object generation');
			}

			log(
				'debug',
				`Generating ${this.name} object ('${params.objectName}') with model: ${params.modelId}`
			);

			const client = await this.getClient(params);

			const result = await generateObject({
				model: client(params.modelId),
				messages: params.messages,
				schema: params.schema,
				mode: this.needsExplicitJsonSchema ? 'json' : 'auto',
				schemaName: params.objectName,
				schemaDescription: `Generate a valid JSON object for ${params.objectName}`,
				maxTokens: params.maxTokens,
				...(this.supportsTemperature && params.temperature !== undefined
					? { temperature: params.temperature }
					: {})
			});

			log(
				'debug',
				`${this.name} generateObject completed successfully for model: ${params.modelId}`
			);

			const inputTokens =
				result.usage?.inputTokens ?? result.usage?.promptTokens ?? 0;
			const outputTokens =
				result.usage?.outputTokens ?? result.usage?.completionTokens ?? 0;
			const totalTokens =
				result.usage?.totalTokens ?? inputTokens + outputTokens;

			return {
				object: result.object,
				usage: {
					inputTokens,
					outputTokens,
					totalTokens
				}
			};
		} catch (error) {
			// Check if this is a JSON parsing error that we can potentially fix
			if (
				NoObjectGeneratedError.isInstance(error) &&
				error.cause instanceof JSONParseError &&
				error.cause.text
			) {
				log(
					'warn',
					`${this.name} generated malformed JSON, attempting to repair...`
				);

				try {
					// Use jsonrepair to fix the malformed JSON
					const repairedJson = jsonrepair(error.cause.text);
					const parsed = JSON.parse(repairedJson);

					log('info', `Successfully repaired ${this.name} JSON output`);

					// Return in the expected format
					return {
						object: parsed,
						usage: {
							// Extract usage information from the error if available
							inputTokens:
								error.usage?.promptTokens || error.usage?.inputTokens || 0,
							outputTokens:
								error.usage?.completionTokens || error.usage?.outputTokens || 0,
							totalTokens: error.usage?.totalTokens || 0
						}
					};
				} catch (repairError) {
					log(
						'error',
						`Failed to repair ${this.name} JSON: ${repairError.message}`
					);
					// Fall through to handleError with original error
				}
			}

			this.handleError('object generation', error);
		}
	}
}

```

--------------------------------------------------------------------------------
/tests/integration/cli/commands.test.js:
--------------------------------------------------------------------------------

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

// --- Define mock functions ---
const mockGetMainModelId = jest.fn().mockReturnValue('claude-3-opus');
const mockGetResearchModelId = jest.fn().mockReturnValue('gpt-4-turbo');
const mockGetFallbackModelId = jest.fn().mockReturnValue('claude-3-haiku');
const mockSetMainModel = jest.fn().mockResolvedValue(true);
const mockSetResearchModel = jest.fn().mockResolvedValue(true);
const mockSetFallbackModel = jest.fn().mockResolvedValue(true);
const mockGetAvailableModels = jest.fn().mockReturnValue([
	{ id: 'claude-3-opus', name: 'Claude 3 Opus', provider: 'anthropic' },
	{ id: 'gpt-4-turbo', name: 'GPT-4 Turbo', provider: 'openai' },
	{ id: 'claude-3-haiku', name: 'Claude 3 Haiku', provider: 'anthropic' },
	{ id: 'claude-3-sonnet', name: 'Claude 3 Sonnet', provider: 'anthropic' }
]);

// Mock UI related functions
const mockDisplayHelp = jest.fn();
const mockDisplayBanner = jest.fn();
const mockLog = jest.fn();
const mockStartLoadingIndicator = jest.fn(() => ({ stop: jest.fn() }));
const mockStopLoadingIndicator = jest.fn();

// --- Setup mocks using unstable_mockModule (recommended for ES modules) ---
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
	getMainModelId: mockGetMainModelId,
	getResearchModelId: mockGetResearchModelId,
	getFallbackModelId: mockGetFallbackModelId,
	setMainModel: mockSetMainModel,
	setResearchModel: mockSetResearchModel,
	setFallbackModel: mockSetFallbackModel,
	getAvailableModels: mockGetAvailableModels,
	VALID_PROVIDERS: ['anthropic', 'openai']
}));

jest.unstable_mockModule('../../../scripts/modules/ui.js', () => ({
	displayHelp: mockDisplayHelp,
	displayBanner: mockDisplayBanner,
	log: mockLog,
	startLoadingIndicator: mockStartLoadingIndicator,
	stopLoadingIndicator: mockStopLoadingIndicator
}));

// --- Mock chalk for consistent output formatting ---
const mockChalk = {
	red: jest.fn((text) => text),
	yellow: jest.fn((text) => text),
	blue: jest.fn((text) => text),
	green: jest.fn((text) => text),
	gray: jest.fn((text) => text),
	dim: jest.fn((text) => text),
	bold: {
		cyan: jest.fn((text) => text),
		white: jest.fn((text) => text),
		red: jest.fn((text) => text)
	},
	cyan: {
		bold: jest.fn((text) => text)
	},
	white: {
		bold: jest.fn((text) => text)
	}
};
// Default function for chalk itself
mockChalk.default = jest.fn((text) => text);
// Add the methods to the function itself for dual usage
Object.keys(mockChalk).forEach((key) => {
	if (key !== 'default') mockChalk.default[key] = mockChalk[key];
});

jest.unstable_mockModule('chalk', () => ({
	default: mockChalk.default
}));

// --- Import modules (AFTER mock setup) ---
let configManager, ui, chalk;

describe('CLI Models Command (Action Handler Test)', () => {
	// Setup dynamic imports before tests run
	beforeAll(async () => {
		configManager = await import('../../../scripts/modules/config-manager.js');
		ui = await import('../../../scripts/modules/ui.js');
		chalk = (await import('chalk')).default;
	});

	// --- Replicate the action handler logic from commands.js ---
	async function modelsAction(options) {
		options = options || {}; // Ensure options object exists
		const availableModels = configManager.getAvailableModels();

		const findProvider = (modelId) => {
			const modelInfo = availableModels.find((m) => m.id === modelId);
			return modelInfo?.provider;
		};

		let modelSetAction = false;

		try {
			if (options.setMain) {
				const modelId = options.setMain;
				if (typeof modelId !== 'string' || modelId.trim() === '') {
					console.error(
						chalk.red('Error: --set-main flag requires a valid model ID.')
					);
					process.exit(1);
				}
				const provider = findProvider(modelId);
				if (!provider) {
					console.error(
						chalk.red(
							`Error: Model ID "${modelId}" not found in available models.`
						)
					);
					process.exit(1);
				}
				if (await configManager.setMainModel(provider, modelId)) {
					console.log(
						chalk.green(`Main model set to: ${modelId} (Provider: ${provider})`)
					);
					modelSetAction = true;
				} else {
					console.error(chalk.red(`Failed to set main model.`));
					process.exit(1);
				}
			}

			if (options.setResearch) {
				const modelId = options.setResearch;
				if (typeof modelId !== 'string' || modelId.trim() === '') {
					console.error(
						chalk.red('Error: --set-research flag requires a valid model ID.')
					);
					process.exit(1);
				}
				const provider = findProvider(modelId);
				if (!provider) {
					console.error(
						chalk.red(
							`Error: Model ID "${modelId}" not found in available models.`
						)
					);
					process.exit(1);
				}
				if (await configManager.setResearchModel(provider, modelId)) {
					console.log(
						chalk.green(
							`Research model set to: ${modelId} (Provider: ${provider})`
						)
					);
					modelSetAction = true;
				} else {
					console.error(chalk.red(`Failed to set research model.`));
					process.exit(1);
				}
			}

			if (options.setFallback) {
				const modelId = options.setFallback;
				if (typeof modelId !== 'string' || modelId.trim() === '') {
					console.error(
						chalk.red('Error: --set-fallback flag requires a valid model ID.')
					);
					process.exit(1);
				}
				const provider = findProvider(modelId);
				if (!provider) {
					console.error(
						chalk.red(
							`Error: Model ID "${modelId}" not found in available models.`
						)
					);
					process.exit(1);
				}
				if (await configManager.setFallbackModel(provider, modelId)) {
					console.log(
						chalk.green(
							`Fallback model set to: ${modelId} (Provider: ${provider})`
						)
					);
					modelSetAction = true;
				} else {
					console.error(chalk.red(`Failed to set fallback model.`));
					process.exit(1);
				}
			}

			if (!modelSetAction) {
				const currentMain = configManager.getMainModelId();
				const currentResearch = configManager.getResearchModelId();
				const currentFallback = configManager.getFallbackModelId();

				if (!availableModels || availableModels.length === 0) {
					console.log(chalk.yellow('No models defined in configuration.'));
					return;
				}

				// Create a mock table for testing - avoid using Table constructor
				const mockTableData = [];
				availableModels.forEach((model) => {
					if (model.id.startsWith('[') && model.id.endsWith(']')) return;
					mockTableData.push([
						model.id,
						model.name || 'N/A',
						model.provider || 'N/A',
						model.id === currentMain ? chalk.green('   ✓') : '',
						model.id === currentResearch ? chalk.green('     ✓') : '',
						model.id === currentFallback ? chalk.green('     ✓') : ''
					]);
				});

				// In a real implementation, we would use cli-table3, but for testing
				// we'll just log 'Mock Table Output'
				console.log('Mock Table Output');
			}
		} catch (error) {
			// Use ui.log mock if available, otherwise console.error
			(ui.log || console.error)(
				`Error processing models command: ${error.message}`,
				'error'
			);
			if (error.stack) {
				(ui.log || console.error)(error.stack, 'debug');
			}
			throw error; // Re-throw for test failure
		}
	}
	// --- End of Action Handler Logic ---

	let originalConsoleLog;
	let originalConsoleError;
	let originalProcessExit;

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

		// Save original console methods
		originalConsoleLog = console.log;
		originalConsoleError = console.error;
		originalProcessExit = process.exit;

		// Mock console and process.exit
		console.log = jest.fn();
		console.error = jest.fn();
		process.exit = jest.fn((code) => {
			throw new Error(`process.exit(${code}) called`);
		});
	});

	afterEach(() => {
		// Restore original console methods
		console.log = originalConsoleLog;
		console.error = originalConsoleError;
		process.exit = originalProcessExit;
	});

	// --- Test Cases (Calling modelsAction directly) ---

	it('should call setMainModel with correct provider and ID', async () => {
		const modelId = 'claude-3-opus';
		const expectedProvider = 'anthropic';
		await modelsAction({ setMain: modelId });
		expect(mockSetMainModel).toHaveBeenCalledWith(expectedProvider, modelId);
		expect(console.log).toHaveBeenCalledWith(
			expect.stringContaining(`Main model set to: ${modelId}`)
		);
		expect(console.log).toHaveBeenCalledWith(
			expect.stringContaining(`(Provider: ${expectedProvider})`)
		);
	});

	it('should show an error if --set-main model ID is not found', async () => {
		await expect(
			modelsAction({ setMain: 'non-existent-model' })
		).rejects.toThrow(/process.exit/); // Expect exit call
		expect(mockSetMainModel).not.toHaveBeenCalled();
		expect(console.error).toHaveBeenCalledWith(
			expect.stringContaining('Model ID "non-existent-model" not found')
		);
	});

	it('should call setResearchModel with correct provider and ID', async () => {
		const modelId = 'gpt-4-turbo';
		const expectedProvider = 'openai';
		await modelsAction({ setResearch: modelId });
		expect(mockSetResearchModel).toHaveBeenCalledWith(
			expectedProvider,
			modelId
		);
		expect(console.log).toHaveBeenCalledWith(
			expect.stringContaining(`Research model set to: ${modelId}`)
		);
		expect(console.log).toHaveBeenCalledWith(
			expect.stringContaining(`(Provider: ${expectedProvider})`)
		);
	});

	it('should call setFallbackModel with correct provider and ID', async () => {
		const modelId = 'claude-3-haiku';
		const expectedProvider = 'anthropic';
		await modelsAction({ setFallback: modelId });
		expect(mockSetFallbackModel).toHaveBeenCalledWith(
			expectedProvider,
			modelId
		);
		expect(console.log).toHaveBeenCalledWith(
			expect.stringContaining(`Fallback model set to: ${modelId}`)
		);
		expect(console.log).toHaveBeenCalledWith(
			expect.stringContaining(`(Provider: ${expectedProvider})`)
		);
	});

	it('should call all set*Model functions when all flags are used', async () => {
		const mainModelId = 'claude-3-opus';
		const researchModelId = 'gpt-4-turbo';
		const fallbackModelId = 'claude-3-haiku';
		const mainProvider = 'anthropic';
		const researchProvider = 'openai';
		const fallbackProvider = 'anthropic';

		await modelsAction({
			setMain: mainModelId,
			setResearch: researchModelId,
			setFallback: fallbackModelId
		});
		expect(mockSetMainModel).toHaveBeenCalledWith(mainProvider, mainModelId);
		expect(mockSetResearchModel).toHaveBeenCalledWith(
			researchProvider,
			researchModelId
		);
		expect(mockSetFallbackModel).toHaveBeenCalledWith(
			fallbackProvider,
			fallbackModelId
		);
	});

	it('should call specific get*ModelId and getAvailableModels and log table when run without flags', async () => {
		await modelsAction({}); // Call with empty options

		expect(mockGetMainModelId).toHaveBeenCalled();
		expect(mockGetResearchModelId).toHaveBeenCalled();
		expect(mockGetFallbackModelId).toHaveBeenCalled();
		expect(mockGetAvailableModels).toHaveBeenCalled();

		expect(console.log).toHaveBeenCalled();
		// Check the mocked Table.toString() was used via console.log
		expect(console.log).toHaveBeenCalledWith('Mock Table Output');
	});
});

```

--------------------------------------------------------------------------------
/packages/tm-core/tests/integration/storage/activity-logger.test.ts:
--------------------------------------------------------------------------------

```typescript
import os from 'os';
import path from 'path';
import fs from 'fs-extra';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
	filterActivityLog,
	logActivity,
	readActivityLog
} from '../../../src/storage/activity-logger.js';

describe('Activity Logger', () => {
	let testDir: string;
	let activityPath: string;

	beforeEach(async () => {
		// Create a unique temporary test directory
		const prefix = path.join(os.tmpdir(), 'activity-test-');
		testDir = await fs.mkdtemp(prefix);
		activityPath = path.join(testDir, 'activity.jsonl');
	});

	afterEach(async () => {
		// Clean up test directory
		await fs.remove(testDir);
	});

	describe('logActivity', () => {
		it('should create activity log file on first write', async () => {
			await logActivity(activityPath, {
				type: 'phase-start',
				phase: 'red',
				data: {}
			});

			const exists = await fs.pathExists(activityPath);
			expect(exists).toBe(true);
		});

		it('should append event to log file', async () => {
			await logActivity(activityPath, {
				type: 'phase-start',
				phase: 'red'
			});

			const content = await fs.readFile(activityPath, 'utf-8');
			const lines = content.trim().split(/\r?\n/);

			expect(lines.length).toBe(1);
		});

		it('should write valid JSONL format', async () => {
			await logActivity(activityPath, {
				type: 'test-run',
				result: 'pass'
			});

			const content = await fs.readFile(activityPath, 'utf-8');
			const line = content.trim();
			const parsed = JSON.parse(line);

			expect(parsed).toBeDefined();
			expect(parsed.type).toBe('test-run');
		});

		it('should include timestamp in log entry', async () => {
			const before = new Date().toISOString();
			await logActivity(activityPath, {
				type: 'phase-start',
				phase: 'red'
			});
			const after = new Date().toISOString();

			const logs = await readActivityLog(activityPath);
			expect(logs[0].timestamp).toBeDefined();
			expect(logs[0].timestamp >= before).toBe(true);
			expect(logs[0].timestamp <= after).toBe(true);
		});

		it('should append multiple events', async () => {
			await logActivity(activityPath, { type: 'event1' });
			await logActivity(activityPath, { type: 'event2' });
			await logActivity(activityPath, { type: 'event3' });

			const logs = await readActivityLog(activityPath);
			expect(logs.length).toBe(3);
			expect(logs[0].type).toBe('event1');
			expect(logs[1].type).toBe('event2');
			expect(logs[2].type).toBe('event3');
		});

		it('should preserve event data', async () => {
			const eventData = {
				type: 'git-commit',
				hash: 'abc123',
				message: 'test commit',
				files: ['file1.ts', 'file2.ts']
			};

			await logActivity(activityPath, eventData);

			const logs = await readActivityLog(activityPath);
			expect(logs[0].type).toBe('git-commit');
			expect(logs[0].hash).toBe('abc123');
			expect(logs[0].message).toBe('test commit');
			expect(logs[0].files).toEqual(['file1.ts', 'file2.ts']);
		});

		it('should handle nested objects in event data', async () => {
			await logActivity(activityPath, {
				type: 'test-results',
				results: {
					passed: 10,
					failed: 2,
					details: { coverage: 85 }
				}
			});

			const logs = await readActivityLog(activityPath);
			expect(logs[0].results.details.coverage).toBe(85);
		});

		it('should handle special characters in event data', async () => {
			await logActivity(activityPath, {
				type: 'error',
				message: 'Error: "Something went wrong"\nLine 2'
			});

			const logs = await readActivityLog(activityPath);
			expect(logs[0].message).toBe('Error: "Something went wrong"\nLine 2');
		});

		it('should create parent directory if it does not exist', async () => {
			const nestedPath = path.join(testDir, 'nested', 'dir', 'activity.jsonl');

			await logActivity(nestedPath, { type: 'test' });

			const exists = await fs.pathExists(nestedPath);
			expect(exists).toBe(true);
		});
	});

	describe('readActivityLog', () => {
		it('should read all events from log', async () => {
			await logActivity(activityPath, { type: 'event1' });
			await logActivity(activityPath, { type: 'event2' });

			const logs = await readActivityLog(activityPath);

			expect(logs.length).toBe(2);
			expect(logs[0].type).toBe('event1');
			expect(logs[1].type).toBe('event2');
		});

		it('should return empty array for non-existent file', async () => {
			const logs = await readActivityLog(activityPath);
			expect(logs).toEqual([]);
		});

		it('should parse JSONL correctly', async () => {
			await logActivity(activityPath, { type: 'event1', data: 'test1' });
			await logActivity(activityPath, { type: 'event2', data: 'test2' });

			const logs = await readActivityLog(activityPath);

			expect(logs[0].data).toBe('test1');
			expect(logs[1].data).toBe('test2');
		});

		it('should handle empty lines', async () => {
			await fs.writeFile(
				activityPath,
				'{"type":"event1"}\n\n{"type":"event2"}\n'
			);

			const logs = await readActivityLog(activityPath);

			expect(logs.length).toBe(2);
			expect(logs[0].type).toBe('event1');
			expect(logs[1].type).toBe('event2');
		});

		it('should throw error for invalid JSON line', async () => {
			await fs.writeFile(activityPath, '{"type":"event1"}\ninvalid json\n');

			await expect(readActivityLog(activityPath)).rejects.toThrow(
				/Invalid JSON/i
			);
		});

		it('should preserve chronological order', async () => {
			for (let i = 0; i < 10; i++) {
				await logActivity(activityPath, { type: 'event', index: i });
			}

			const logs = await readActivityLog(activityPath);

			for (let i = 0; i < 10; i++) {
				expect(logs[i].index).toBe(i);
			}
		});
	});

	describe('filterActivityLog', () => {
		beforeEach(async () => {
			// Create sample log entries
			await logActivity(activityPath, { type: 'phase-start', phase: 'red' });
			await logActivity(activityPath, { type: 'test-run', result: 'fail' });
			await logActivity(activityPath, { type: 'phase-start', phase: 'green' });
			await logActivity(activityPath, { type: 'test-run', result: 'pass' });
			await logActivity(activityPath, { type: 'git-commit', hash: 'abc123' });
		});

		it('should filter by event type', async () => {
			const filtered = await filterActivityLog(activityPath, {
				type: 'phase-start'
			});

			expect(filtered.length).toBe(2);
			expect(filtered[0].type).toBe('phase-start');
			expect(filtered[1].type).toBe('phase-start');
		});

		it('should filter by multiple criteria', async () => {
			const filtered = await filterActivityLog(activityPath, {
				type: 'test-run',
				result: 'pass'
			});

			expect(filtered.length).toBe(1);
			expect(filtered[0].result).toBe('pass');
		});

		it('should return all events when no filter provided', async () => {
			const filtered = await filterActivityLog(activityPath, {});

			expect(filtered.length).toBe(5);
		});

		it('should filter by timestamp range', async () => {
			const logs = await readActivityLog(activityPath);
			const midpoint = logs[2].timestamp;

			const filtered = await filterActivityLog(activityPath, {
				timestampFrom: midpoint
			});

			// Should get events from midpoint onwards (inclusive)
			// Expect at least 3 events, may be more due to timestamp collisions
			expect(filtered.length).toBeGreaterThanOrEqual(3);
			expect(filtered.length).toBeLessThanOrEqual(5);
		});

		it('should filter by custom predicate', async () => {
			const filtered = await filterActivityLog(activityPath, {
				predicate: (event: any) => event.phase === 'red'
			});

			expect(filtered.length).toBe(1);
			expect(filtered[0].phase).toBe('red');
		});

		it('should return empty array for non-matching filter', async () => {
			const filtered = await filterActivityLog(activityPath, {
				type: 'non-existent'
			});

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

		it('should handle nested property filters', async () => {
			await logActivity(activityPath, {
				type: 'test-results',
				results: { coverage: 85 }
			});

			const filtered = await filterActivityLog(activityPath, {
				predicate: (event: any) => event.results?.coverage > 80
			});

			expect(filtered.length).toBe(1);
			expect(filtered[0].results.coverage).toBe(85);
		});
	});

	describe('Event types', () => {
		it('should support phase-transition events', async () => {
			await logActivity(activityPath, {
				type: 'phase-transition',
				from: 'red',
				to: 'green'
			});

			const logs = await readActivityLog(activityPath);
			expect(logs[0].type).toBe('phase-transition');
			expect(logs[0].from).toBe('red');
			expect(logs[0].to).toBe('green');
		});

		it('should support test-run events', async () => {
			await logActivity(activityPath, {
				type: 'test-run',
				result: 'pass',
				testsRun: 50,
				testsPassed: 50,
				testsFailed: 0,
				coverage: 85.5
			});

			const logs = await readActivityLog(activityPath);
			expect(logs[0].testsRun).toBe(50);
			expect(logs[0].coverage).toBe(85.5);
		});

		it('should support git-operation events', async () => {
			await logActivity(activityPath, {
				type: 'git-commit',
				hash: 'abc123def456',
				message: 'feat: add new feature',
				files: ['file1.ts', 'file2.ts']
			});

			const logs = await readActivityLog(activityPath);
			expect(logs[0].hash).toBe('abc123def456');
			expect(logs[0].files.length).toBe(2);
		});

		it('should support error events', async () => {
			await logActivity(activityPath, {
				type: 'error',
				phase: 'red',
				error: 'Test failed',
				stack: 'Error stack trace...'
			});

			const logs = await readActivityLog(activityPath);
			expect(logs[0].type).toBe('error');
			expect(logs[0].error).toBe('Test failed');
		});
	});

	describe('Concurrency handling', () => {
		it('should handle rapid concurrent writes', async () => {
			const writes: Promise<void>[] = [];
			for (let i = 0; i < 50; i++) {
				writes.push(logActivity(activityPath, { type: 'event', index: i }));
			}

			await Promise.all(writes);

			const logs = await readActivityLog(activityPath);
			expect(logs.length).toBe(50);
		});

		it('should maintain data integrity with concurrent writes', async () => {
			const writes: Promise<void>[] = [];
			for (let i = 0; i < 20; i++) {
				writes.push(
					logActivity(activityPath, {
						type: 'concurrent-test',
						id: i,
						data: `data-${i}`
					})
				);
			}

			await Promise.all(writes);

			const logs = await readActivityLog(activityPath);

			// All events should be present
			expect(logs.length).toBe(20);
			// Validate ids set
			const ids = new Set(logs.map((l) => l.id));
			expect([...ids].sort((a, b) => a - b)).toEqual([...Array(20).keys()]);
			// Validate shape
			for (const log of logs) {
				expect(log.type).toBe('concurrent-test');
				expect(typeof log.id).toBe('number');
				expect(log.data).toMatch(/^data-\d+$/);
			}
		});
	});

	describe('File integrity', () => {
		it('should maintain valid JSONL after many operations', async () => {
			for (let i = 0; i < 100; i++) {
				await logActivity(activityPath, { type: 'test', iteration: i });
			}

			const content = await fs.readFile(activityPath, 'utf-8');
			const lines = content.trim().split(/\r?\n/);

			expect(lines.length).toBe(100);

			// All lines should be valid JSON
			for (const line of lines) {
				expect(() => JSON.parse(line)).not.toThrow();
			}
		});
	});
});

```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/analyze-complexity.test.js:
--------------------------------------------------------------------------------

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

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

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

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

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

// This is a more complex mock of Zod to test actual validation
const createZodMock = () => {
	// Storage for validation rules
	const validationRules = {
		threshold: {
			type: 'coerce.number',
			min: 1,
			max: 10,
			optional: true
		}
	};

	// Create validator functions
	const validateThreshold = (value) => {
		if (value === undefined && validationRules.threshold.optional) {
			return true;
		}

		// Attempt to coerce to number (if string)
		const numValue = typeof value === 'string' ? Number(value) : value;

		// Check if it's a valid number
		if (isNaN(numValue)) {
			throw new Error(`Invalid type for parameter 'threshold'`);
		}

		// Check min/max constraints
		if (numValue < validationRules.threshold.min) {
			throw new Error(
				`Threshold must be at least ${validationRules.threshold.min}`
			);
		}

		if (numValue > validationRules.threshold.max) {
			throw new Error(
				`Threshold must be at most ${validationRules.threshold.max}`
			);
		}

		return true;
	};

	// Create actual validators for parameters
	const validators = {
		threshold: validateThreshold
	};

	// Main validation function for the entire object
	const validateObject = (obj) => {
		// Validate each field
		if (obj.threshold !== undefined) {
			validators.threshold(obj.threshold);
		}

		// If we get here, all validations passed
		return obj;
	};

	// Base object with chainable methods
	const zodBase = {
		optional: () => {
			return zodBase;
		},
		describe: (desc) => {
			return zodBase;
		}
	};

	// Number-specific methods
	const zodNumber = {
		...zodBase,
		min: (value) => {
			return zodNumber;
		},
		max: (value) => {
			return zodNumber;
		}
	};

	// Main mock implementation
	const mockZod = {
		object: () => ({
			...zodBase,
			// This parse method will be called by the tool execution
			parse: validateObject
		}),
		string: () => zodBase,
		boolean: () => zodBase,
		number: () => zodNumber,
		coerce: {
			number: () => zodNumber
		},
		union: (schemas) => zodBase,
		_def: {
			shape: () => ({
				output: {},
				model: {},
				threshold: {},
				file: {},
				research: {},
				projectRoot: {}
			})
		}
	};

	return mockZod;
};

// Create our Zod mock
const mockZod = createZodMock();

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

// DO NOT import the real module - create a fake implementation
// This is the fake implementation of registerAnalyzeTool
const registerAnalyzeTool = (server) => {
	// Create simplified version of the tool config
	const toolConfig = {
		name: 'analyze_project_complexity',
		description:
			'Analyze task complexity and generate expansion recommendations',
		parameters: mockZod.object(),

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

			try {
				log.info &&
					log.info(
						`Analyzing task complexity with args: ${JSON.stringify(args)}`
					);

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

				// Call analyzeTaskComplexityDirect
				const result = mockAnalyzeTaskComplexityDirect(
					{
						...args,
						projectRoot: rootFolder
					},
					log,
					{ session }
				);

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

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

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

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

	// Test data
	const validArgs = {
		output: 'output/path/report.json',
		model: 'claude-3-opus-20240229',
		threshold: 5,
		research: true
	};

	// Standard responses
	const successResponse = {
		success: true,
		data: {
			message: 'Task complexity analysis complete',
			reportPath: '/mock/project/root/output/path/report.json',
			reportSummary: {
				taskCount: 10,
				highComplexityTasks: 3,
				mediumComplexityTasks: 5,
				lowComplexityTasks: 2
			}
		}
	};

	const errorResponse = {
		success: false,
		error: {
			code: 'ANALYZE_ERROR',
			message: 'Failed to analyze task complexity'
		}
	};

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

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

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

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

	test('should register the tool correctly', () => {
		// Verify tool was registered
		expect(mockServer.addTool).toHaveBeenCalledWith(
			expect.objectContaining({
				name: 'analyze_project_complexity',
				description:
					'Analyze task complexity and generate expansion recommendations',
				parameters: expect.any(Object),
				execute: expect.any(Function)
			})
		);

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

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

		// Test with valid numeric threshold
		const args = { ...validArgs, threshold: 7 };
		executeFunction(args, mockContext);

		// Verify analyzeTaskComplexityDirect was called with correct arguments
		expect(mockAnalyzeTaskComplexityDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				threshold: 7,
				projectRoot: '/mock/project/root'
			}),
			mockLogger,
			{ session: mockContext.session }
		);

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

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

		// Test with valid string threshold
		const args = { ...validArgs, threshold: '7' };
		executeFunction(args, mockContext);

		// The mock doesn't actually coerce the string, just verify that the string is passed correctly
		expect(mockAnalyzeTaskComplexityDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				threshold: '7', // Expect string value, not coerced to number in our mock
				projectRoot: '/mock/project/root'
			}),
			mockLogger,
			{ session: mockContext.session }
		);
	});

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

		// Test with decimal threshold
		const args = { ...validArgs, threshold: 6.5 };
		executeFunction(args, mockContext);

		// Verify it was passed correctly
		expect(mockAnalyzeTaskComplexityDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				threshold: 6.5,
				projectRoot: '/mock/project/root'
			}),
			mockLogger,
			{ session: mockContext.session }
		);
	});

	test('should execute the tool without threshold parameter', () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

		// Test without threshold (should use default)
		const { threshold, ...argsWithoutThreshold } = validArgs;
		executeFunction(argsWithoutThreshold, mockContext);

		// Verify threshold is undefined
		expect(mockAnalyzeTaskComplexityDirect).toHaveBeenCalledWith(
			expect.objectContaining({
				projectRoot: '/mock/project/root'
			}),
			mockLogger,
			{ session: mockContext.session }
		);

		// Check threshold is not included
		const callArgs = mockAnalyzeTaskComplexityDirect.mock.calls[0][0];
		expect(callArgs).not.toHaveProperty('threshold');
	});

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

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

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

		// Verify analyzeTaskComplexityDirect was called
		expect(mockAnalyzeTaskComplexityDirect).toHaveBeenCalled();

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

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

		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

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

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

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

	test('should verify research parameter is correctly passed', () => {
		// Setup context
		const mockContext = {
			log: mockLogger,
			session: { workingDirectory: '/mock/dir' }
		};

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

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

		// Reset mocks
		jest.clearAllMocks();

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

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

```

--------------------------------------------------------------------------------
/tests/unit/mcp/tools/tool-registration.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * tool-registration.test.js
 * Comprehensive unit tests for the Task Master MCP tool registration system
 * Tests environment variable control system covering all configuration modes and edge cases
 */

import {
	describe,
	it,
	expect,
	beforeEach,
	afterEach,
	jest
} from '@jest/globals';

import {
	EXPECTED_TOOL_COUNTS,
	EXPECTED_CORE_TOOLS,
	validateToolCounts,
	validateToolStructure
} from '../../../helpers/tool-counts.js';

import { registerTaskMasterTools } from '../../../../mcp-server/src/tools/index.js';
import {
	toolRegistry,
	coreTools,
	standardTools
} from '../../../../mcp-server/src/tools/tool-registry.js';

// Derive constants from imported registry to avoid brittle magic numbers
const ALL_COUNT = Object.keys(toolRegistry).length;
const CORE_COUNT = coreTools.length;
const STANDARD_COUNT = standardTools.length;

describe('Task Master Tool Registration System', () => {
	let mockServer;
	let originalEnv;

	beforeEach(() => {
		originalEnv = process.env.TASK_MASTER_TOOLS;

		mockServer = {
			tools: [],
			addTool: jest.fn((tool) => {
				mockServer.tools.push(tool);
				return tool;
			})
		};

		delete process.env.TASK_MASTER_TOOLS;
	});

	afterEach(() => {
		if (originalEnv !== undefined) {
			process.env.TASK_MASTER_TOOLS = originalEnv;
		} else {
			delete process.env.TASK_MASTER_TOOLS;
		}

		jest.clearAllMocks();
	});

	describe('Test Environment Setup', () => {
		it('should have properly configured mock server', () => {
			expect(mockServer).toBeDefined();
			expect(typeof mockServer.addTool).toBe('function');
			expect(Array.isArray(mockServer.tools)).toBe(true);
			expect(mockServer.tools.length).toBe(0);
		});

		it('should have correct tool registry structure', () => {
			const validation = validateToolCounts();
			expect(validation.isValid).toBe(true);

			if (!validation.isValid) {
				console.error('Tool count validation failed:', validation);
			}

			expect(validation.actual.total).toBe(EXPECTED_TOOL_COUNTS.total);
			expect(validation.actual.core).toBe(EXPECTED_TOOL_COUNTS.core);
			expect(validation.actual.standard).toBe(EXPECTED_TOOL_COUNTS.standard);
		});

		it('should have correct core tools', () => {
			const structure = validateToolStructure();
			expect(structure.isValid).toBe(true);

			if (!structure.isValid) {
				console.error('Tool structure validation failed:', structure);
			}

			expect(coreTools).toEqual(expect.arrayContaining(EXPECTED_CORE_TOOLS));
			expect(coreTools.length).toBe(EXPECTED_TOOL_COUNTS.core);
		});

		it('should have correct standard tools that include all core tools', () => {
			const structure = validateToolStructure();
			expect(structure.details.coreInStandard).toBe(true);
			expect(standardTools.length).toBe(EXPECTED_TOOL_COUNTS.standard);

			coreTools.forEach((tool) => {
				expect(standardTools).toContain(tool);
			});
		});

		it('should have all expected tools in registry', () => {
			const expectedTools = [
				'initialize_project',
				'models',
				'research',
				'add_tag',
				'delete_tag',
				'get_tasks',
				'next_task',
				'get_task'
			];
			expectedTools.forEach((tool) => {
				expect(toolRegistry).toHaveProperty(tool);
			});
		});
	});

	describe('Configuration Modes', () => {
		it(`should register all tools (${ALL_COUNT}) when TASK_MASTER_TOOLS is not set (default behavior)`, () => {
			delete process.env.TASK_MASTER_TOOLS;

			registerTaskMasterTools(mockServer);

			expect(mockServer.addTool).toHaveBeenCalledTimes(
				EXPECTED_TOOL_COUNTS.total
			);
		});

		it(`should register all tools (${ALL_COUNT}) when TASK_MASTER_TOOLS=all`, () => {
			process.env.TASK_MASTER_TOOLS = 'all';

			registerTaskMasterTools(mockServer);

			expect(mockServer.addTool).toHaveBeenCalledTimes(ALL_COUNT);
		});

		it(`should register exactly ${CORE_COUNT} core tools when TASK_MASTER_TOOLS=core`, () => {
			process.env.TASK_MASTER_TOOLS = 'core';

			registerTaskMasterTools(mockServer, 'core');

			expect(mockServer.addTool).toHaveBeenCalledTimes(
				EXPECTED_TOOL_COUNTS.core
			);
		});

		it(`should register exactly ${STANDARD_COUNT} standard tools when TASK_MASTER_TOOLS=standard`, () => {
			process.env.TASK_MASTER_TOOLS = 'standard';

			registerTaskMasterTools(mockServer, 'standard');

			expect(mockServer.addTool).toHaveBeenCalledTimes(
				EXPECTED_TOOL_COUNTS.standard
			);
		});

		it(`should treat lean as alias for core mode (${CORE_COUNT} tools)`, () => {
			process.env.TASK_MASTER_TOOLS = 'lean';

			registerTaskMasterTools(mockServer, 'lean');

			expect(mockServer.addTool).toHaveBeenCalledTimes(CORE_COUNT);
		});

		it('should handle case insensitive configuration values', () => {
			process.env.TASK_MASTER_TOOLS = 'CORE';

			registerTaskMasterTools(mockServer, 'CORE');

			expect(mockServer.addTool).toHaveBeenCalledTimes(CORE_COUNT);
		});
	});

	describe('Custom Tool Selection and Edge Cases', () => {
		it('should register specific tools from comma-separated list', () => {
			process.env.TASK_MASTER_TOOLS = 'get_tasks,next_task,get_task';

			registerTaskMasterTools(mockServer, 'get_tasks,next_task,get_task');

			expect(mockServer.addTool).toHaveBeenCalledTimes(3);
		});

		it('should handle mixed valid and invalid tool names gracefully', () => {
			process.env.TASK_MASTER_TOOLS =
				'invalid_tool,get_tasks,fake_tool,next_task';

			registerTaskMasterTools(
				mockServer,
				'invalid_tool,get_tasks,fake_tool,next_task'
			);

			expect(mockServer.addTool).toHaveBeenCalledTimes(2);
		});

		it('should default to all tools with completely invalid input', () => {
			process.env.TASK_MASTER_TOOLS = 'completely_invalid';

			registerTaskMasterTools(mockServer);

			expect(mockServer.addTool).toHaveBeenCalledTimes(ALL_COUNT);
		});

		it('should handle empty string environment variable', () => {
			process.env.TASK_MASTER_TOOLS = '';

			registerTaskMasterTools(mockServer);

			expect(mockServer.addTool).toHaveBeenCalledTimes(ALL_COUNT);
		});

		it('should handle whitespace in comma-separated lists', () => {
			process.env.TASK_MASTER_TOOLS = ' get_tasks , next_task , get_task ';

			registerTaskMasterTools(mockServer, ' get_tasks , next_task , get_task ');

			expect(mockServer.addTool).toHaveBeenCalledTimes(3);
		});

		it('should ignore duplicate tools in list', () => {
			process.env.TASK_MASTER_TOOLS = 'get_tasks,get_tasks,next_task,get_tasks';

			registerTaskMasterTools(
				mockServer,
				'get_tasks,get_tasks,next_task,get_tasks'
			);

			expect(mockServer.addTool).toHaveBeenCalledTimes(2);
		});

		it('should handle only commas and empty entries', () => {
			process.env.TASK_MASTER_TOOLS = ',,,';

			registerTaskMasterTools(mockServer);

			expect(mockServer.addTool).toHaveBeenCalledTimes(ALL_COUNT);
		});

		it('should handle single tool selection', () => {
			process.env.TASK_MASTER_TOOLS = 'get_tasks';

			registerTaskMasterTools(mockServer, 'get_tasks');

			expect(mockServer.addTool).toHaveBeenCalledTimes(1);
		});
	});

	describe('Coverage Analysis and Integration Tests', () => {
		it('should provide 100% code coverage for environment control logic', () => {
			const testCases = [
				{
					env: undefined,
					expectedCount: ALL_COUNT,
					description: 'undefined env (all)'
				},
				{
					env: '',
					expectedCount: ALL_COUNT,
					description: 'empty string (all)'
				},
				{ env: 'all', expectedCount: ALL_COUNT, description: 'all mode' },
				{ env: 'core', expectedCount: CORE_COUNT, description: 'core mode' },
				{
					env: 'lean',
					expectedCount: CORE_COUNT,
					description: 'lean mode (alias)'
				},
				{
					env: 'standard',
					expectedCount: STANDARD_COUNT,
					description: 'standard mode'
				},
				{
					env: 'get_tasks,next_task',
					expectedCount: 2,
					description: 'custom list'
				},
				{
					env: 'invalid_tool',
					expectedCount: ALL_COUNT,
					description: 'invalid fallback'
				}
			];

			testCases.forEach((testCase) => {
				delete process.env.TASK_MASTER_TOOLS;
				if (testCase.env !== undefined) {
					process.env.TASK_MASTER_TOOLS = testCase.env;
				}

				mockServer.tools = [];
				mockServer.addTool.mockClear();

				registerTaskMasterTools(mockServer, testCase.env || 'all');

				expect(mockServer.addTool).toHaveBeenCalledTimes(
					testCase.expectedCount
				);
			});
		});

		it('should have optimal performance characteristics', () => {
			const startTime = Date.now();

			process.env.TASK_MASTER_TOOLS = 'all';

			registerTaskMasterTools(mockServer);

			const endTime = Date.now();
			const executionTime = endTime - startTime;

			expect(executionTime).toBeLessThan(100);
			expect(mockServer.addTool).toHaveBeenCalledTimes(ALL_COUNT);
		});

		it('should validate token reduction claims', () => {
			expect(coreTools.length).toBeLessThan(standardTools.length);
			expect(standardTools.length).toBeLessThan(
				Object.keys(toolRegistry).length
			);

			expect(coreTools.length).toBe(CORE_COUNT);
			expect(standardTools.length).toBe(STANDARD_COUNT);
			expect(Object.keys(toolRegistry).length).toBe(ALL_COUNT);

			const allToolsCount = Object.keys(toolRegistry).length;
			const coreReduction =
				((allToolsCount - coreTools.length) / allToolsCount) * 100;
			const standardReduction =
				((allToolsCount - standardTools.length) / allToolsCount) * 100;

			expect(coreReduction).toBeGreaterThan(80);
			expect(standardReduction).toBeGreaterThan(50);
		});

		it('should maintain referential integrity of tool registry', () => {
			coreTools.forEach((tool) => {
				expect(standardTools).toContain(tool);
			});

			standardTools.forEach((tool) => {
				expect(toolRegistry).toHaveProperty(tool);
			});

			Object.keys(toolRegistry).forEach((tool) => {
				expect(typeof toolRegistry[tool]).toBe('function');
			});
		});

		it('should handle concurrent registration attempts', () => {
			process.env.TASK_MASTER_TOOLS = 'core';

			registerTaskMasterTools(mockServer, 'core');
			registerTaskMasterTools(mockServer, 'core');
			registerTaskMasterTools(mockServer, 'core');

			expect(mockServer.addTool).toHaveBeenCalledTimes(CORE_COUNT * 3);
		});

		it('should validate all documented tool categories exist', () => {
			const allTools = Object.keys(toolRegistry);

			const projectSetupTools = allTools.filter((tool) =>
				['initialize_project', 'models', 'rules', 'parse_prd'].includes(tool)
			);
			expect(projectSetupTools.length).toBeGreaterThan(0);

			const taskManagementTools = allTools.filter((tool) =>
				['get_tasks', 'get_task', 'next_task', 'set_task_status'].includes(tool)
			);
			expect(taskManagementTools.length).toBeGreaterThan(0);

			const analysisTools = allTools.filter((tool) =>
				['analyze_project_complexity', 'complexity_report'].includes(tool)
			);
			expect(analysisTools.length).toBeGreaterThan(0);

			const tagManagementTools = allTools.filter((tool) =>
				['add_tag', 'delete_tag', 'list_tags', 'use_tag'].includes(tool)
			);
			expect(tagManagementTools.length).toBeGreaterThan(0);
		});

		it('should handle error conditions gracefully', () => {
			const problematicInputs = [
				'null',
				'undefined',
				'   ',
				'\n\t',
				'special!@#$%^&*()characters',
				'very,very,very,very,very,very,very,long,comma,separated,list,with,invalid,tools,that,should,fallback,to,all'
			];

			problematicInputs.forEach((input) => {
				mockServer.tools = [];
				mockServer.addTool.mockClear();

				process.env.TASK_MASTER_TOOLS = input;

				expect(() => registerTaskMasterTools(mockServer)).not.toThrow();

				expect(mockServer.addTool).toHaveBeenCalledTimes(ALL_COUNT);
			});
		});
	});
});

```

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

```typescript
/**
 * TaskMaster API
 * Main API class that coordinates all modules
 */

import * as vscode from 'vscode';
import { ExtensionLogger } from '../logger';
import type { MCPClientManager } from '../mcpClient';
import { CacheManager } from './cache/cache-manager';
import { MCPClient } from './mcp-client';
import { TaskTransformer } from './transformers/task-transformer';
import type {
	AddSubtaskOptions,
	CacheConfig,
	GetTasksOptions,
	SubtaskData,
	TaskMasterApiConfig,
	TaskMasterApiResponse,
	TaskMasterTask,
	TaskUpdate,
	UpdateSubtaskOptions,
	UpdateTaskOptions,
	UpdateTaskStatusOptions
} from './types';

// Re-export types for backward compatibility
export * from './types';

export class TaskMasterApi {
	private mcpWrapper: MCPClient;
	private cache: CacheManager;
	private transformer: TaskTransformer;
	private config: TaskMasterApiConfig;
	private logger: ExtensionLogger;

	private readonly defaultCacheConfig: CacheConfig = {
		maxSize: 100,
		enableBackgroundRefresh: true,
		refreshInterval: 5 * 60 * 1000, // 5 minutes
		enableAnalytics: true,
		enablePrefetch: true,
		compressionEnabled: false,
		persistToDisk: false
	};

	private readonly defaultConfig: TaskMasterApiConfig = {
		timeout: 30000,
		retryAttempts: 3,
		cacheDuration: 5 * 60 * 1000, // 5 minutes
		cache: this.defaultCacheConfig
	};

	constructor(
		mcpClient: MCPClientManager,
		config?: Partial<TaskMasterApiConfig>
	) {
		this.logger = ExtensionLogger.getInstance();

		// Merge config - ensure cache is always fully defined
		const mergedCache: CacheConfig = {
			maxSize: config?.cache?.maxSize ?? this.defaultCacheConfig.maxSize,
			enableBackgroundRefresh:
				config?.cache?.enableBackgroundRefresh ??
				this.defaultCacheConfig.enableBackgroundRefresh,
			refreshInterval:
				config?.cache?.refreshInterval ??
				this.defaultCacheConfig.refreshInterval,
			enableAnalytics:
				config?.cache?.enableAnalytics ??
				this.defaultCacheConfig.enableAnalytics,
			enablePrefetch:
				config?.cache?.enablePrefetch ?? this.defaultCacheConfig.enablePrefetch,
			compressionEnabled:
				config?.cache?.compressionEnabled ??
				this.defaultCacheConfig.compressionEnabled,
			persistToDisk:
				config?.cache?.persistToDisk ?? this.defaultCacheConfig.persistToDisk
		};

		this.config = {
			...this.defaultConfig,
			...config,
			cache: mergedCache
		};

		// Initialize modules
		this.mcpWrapper = new MCPClient(mcpClient, this.logger, {
			timeout: this.config.timeout,
			retryAttempts: this.config.retryAttempts
		});

		this.cache = new CacheManager(
			{ ...mergedCache, cacheDuration: this.config.cacheDuration },
			this.logger
		);

		this.transformer = new TaskTransformer(this.logger);

		// Start background refresh if enabled
		if (this.config.cache?.enableBackgroundRefresh) {
			this.startBackgroundRefresh();
		}

		this.logger.log('TaskMasterApi: Initialized with modular architecture');
	}

	/**
	 * Get tasks from TaskMaster
	 */
	async getTasks(
		options?: GetTasksOptions
	): Promise<TaskMasterApiResponse<TaskMasterTask[]>> {
		const startTime = Date.now();
		const cacheKey = `get_tasks_${JSON.stringify(options || {})}`;

		try {
			// Check cache first
			const cached = this.cache.get(cacheKey);
			if (cached) {
				return {
					success: true,
					data: cached,
					requestDuration: Date.now() - startTime
				};
			}

			// Prepare MCP tool arguments
			const mcpArgs: Record<string, unknown> = {
				projectRoot: options?.projectRoot || this.getWorkspaceRoot(),
				withSubtasks: options?.withSubtasks ?? true
			};

			if (options?.status) {
				mcpArgs.status = options.status;
			}
			if (options?.tag) {
				mcpArgs.tag = options.tag;
			}

			this.logger.log('Calling get_tasks with args:', mcpArgs);

			// Call MCP tool
			const mcpResponse = await this.mcpWrapper.callTool('get_tasks', mcpArgs);

			// Transform response
			const transformedTasks =
				this.transformer.transformMCPTasksResponse(mcpResponse);

			// Cache the result
			this.cache.set(cacheKey, transformedTasks);

			return {
				success: true,
				data: transformedTasks,
				requestDuration: Date.now() - startTime
			};
		} catch (error) {
			this.logger.error('Error getting tasks:', error);
			return {
				success: false,
				error: error instanceof Error ? error.message : 'Unknown error',
				requestDuration: Date.now() - startTime
			};
		}
	}

	/**
	 * Update task status
	 */
	async updateTaskStatus(
		taskId: string,
		status: string,
		options?: UpdateTaskStatusOptions
	): Promise<TaskMasterApiResponse<boolean>> {
		const startTime = Date.now();

		try {
			const mcpArgs: Record<string, unknown> = {
				id: String(taskId),
				status: status,
				projectRoot: options?.projectRoot || this.getWorkspaceRoot()
			};

			this.logger.log('Calling set_task_status with args:', mcpArgs);

			await this.mcpWrapper.callTool('set_task_status', mcpArgs);

			// Clear relevant caches
			this.cache.clearPattern('get_tasks');

			return {
				success: true,
				data: true,
				requestDuration: Date.now() - startTime
			};
		} catch (error) {
			this.logger.error('Error updating task status:', error);
			return {
				success: false,
				error: error instanceof Error ? error.message : 'Unknown error',
				requestDuration: Date.now() - startTime
			};
		}
	}

	/**
	 * Update task content
	 */
	async updateTask(
		taskId: string,
		updates: TaskUpdate,
		options?: UpdateTaskOptions
	): Promise<TaskMasterApiResponse<boolean>> {
		const startTime = Date.now();

		try {
			// Build update prompt
			const updateFields: string[] = [];
			if (updates.title !== undefined) {
				updateFields.push(`Title: ${updates.title}`);
			}
			if (updates.description !== undefined) {
				updateFields.push(`Description: ${updates.description}`);
			}
			if (updates.details !== undefined) {
				updateFields.push(`Details: ${updates.details}`);
			}
			if (updates.priority !== undefined) {
				updateFields.push(`Priority: ${updates.priority}`);
			}
			if (updates.testStrategy !== undefined) {
				updateFields.push(`Test Strategy: ${updates.testStrategy}`);
			}
			if (updates.dependencies !== undefined) {
				updateFields.push(`Dependencies: ${updates.dependencies.join(', ')}`);
			}

			const prompt = `Update task with the following changes:\n${updateFields.join('\n')}`;

			const mcpArgs: Record<string, unknown> = {
				id: String(taskId),
				prompt: prompt,
				projectRoot: options?.projectRoot || this.getWorkspaceRoot()
			};

			if (options?.append !== undefined) {
				mcpArgs.append = options.append;
			}
			if (options?.research !== undefined) {
				mcpArgs.research = options.research;
			}

			this.logger.log('Calling update_task with args:', mcpArgs);

			await this.mcpWrapper.callTool('update_task', mcpArgs);

			// Clear relevant caches
			this.cache.clearPattern('get_tasks');

			return {
				success: true,
				data: true,
				requestDuration: Date.now() - startTime
			};
		} catch (error) {
			this.logger.error('Error updating task:', error);
			return {
				success: false,
				error: error instanceof Error ? error.message : 'Unknown error',
				requestDuration: Date.now() - startTime
			};
		}
	}

	/**
	 * Update subtask content
	 */
	async updateSubtask(
		taskId: string,
		prompt: string,
		options?: UpdateSubtaskOptions
	): Promise<TaskMasterApiResponse<boolean>> {
		const startTime = Date.now();

		try {
			const mcpArgs: Record<string, unknown> = {
				id: String(taskId),
				prompt: prompt,
				projectRoot: options?.projectRoot || this.getWorkspaceRoot()
			};

			if (options?.research !== undefined) {
				mcpArgs.research = options.research;
			}

			this.logger.log('Calling update_subtask with args:', mcpArgs);

			await this.mcpWrapper.callTool('update_subtask', mcpArgs);

			// Clear relevant caches
			this.cache.clearPattern('get_tasks');

			return {
				success: true,
				data: true,
				requestDuration: Date.now() - startTime
			};
		} catch (error) {
			this.logger.error('Error updating subtask:', error);
			return {
				success: false,
				error: error instanceof Error ? error.message : 'Unknown error',
				requestDuration: Date.now() - startTime
			};
		}
	}

	/**
	 * Add a new subtask
	 */
	async addSubtask(
		parentTaskId: string,
		subtaskData: SubtaskData,
		options?: AddSubtaskOptions
	): Promise<TaskMasterApiResponse<boolean>> {
		const startTime = Date.now();

		try {
			const mcpArgs: Record<string, unknown> = {
				id: String(parentTaskId),
				title: subtaskData.title,
				projectRoot: options?.projectRoot || this.getWorkspaceRoot()
			};

			if (subtaskData.description) {
				mcpArgs.description = subtaskData.description;
			}
			if (subtaskData.dependencies && subtaskData.dependencies.length > 0) {
				mcpArgs.dependencies = subtaskData.dependencies.join(',');
			}
			if (subtaskData.status) {
				mcpArgs.status = subtaskData.status;
			}

			this.logger.log('Calling add_subtask with args:', mcpArgs);

			await this.mcpWrapper.callTool('add_subtask', mcpArgs);

			// Clear relevant caches
			this.cache.clearPattern('get_tasks');

			return {
				success: true,
				data: true,
				requestDuration: Date.now() - startTime
			};
		} catch (error) {
			this.logger.error('Error adding subtask:', error);
			return {
				success: false,
				error: error instanceof Error ? error.message : 'Unknown error',
				requestDuration: Date.now() - startTime
			};
		}
	}

	/**
	 * Get connection status
	 */
	getConnectionStatus(): { isConnected: boolean; error?: string } {
		const status = this.mcpWrapper.getStatus();
		return {
			isConnected: status.isRunning,
			error: status.error
		};
	}

	/**
	 * Test connection
	 */
	async testConnection(): Promise<TaskMasterApiResponse<boolean>> {
		const startTime = Date.now();

		try {
			const isConnected = await this.mcpWrapper.testConnection();
			return {
				success: true,
				data: isConnected,
				requestDuration: Date.now() - startTime
			};
		} catch (error) {
			this.logger.error('Connection test failed:', error);
			return {
				success: false,
				error:
					error instanceof Error ? error.message : 'Connection test failed',
				requestDuration: Date.now() - startTime
			};
		}
	}

	/**
	 * Clear all cached data
	 */
	clearCache(): void {
		this.cache.clear();
	}

	/**
	 * Get cache analytics
	 */
	getCacheAnalytics() {
		return this.cache.getAnalytics();
	}

	/**
	 * Cleanup resources
	 */
	destroy(): void {
		this.cache.destroy();
		this.logger.log('TaskMasterApi: Destroyed and cleaned up resources');
	}

	/**
	 * Start background refresh
	 */
	private startBackgroundRefresh(): void {
		const interval = this.config.cache?.refreshInterval || 5 * 60 * 1000;
		setInterval(() => {
			this.performBackgroundRefresh();
		}, interval);
	}

	/**
	 * Perform background refresh of frequently accessed cache entries
	 */
	private async performBackgroundRefresh(): Promise<void> {
		if (!this.config.cache?.enableBackgroundRefresh) {
			return;
		}

		this.logger.log('Starting background cache refresh');
		const candidates = this.cache.getRefreshCandidates();

		let refreshedCount = 0;
		for (const [key, entry] of candidates) {
			try {
				const optionsMatch = key.match(/get_tasks_(.+)/);
				if (optionsMatch) {
					const options = JSON.parse(optionsMatch[1]);
					await this.getTasks(options);
					refreshedCount++;
					this.cache.incrementRefreshes();
				}
			} catch (error) {
				this.logger.warn(`Background refresh failed for key ${key}:`, error);
			}
		}

		this.logger.log(
			`Background refresh completed, refreshed ${refreshedCount} entries`
		);
	}

	/**
	 * Get workspace root path
	 */
	private getWorkspaceRoot(): string {
		return vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd();
	}
}

```

--------------------------------------------------------------------------------
/tests/unit/manage-gitignore.test.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Unit tests for manage-gitignore.js module
 * Tests the logic with Jest spies instead of mocked modules
 */

import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';

// Import the module under test and its exports
import manageGitignoreFile, {
	normalizeLine,
	isTaskLine,
	buildTaskFilesSection,
	TASK_FILES_COMMENT,
	TASK_JSON_PATTERN,
	TASK_DIR_PATTERN
} from '../../src/utils/manage-gitignore.js';

describe('manage-gitignore.js Unit Tests', () => {
	let tempDir;

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

		// Create a temporary directory for testing
		tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'manage-gitignore-test-'));
	});

	afterEach(() => {
		// Clean up the temporary directory
		try {
			fs.rmSync(tempDir, { recursive: true, force: true });
		} catch (err) {
			// Ignore cleanup errors
		}
	});

	describe('Constants', () => {
		test('should have correct constant values', () => {
			expect(TASK_FILES_COMMENT).toBe('# Task files');
			expect(TASK_JSON_PATTERN).toBe('tasks.json');
			expect(TASK_DIR_PATTERN).toBe('tasks/');
		});
	});

	describe('normalizeLine function', () => {
		test('should remove leading/trailing whitespace', () => {
			expect(normalizeLine('  test  ')).toBe('test');
		});

		test('should remove comment hash and trim', () => {
			expect(normalizeLine('# tasks.json')).toBe('tasks.json');
			expect(normalizeLine('#tasks/')).toBe('tasks/');
		});

		test('should handle empty strings', () => {
			expect(normalizeLine('')).toBe('');
			expect(normalizeLine('   ')).toBe('');
		});

		test('should handle lines without comments', () => {
			expect(normalizeLine('tasks.json')).toBe('tasks.json');
		});
	});

	describe('isTaskLine function', () => {
		test('should identify task.json patterns', () => {
			expect(isTaskLine('tasks.json')).toBe(true);
			expect(isTaskLine('# tasks.json')).toBe(true);
			expect(isTaskLine('  # tasks.json  ')).toBe(true);
		});

		test('should identify tasks/ patterns', () => {
			expect(isTaskLine('tasks/')).toBe(true);
			expect(isTaskLine('# tasks/')).toBe(true);
			expect(isTaskLine('  # tasks/  ')).toBe(true);
		});

		test('should reject non-task patterns', () => {
			expect(isTaskLine('node_modules/')).toBe(false);
			expect(isTaskLine('# Some comment')).toBe(false);
			expect(isTaskLine('')).toBe(false);
			expect(isTaskLine('tasks.txt')).toBe(false);
		});
	});

	describe('buildTaskFilesSection function', () => {
		test('should build commented section when storeTasksInGit is true (tasks stored in git)', () => {
			const result = buildTaskFilesSection(true);
			expect(result).toEqual(['# Task files', '# tasks.json', '# tasks/ ']);
		});

		test('should build uncommented section when storeTasksInGit is false (tasks ignored)', () => {
			const result = buildTaskFilesSection(false);
			expect(result).toEqual(['# Task files', 'tasks.json', 'tasks/ ']);
		});
	});

	describe('manageGitignoreFile function - Input Validation', () => {
		test('should throw error for invalid targetPath', () => {
			expect(() => {
				manageGitignoreFile('', 'content', false);
			}).toThrow('targetPath must be a non-empty string');

			expect(() => {
				manageGitignoreFile(null, 'content', false);
			}).toThrow('targetPath must be a non-empty string');

			expect(() => {
				manageGitignoreFile('invalid.txt', 'content', false);
			}).toThrow('targetPath must end with .gitignore');
		});

		test('should throw error for invalid content', () => {
			expect(() => {
				manageGitignoreFile('.gitignore', '', false);
			}).toThrow('content must be a non-empty string');

			expect(() => {
				manageGitignoreFile('.gitignore', null, false);
			}).toThrow('content must be a non-empty string');
		});

		test('should throw error for invalid storeTasksInGit', () => {
			expect(() => {
				manageGitignoreFile('.gitignore', 'content', 'not-boolean');
			}).toThrow('storeTasksInGit must be a boolean');
		});
	});

	describe('manageGitignoreFile function - File Operations with Spies', () => {
		let writeFileSyncSpy;
		let readFileSyncSpy;
		let existsSyncSpy;
		let mockLog;

		beforeEach(() => {
			// Set up spies
			writeFileSyncSpy = jest
				.spyOn(fs, 'writeFileSync')
				.mockImplementation(() => {});
			readFileSyncSpy = jest
				.spyOn(fs, 'readFileSync')
				.mockImplementation(() => '');
			existsSyncSpy = jest
				.spyOn(fs, 'existsSync')
				.mockImplementation(() => false);
			mockLog = jest.fn();
		});

		afterEach(() => {
			// Restore original implementations
			writeFileSyncSpy.mockRestore();
			readFileSyncSpy.mockRestore();
			existsSyncSpy.mockRestore();
		});

		describe('New File Creation', () => {
			const templateContent = `# Logs
logs
*.log

# Task files
tasks.json
tasks/ `;

			test('should create new file with commented task lines when storeTasksInGit is true', () => {
				existsSyncSpy.mockReturnValue(false); // File doesn't exist

				manageGitignoreFile('.gitignore', templateContent, true, mockLog);

				expect(writeFileSyncSpy).toHaveBeenCalledWith(
					'.gitignore',
					`# Logs
logs
*.log

# Task files
# tasks.json
# tasks/ 
`
				);
				expect(mockLog).toHaveBeenCalledWith(
					'success',
					'Created .gitignore with full template'
				);
			});

			test('should create new file with uncommented task lines when storeTasksInGit is false', () => {
				existsSyncSpy.mockReturnValue(false); // File doesn't exist

				manageGitignoreFile('.gitignore', templateContent, false, mockLog);

				expect(writeFileSyncSpy).toHaveBeenCalledWith(
					'.gitignore',
					`# Logs
logs
*.log

# Task files
tasks.json
tasks/ 
`
				);
				expect(mockLog).toHaveBeenCalledWith(
					'success',
					'Created .gitignore with full template'
				);
			});

			test('should handle write errors gracefully', () => {
				existsSyncSpy.mockReturnValue(false);
				const writeError = new Error('Permission denied');
				writeFileSyncSpy.mockImplementation(() => {
					throw writeError;
				});

				expect(() => {
					manageGitignoreFile('.gitignore', templateContent, false, mockLog);
				}).toThrow('Permission denied');

				expect(mockLog).toHaveBeenCalledWith(
					'error',
					'Failed to create .gitignore: Permission denied'
				);
			});
		});

		describe('File Merging', () => {
			const templateContent = `# Logs
logs
*.log

# Dependencies
node_modules/

# Task files
tasks.json
tasks/ `;

			test('should merge with existing file and add new content', () => {
				const existingContent = `# Old content
old-file.txt

# Task files
# tasks.json
# tasks/`;

				existsSyncSpy.mockReturnValue(true); // File exists
				readFileSyncSpy.mockReturnValue(existingContent);

				manageGitignoreFile('.gitignore', templateContent, false, mockLog);

				expect(writeFileSyncSpy).toHaveBeenCalledWith(
					'.gitignore',
					expect.stringContaining('# Old content')
				);
				expect(writeFileSyncSpy).toHaveBeenCalledWith(
					'.gitignore',
					expect.stringContaining('# Logs')
				);
				expect(writeFileSyncSpy).toHaveBeenCalledWith(
					'.gitignore',
					expect.stringContaining('# Dependencies')
				);
				expect(writeFileSyncSpy).toHaveBeenCalledWith(
					'.gitignore',
					expect.stringContaining('# Task files')
				);
			});

			test('should remove existing task section and replace with new preferences', () => {
				const existingContent = `# Existing
existing.txt

# Task files
tasks.json
tasks/

# More content
more.txt`;

				existsSyncSpy.mockReturnValue(true);
				readFileSyncSpy.mockReturnValue(existingContent);

				manageGitignoreFile('.gitignore', templateContent, false, mockLog);

				const writtenContent = writeFileSyncSpy.mock.calls[0][1];

				// Should contain existing non-task content
				expect(writtenContent).toContain('# Existing');
				expect(writtenContent).toContain('existing.txt');
				expect(writtenContent).toContain('# More content');
				expect(writtenContent).toContain('more.txt');

				// Should contain new template content
				expect(writtenContent).toContain('# Logs');
				expect(writtenContent).toContain('# Dependencies');

				// Should have uncommented task lines (storeTasksInGit = false means ignore tasks)
				expect(writtenContent).toMatch(
					/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
				);
			});

			test('should handle different task preferences correctly', () => {
				const existingContent = `# Existing
existing.txt

# Task files
# tasks.json
# tasks/`;

				existsSyncSpy.mockReturnValue(true);
				readFileSyncSpy.mockReturnValue(existingContent);

				// Test with storeTasksInGit = true (commented)
				manageGitignoreFile('.gitignore', templateContent, true, mockLog);

				const writtenContent = writeFileSyncSpy.mock.calls[0][1];
				expect(writtenContent).toMatch(
					/# Task files\s*[\r\n]+# tasks\.json\s*[\r\n]+# tasks\/ /
				);
			});

			test('should not duplicate existing template content', () => {
				const existingContent = `# Logs
logs
*.log

# Dependencies  
node_modules/

# Task files
# tasks.json
# tasks/`;

				existsSyncSpy.mockReturnValue(true);
				readFileSyncSpy.mockReturnValue(existingContent);

				manageGitignoreFile('.gitignore', templateContent, false, mockLog);

				const writtenContent = writeFileSyncSpy.mock.calls[0][1];

				// Should not duplicate the logs section
				const logsCount = (writtenContent.match(/# Logs/g) || []).length;
				expect(logsCount).toBe(1);

				// Should not duplicate dependencies
				const depsCount = (writtenContent.match(/# Dependencies/g) || [])
					.length;
				expect(depsCount).toBe(1);
			});

			test('should handle read errors gracefully', () => {
				existsSyncSpy.mockReturnValue(true);
				const readError = new Error('File not readable');
				readFileSyncSpy.mockImplementation(() => {
					throw readError;
				});

				expect(() => {
					manageGitignoreFile('.gitignore', templateContent, false, mockLog);
				}).toThrow('File not readable');

				expect(mockLog).toHaveBeenCalledWith(
					'error',
					'Failed to merge content with .gitignore: File not readable'
				);
			});

			test('should handle write errors during merge gracefully', () => {
				existsSyncSpy.mockReturnValue(true);
				readFileSyncSpy.mockReturnValue('existing content');

				const writeError = new Error('Disk full');
				writeFileSyncSpy.mockImplementation(() => {
					throw writeError;
				});

				expect(() => {
					manageGitignoreFile('.gitignore', templateContent, false, mockLog);
				}).toThrow('Disk full');

				expect(mockLog).toHaveBeenCalledWith(
					'error',
					'Failed to merge content with .gitignore: Disk full'
				);
			});
		});

		describe('Edge Cases', () => {
			test('should work without log function', () => {
				existsSyncSpy.mockReturnValue(false);
				const templateContent = `# Test
test.txt

# Task files
tasks.json
tasks/`;

				expect(() => {
					manageGitignoreFile('.gitignore', templateContent, false);
				}).not.toThrow();

				expect(writeFileSyncSpy).toHaveBeenCalled();
			});

			test('should handle empty existing file', () => {
				existsSyncSpy.mockReturnValue(true);
				readFileSyncSpy.mockReturnValue('');

				const templateContent = `# Task files
tasks.json
tasks/`;

				manageGitignoreFile('.gitignore', templateContent, false, mockLog);

				expect(writeFileSyncSpy).toHaveBeenCalled();
				const writtenContent = writeFileSyncSpy.mock.calls[0][1];
				expect(writtenContent).toContain('# Task files');
			});

			test('should handle template with only task files', () => {
				existsSyncSpy.mockReturnValue(false);
				const templateContent = `# Task files
tasks.json
tasks/ `;

				manageGitignoreFile('.gitignore', templateContent, true, mockLog);

				const writtenContent = writeFileSyncSpy.mock.calls[0][1];
				expect(writtenContent).toBe(`# Task files
# tasks.json
# tasks/ 
`);
			});
		});
	});
});

```
Page 23/50FirstPrevNextLast