This is page 14 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
--------------------------------------------------------------------------------
/docs/migration-guide.md:
--------------------------------------------------------------------------------
```markdown
# Migration Guide: New .taskmaster Directory Structure
## Overview
Task Master v0.16.0 introduces a new `.taskmaster/` directory structure to keep your project directories clean and organized. This guide explains the benefits of the new structure and how to migrate existing projects.
## What's New
### Before (Legacy Structure)
```
your-project/
├── tasks/ # Task files
│ ├── tasks.json
│ ├── task-1.txt
│ └── task-2.txt
├── scripts/ # PRD and reports
│ ├── prd.txt
│ ├── example_prd.txt
│ └── task-complexity-report.json
├── .taskmasterconfig # Configuration
└── ... (your project files)
```
### After (New Structure)
```
your-project/
├── .taskmaster/ # Consolidated Task Master files
│ ├── config.json # Configuration (was .taskmasterconfig)
│ ├── tasks/ # Task files
│ │ ├── tasks.json
│ │ ├── task-1.txt
│ │ └── task-2.txt
│ ├── docs/ # Project documentation
│ │ └── prd.txt
│ ├── reports/ # Generated reports
│ │ └── task-complexity-report.json
│ └── templates/ # Example/template files
│ └── example_prd.txt
└── ... (your project files)
```
## Benefits of the New Structure
✅ **Cleaner Project Root**: No more scattered Task Master files
✅ **Better Organization**: Logical separation of tasks, docs, reports, and templates
✅ **Hidden by Default**: `.taskmaster/` directory is hidden from most file browsers
✅ **Future-Proof**: Centralized location for Task Master extensions
✅ **Backward Compatible**: Existing projects continue to work until migrated
## Migration Options
### Option 1: Automatic Migration (Recommended)
Task Master provides a built-in migration command that handles everything automatically:
#### CLI Migration
```bash
# Dry run to see what would be migrated
task-master migrate --dry-run
# Perform the migration with backup
task-master migrate --backup
# Force migration (overwrites existing files)
task-master migrate --force
# Clean up legacy files after migration
task-master migrate --cleanup
```
#### MCP Migration (Cursor/AI Editors)
Ask your AI assistant:
```
Please migrate my Task Master project to the new .taskmaster directory structure
```
### Option 2: Manual Migration
If you prefer to migrate manually:
1. **Create the new directory structure:**
```bash
mkdir -p .taskmaster/{tasks,docs,reports,templates}
```
2. **Move your files:**
```bash
# Move tasks
mv tasks/* .taskmaster/tasks/
# Move configuration
mv .taskmasterconfig .taskmaster/config.json
# Move PRD and documentation
mv scripts/prd.txt .taskmaster/docs/
mv scripts/example_prd.txt .taskmaster/templates/
# Move reports (if they exist)
mv scripts/task-complexity-report.json .taskmaster/reports/ 2>/dev/null || true
```
3. **Clean up empty directories:**
```bash
rmdir tasks scripts 2>/dev/null || true
```
## What Gets Migrated
The migration process handles these file types:
### Tasks Directory → `.taskmaster/tasks/`
- `tasks.json`
- Individual task text files (`.txt`)
### Scripts Directory → Multiple Destinations
- **PRD files** → `.taskmaster/docs/`
- `prd.txt`, `requirements.txt`, etc.
- **Example/Template files** → `.taskmaster/templates/`
- `example_prd.txt`, template files
- **Reports** → `.taskmaster/reports/`
- `task-complexity-report.json`
### Configuration
- `.taskmasterconfig` → `.taskmaster/config.json`
## After Migration
Once migrated, Task Master will:
✅ **Automatically use** the new directory structure
✅ **Show deprecation warnings** when legacy files are detected
✅ **Create new files** in the proper locations
✅ **Fall back gracefully** to legacy locations if new ones don't exist
### Verification
After migration, verify everything works:
1. **List your tasks:**
```bash
task-master list
```
2. **Check your configuration:**
```bash
task-master models
```
3. **Generate new task files:**
```bash
task-master generate
```
## Troubleshooting
### Migration Issues
**Q: Migration says "no files to migrate"**
A: Your project may already be using the new structure or have no Task Master files to migrate.
**Q: Migration fails with permission errors**
A: Ensure you have write permissions in your project directory.
**Q: Some files weren't migrated**
A: Check the migration output - some files may not match the expected patterns. You can migrate these manually.
### Working with Legacy Projects
If you're working with an older project that hasn't been migrated:
- Task Master will continue to work with the old structure
- You'll see deprecation warnings in the output
- New files will still be created in legacy locations
- Use the migration command when ready to upgrade
### New Project Initialization
New projects automatically use the new structure:
```bash
task-master init # Creates .taskmaster/ structure
```
## Path Changes for Developers
If you're developing tools or scripts that interact with Task Master files:
### Configuration File
- **Old:** `.taskmasterconfig`
- **New:** `.taskmaster/config.json`
- **Fallback:** Task Master checks both locations
### Tasks File
- **Old:** `tasks/tasks.json`
- **New:** `.taskmaster/tasks/tasks.json`
- **Fallback:** Task Master checks both locations
### Reports
- **Old:** `scripts/task-complexity-report.json`
- **New:** `.taskmaster/reports/task-complexity-report.json`
- **Fallback:** Task Master checks both locations
### PRD Files
- **Old:** `scripts/prd.txt`
- **New:** `.taskmaster/docs/prd.txt`
- **Fallback:** Task Master checks both locations
## Need Help?
If you encounter issues during migration:
1. **Check the logs:** Add `--debug` flag for detailed output
2. **Backup first:** Always use `--backup` option for safety
3. **Test with dry-run:** Use `--dry-run` to preview changes
4. **Ask for help:** Use our Discord community or GitHub issues
---
_This migration guide applies to Task Master v0.15.x and later. For older versions, please upgrade to the latest version first._
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/parse-prd.js:
--------------------------------------------------------------------------------
```javascript
/**
* parse-prd.js
* Direct function implementation for parsing PRD documents
*/
import path from 'path';
import fs from 'fs';
import { parsePRD } from '../../../../scripts/modules/task-manager.js';
import {
enableSilentMode,
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
import { getDefaultNumTasks } from '../../../../scripts/modules/config-manager.js';
import { resolvePrdPath, resolveProjectPath } from '../utils/path-utils.js';
import { TASKMASTER_TASKS_FILE } from '../../../../src/constants/paths.js';
/**
* Direct function wrapper for parsing PRD documents and generating tasks.
*
* @param {Object} args - Command arguments containing projectRoot, input, output, numTasks options.
* @param {string} args.input - Path to the input PRD file.
* @param {string} args.output - Path to the output directory.
* @param {string} args.numTasks - Number of tasks to generate.
* @param {boolean} args.force - Whether to force parsing.
* @param {boolean} args.append - Whether to append to the output file.
* @param {boolean} args.research - Whether to use research mode.
* @param {string} args.tag - Tag context for organizing tasks into separate task lists.
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function parsePRDDirect(args, log, context = {}) {
const { session, reportProgress } = context;
// Extract projectRoot from args
const {
input: inputArg,
output: outputArg,
numTasks: numTasksArg,
force,
append,
research,
projectRoot,
tag
} = args;
// Create the standard logger wrapper
const logWrapper = createLogWrapper(log);
// --- Input Validation and Path Resolution ---
if (!projectRoot) {
logWrapper.error('parsePRDDirect requires a projectRoot argument.');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'projectRoot is required.'
}
};
}
// Resolve input path using path utilities
let inputPath;
if (inputArg) {
try {
inputPath = resolvePrdPath({ input: inputArg, projectRoot }, session);
} catch (error) {
logWrapper.error(`Error resolving PRD path: ${error.message}`);
return {
success: false,
error: { code: 'FILE_NOT_FOUND', message: error.message }
};
}
} else {
logWrapper.error('parsePRDDirect called without input path');
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: 'Input path is required' }
};
}
// Resolve output path - use new path utilities for default
const outputPath = outputArg
? path.isAbsolute(outputArg)
? outputArg
: path.resolve(projectRoot, outputArg)
: resolveProjectPath(TASKMASTER_TASKS_FILE, args) ||
path.resolve(projectRoot, TASKMASTER_TASKS_FILE);
// Check if input file exists
if (!fs.existsSync(inputPath)) {
const errorMsg = `Input PRD file not found at resolved path: ${inputPath}`;
logWrapper.error(errorMsg);
return {
success: false,
error: { code: 'FILE_NOT_FOUND', message: errorMsg }
};
}
const outputDir = path.dirname(outputPath);
try {
if (!fs.existsSync(outputDir)) {
logWrapper.info(`Creating output directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
} catch (error) {
const errorMsg = `Failed to create output directory ${outputDir}: ${error.message}`;
logWrapper.error(errorMsg);
return {
success: false,
error: { code: 'DIRECTORY_CREATE_FAILED', message: errorMsg }
};
}
let numTasks = getDefaultNumTasks(projectRoot);
if (numTasksArg) {
numTasks =
typeof numTasksArg === 'string' ? parseInt(numTasksArg, 10) : numTasksArg;
if (Number.isNaN(numTasks) || numTasks < 0) {
// Ensure positive number
numTasks = getDefaultNumTasks(projectRoot); // Fallback to default if parsing fails or invalid
logWrapper.warn(
`Invalid numTasks value: ${numTasksArg}. Using default: ${numTasks}`
);
}
}
if (append) {
logWrapper.info('Append mode enabled.');
if (force) {
logWrapper.warn(
'Both --force and --append flags were provided. --force takes precedence; append mode will be ignored.'
);
}
}
if (research) {
logWrapper.info(
'Research mode enabled. Using Perplexity AI for enhanced PRD analysis.'
);
}
logWrapper.info(
`Parsing PRD via direct function. Input: ${inputPath}, Output: ${outputPath}, NumTasks: ${numTasks}, Force: ${force}, Append: ${append}, Research: ${research}, ProjectRoot: ${projectRoot}`
);
const wasSilent = isSilentMode();
if (!wasSilent) {
enableSilentMode();
}
try {
// Call the core parsePRD function
const result = await parsePRD(
inputPath,
outputPath,
numTasks,
{
session,
mcpLog: logWrapper,
projectRoot,
tag,
force,
append,
research,
reportProgress,
commandName: 'parse-prd',
outputType: 'mcp'
},
'json'
);
// Adjust check for the new return structure
if (result && result.success) {
const successMsg = `Successfully parsed PRD and generated tasks in ${result.tasksPath}`;
logWrapper.success(successMsg);
return {
success: true,
data: {
message: successMsg,
outputPath: result.tasksPath,
telemetryData: result.telemetryData,
tagInfo: result.tagInfo
}
};
} else {
// Handle case where core function didn't return expected success structure
logWrapper.error(
'Core parsePRD function did not return a successful structure.'
);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message:
result?.message ||
'Core function failed to parse PRD or returned unexpected result.'
}
};
}
} catch (error) {
logWrapper.error(`Error executing core parsePRD: ${error.message}`);
return {
success: false,
error: {
code: 'PARSE_PRD_CORE_ERROR',
message: error.message || 'Unknown error parsing PRD'
}
};
} finally {
if (!wasSilent && isSilentMode()) {
disableSilentMode();
}
}
}
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-cline.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
mkdirSync: jest.fn()
}));
// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { clineProfile } from '../../../src/profiles/cline.js';
describe('Cline Rule Transformer', () => {
// Set up spies on the mocked modules
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
const mockExistsSync = jest.spyOn(fs, 'existsSync');
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
const mockConsoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockReadFileSync.mockReturnValue('');
mockWriteFileSync.mockImplementation(() => {});
mockExistsSync.mockReturnValue(true);
mockMkdirSync.mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should correctly convert basic terms', () => {
const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files.`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
clineProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Verify file operations were called correctly
expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations
expect(transformedContent).toContain('Cline');
expect(transformedContent).toContain('cline.bot');
expect(transformedContent).toContain('.md');
expect(transformedContent).not.toContain('cursor.so');
expect(transformedContent).not.toContain('Cursor rule');
});
it('should correctly convert tool references', () => {
const testContent = `---
description: Test Cursor rule for tool references
globs: **/*
alwaysApply: true
---
- Use the search tool to find code
- The edit_file tool lets you modify files
- run_command executes terminal commands
- use_mcp connects to external services`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
clineProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Cline uses standard tool names, so no transformation)
expect(transformedContent).toContain('search tool');
expect(transformedContent).toContain('edit_file tool');
expect(transformedContent).toContain('run_command');
expect(transformedContent).toContain('use_mcp');
});
it('should correctly update file references', () => {
const testContent = `---
description: Test Cursor rule for file references
globs: **/*
alwaysApply: true
---
This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
clineProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify file path transformations - no taskmaster subdirectory for Cline
expect(transformedContent).toContain('(.clinerules/dev_workflow.md)');
expect(transformedContent).toContain('(.clinerules/taskmaster.md)');
expect(transformedContent).not.toContain('(mdc:.cursor/rules/');
});
it('should handle file read errors', () => {
// Mock file read to throw an error
mockReadFileSync.mockImplementation(() => {
throw new Error('File not found');
});
// Call the actual function
const result = convertRuleToProfileRule(
'nonexistent.mdc',
'target.md',
clineProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify writeFileSync was not called
expect(mockWriteFileSync).not.toHaveBeenCalled();
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: File not found'
);
});
it('should handle file write errors', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock file write to throw an error
mockWriteFileSync.mockImplementation(() => {
throw new Error('Permission denied');
});
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
clineProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: Permission denied'
);
});
it('should create target directory if it does not exist', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock directory doesn't exist initially
mockExistsSync.mockReturnValue(false);
// Call the actual function
convertRuleToProfileRule(
'source.mdc',
'some/deep/path/target.md',
clineProfile
);
// Verify directory creation was called
expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', {
recursive: true
});
});
});
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-cursor.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
mkdirSync: jest.fn()
}));
// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { cursorProfile } from '../../../src/profiles/cursor.js';
describe('Cursor Rule Transformer', () => {
// Set up spies on the mocked modules
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
const mockExistsSync = jest.spyOn(fs, 'existsSync');
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
const mockConsoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockReadFileSync.mockReturnValue('');
mockWriteFileSync.mockImplementation(() => {});
mockExistsSync.mockReturnValue(true);
mockMkdirSync.mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should correctly convert basic terms', () => {
const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files.`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.mdc',
cursorProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Verify file operations were called correctly
expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Cursor profile should keep everything the same)
expect(transformedContent).toContain('Cursor');
expect(transformedContent).toContain('cursor.so');
expect(transformedContent).toContain('.mdc');
expect(transformedContent).toContain('Cursor rule');
});
it('should correctly convert tool references', () => {
const testContent = `---
description: Test Cursor rule for tool references
globs: **/*
alwaysApply: true
---
- Use the search tool to find code
- The edit_file tool lets you modify files
- run_command executes terminal commands
- use_mcp connects to external services`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.mdc',
cursorProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Cursor uses standard tool names, so no transformation)
expect(transformedContent).toContain('search tool');
expect(transformedContent).toContain('edit_file tool');
expect(transformedContent).toContain('run_command');
expect(transformedContent).toContain('use_mcp');
});
it('should correctly update file references', () => {
const testContent = `---
description: Test Cursor rule for file references
globs: **/*
alwaysApply: true
---
This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.mdc',
cursorProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Cursor should keep the same references but in taskmaster subdirectory)
expect(transformedContent).toContain(
'(mdc:.cursor/rules/taskmaster/dev_workflow.mdc)'
);
expect(transformedContent).toContain(
'(mdc:.cursor/rules/taskmaster/taskmaster.mdc)'
);
});
it('should handle file read errors', () => {
// Mock file read to throw an error
mockReadFileSync.mockImplementation(() => {
throw new Error('File not found');
});
// Call the actual function
const result = convertRuleToProfileRule(
'nonexistent.mdc',
'target.mdc',
cursorProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify writeFileSync was not called
expect(mockWriteFileSync).not.toHaveBeenCalled();
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: File not found'
);
});
it('should handle file write errors', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock file write to throw an error
mockWriteFileSync.mockImplementation(() => {
throw new Error('Permission denied');
});
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.mdc',
cursorProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: Permission denied'
);
});
it('should create target directory if it does not exist', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock directory doesn't exist initially
mockExistsSync.mockReturnValue(false);
// Call the actual function
convertRuleToProfileRule(
'source.mdc',
'some/deep/path/target.mdc',
cursorProfile
);
// Verify directory creation was called
expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', {
recursive: true
});
});
});
```
--------------------------------------------------------------------------------
/.taskmaster/reports/tm-core-complexity.json:
--------------------------------------------------------------------------------
```json
{
"meta": {
"generatedAt": "2025-08-06T12:15:01.327Z",
"tasksAnalyzed": 8,
"totalTasks": 11,
"analysisCount": 8,
"thresholdScore": 5,
"projectName": "Taskmaster",
"usedResearch": false
},
"complexityAnalysis": [
{
"taskId": 118,
"taskTitle": "Create AI Provider Base Architecture",
"complexityScore": 4,
"recommendedSubtasks": 5,
"expansionPrompt": "Break down the conversion of base-provider.js to TypeScript BaseProvider class: 1) Convert to TypeScript and define IAIProvider interface, 2) Implement abstract class with core properties, 3) Define abstract methods and Template Method pattern, 4) Add retry logic with exponential backoff, 5) Implement validation and logging. Focus on maintaining compatibility with existing provider pattern while adding type safety.",
"reasoning": "The codebase already has a well-established BaseAIProvider class in JavaScript. Converting to TypeScript mainly involves adding type definitions and ensuring the existing pattern is preserved. The complexity is moderate because the pattern is already proven in the codebase."
},
{
"taskId": 119,
"taskTitle": "Implement Provider Factory with Dynamic Imports",
"complexityScore": 3,
"recommendedSubtasks": 5,
"expansionPrompt": "Create ProviderFactory implementation: 1) Set up class structure and types, 2) Implement provider selection switch statement, 3) Add dynamic imports for tree-shaking, 4) Handle provider instantiation with config, 5) Add comprehensive error handling. The existing PROVIDERS registry pattern should guide the implementation.",
"reasoning": "The codebase already uses a dual registry pattern (static PROVIDERS and dynamic ProviderRegistry). Creating a factory is straightforward as the provider registration patterns are well-established. Dynamic imports are already used in the codebase."
},
{
"taskId": 120,
"taskTitle": "Implement Anthropic Provider",
"complexityScore": 3,
"recommendedSubtasks": 5,
"expansionPrompt": "Implement AnthropicProvider following existing patterns: 1) Create class structure with imports, 2) Implement constructor and client initialization, 3) Add generateCompletion with Claude API integration, 4) Implement token calculation and utility methods, 5) Add error handling and exports. Use the existing anthropic.js provider as reference.",
"reasoning": "AnthropicProvider already exists in the codebase with full implementation. This task essentially involves adapting the existing implementation to match the new TypeScript architecture, making it relatively straightforward."
},
{
"taskId": 121,
"taskTitle": "Create Prompt Builder and Task Parser",
"complexityScore": 6,
"recommendedSubtasks": 5,
"expansionPrompt": "Build prompt system and parser: 1) Create PromptBuilder with template methods, 2) Implement TaskParser with dependency injection, 3) Add parsePRD core logic with file reading, 4) Implement task enrichment and metadata, 5) Add comprehensive error handling. Leverage the existing prompt management system in src/prompts/.",
"reasoning": "While the codebase has a sophisticated prompt management system, creating a new PromptBuilder and TaskParser requires understanding the existing prompt templates, JSON schema validation, and integration with the AI provider system. The task involves significant new code."
},
{
"taskId": 122,
"taskTitle": "Implement Configuration Management",
"complexityScore": 5,
"recommendedSubtasks": 5,
"expansionPrompt": "Create ConfigManager with validation: 1) Define Zod schema for IConfiguration, 2) Implement constructor with defaults, 3) Add validate method with error handling, 4) Create type-safe get method with generics, 5) Implement getAll and finalize exports. Reference existing config-manager.js for patterns.",
"reasoning": "The codebase has an existing config-manager.js with sophisticated configuration handling. Adding Zod validation and TypeScript generics adds complexity, but the existing patterns provide a solid foundation."
},
{
"taskId": 123,
"taskTitle": "Create Utility Functions and Error Handling",
"complexityScore": 2,
"recommendedSubtasks": 5,
"expansionPrompt": "Implement utilities and error handling: 1) Create ID generation module with unique formats, 2) Build TaskMasterError base class, 3) Add error sanitization for security, 4) Implement development-only logging, 5) Create specialized error subclasses. Keep implementation simple and focused.",
"reasoning": "This is a straightforward utility implementation task. The codebase already has error handling patterns, and ID generation is a simple algorithmic task. The main work is creating clean, reusable utilities."
},
{
"taskId": 124,
"taskTitle": "Implement TaskMasterCore Facade",
"complexityScore": 7,
"recommendedSubtasks": 5,
"expansionPrompt": "Create main facade class: 1) Set up TaskMasterCore structure with imports, 2) Implement lazy initialization logic, 3) Add parsePRD coordination method, 4) Implement getTasks and other facade methods, 5) Create factory function and exports. This ties together all other components into a cohesive API.",
"reasoning": "This is the most complex task as it requires understanding and integrating all other components. The facade must coordinate between configuration, providers, storage, and parsing while maintaining a clean API. It's the architectural keystone of the system."
},
{
"taskId": 125,
"taskTitle": "Create Placeholder Providers and Complete Testing",
"complexityScore": 5,
"recommendedSubtasks": 5,
"expansionPrompt": "Implement testing infrastructure: 1) Create OpenAIProvider placeholder, 2) Create GoogleProvider placeholder, 3) Build MockProvider for testing, 4) Write TaskParser unit tests, 5) Create integration tests for parse-prd flow. Follow the existing test patterns in tests/ directory.",
"reasoning": "While creating placeholder providers is simple, the testing infrastructure requires understanding Jest with ES modules, mocking patterns, and comprehensive test coverage. The existing test structure provides good examples to follow."
}
]
}
```
--------------------------------------------------------------------------------
/apps/extension/src/components/TaskDetailsView.tsx:
--------------------------------------------------------------------------------
```typescript
import type React from 'react';
import { useContext, useState, useCallback } from 'react';
import { Button } from '@/components/ui/button';
import { useQueryClient } from '@tanstack/react-query';
import { RefreshCw } from 'lucide-react';
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbSeparator
} from '@/components/ui/breadcrumb';
import { VSCodeContext } from '../webview/contexts/VSCodeContext';
import { AIActionsSection } from './TaskDetails/AIActionsSection';
import { SubtasksSection } from './TaskDetails/SubtasksSection';
import { TaskMetadataSidebar } from './TaskDetails/TaskMetadataSidebar';
import { DetailsSection } from './TaskDetails/DetailsSection';
import { useTaskDetails } from './TaskDetails/useTaskDetails';
import { useTasks, taskKeys } from '../webview/hooks/useTaskQueries';
import type { TaskMasterTask } from '../webview/types';
interface TaskDetailsViewProps {
taskId: string;
onNavigateBack: () => void;
onNavigateToTask: (taskId: string) => void;
}
export const TaskDetailsView: React.FC<TaskDetailsViewProps> = ({
taskId,
onNavigateBack,
onNavigateToTask
}) => {
const context = useContext(VSCodeContext);
if (!context) {
throw new Error('TaskDetailsView must be used within VSCodeProvider');
}
const { state, sendMessage } = context;
const { currentTag } = state;
const queryClient = useQueryClient();
const [isRefreshing, setIsRefreshing] = useState(false);
// Use React Query to fetch all tasks
const { data: allTasks = [] } = useTasks({ tag: currentTag });
const {
currentTask,
parentTask,
isSubtask,
taskFileData,
taskFileDataError,
complexity,
refreshComplexityAfterAI
} = useTaskDetails({ taskId, sendMessage, tasks: allTasks });
const displayId =
isSubtask && parentTask
? `${parentTask.id}.${currentTask?.id}`
: currentTask?.id;
const handleStatusChange = async (newStatus: TaskMasterTask['status']) => {
if (!currentTask) return;
try {
await sendMessage({
type: 'updateTaskStatus',
data: {
taskId: displayId,
newStatus: newStatus
}
});
} catch (error) {
console.error('❌ TaskDetailsView: Failed to update task status:', error);
}
};
const handleDependencyClick = (depId: string) => {
onNavigateToTask(depId);
};
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
try {
// Invalidate all task queries
await queryClient.invalidateQueries({ queryKey: taskKeys.all });
} finally {
// Reset after a short delay to show the animation
setTimeout(() => setIsRefreshing(false), 500);
}
}, [queryClient]);
if (!currentTask) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<p className="text-lg text-vscode-foreground/70 mb-4">
Task not found
</p>
<Button onClick={onNavigateBack} variant="outline">
Back to Kanban Board
</Button>
</div>
</div>
);
}
return (
<div className="h-full flex flex-col">
<div className="flex-1 grid grid-cols-1 md:grid-cols-3 gap-6 p-6 overflow-auto">
{/* Left column - Main content (2/3 width) */}
<div className="md:col-span-2 space-y-6">
{/* Breadcrumb navigation */}
<div className="flex items-center justify-between">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink
onClick={onNavigateBack}
className="cursor-pointer hover:text-vscode-foreground text-link"
>
Kanban Board
</BreadcrumbLink>
</BreadcrumbItem>
{isSubtask && parentTask && (
<>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink
onClick={() => onNavigateToTask(parentTask.id)}
className="cursor-pointer hover:text-vscode-foreground"
>
{parentTask.title}
</BreadcrumbLink>
</BreadcrumbItem>
</>
)}
<BreadcrumbSeparator />
<BreadcrumbItem>
<span className="text-vscode-foreground">
#{displayId} {currentTask.title}
</span>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<button
onClick={handleRefresh}
disabled={isRefreshing}
className="p-1.5 rounded hover:bg-vscode-button-hoverBackground transition-colors"
title="Refresh task details"
>
<RefreshCw
className={`w-4 h-4 text-vscode-foreground/70 ${isRefreshing ? 'animate-spin' : ''}`}
/>
</button>
</div>
{/* Task ID and title */}
<h1 className="text-2xl font-bold tracking-tight text-vscode-foreground">
#{displayId} {currentTask.title}
</h1>
{/* Description */}
<div className="mb-8">
<p className="text-vscode-foreground/80 leading-relaxed">
{currentTask.description || 'No description available.'}
</p>
</div>
{/* AI Actions */}
<AIActionsSection
currentTask={currentTask}
isSubtask={isSubtask}
parentTask={parentTask}
sendMessage={sendMessage}
refreshComplexityAfterAI={refreshComplexityAfterAI}
/>
{/* Implementation Details */}
<DetailsSection
title="Implementation Details"
content={taskFileData.details}
error={taskFileDataError}
emptyMessage="No implementation details available"
defaultExpanded={false}
/>
{/* Test Strategy */}
<DetailsSection
title="Test Strategy"
content={taskFileData.testStrategy}
error={taskFileDataError}
emptyMessage="No test strategy available"
defaultExpanded={false}
/>
{/* Subtasks */}
<SubtasksSection
currentTask={currentTask}
isSubtask={isSubtask}
sendMessage={sendMessage}
onNavigateToTask={onNavigateToTask}
/>
</div>
{/* Right column - Metadata (1/3 width) */}
<TaskMetadataSidebar
currentTask={currentTask}
tasks={allTasks}
complexity={complexity}
isSubtask={isSubtask}
onStatusChange={handleStatusChange}
onDependencyClick={handleDependencyClick}
/>
</div>
</div>
);
};
export default TaskDetailsView;
```
--------------------------------------------------------------------------------
/docs/claude-code-integration.md:
--------------------------------------------------------------------------------
```markdown
# TODO: Move to apps/docs inside our documentation website
# Claude Code Integration Guide
This guide covers how to use Task Master with Claude Code AI SDK integration for enhanced AI-powered development workflows.
## Overview
Claude Code integration allows Task Master to leverage the Claude Code CLI for AI operations without requiring direct API keys. The integration uses OAuth tokens managed by the Claude Code CLI itself.
## Authentication Setup
The Claude Code provider uses token authentication managed by the Claude Code CLI.
### Prerequisites
1. **Install Claude Code CLI** (if not already installed):
```bash
# Installation method depends on your system
# Follow Claude Code documentation for installation
```
2. **Set up OAuth token** using Claude Code CLI:
```bash
claude setup-token
```
This command will:
- Guide you through OAuth authentication
- Store the token securely for CLI usage
- Enable Task Master to use Claude Code without manual API key configuration
### Authentication Priority
Task Master will attempt authentication in this order:
1. **Environment Variable** (optional): `CLAUDE_CODE_OAUTH_TOKEN`
- Useful for CI/CD environments or when you want to override the default token
- Not required if you've set up the CLI token
2. **Claude Code CLI Token** (recommended): Token managed by `claude setup-token`
- Automatically used when available
- Most convenient for local development
3. **Fallback**: Error if neither is available
## Configuration
### Basic Configuration
Add Claude Code to your Task Master configuration:
```javascript
// In your .taskmaster/config.json or via task-master models command
{
"models": {
"main": "claude-code:sonnet", // Use Claude Code with Sonnet
"research": "perplexity-llama-3.1-sonar-large-128k-online",
"fallback": "claude-code:opus" // Use Claude Code with Opus as fallback
}
}
```
### Supported Models
- `claude-code:sonnet` - Claude 3.5 Sonnet via Claude Code CLI
- `claude-code:opus` - Claude 3 Opus via Claude Code CLI
### Environment Variables (Optional)
While not required, you can optionally set:
```bash
export CLAUDE_CODE_OAUTH_TOKEN="your_oauth_token_here"
```
This is only needed in specific scenarios like:
- CI/CD pipelines
- Docker containers
- When you want to use a different token than the CLI default
## Usage Examples
### Basic Task Operations
```bash
# Use Claude Code for task operations
task-master add-task --prompt="Implement user authentication system" --research
task-master expand --id=1 --research
task-master update-task --id=1.1 --prompt="Add JWT token validation"
```
### Model Configuration Commands
```bash
# Set Claude Code as main model
task-master models --set-main claude-code:sonnet
# Use interactive setup
task-master models --setup
# Then select "claude-code" from the provider list
```
## Troubleshooting
### Common Issues
#### 1. "Claude Code CLI not available" Error
**Problem**: Task Master cannot connect to Claude Code CLI.
**Solutions**:
- Ensure Claude Code CLI is installed and in your PATH
- Run `claude setup-token` to configure authentication
- Verify Claude Code CLI works: `claude --help`
#### 2. Authentication Failures
**Problem**: Token authentication is failing.
**Solutions**:
- Re-run `claude setup-token` to refresh your OAuth token
- Check if your token has expired
- Verify Claude Code CLI can authenticate: try a simple `claude` command
#### 3. Model Not Available
**Problem**: Specified Claude Code model is not supported.
**Solutions**:
- Use supported models: `sonnet` or `opus`
- Check model availability: `task-master models --list`
- Verify your Claude Code CLI has access to the requested model
### Debug Steps
1. **Test Claude Code CLI directly**:
```bash
claude --help
# Should show help without errors
```
2. **Test authentication**:
```bash
claude setup-token --verify
# Should confirm token is valid
```
3. **Test Task Master integration**:
```bash
task-master models --test claude-code:sonnet
# Should successfully connect and test the model
```
4. **Check logs**:
- Task Master logs will show detailed error messages
- Use `--verbose` flag for more detailed output
### Environment-Specific Configuration
#### Docker/Containers
When running in Docker, you'll need to:
1. Install Claude Code CLI in your container
2. Set up authentication via environment variable:
```dockerfile
ENV CLAUDE_CODE_OAUTH_TOKEN="your_token_here"
```
#### CI/CD Pipelines
For automated environments:
1. Set up a service account token or use environment variables
2. Ensure Claude Code CLI is available in the pipeline environment
3. Configure authentication before running Task Master commands
## Integration with AI SDK
Task Master's Claude Code integration uses the official `ai-sdk-provider-claude-code` package, providing:
- **Streaming Support**: Real-time token streaming for interactive experiences
- **Full AI SDK Compatibility**: Works with generateText, streamText, and other AI SDK functions
- **Automatic Error Handling**: Graceful degradation when Claude Code is unavailable
- **Type Safety**: Full TypeScript support with proper type definitions
### Example AI SDK Usage
```javascript
import { generateText } from 'ai';
import { ClaudeCodeProvider } from './src/ai-providers/claude-code.js';
const provider = new ClaudeCodeProvider();
const client = provider.getClient();
const result = await generateText({
model: client('sonnet'),
messages: [
{ role: 'user', content: 'Hello Claude!' }
]
});
console.log(result.text);
```
## Security Notes
- OAuth tokens are managed securely by Claude Code CLI
- No API keys need to be stored in your project files
- Tokens are automatically refreshed by the Claude Code CLI
- Environment variables should only be used in secure environments
## Getting Help
If you encounter issues:
1. Check the Claude Code CLI documentation
2. Verify your authentication setup with `claude setup-token --verify`
3. Review Task Master logs for detailed error messages
4. Open an issue with both Task Master and Claude Code version information
```
--------------------------------------------------------------------------------
/src/profiles/kilo.js:
--------------------------------------------------------------------------------
```javascript
// Kilo Code conversion profile for rule-transformer
import path from 'path';
import fs from 'fs';
import { isSilentMode, log } from '../../scripts/modules/utils.js';
import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js';
import { ROO_MODES } from '../constants/profiles.js';
// Utility function to apply kilo transformations to content
function applyKiloTransformations(content) {
const customReplacements = [
// Replace roo-specific terms with kilo equivalents
{
from: /\broo\b/gi,
to: (match) => (match.charAt(0) === 'R' ? 'Kilo' : 'kilo')
},
{ from: /Roo/g, to: 'Kilo' },
{ from: /ROO/g, to: 'KILO' },
{ from: /roocode\.com/gi, to: 'kilocode.com' },
{ from: /docs\.roocode\.com/gi, to: 'docs.kilocode.com' },
{ from: /https?:\/\/roocode\.com/gi, to: 'https://kilocode.com' },
{
from: /https?:\/\/docs\.roocode\.com/gi,
to: 'https://docs.kilocode.com'
},
{ from: /\.roo\//g, to: '.kilo/' },
{ from: /\.roomodes/g, to: '.kilocodemodes' },
// Handle file extensions and directory references
{ from: /roo-rules/g, to: 'kilo-rules' },
{ from: /rules-roo/g, to: 'rules-kilo' }
];
let transformedContent = content;
for (const replacement of customReplacements) {
transformedContent = transformedContent.replace(
replacement.from,
replacement.to
);
}
return transformedContent;
}
// Utility function to copy files recursively
function copyRecursiveSync(src, dest) {
const exists = fs.existsSync(src);
const stats = exists && fs.statSync(src);
const isDirectory = exists && stats.isDirectory();
if (isDirectory) {
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
fs.readdirSync(src).forEach((childItemName) => {
copyRecursiveSync(
path.join(src, childItemName),
path.join(dest, childItemName)
);
});
} else {
fs.copyFileSync(src, dest);
}
}
// Lifecycle functions for Kilo profile
function onAddRulesProfile(targetDir, assetsDir) {
// Use the provided assets directory to find the roocode directory
const sourceDir = path.join(assetsDir, 'roocode');
if (!fs.existsSync(sourceDir)) {
log('error', `[Kilo] Source directory does not exist: ${sourceDir}`);
return;
}
// Copy basic roocode structure first
copyRecursiveSync(sourceDir, targetDir);
log('debug', `[Kilo] Copied roocode directory to ${targetDir}`);
// Transform .roomodes to .kilocodemodes
const roomodesSrc = path.join(sourceDir, '.roomodes');
const kilocodemodesDest = path.join(targetDir, '.kilocodemodes');
if (fs.existsSync(roomodesSrc)) {
try {
const roomodesContent = fs.readFileSync(roomodesSrc, 'utf8');
const transformedContent = applyKiloTransformations(roomodesContent);
fs.writeFileSync(kilocodemodesDest, transformedContent);
log('debug', `[Kilo] Created .kilocodemodes at ${kilocodemodesDest}`);
// Remove the original .roomodes file
fs.unlinkSync(path.join(targetDir, '.roomodes'));
} catch (err) {
log('error', `[Kilo] Failed to transform .roomodes: ${err.message}`);
}
}
// Transform .roo directory to .kilo and apply kilo transformations to mode-specific rules
const rooModesDir = path.join(sourceDir, '.roo');
const kiloModesDir = path.join(targetDir, '.kilo');
// Remove the copied .roo directory and create .kilo
if (fs.existsSync(path.join(targetDir, '.roo'))) {
fs.rmSync(path.join(targetDir, '.roo'), { recursive: true, force: true });
}
for (const mode of ROO_MODES) {
const src = path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`);
const dest = path.join(kiloModesDir, `rules-${mode}`, `${mode}-rules`);
if (fs.existsSync(src)) {
try {
const destDir = path.dirname(dest);
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
// Read, transform, and write the rule file
const ruleContent = fs.readFileSync(src, 'utf8');
const transformedContent = applyKiloTransformations(ruleContent);
fs.writeFileSync(dest, transformedContent);
log('debug', `[Kilo] Transformed and copied ${mode}-rules to ${dest}`);
} catch (err) {
log(
'error',
`[Kilo] Failed to transform ${src} to ${dest}: ${err.message}`
);
}
}
}
}
function onRemoveRulesProfile(targetDir) {
const kilocodemodespath = path.join(targetDir, '.kilocodemodes');
if (fs.existsSync(kilocodemodespath)) {
try {
fs.rmSync(kilocodemodespath, { force: true });
log('debug', `[Kilo] Removed .kilocodemodes from ${kilocodemodespath}`);
} catch (err) {
log('error', `[Kilo] Failed to remove .kilocodemodes: ${err.message}`);
}
}
const kiloDir = path.join(targetDir, '.kilo');
if (fs.existsSync(kiloDir)) {
fs.readdirSync(kiloDir).forEach((entry) => {
if (entry.startsWith('rules-')) {
const modeDir = path.join(kiloDir, entry);
try {
fs.rmSync(modeDir, { recursive: true, force: true });
log('debug', `[Kilo] Removed ${entry} directory from ${modeDir}`);
} catch (err) {
log('error', `[Kilo] Failed to remove ${modeDir}: ${err.message}`);
}
}
});
if (fs.readdirSync(kiloDir).length === 0) {
try {
fs.rmSync(kiloDir, { recursive: true, force: true });
log('debug', `[Kilo] Removed empty .kilo directory from ${kiloDir}`);
} catch (err) {
log('error', `[Kilo] Failed to remove .kilo directory: ${err.message}`);
}
}
}
}
function onPostConvertRulesProfile(targetDir, assetsDir) {
onAddRulesProfile(targetDir, assetsDir);
}
// Create and export kilo profile using the base factory with roo rule reuse
export const kiloProfile = createProfile({
name: 'kilo',
displayName: 'Kilo Code',
url: 'kilocode.com',
docsUrl: 'docs.kilocode.com',
profileDir: '.kilo',
rulesDir: '.kilo/rules',
toolMappings: COMMON_TOOL_MAPPINGS.ROO_STYLE,
fileMap: {
// Map roo rule files to kilo equivalents
'rules/cursor_rules.mdc': 'kilo_rules.md',
'rules/dev_workflow.mdc': 'dev_workflow.md',
'rules/self_improve.mdc': 'self_improve.md',
'rules/taskmaster.mdc': 'taskmaster.md'
},
onAdd: onAddRulesProfile,
onRemove: onRemoveRulesProfile,
onPostConvert: onPostConvertRulesProfile
});
// Export lifecycle functions separately to avoid naming conflicts
export { onAddRulesProfile, onRemoveRulesProfile, onPostConvertRulesProfile };
```
--------------------------------------------------------------------------------
/packages/tm-core/src/common/types/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Core type definitions for Task Master
*/
/**
* Storage type options
* - 'file': Local file system storage
* - 'api': Remote API storage (Hamster integration)
* - 'auto': Automatically detect based on auth status
*/
export type StorageType = 'file' | 'api' | 'auto';
// ============================================================================
// Type Literals
// ============================================================================
/**
* Task status values
*/
export type TaskStatus =
| 'pending'
| 'in-progress'
| 'done'
| 'deferred'
| 'cancelled'
| 'blocked'
| 'review'
| 'completed';
/**
* Task priority levels
*/
export type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
/**
* Task complexity levels
*/
export type TaskComplexity = 'simple' | 'moderate' | 'complex' | 'very-complex';
// ============================================================================
// Core Interfaces
// ============================================================================
/**
* Placeholder task interface for temporary/minimal task objects
*/
export interface PlaceholderTask {
id: string;
title: string;
status: TaskStatus;
priority: TaskPriority;
}
/**
* Base task interface
*/
export interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
dependencies: string[];
details: string;
testStrategy: string;
subtasks: Subtask[];
// Optional enhanced properties
createdAt?: string;
updatedAt?: string;
effort?: number;
actualEffort?: number;
tags?: string[];
assignee?: string;
// Database UUID (for API calls that need the actual UUID instead of display_id)
databaseId?: string;
// Complexity analysis (from complexity report)
// Can be either enum ('simple' | 'moderate' | 'complex' | 'very-complex') or numeric score (1-10)
complexity?: TaskComplexity | number;
recommendedSubtasks?: number;
expansionPrompt?: string;
complexityReasoning?: string;
}
/**
* Subtask interface extending Task
* ID can be number (file storage) or string (API storage with display_id)
*/
export interface Subtask extends Omit<Task, 'id' | 'subtasks'> {
id: number | string;
parentId: string;
subtasks?: never; // Subtasks cannot have their own subtasks
}
/**
* Task metadata for tracking overall project state
*/
export interface TaskMetadata {
version: string;
lastModified: string;
taskCount: number;
completedCount: number;
projectName?: string;
description?: string;
tags?: string[];
created?: string;
updated?: string;
}
/**
* Task collection with metadata
*/
export interface TaskCollection {
tasks: Task[];
metadata: TaskMetadata;
}
/**
* Task tag for organizing tasks
*/
export interface TaskTag {
name: string;
tasks: string[]; // Task IDs belonging to this tag
metadata: Record<string, any>;
}
// ============================================================================
// Utility Types
// ============================================================================
/**
* Type for creating a new task (without generated fields)
*/
export type CreateTask = Omit<
Task,
'id' | 'createdAt' | 'updatedAt' | 'subtasks'
> & {
subtasks?: Omit<Subtask, 'id' | 'parentId' | 'createdAt' | 'updatedAt'>[];
};
/**
* Type for updating a task (all fields optional except ID)
*/
export type UpdateTask = Partial<Omit<Task, 'id'>> & {
id: string;
};
/**
* Type for task filters
*/
export interface TaskFilter {
status?: TaskStatus | TaskStatus[];
priority?: TaskPriority | TaskPriority[];
tags?: string[];
hasSubtasks?: boolean;
search?: string;
assignee?: string;
}
/**
* Type for sort options
*/
export interface TaskSortOptions {
field: keyof Task;
direction: 'asc' | 'desc';
}
// ============================================================================
// Type Guards
// ============================================================================
/**
* Type guard to check if a value is a valid TaskStatus
*/
export function isTaskStatus(value: unknown): value is TaskStatus {
return (
typeof value === 'string' &&
[
'pending',
'in-progress',
'done',
'deferred',
'cancelled',
'blocked',
'review'
].includes(value)
);
}
/**
* Type guard to check if a value is a valid TaskPriority
*/
export function isTaskPriority(value: unknown): value is TaskPriority {
return (
typeof value === 'string' &&
['low', 'medium', 'high', 'critical'].includes(value)
);
}
/**
* Type guard to check if a value is a valid TaskComplexity
*/
export function isTaskComplexity(value: unknown): value is TaskComplexity {
return (
typeof value === 'string' &&
['simple', 'moderate', 'complex', 'very-complex'].includes(value)
);
}
/**
* Type guard to check if an object is a Task
*/
export function isTask(obj: unknown): obj is Task {
if (!obj || typeof obj !== 'object') return false;
const task = obj as Record<string, unknown>;
return (
typeof task.id === 'string' &&
typeof task.title === 'string' &&
typeof task.description === 'string' &&
isTaskStatus(task.status) &&
isTaskPriority(task.priority) &&
Array.isArray(task.dependencies) &&
typeof task.details === 'string' &&
typeof task.testStrategy === 'string' &&
Array.isArray(task.subtasks)
);
}
/**
* Type guard to check if an object is a Subtask
*/
export function isSubtask(obj: unknown): obj is Subtask {
if (!obj || typeof obj !== 'object') return false;
const subtask = obj as Record<string, unknown>;
return (
typeof subtask.id === 'number' &&
typeof subtask.parentId === 'string' &&
typeof subtask.title === 'string' &&
typeof subtask.description === 'string' &&
isTaskStatus(subtask.status) &&
isTaskPriority(subtask.priority) &&
!('subtasks' in subtask)
);
}
// ============================================================================
// Deprecated Types (for backwards compatibility)
// ============================================================================
/**
* @deprecated Use TaskStatus instead
*/
export type Status = TaskStatus;
/**
* @deprecated Use TaskPriority instead
*/
export type Priority = TaskPriority;
/**
* @deprecated Use TaskComplexity instead
*/
export type Complexity = TaskComplexity;
```
--------------------------------------------------------------------------------
/apps/docs/capabilities/mcp.mdx:
--------------------------------------------------------------------------------
```markdown
---
title: MCP Tools
sidebarTitle: "MCP Tools"
---
# MCP Tools
This document provides an overview of the MCP (Machine-to-Machine Communication Protocol) interface for the Task Master application. The MCP interface is defined in the `mcp-server/` directory and exposes the application's core functionalities as a set of tools that can be called remotely.
## Core Concepts
The MCP interface is built on top of the `fastmcp` library and registers a set of tools that correspond to the core functionalities of the Task Master application. These tools are defined in the `mcp-server/src/tools/` directory and are registered with the MCP server in `mcp-server/src/tools/index.js`.
Each tool is defined with a name, a description, and a set of parameters that are validated using the `zod` library. The `execute` function of each tool calls the corresponding core logic function from `scripts/modules/task-manager.js`.
## Configurable Tool Loading
To optimize LLM context usage, you can control which Task Master MCP tools are loaded using the `TASK_MASTER_TOOLS` environment variable. This is particularly useful when working with LLMs that have context limits or when you only need a subset of tools.
### Configuration Modes
#### All Tools (Default)
Loads all 36 available tools. Use when you need full Task Master functionality.
```json
{
"mcpServers": {
"task-master-ai": {
"command": "npx",
"args": ["-y", "task-master-ai"],
"env": {
"TASK_MASTER_TOOLS": "all",
"ANTHROPIC_API_KEY": "your_key_here"
}
}
}
}
```
If `TASK_MASTER_TOOLS` is not set, all tools are loaded by default.
#### Core Tools (Lean Mode)
Loads only 7 essential tools for daily development. Ideal for minimal context usage.
**Core tools included:**
- `get_tasks` - List all tasks
- `next_task` - Find the next task to work on
- `get_task` - Get detailed task information
- `set_task_status` - Update task status
- `update_subtask` - Add implementation notes
- `parse_prd` - Generate tasks from PRD
- `expand_task` - Break down tasks into subtasks
```json
{
"mcpServers": {
"task-master-ai": {
"command": "npx",
"args": ["-y", "task-master-ai"],
"env": {
"TASK_MASTER_TOOLS": "core",
"ANTHROPIC_API_KEY": "your_key_here"
}
}
}
}
```
You can also use `"lean"` as an alias for `"core"`.
#### Standard Tools
Loads 15 commonly used tools. Balances functionality with context efficiency.
**Standard tools include all core tools plus:**
- `initialize_project` - Set up new projects
- `analyze_project_complexity` - Analyze task complexity
- `expand_all` - Expand all eligible tasks
- `add_subtask` - Add subtasks manually
- `remove_task` - Remove tasks
- `generate` - Generate task markdown files
- `add_task` - Create new tasks
- `complexity_report` - View complexity analysis
```json
{
"mcpServers": {
"task-master-ai": {
"command": "npx",
"args": ["-y", "task-master-ai"],
"env": {
"TASK_MASTER_TOOLS": "standard",
"ANTHROPIC_API_KEY": "your_key_here"
}
}
}
}
```
#### Custom Tool Selection
Specify exactly which tools to load using a comma-separated list. Tool names are case-insensitive and support both underscores and hyphens.
```json
{
"mcpServers": {
"task-master-ai": {
"command": "npx",
"args": ["-y", "task-master-ai"],
"env": {
"TASK_MASTER_TOOLS": "get_tasks,next_task,set_task_status,update_subtask",
"ANTHROPIC_API_KEY": "your_key_here"
}
}
}
}
```
### Choosing the Right Configuration
- **Use `core`/`lean`**: When working with basic task management workflows or when context limits are strict
- **Use `standard`**: For most development workflows that include task creation and analysis
- **Use `all`**: When you need full functionality including tag management, dependencies, and advanced features
- **Use custom list**: When you have specific tool requirements or want to experiment with minimal sets
### Verification
When the MCP server starts, it logs which tools were loaded:
```
Task Master MCP Server starting...
Tool mode configuration: standard
Loading standard tools
Registering 15 MCP tools (mode: standard)
Successfully registered 15/15 tools
```
## Tool Categories
The MCP tools can be categorized in the same way as the core functionalities:
### 1. Task and Subtask Management
- **`add_task`**: Creates a new task.
- **`add_subtask`**: Adds a subtask to a parent task.
- **`remove_task`**: Removes one or more tasks or subtasks.
- **`remove_subtask`**: Removes a subtask from its parent.
- **`update_task`**: Updates a single task.
- **`update_subtask`**: Appends information to a subtask.
- **`update`**: Updates multiple tasks.
- **`move_task`**: Moves a task or subtask.
- **`clear_subtasks`**: Clears all subtasks from one or more tasks.
### 2. Task Information and Status
- **`get_tasks`**: Lists all tasks.
- **`get_task`**: Shows the details of a specific task.
- **`next_task`**: Shows the next task to work on.
- **`set_task_status`**: Sets the status of a task or subtask.
### 3. Task Analysis and Expansion
- **`parse_prd`**: Parses a PRD to generate tasks.
- **`expand_task`**: Expands a task into subtasks.
- **`expand_all`**: Expands all eligible tasks.
- **`analyze_project_complexity`**: Analyzes task complexity.
- **`complexity_report`**: Displays the complexity analysis report.
### 4. Dependency Management
- **`add_dependency`**: Adds a dependency to a task.
- **`remove_dependency`**: Removes a dependency from a task.
- **`validate_dependencies`**: Validates the dependencies of all tasks.
- **`fix_dependencies`**: Fixes any invalid dependencies.
### 5. Project and Configuration
- **`initialize_project`**: Initializes a new project.
- **`generate`**: Generates individual task files.
- **`models`**: Manages AI model configurations.
- **`research`**: Performs AI-powered research.
### 6. Tag Management
- **`add_tag`**: Creates a new tag.
- **`delete_tag`**: Deletes a tag.
- **`list_tags`**: Lists all tags.
- **`use_tag`**: Switches to a different tag.
- **`rename_tag`**: Renames a tag.
- **`copy_tag`**: Copies a tag.
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-roo.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
mkdirSync: jest.fn()
}));
// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { rooProfile } from '../../../src/profiles/roo.js';
describe('Roo Rule Transformer', () => {
// Set up spies on the mocked modules
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
const mockExistsSync = jest.spyOn(fs, 'existsSync');
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
const mockConsoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockReadFileSync.mockReturnValue('');
mockWriteFileSync.mockImplementation(() => {});
mockExistsSync.mockReturnValue(true);
mockMkdirSync.mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should correctly convert basic terms', () => {
const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files.`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
rooProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Verify file operations were called correctly
expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations
expect(transformedContent).toContain('Roo');
expect(transformedContent).toContain('roocode.com');
expect(transformedContent).toContain('.md');
expect(transformedContent).not.toContain('cursor.so');
expect(transformedContent).not.toContain('Cursor rule');
});
it('should correctly convert tool references', () => {
const testContent = `---
description: Test Cursor rule for tool references
globs: **/*
alwaysApply: true
---
- Use the search tool to find code
- The edit_file tool lets you modify files
- run_command executes terminal commands
- use_mcp connects to external services`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
rooProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Roo uses different tool names)
expect(transformedContent).toContain('search_files tool');
expect(transformedContent).toContain('apply_diff tool');
expect(transformedContent).toContain('execute_command');
expect(transformedContent).toContain('use_mcp_tool');
});
it('should correctly update file references', () => {
const testContent = `---
description: Test Cursor rule for file references
globs: **/*
alwaysApply: true
---
This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
rooProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations - no taskmaster subdirectory for Roo
expect(transformedContent).toContain('(.roo/rules/dev_workflow.md)'); // File path transformation - no taskmaster subdirectory for Roo
expect(transformedContent).toContain('(.roo/rules/taskmaster.md)'); // File path transformation - no taskmaster subdirectory for Roo
expect(transformedContent).not.toContain('(mdc:.cursor/rules/');
});
it('should handle file read errors', () => {
// Mock file read to throw an error
mockReadFileSync.mockImplementation(() => {
throw new Error('File not found');
});
// Call the actual function
const result = convertRuleToProfileRule(
'nonexistent.mdc',
'target.md',
rooProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify writeFileSync was not called
expect(mockWriteFileSync).not.toHaveBeenCalled();
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: File not found'
);
});
it('should handle file write errors', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock file write to throw an error
mockWriteFileSync.mockImplementation(() => {
throw new Error('Permission denied');
});
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
rooProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: Permission denied'
);
});
it('should create target directory if it does not exist', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock directory doesn't exist initially
mockExistsSync.mockReturnValue(false);
// Call the actual function
convertRuleToProfileRule(
'source.mdc',
'some/deep/path/target.md',
rooProfile
);
// Verify directory creation was called
expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', {
recursive: true
});
});
});
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-trae.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
mkdirSync: jest.fn()
}));
// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { traeProfile } from '../../../src/profiles/trae.js';
describe('Trae Rule Transformer', () => {
// Set up spies on the mocked modules
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
const mockExistsSync = jest.spyOn(fs, 'existsSync');
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
const mockConsoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockReadFileSync.mockReturnValue('');
mockWriteFileSync.mockImplementation(() => {});
mockExistsSync.mockReturnValue(true);
mockMkdirSync.mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should correctly convert basic terms', () => {
const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files.`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
traeProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Verify file operations were called correctly
expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations
expect(transformedContent).toContain('Trae');
expect(transformedContent).toContain('trae.ai');
expect(transformedContent).toContain('.md');
expect(transformedContent).not.toContain('cursor.so');
expect(transformedContent).not.toContain('Cursor rule');
});
it('should correctly convert tool references', () => {
const testContent = `---
description: Test Cursor rule for tool references
globs: **/*
alwaysApply: true
---
- Use the search tool to find code
- The edit_file tool lets you modify files
- run_command executes terminal commands
- use_mcp connects to external services`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
traeProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Trae uses standard tool names, so no transformation)
expect(transformedContent).toContain('search tool');
expect(transformedContent).toContain('edit_file tool');
expect(transformedContent).toContain('run_command');
expect(transformedContent).toContain('use_mcp');
});
it('should correctly update file references', () => {
const testContent = `---
description: Test Cursor rule for file references
globs: **/*
alwaysApply: true
---
This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
traeProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations - no taskmaster subdirectory for Trae
expect(transformedContent).toContain('(.trae/rules/dev_workflow.md)'); // File path transformation - no taskmaster subdirectory for Trae
expect(transformedContent).toContain('(.trae/rules/taskmaster.md)'); // File path transformation - no taskmaster subdirectory for Trae
expect(transformedContent).not.toContain('(mdc:.cursor/rules/');
});
it('should handle file read errors', () => {
// Mock file read to throw an error
mockReadFileSync.mockImplementation(() => {
throw new Error('File not found');
});
// Call the actual function
const result = convertRuleToProfileRule(
'nonexistent.mdc',
'target.md',
traeProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify writeFileSync was not called
expect(mockWriteFileSync).not.toHaveBeenCalled();
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: File not found'
);
});
it('should handle file write errors', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock file write to throw an error
mockWriteFileSync.mockImplementation(() => {
throw new Error('Permission denied');
});
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
traeProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: Permission denied'
);
});
it('should create target directory if it does not exist', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock directory doesn't exist initially
mockExistsSync.mockReturnValue(false);
// Call the actual function
convertRuleToProfileRule(
'source.mdc',
'some/deep/path/target.md',
traeProfile
);
// Verify directory creation was called
expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', {
recursive: true
});
});
});
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-kilo.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
mkdirSync: jest.fn()
}));
// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { kiloProfile } from '../../../src/profiles/kilo.js';
describe('Kilo Rule Transformer', () => {
// Set up spies on the mocked modules
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
const mockExistsSync = jest.spyOn(fs, 'existsSync');
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
const mockConsoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockReadFileSync.mockReturnValue('');
mockWriteFileSync.mockImplementation(() => {});
mockExistsSync.mockReturnValue(true);
mockMkdirSync.mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should correctly convert basic terms', () => {
const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files.`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
kiloProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Verify file operations were called correctly
expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations
expect(transformedContent).toContain('Kilo');
expect(transformedContent).toContain('kilocode.com');
expect(transformedContent).toContain('.md');
expect(transformedContent).not.toContain('cursor.so');
expect(transformedContent).not.toContain('Cursor rule');
});
it('should correctly convert tool references', () => {
const testContent = `---
description: Test Cursor rule for tool references
globs: **/*
alwaysApply: true
---
- Use the search tool to find code
- The edit_file tool lets you modify files
- run_command executes terminal commands
- use_mcp connects to external services`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
kiloProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Kilo uses different tool names)
expect(transformedContent).toContain('search_files tool');
expect(transformedContent).toContain('apply_diff tool');
expect(transformedContent).toContain('execute_command');
expect(transformedContent).toContain('use_mcp_tool');
});
it('should correctly update file references', () => {
const testContent = `---
description: Test Cursor rule for file references
globs: **/*
alwaysApply: true
---
This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
kiloProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations - no taskmaster subdirectory for Kilo
expect(transformedContent).toContain('(.kilo/rules/dev_workflow.md)'); // File path transformation for dev_workflow - no taskmaster subdirectory for Kilo
expect(transformedContent).toContain('(.kilo/rules/taskmaster.md)'); // File path transformation for taskmaster - no taskmaster subdirectory for Kilo
expect(transformedContent).not.toContain('(mdc:.cursor/rules/');
});
it('should handle file read errors', () => {
// Mock file read to throw an error
mockReadFileSync.mockImplementation(() => {
throw new Error('File not found');
});
// Call the actual function
const result = convertRuleToProfileRule(
'nonexistent.mdc',
'target.md',
kiloProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify writeFileSync was not called
expect(mockWriteFileSync).not.toHaveBeenCalled();
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: File not found'
);
});
it('should handle file write errors', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock file write to throw an error
mockWriteFileSync.mockImplementation(() => {
throw new Error('Permission denied');
});
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
kiloProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: Permission denied'
);
});
it('should create target directory if it does not exist', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock directory doesn't exist initially
mockExistsSync.mockReturnValue(false);
// Call the actual function
convertRuleToProfileRule(
'source.mdc',
'some/deep/path/target.md',
kiloProfile
);
// Verify directory creation was called
expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', {
recursive: true
});
});
});
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-zed.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
mkdirSync: jest.fn()
}));
// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { zedProfile } from '../../../src/profiles/zed.js';
describe('Zed Rule Transformer', () => {
// Set up spies on the mocked modules
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
const mockExistsSync = jest.spyOn(fs, 'existsSync');
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
const mockConsoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockReadFileSync.mockReturnValue('');
mockWriteFileSync.mockImplementation(() => {});
mockExistsSync.mockReturnValue(true);
mockMkdirSync.mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should correctly convert basic terms', () => {
const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files.`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Mock file system operations
mockExistsSync.mockReturnValue(true);
// Call the function
const result = convertRuleToProfileRule(
'test-source.mdc',
'test-target.md',
zedProfile
);
// Verify the result
expect(result).toBe(true);
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
// Get the transformed content
const transformedContent = mockWriteFileSync.mock.calls[0][1];
// Verify Cursor -> Zed transformations
expect(transformedContent).toContain('zed.dev');
expect(transformedContent).toContain('Zed');
expect(transformedContent).not.toContain('cursor.so');
expect(transformedContent).not.toContain('Cursor');
expect(transformedContent).toContain('.md');
expect(transformedContent).not.toContain('.mdc');
});
it('should handle URL transformations', () => {
const testContent = `Visit https://cursor.so/docs for more information.
Also check out cursor.so and www.cursor.so for updates.`;
mockReadFileSync.mockReturnValue(testContent);
mockExistsSync.mockReturnValue(true);
const result = convertRuleToProfileRule(
'test-source.mdc',
'test-target.md',
zedProfile
);
expect(result).toBe(true);
const transformedContent = mockWriteFileSync.mock.calls[0][1];
// Verify URL transformations
expect(transformedContent).toContain('https://zed.dev');
expect(transformedContent).toContain('zed.dev');
expect(transformedContent).not.toContain('cursor.so');
});
it('should handle file extension transformations', () => {
const testContent = `This rule references file.mdc and another.mdc file.
Use the .mdc extension for all rule files.`;
mockReadFileSync.mockReturnValue(testContent);
mockExistsSync.mockReturnValue(true);
const result = convertRuleToProfileRule(
'test-source.mdc',
'test-target.md',
zedProfile
);
expect(result).toBe(true);
const transformedContent = mockWriteFileSync.mock.calls[0][1];
// Verify file extension transformations
expect(transformedContent).toContain('file.md');
expect(transformedContent).toContain('another.md');
expect(transformedContent).toContain('.md extension');
expect(transformedContent).not.toContain('.mdc');
});
it('should handle case variations', () => {
const testContent = `CURSOR, Cursor, cursor should all be transformed.`;
mockReadFileSync.mockReturnValue(testContent);
mockExistsSync.mockReturnValue(true);
const result = convertRuleToProfileRule(
'test-source.mdc',
'test-target.md',
zedProfile
);
expect(result).toBe(true);
const transformedContent = mockWriteFileSync.mock.calls[0][1];
// Verify case transformations
// Due to regex order, the case-insensitive rule runs first:
// CURSOR -> Zed (because it starts with 'C'), Cursor -> Zed, cursor -> zed
expect(transformedContent).toContain('Zed');
expect(transformedContent).toContain('zed');
expect(transformedContent).not.toContain('CURSOR');
expect(transformedContent).not.toContain('Cursor');
expect(transformedContent).not.toContain('cursor');
});
it('should create target directory if it does not exist', () => {
const testContent = 'Test content';
mockReadFileSync.mockReturnValue(testContent);
mockExistsSync.mockReturnValue(false);
const result = convertRuleToProfileRule(
'test-source.mdc',
'nested/path/test-target.md',
zedProfile
);
expect(result).toBe(true);
expect(mockMkdirSync).toHaveBeenCalledWith('nested/path', {
recursive: true
});
});
it('should handle file system errors gracefully', () => {
mockReadFileSync.mockImplementation(() => {
throw new Error('File not found');
});
const result = convertRuleToProfileRule(
'test-source.mdc',
'test-target.md',
zedProfile
);
expect(result).toBe(false);
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: File not found'
);
});
it('should handle write errors gracefully', () => {
mockReadFileSync.mockReturnValue('Test content');
mockWriteFileSync.mockImplementation(() => {
throw new Error('Write permission denied');
});
const result = convertRuleToProfileRule(
'test-source.mdc',
'test-target.md',
zedProfile
);
expect(result).toBe(false);
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: Write permission denied'
);
});
it('should verify profile configuration', () => {
expect(zedProfile.profileName).toBe('zed');
expect(zedProfile.displayName).toBe('Zed');
expect(zedProfile.profileDir).toBe('.zed');
expect(zedProfile.mcpConfig).toBe(true);
expect(zedProfile.mcpConfigName).toBe('settings.json');
expect(zedProfile.mcpConfigPath).toBe('.zed/settings.json');
expect(zedProfile.includeDefaultRules).toBe(false);
expect(zedProfile.fileMap).toEqual({
'AGENTS.md': '.rules'
});
});
});
```
--------------------------------------------------------------------------------
/packages/tm-core/src/common/mappers/TaskMapper.ts:
--------------------------------------------------------------------------------
```typescript
import { Database, Tables } from '../types/database.types.js';
import { Subtask, Task } from '../types/index.js';
type TaskRow = Tables<'tasks'>;
// Legacy type for backward compatibility
type DependencyRow = Tables<'task_dependencies'> & {
depends_on_task?: { display_id: string } | null;
depends_on_task_id?: string;
};
export class TaskMapper {
/**
* Maps database tasks to internal Task format
* @param dbTasks - Array of tasks from database
* @param dependencies - Either a Map of task_id to display_ids or legacy array format
*/
static mapDatabaseTasksToTasks(
dbTasks: TaskRow[],
dependencies: Map<string, string[]> | DependencyRow[]
): Task[] {
if (!dbTasks || dbTasks.length === 0) {
return [];
}
// Handle both Map and array formats for backward compatibility
const dependenciesByTaskId =
dependencies instanceof Map
? dependencies
: this.groupDependenciesByTaskId(dependencies);
// Separate parent tasks and subtasks
const parentTasks = dbTasks.filter((t) => !t.parent_task_id);
const subtasksByParentId = this.groupSubtasksByParentId(dbTasks);
// Map parent tasks with their subtasks
return parentTasks.map((taskRow) =>
this.mapDatabaseTaskToTask(
taskRow,
subtasksByParentId.get(taskRow.id) || [],
dependenciesByTaskId
)
);
}
/**
* Maps a single database task to internal Task format
*/
static mapDatabaseTaskToTask(
dbTask: TaskRow,
dbSubtasks: TaskRow[],
dependenciesByTaskId: Map<string, string[]>
): Task {
// Map subtasks
const subtasks: Subtask[] = dbSubtasks.map((subtask, index) => ({
id: subtask.display_id || String(index + 1), // Use display_id if available (API storage), fallback to numeric (file storage)
parentId: dbTask.id,
title: subtask.title,
description: subtask.description || '',
status: this.mapStatus(subtask.status),
priority: this.mapPriority(subtask.priority),
dependencies: dependenciesByTaskId.get(subtask.id) || [],
details: this.extractMetadataField(subtask.metadata, 'details', ''),
testStrategy: this.extractMetadataField(
subtask.metadata,
'testStrategy',
''
),
createdAt: subtask.created_at,
updatedAt: subtask.updated_at,
assignee: subtask.assignee_id || undefined,
complexity: subtask.complexity ?? undefined,
databaseId: subtask.id // Include the actual database UUID
}));
return {
id: dbTask.display_id || dbTask.id, // Use display_id if available
databaseId: dbTask.id, // Include the actual database UUID
title: dbTask.title,
description: dbTask.description || '',
status: this.mapStatus(dbTask.status),
priority: this.mapPriority(dbTask.priority),
dependencies: dependenciesByTaskId.get(dbTask.id) || [],
details: this.extractMetadataField(dbTask.metadata, 'details', ''),
testStrategy: this.extractMetadataField(
dbTask.metadata,
'testStrategy',
''
),
subtasks,
createdAt: dbTask.created_at,
updatedAt: dbTask.updated_at,
assignee: dbTask.assignee_id || undefined,
complexity: dbTask.complexity ?? undefined,
effort: dbTask.estimated_hours || undefined,
actualEffort: dbTask.actual_hours || undefined
};
}
/**
* Groups dependencies by task ID (legacy method for backward compatibility)
* @deprecated Use DependencyFetcher.fetchDependenciesWithDisplayIds instead
*/
private static groupDependenciesByTaskId(
dependencies: DependencyRow[]
): Map<string, string[]> {
const dependenciesByTaskId = new Map<string, string[]>();
if (dependencies) {
for (const dep of dependencies) {
const deps = dependenciesByTaskId.get(dep.task_id) || [];
// Handle both old format (UUID string) and new format (object with display_id)
const dependencyId =
typeof dep.depends_on_task === 'object'
? dep.depends_on_task?.display_id
: dep.depends_on_task_id;
if (dependencyId) {
deps.push(dependencyId);
}
dependenciesByTaskId.set(dep.task_id, deps);
}
}
return dependenciesByTaskId;
}
/**
* Groups subtasks by their parent ID
*/
private static groupSubtasksByParentId(
tasks: TaskRow[]
): Map<string, TaskRow[]> {
const subtasksByParentId = new Map<string, TaskRow[]>();
for (const task of tasks) {
if (task.parent_task_id) {
const subtasks = subtasksByParentId.get(task.parent_task_id) || [];
subtasks.push(task);
subtasksByParentId.set(task.parent_task_id, subtasks);
}
}
// Sort subtasks by subtask_position for each parent
for (const subtasks of subtasksByParentId.values()) {
subtasks.sort((a, b) => a.subtask_position - b.subtask_position);
}
return subtasksByParentId;
}
/**
* Maps database status to internal status
*/
static mapStatus(
status: Database['public']['Enums']['task_status']
): Task['status'] {
switch (status) {
case 'todo':
return 'pending';
case 'in_progress':
return 'in-progress';
case 'done':
return 'done';
default:
return 'pending';
}
}
/**
* Maps database priority to internal priority
*/
private static mapPriority(
priority: Database['public']['Enums']['task_priority']
): Task['priority'] {
switch (priority) {
case 'urgent':
return 'critical';
default:
return priority as Task['priority'];
}
}
/**
* Safely extracts a field from metadata JSON with runtime type validation
* @param metadata The metadata object (could be null or any type)
* @param field The field to extract
* @param defaultValue Default value if field doesn't exist
* @returns The extracted value if it matches the expected type, otherwise defaultValue
*/
private static extractMetadataField<T>(
metadata: unknown,
field: string,
defaultValue: T
): T {
if (!metadata || typeof metadata !== 'object') {
return defaultValue;
}
const value = (metadata as Record<string, unknown>)[field];
if (value === undefined) {
return defaultValue;
}
// Runtime type validation: ensure value matches the type of defaultValue
const expectedType = typeof defaultValue;
const actualType = typeof value;
if (expectedType !== actualType) {
console.warn(
`Type mismatch in metadata field "${field}": expected ${expectedType}, got ${actualType}. Using default value.`
);
return defaultValue;
}
return value as T;
}
}
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-windsurf.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock fs module before importing anything that uses it
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
mkdirSync: jest.fn()
}));
// Import modules after mocking
import fs from 'fs';
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
import { windsurfProfile } from '../../../src/profiles/windsurf.js';
describe('Windsurf Rule Transformer', () => {
// Set up spies on the mocked modules
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
const mockExistsSync = jest.spyOn(fs, 'existsSync');
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
const mockConsoleError = jest
.spyOn(console, 'error')
.mockImplementation(() => {});
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockReadFileSync.mockReturnValue('');
mockWriteFileSync.mockImplementation(() => {});
mockExistsSync.mockReturnValue(true);
mockMkdirSync.mockImplementation(() => {});
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should correctly convert basic terms', () => {
const testContent = `---
description: Test Cursor rule for basic terms
globs: **/*
alwaysApply: true
---
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
Also has references to .mdc files.`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
windsurfProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Verify file operations were called correctly
expect(mockReadFileSync).toHaveBeenCalledWith('source.mdc', 'utf8');
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations
expect(transformedContent).toContain('Windsurf');
expect(transformedContent).toContain('windsurf.com');
expect(transformedContent).toContain('.md');
expect(transformedContent).not.toContain('cursor.so');
expect(transformedContent).not.toContain('Cursor rule');
});
it('should correctly convert tool references', () => {
const testContent = `---
description: Test Cursor rule for tool references
globs: **/*
alwaysApply: true
---
- Use the search tool to find code
- The edit_file tool lets you modify files
- run_command executes terminal commands
- use_mcp connects to external services`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
windsurfProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations (Windsurf uses standard tool names, so no transformation)
expect(transformedContent).toContain('search tool');
expect(transformedContent).toContain('edit_file tool');
expect(transformedContent).toContain('run_command');
expect(transformedContent).toContain('use_mcp');
});
it('should correctly update file references', () => {
const testContent = `---
description: Test Cursor rule for file references
globs: **/*
alwaysApply: true
---
This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`;
// Mock file read to return our test content
mockReadFileSync.mockReturnValue(testContent);
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
windsurfProfile
);
// Verify the function succeeded
expect(result).toBe(true);
// Get the transformed content that was written
const writeCall = mockWriteFileSync.mock.calls[0];
const transformedContent = writeCall[1];
// Verify transformations - no taskmaster subdirectory for Windsurf
expect(transformedContent).toContain('(.windsurf/rules/dev_workflow.md)'); // File path transformation - no taskmaster subdirectory for Windsurf
expect(transformedContent).toContain('(.windsurf/rules/taskmaster.md)'); // File path transformation - no taskmaster subdirectory for Windsurf
expect(transformedContent).not.toContain('(mdc:.cursor/rules/');
});
it('should handle file read errors', () => {
// Mock file read to throw an error
mockReadFileSync.mockImplementation(() => {
throw new Error('File not found');
});
// Call the actual function
const result = convertRuleToProfileRule(
'nonexistent.mdc',
'target.md',
windsurfProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify writeFileSync was not called
expect(mockWriteFileSync).not.toHaveBeenCalled();
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: File not found'
);
});
it('should handle file write errors', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock file write to throw an error
mockWriteFileSync.mockImplementation(() => {
throw new Error('Permission denied');
});
// Call the actual function
const result = convertRuleToProfileRule(
'source.mdc',
'target.md',
windsurfProfile
);
// Verify the function failed gracefully
expect(result).toBe(false);
// Verify error was logged
expect(mockConsoleError).toHaveBeenCalledWith(
'Error converting rule file: Permission denied'
);
});
it('should create target directory if it does not exist', () => {
const testContent = 'test content';
mockReadFileSync.mockReturnValue(testContent);
// Mock directory doesn't exist initially
mockExistsSync.mockReturnValue(false);
// Call the actual function
convertRuleToProfileRule(
'source.mdc',
'some/deep/path/target.md',
windsurfProfile
);
// Verify directory creation was called
expect(mockMkdirSync).toHaveBeenCalledWith('some/deep/path', {
recursive: true
});
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/tool-registry.js:
--------------------------------------------------------------------------------
```javascript
/**
* tool-registry.js
* Tool Registry - Maps tool names to registration functions
*/
import { registerSetTaskStatusTool } from './set-task-status.js';
import { registerParsePRDTool } from './parse-prd.js';
import { registerUpdateTool } from './update.js';
import { registerUpdateTaskTool } from './update-task.js';
import { registerUpdateSubtaskTool } from './update-subtask.js';
import { registerGenerateTool } from './generate.js';
import { registerNextTaskTool } from './next-task.js';
import { registerExpandTaskTool } from './expand-task.js';
import { registerAddTaskTool } from './add-task.js';
import { registerAddSubtaskTool } from './add-subtask.js';
import { registerRemoveSubtaskTool } from './remove-subtask.js';
import { registerAnalyzeProjectComplexityTool } from './analyze.js';
import { registerClearSubtasksTool } from './clear-subtasks.js';
import { registerExpandAllTool } from './expand-all.js';
import { registerRemoveDependencyTool } from './remove-dependency.js';
import { registerValidateDependenciesTool } from './validate-dependencies.js';
import { registerFixDependenciesTool } from './fix-dependencies.js';
import { registerComplexityReportTool } from './complexity-report.js';
import { registerAddDependencyTool } from './add-dependency.js';
import { registerRemoveTaskTool } from './remove-task.js';
import { registerInitializeProjectTool } from './initialize-project.js';
import { registerModelsTool } from './models.js';
import { registerMoveTaskTool } from './move-task.js';
import { registerResponseLanguageTool } from './response-language.js';
import { registerAddTagTool } from './add-tag.js';
import { registerDeleteTagTool } from './delete-tag.js';
import { registerListTagsTool } from './list-tags.js';
import { registerUseTagTool } from './use-tag.js';
import { registerRenameTagTool } from './rename-tag.js';
import { registerCopyTagTool } from './copy-tag.js';
import { registerResearchTool } from './research.js';
import { registerRulesTool } from './rules.js';
import { registerScopeUpTool } from './scope-up.js';
import { registerScopeDownTool } from './scope-down.js';
// Import TypeScript tools from apps/mcp
import {
registerAutopilotStartTool,
registerAutopilotResumeTool,
registerAutopilotNextTool,
registerAutopilotStatusTool,
registerAutopilotCompleteTool,
registerAutopilotCommitTool,
registerAutopilotFinalizeTool,
registerAutopilotAbortTool,
registerGetTasksTool,
registerGetTaskTool
} from '@tm/mcp';
/**
* Comprehensive tool registry mapping all 44 tool names to their registration functions
* Used for dynamic tool registration and validation
*/
export const toolRegistry = {
initialize_project: registerInitializeProjectTool,
models: registerModelsTool,
rules: registerRulesTool,
parse_prd: registerParsePRDTool,
'response-language': registerResponseLanguageTool,
analyze_project_complexity: registerAnalyzeProjectComplexityTool,
expand_task: registerExpandTaskTool,
expand_all: registerExpandAllTool,
scope_up_task: registerScopeUpTool,
scope_down_task: registerScopeDownTool,
get_tasks: registerGetTasksTool,
get_task: registerGetTaskTool,
next_task: registerNextTaskTool,
complexity_report: registerComplexityReportTool,
set_task_status: registerSetTaskStatusTool,
generate: registerGenerateTool,
add_task: registerAddTaskTool,
add_subtask: registerAddSubtaskTool,
update: registerUpdateTool,
update_task: registerUpdateTaskTool,
update_subtask: registerUpdateSubtaskTool,
remove_task: registerRemoveTaskTool,
remove_subtask: registerRemoveSubtaskTool,
clear_subtasks: registerClearSubtasksTool,
move_task: registerMoveTaskTool,
add_dependency: registerAddDependencyTool,
remove_dependency: registerRemoveDependencyTool,
validate_dependencies: registerValidateDependenciesTool,
fix_dependencies: registerFixDependenciesTool,
list_tags: registerListTagsTool,
add_tag: registerAddTagTool,
delete_tag: registerDeleteTagTool,
use_tag: registerUseTagTool,
rename_tag: registerRenameTagTool,
copy_tag: registerCopyTagTool,
research: registerResearchTool,
autopilot_start: registerAutopilotStartTool,
autopilot_resume: registerAutopilotResumeTool,
autopilot_next: registerAutopilotNextTool,
autopilot_status: registerAutopilotStatusTool,
autopilot_complete: registerAutopilotCompleteTool,
autopilot_commit: registerAutopilotCommitTool,
autopilot_finalize: registerAutopilotFinalizeTool,
autopilot_abort: registerAutopilotAbortTool
};
/**
* Core tools array containing the 7 essential tools for daily development
* These represent the minimal set needed for basic task management operations
*/
export const coreTools = [
'get_tasks',
'next_task',
'get_task',
'set_task_status',
'update_subtask',
'parse_prd',
'expand_task'
];
/**
* Standard tools array containing the 15 most commonly used tools
* Includes all core tools plus frequently used additional tools
*/
export const standardTools = [
...coreTools,
'initialize_project',
'analyze_project_complexity',
'expand_all',
'add_subtask',
'remove_task',
'generate',
'add_task',
'complexity_report'
];
/**
* Get all available tool names
* @returns {string[]} Array of tool names
*/
export function getAvailableTools() {
return Object.keys(toolRegistry);
}
/**
* Get tool counts for all categories
* @returns {Object} Object with core, standard, and total counts
*/
export function getToolCounts() {
return {
core: coreTools.length,
standard: standardTools.length,
total: Object.keys(toolRegistry).length
};
}
/**
* Get tool arrays organized by category
* @returns {Object} Object with arrays for each category
*/
export function getToolCategories() {
const allTools = Object.keys(toolRegistry);
return {
core: [...coreTools],
standard: [...standardTools],
all: [...allTools],
extended: allTools.filter((t) => !standardTools.includes(t))
};
}
/**
* Get registration function for a specific tool
* @param {string} toolName - Name of the tool
* @returns {Function|null} Registration function or null if not found
*/
export function getToolRegistration(toolName) {
return toolRegistry[toolName] || null;
}
/**
* Validate if a tool exists in the registry
* @param {string} toolName - Name of the tool
* @returns {boolean} True if tool exists
*/
export function isValidTool(toolName) {
return toolName in toolRegistry;
}
export default toolRegistry;
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/index.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/index.js
* Export all Task Master CLI tools for MCP server
*/
import logger from '../logger.js';
import {
toolRegistry,
coreTools,
standardTools,
getAvailableTools,
getToolRegistration,
isValidTool
} from './tool-registry.js';
/**
* Helper function to safely read and normalize the TASK_MASTER_TOOLS environment variable
* @returns {string} The tools configuration string, defaults to 'all'
*/
export function getToolsConfiguration() {
const rawValue = process.env.TASK_MASTER_TOOLS;
if (!rawValue || rawValue.trim() === '') {
logger.debug('No TASK_MASTER_TOOLS env var found, defaulting to "all"');
return 'all';
}
const normalizedValue = rawValue.trim();
logger.debug(`TASK_MASTER_TOOLS env var: "${normalizedValue}"`);
return normalizedValue;
}
/**
* Register Task Master tools with the MCP server
* Supports selective tool loading via TASK_MASTER_TOOLS environment variable
* @param {Object} server - FastMCP server instance
* @param {string} toolMode - The tool mode configuration (defaults to 'all')
* @returns {Object} Object containing registered tools, failed tools, and normalized mode
*/
export function registerTaskMasterTools(server, toolMode = 'all') {
const registeredTools = [];
const failedTools = [];
try {
const enabledTools = toolMode.trim();
let toolsToRegister = [];
const lowerCaseConfig = enabledTools.toLowerCase();
switch (lowerCaseConfig) {
case 'all':
toolsToRegister = Object.keys(toolRegistry);
logger.info('Loading all available tools');
break;
case 'core':
case 'lean':
toolsToRegister = coreTools;
logger.info('Loading core tools only');
break;
case 'standard':
toolsToRegister = standardTools;
logger.info('Loading standard tools');
break;
default:
const requestedTools = enabledTools
.split(',')
.map((t) => t.trim())
.filter((t) => t.length > 0);
const uniqueTools = new Set();
const unknownTools = [];
const aliasMap = {
response_language: 'response-language'
};
for (const toolName of requestedTools) {
let resolvedName = null;
const lowerToolName = toolName.toLowerCase();
if (aliasMap[lowerToolName]) {
const aliasTarget = aliasMap[lowerToolName];
for (const registryKey of Object.keys(toolRegistry)) {
if (registryKey.toLowerCase() === aliasTarget.toLowerCase()) {
resolvedName = registryKey;
break;
}
}
}
if (!resolvedName) {
for (const registryKey of Object.keys(toolRegistry)) {
if (registryKey.toLowerCase() === lowerToolName) {
resolvedName = registryKey;
break;
}
}
}
if (!resolvedName) {
const withHyphens = lowerToolName.replace(/_/g, '-');
for (const registryKey of Object.keys(toolRegistry)) {
if (registryKey.toLowerCase() === withHyphens) {
resolvedName = registryKey;
break;
}
}
}
if (!resolvedName) {
const withUnderscores = lowerToolName.replace(/-/g, '_');
for (const registryKey of Object.keys(toolRegistry)) {
if (registryKey.toLowerCase() === withUnderscores) {
resolvedName = registryKey;
break;
}
}
}
if (resolvedName) {
uniqueTools.add(resolvedName);
logger.debug(`Resolved tool "${toolName}" to "${resolvedName}"`);
} else {
unknownTools.push(toolName);
logger.warn(`Unknown tool specified: "${toolName}"`);
}
}
toolsToRegister = Array.from(uniqueTools);
if (unknownTools.length > 0) {
logger.warn(`Unknown tools: ${unknownTools.join(', ')}`);
}
if (toolsToRegister.length === 0) {
logger.warn(
`No valid tools found in custom list. Loading all tools as fallback.`
);
toolsToRegister = Object.keys(toolRegistry);
} else {
logger.info(
`Loading ${toolsToRegister.length} custom tools from list (${uniqueTools.size} unique after normalization)`
);
}
break;
}
logger.info(
`Registering ${toolsToRegister.length} MCP tools (mode: ${enabledTools})`
);
toolsToRegister.forEach((toolName) => {
try {
const registerFunction = getToolRegistration(toolName);
if (registerFunction) {
registerFunction(server);
logger.debug(`Registered tool: ${toolName}`);
registeredTools.push(toolName);
} else {
logger.warn(`Tool ${toolName} not found in registry`);
failedTools.push(toolName);
}
} catch (error) {
if (error.message && error.message.includes('already registered')) {
logger.debug(`Tool ${toolName} already registered, skipping`);
registeredTools.push(toolName);
} else {
logger.error(`Failed to register tool ${toolName}: ${error.message}`);
failedTools.push(toolName);
}
}
});
logger.info(
`Successfully registered ${registeredTools.length}/${toolsToRegister.length} tools`
);
if (failedTools.length > 0) {
logger.warn(`Failed tools: ${failedTools.join(', ')}`);
}
return {
registeredTools,
failedTools,
normalizedMode: lowerCaseConfig
};
} catch (error) {
logger.error(
`Error parsing TASK_MASTER_TOOLS environment variable: ${error.message}`
);
logger.info('Falling back to loading all tools');
const fallbackTools = Object.keys(toolRegistry);
for (const toolName of fallbackTools) {
const registerFunction = getToolRegistration(toolName);
if (registerFunction) {
try {
registerFunction(server);
registeredTools.push(toolName);
} catch (err) {
if (err.message && err.message.includes('already registered')) {
logger.debug(
`Fallback tool ${toolName} already registered, skipping`
);
registeredTools.push(toolName);
} else {
logger.warn(
`Failed to register fallback tool '${toolName}': ${err.message}`
);
failedTools.push(toolName);
}
}
} else {
logger.warn(`Tool '${toolName}' not found in registry`);
failedTools.push(toolName);
}
}
logger.info(
`Successfully registered ${registeredTools.length} fallback tools`
);
return {
registeredTools,
failedTools,
normalizedMode: 'all'
};
}
}
export {
toolRegistry,
coreTools,
standardTools,
getAvailableTools,
getToolRegistration,
isValidTool
};
export default {
registerTaskMasterTools
};
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/move-task.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/move-task.js
* Tool for moving tasks or subtasks to a new position
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import {
moveTaskDirect,
moveTaskCrossTagDirect
} from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the moveTask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerMoveTaskTool(server) {
server.addTool({
name: 'move_task',
description: 'Move a task or subtask to a new position',
parameters: z.object({
from: z
.string()
.describe(
'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")'
),
to: z
.string()
.optional()
.describe(
'ID of the destination (e.g., "7" or "7.3"). Required for within-tag moves. For cross-tag moves, if omitted, task will be moved to the target tag maintaining its ID'
),
file: z.string().optional().describe('Custom path to tasks.json file'),
projectRoot: z
.string()
.describe(
'Root directory of the project (typically derived from session)'
),
tag: z.string().optional().describe('Tag context to operate on'),
fromTag: z.string().optional().describe('Source tag for cross-tag moves'),
toTag: z.string().optional().describe('Target tag for cross-tag moves'),
withDependencies: z
.boolean()
.optional()
.describe('Move dependent tasks along with main task'),
ignoreDependencies: z
.boolean()
.optional()
.describe('Break cross-tag dependencies during move')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
// Check if this is a cross-tag move
const isCrossTagMove =
args.fromTag && args.toTag && args.fromTag !== args.toTag;
if (isCrossTagMove) {
// Cross-tag move logic
if (!args.from) {
return createErrorResponse(
'Source IDs are required for cross-tag moves',
'MISSING_SOURCE_IDS'
);
}
// Warn if 'to' parameter is provided for cross-tag moves
if (args.to) {
log.warn(
'The "to" parameter is not used for cross-tag moves and will be ignored. Tasks retain their original IDs in the target tag.'
);
}
// Find tasks.json path if not provided
let tasksJsonPath = args.file;
if (!tasksJsonPath) {
tasksJsonPath = findTasksPath(args, log);
}
// Use cross-tag move function
return handleApiResult(
await moveTaskCrossTagDirect(
{
sourceIds: args.from,
sourceTag: args.fromTag,
targetTag: args.toTag,
withDependencies: args.withDependencies || false,
ignoreDependencies: args.ignoreDependencies || false,
tasksJsonPath,
projectRoot: args.projectRoot
},
log,
{ session }
),
log,
'Error moving tasks between tags',
undefined,
args.projectRoot
);
} else {
// Within-tag move logic (existing functionality)
if (!args.to) {
return createErrorResponse(
'Destination ID is required for within-tag moves',
'MISSING_DESTINATION_ID'
);
}
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Find tasks.json path if not provided
let tasksJsonPath = args.file;
if (!tasksJsonPath) {
tasksJsonPath = findTasksPath(args, log);
}
// Parse comma-separated IDs
const fromIds = args.from.split(',').map((id) => id.trim());
const toIds = args.to.split(',').map((id) => id.trim());
// Validate matching IDs count
if (fromIds.length !== toIds.length) {
if (fromIds.length > 1) {
const results = [];
const skipped = [];
// Move tasks one by one, only generate files on the last move
for (let i = 0; i < fromIds.length; i++) {
const fromId = fromIds[i];
const toId = toIds[i];
// Skip if source and destination are the same
if (fromId === toId) {
log.info(`Skipping ${fromId} -> ${toId} (same ID)`);
skipped.push({ fromId, toId, reason: 'same ID' });
continue;
}
const shouldGenerateFiles = i === fromIds.length - 1;
const result = await moveTaskDirect(
{
sourceId: fromId,
destinationId: toId,
tasksJsonPath,
projectRoot: args.projectRoot,
tag: resolvedTag,
generateFiles: shouldGenerateFiles
},
log,
{ session }
);
if (!result.success) {
log.error(
`Failed to move ${fromId} to ${toId}: ${result.error.message}`
);
} else {
results.push(result.data);
}
}
return handleApiResult(
{
success: true,
data: {
moves: results,
skipped: skipped.length > 0 ? skipped : undefined,
message: `Successfully moved ${results.length} tasks${skipped.length > 0 ? `, skipped ${skipped.length}` : ''}`
}
},
log,
'Error moving multiple tasks',
undefined,
args.projectRoot
);
}
return handleApiResult(
{
success: true,
data: {
moves: results,
skippedMoves: skippedMoves,
message: `Successfully moved ${results.length} tasks${skippedMoves.length > 0 ? `, skipped ${skippedMoves.length} moves` : ''}`
}
},
log,
'Error moving multiple tasks',
undefined,
args.projectRoot
);
} else {
// Moving a single task
return handleApiResult(
await moveTaskDirect(
{
sourceId: args.from,
destinationId: args.to,
tasksJsonPath,
projectRoot: args.projectRoot,
tag: resolvedTag,
generateFiles: true
},
log,
{ session }
),
log,
'Error moving task',
undefined,
args.projectRoot
);
}
}
} catch (error) {
return createErrorResponse(
`Failed to move task: ${error.message}`,
'MOVE_TASK_ERROR'
);
}
})
});
}
```
--------------------------------------------------------------------------------
/apps/extension/src/extension.ts:
--------------------------------------------------------------------------------
```typescript
/**
* TaskMaster Extension - Simplified Architecture
* Only using patterns where they add real value
*/
import * as vscode from 'vscode';
import { ConfigService } from './services/config-service';
import { PollingService } from './services/polling-service';
import { createPollingStrategy } from './services/polling-strategies';
import { TaskRepository } from './services/task-repository';
import { TerminalManager } from './services/terminal-manager';
import { WebviewManager } from './services/webview-manager';
import { EventEmitter } from './utils/event-emitter';
import { ExtensionLogger } from './utils/logger';
import {
MCPClientManager,
createMCPConfigFromSettings
} from './utils/mcpClient';
import { TaskMasterApi } from './utils/task-master-api';
import { SidebarWebviewManager } from './services/sidebar-webview-manager';
let logger: ExtensionLogger;
let mcpClient: MCPClientManager;
let api: TaskMasterApi;
let repository: TaskRepository;
let terminalManager: TerminalManager;
let pollingService: PollingService;
let webviewManager: WebviewManager;
let events: EventEmitter;
let configService: ConfigService;
let sidebarManager: SidebarWebviewManager;
export async function activate(context: vscode.ExtensionContext) {
try {
// Initialize logger (needed to prevent MCP stdio issues)
logger = ExtensionLogger.getInstance();
logger.log('🎉 TaskMaster Extension activating...');
// Simple event emitter for webview communication
events = new EventEmitter();
// Initialize MCP client
mcpClient = new MCPClientManager(createMCPConfigFromSettings());
// Initialize API
api = new TaskMasterApi(mcpClient);
// Repository with caching (actually useful for performance)
repository = new TaskRepository(api, logger);
// Terminal manager for task execution
terminalManager = new TerminalManager(context, logger);
// Config service for TaskMaster config.json
configService = new ConfigService(logger);
// Polling service with strategy pattern (makes sense for different polling behaviors)
const strategy = createPollingStrategy(
vscode.workspace.getConfiguration('taskmaster')
);
pollingService = new PollingService(repository, strategy, logger);
// Webview manager (cleaner than global panel array) - create before connection
webviewManager = new WebviewManager(
context,
repository,
events,
logger,
terminalManager
);
webviewManager.setConfigService(configService);
// Sidebar webview manager
sidebarManager = new SidebarWebviewManager(context.extensionUri);
// Initialize connection
await initializeConnection();
// Set MCP client and API after connection
webviewManager.setMCPClient(mcpClient);
webviewManager.setApi(api);
sidebarManager.setApi(api);
// Register commands
registerCommands(context);
// Handle polling lifecycle
events.on('webview:opened', () => {
if (webviewManager.getPanelCount() === 1) {
pollingService.start();
}
});
events.on('webview:closed', () => {
if (webviewManager.getPanelCount() === 0) {
pollingService.stop();
}
});
// Forward repository updates to webviews
repository.on('tasks:updated', (tasks) => {
webviewManager.broadcast('tasksUpdated', { tasks, source: 'polling' });
});
logger.log('✅ TaskMaster Extension activated');
} catch (error) {
logger?.error('Failed to activate', error);
vscode.window.showErrorMessage(
`Failed to activate TaskMaster: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
async function initializeConnection() {
try {
logger.log('🔗 Connecting to TaskMaster...');
// Notify webviews that we're connecting
if (webviewManager) {
webviewManager.broadcast('connectionStatus', {
isConnected: false,
status: 'Connecting...'
});
}
await mcpClient.connect();
const testResult = await api.testConnection();
if (testResult.success) {
logger.log('✅ Connected to TaskMaster');
vscode.window.showInformationMessage('TaskMaster connected!');
// Notify webviews that we're connected
if (webviewManager) {
webviewManager.broadcast('connectionStatus', {
isConnected: true,
status: 'Connected'
});
}
if (sidebarManager) {
sidebarManager.updateConnectionStatus();
}
} else {
throw new Error(testResult.error || 'Connection test failed');
}
} catch (error) {
logger.error('Connection failed', error);
// Notify webviews that connection failed
if (webviewManager) {
webviewManager.broadcast('connectionStatus', {
isConnected: false,
status: 'Disconnected'
});
}
if (sidebarManager) {
sidebarManager.updateConnectionStatus();
}
handleConnectionError(error);
}
}
function handleConnectionError(error: any) {
const message = error instanceof Error ? error.message : 'Unknown error';
if (message.includes('ENOENT') && message.includes('npx')) {
vscode.window
.showWarningMessage(
'TaskMaster: npx not found. Please ensure Node.js is installed.',
'Open Settings'
)
.then((action) => {
if (action === 'Open Settings') {
vscode.commands.executeCommand(
'workbench.action.openSettings',
'@ext:Hamster.task-master-hamster taskmaster'
);
}
});
} else {
vscode.window.showWarningMessage(
`TaskMaster connection failed: ${message}`
);
}
}
function registerCommands(context: vscode.ExtensionContext) {
// Main command
context.subscriptions.push(
vscode.commands.registerCommand('tm.showKanbanBoard', async () => {
await webviewManager.createOrShowPanel();
})
);
// Utility commands
context.subscriptions.push(
vscode.commands.registerCommand('tm.refreshTasks', async () => {
await repository.refresh();
vscode.window.showInformationMessage('Tasks refreshed!');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('tm.openSettings', () => {
vscode.commands.executeCommand(
'workbench.action.openSettings',
'@ext:Hamster.task-master-hamster taskmaster'
);
})
);
// Register sidebar view provider
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'taskmaster.welcome',
sidebarManager
)
);
}
export async function deactivate() {
logger?.log('👋 TaskMaster Extension deactivating...');
pollingService?.stop();
webviewManager?.dispose();
await terminalManager?.dispose();
api?.destroy();
mcpClient?.disconnect();
}
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/tasks/entities/task.entity.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Task entity with business rules and domain logic
*/
import { ERROR_CODES, TaskMasterError } from '../../../common/errors/task-master-error.js';
import type {
Subtask,
Task,
TaskPriority,
TaskStatus
} from '../../../common/types/index.js';
/**
* Task entity representing a task with business logic
* Encapsulates validation and state management rules
*/
export class TaskEntity implements Task {
readonly id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
dependencies: string[];
details: string;
testStrategy: string;
subtasks: Subtask[];
// Optional properties
createdAt?: string;
updatedAt?: string;
effort?: number;
actualEffort?: number;
tags?: string[];
assignee?: string;
complexity?: Task['complexity'];
recommendedSubtasks?: number;
expansionPrompt?: string;
complexityReasoning?: string;
constructor(data: Task | (Omit<Task, 'id'> & { id: number | string })) {
this.validate(data);
// Always convert ID to string
this.id = String(data.id);
this.title = data.title;
this.description = data.description;
this.status = data.status;
this.priority = data.priority;
// Ensure dependency IDs are also strings
this.dependencies = (data.dependencies || []).map((dep) => String(dep));
this.details = data.details;
this.testStrategy = data.testStrategy;
// Normalize subtask IDs to strings
this.subtasks = (data.subtasks || []).map((subtask) => ({
...subtask,
id: String(subtask.id),
parentId: String(subtask.parentId)
}));
// Optional properties
this.createdAt = data.createdAt;
this.updatedAt = data.updatedAt;
this.effort = data.effort;
this.actualEffort = data.actualEffort;
this.tags = data.tags;
this.assignee = data.assignee;
this.complexity = data.complexity;
this.recommendedSubtasks = data.recommendedSubtasks;
this.expansionPrompt = data.expansionPrompt;
this.complexityReasoning = data.complexityReasoning;
}
/**
* Validate task data
*/
private validate(
data: Partial<Task> | Partial<Omit<Task, 'id'> & { id: number | string }>
): void {
if (
data.id === undefined ||
data.id === null ||
(typeof data.id !== 'string' && typeof data.id !== 'number')
) {
throw new TaskMasterError(
'Task ID is required and must be a string or number',
ERROR_CODES.VALIDATION_ERROR
);
}
if (!data.title || data.title.trim().length === 0) {
throw new TaskMasterError(
'Task title is required',
ERROR_CODES.VALIDATION_ERROR
);
}
if (!data.description || data.description.trim().length === 0) {
throw new TaskMasterError(
'Task description is required',
ERROR_CODES.VALIDATION_ERROR
);
}
if (!this.isValidStatus(data.status)) {
throw new TaskMasterError(
`Invalid task status: ${data.status}`,
ERROR_CODES.VALIDATION_ERROR
);
}
if (!this.isValidPriority(data.priority)) {
throw new TaskMasterError(
`Invalid task priority: ${data.priority}`,
ERROR_CODES.VALIDATION_ERROR
);
}
}
/**
* Check if status is valid
*/
private isValidStatus(status: any): status is TaskStatus {
return [
'pending',
'in-progress',
'done',
'deferred',
'cancelled',
'blocked',
'review'
].includes(status);
}
/**
* Check if priority is valid
*/
private isValidPriority(priority: any): priority is TaskPriority {
return ['low', 'medium', 'high', 'critical'].includes(priority);
}
/**
* Check if task can be marked as complete
*/
canComplete(): boolean {
// Cannot complete if status is already done or cancelled
if (this.status === 'done' || this.status === 'cancelled') {
return false;
}
// Cannot complete if blocked
if (this.status === 'blocked') {
return false;
}
// Check if all subtasks are complete
const allSubtasksComplete = this.subtasks.every(
(subtask) => subtask.status === 'done' || subtask.status === 'cancelled'
);
return allSubtasksComplete;
}
/**
* Mark task as complete
*/
markAsComplete(): void {
if (!this.canComplete()) {
throw new TaskMasterError(
'Task cannot be marked as complete',
ERROR_CODES.TASK_STATUS_ERROR,
{
taskId: this.id,
currentStatus: this.status,
hasIncompleteSubtasks: this.subtasks.some(
(s) => s.status !== 'done' && s.status !== 'cancelled'
)
}
);
}
this.status = 'done';
this.updatedAt = new Date().toISOString();
}
/**
* Check if task has dependencies
*/
hasDependencies(): boolean {
return this.dependencies.length > 0;
}
/**
* Check if task has subtasks
*/
hasSubtasks(): boolean {
return this.subtasks.length > 0;
}
/**
* Add a subtask
*/
addSubtask(subtask: Omit<Subtask, 'id' | 'parentId'>): void {
const nextId = this.subtasks.length + 1;
this.subtasks.push({
...subtask,
id: nextId,
parentId: this.id
});
this.updatedAt = new Date().toISOString();
}
/**
* Update task status
*/
updateStatus(newStatus: TaskStatus): void {
if (!this.isValidStatus(newStatus)) {
throw new TaskMasterError(
`Invalid status: ${newStatus}`,
ERROR_CODES.VALIDATION_ERROR
);
}
// Business rule: Cannot move from done to pending
if (this.status === 'done' && newStatus === 'pending') {
throw new TaskMasterError(
'Cannot move completed task back to pending',
ERROR_CODES.TASK_STATUS_ERROR
);
}
this.status = newStatus;
this.updatedAt = new Date().toISOString();
}
/**
* Convert entity to plain object
*/
toJSON(): Task {
return {
id: this.id,
title: this.title,
description: this.description,
status: this.status,
priority: this.priority,
dependencies: this.dependencies,
details: this.details,
testStrategy: this.testStrategy,
subtasks: this.subtasks,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
effort: this.effort,
actualEffort: this.actualEffort,
tags: this.tags,
assignee: this.assignee,
complexity: this.complexity,
recommendedSubtasks: this.recommendedSubtasks,
expansionPrompt: this.expansionPrompt,
complexityReasoning: this.complexityReasoning
};
}
/**
* Create TaskEntity from plain object
*/
static fromObject(data: Task): TaskEntity {
return new TaskEntity(data);
}
/**
* Create multiple TaskEntities from array
*/
static fromArray(data: Task[]): TaskEntity[] {
return data.map((task) => new TaskEntity(task));
}
}
```
--------------------------------------------------------------------------------
/apps/extension/package.publish.json:
--------------------------------------------------------------------------------
```json
{
"name": "task-master-hamster",
"displayName": "Taskmaster AI",
"description": "A visual Kanban board interface for Taskmaster projects in VS Code",
"version": "0.25.3",
"publisher": "Hamster",
"icon": "assets/icon.png",
"engines": {
"vscode": "^1.93.0"
},
"categories": ["AI", "Visualization", "Education", "Other"],
"keywords": [
"kanban",
"kanban board",
"productivity",
"todo",
"task tracking",
"project management",
"task-master",
"task management",
"agile",
"scrum",
"ai",
"mcp",
"model context protocol",
"dashboard",
"chatgpt",
"claude",
"openai",
"anthropic",
"task",
"npm",
"intellicode",
"react",
"typescript",
"php",
"python",
"node",
"planner",
"organizer",
"workflow",
"boards",
"cards"
],
"repository": "https://github.com/eyaltoledano/claude-task-master",
"activationEvents": ["onStartupFinished", "workspaceContains:.taskmaster/**"],
"main": "./dist/extension.js",
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "taskmaster",
"title": "Taskmaster",
"icon": "assets/sidebar-icon.svg"
}
]
},
"views": {
"taskmaster": [
{
"id": "taskmaster.welcome",
"name": "Taskmaster",
"type": "webview"
}
]
},
"commands": [
{
"command": "tm.showKanbanBoard",
"title": "Taskmaster: Show Board"
},
{
"command": "tm.checkConnection",
"title": "Taskmaster: Check Connection"
},
{
"command": "tm.reconnect",
"title": "Taskmaster: Reconnect"
},
{
"command": "tm.openSettings",
"title": "Taskmaster: Open Settings"
}
],
"configuration": {
"title": "Taskmaster Kanban",
"properties": {
"taskmaster.mcp.command": {
"type": "string",
"default": "npx",
"description": "The command or absolute path to execute for the MCP server (e.g., 'npx' or '/usr/local/bin/task-master-ai')."
},
"taskmaster.mcp.args": {
"type": "array",
"items": {
"type": "string"
},
"default": ["-y", "task-master-ai"],
"description": "An array of arguments to pass to the MCP server command."
},
"taskmaster.mcp.cwd": {
"type": "string",
"description": "Working directory for the Task Master MCP server (defaults to workspace root)"
},
"taskmaster.mcp.env": {
"type": "object",
"description": "Environment variables for the Task Master MCP server"
},
"taskmaster.mcp.timeout": {
"type": "number",
"default": 30000,
"minimum": 1000,
"maximum": 300000,
"description": "Connection timeout in milliseconds"
},
"taskmaster.mcp.maxReconnectAttempts": {
"type": "number",
"default": 5,
"minimum": 1,
"maximum": 20,
"description": "Maximum number of reconnection attempts"
},
"taskmaster.mcp.reconnectBackoffMs": {
"type": "number",
"default": 1000,
"minimum": 100,
"maximum": 10000,
"description": "Initial reconnection backoff delay in milliseconds"
},
"taskmaster.mcp.maxBackoffMs": {
"type": "number",
"default": 30000,
"minimum": 1000,
"maximum": 300000,
"description": "Maximum reconnection backoff delay in milliseconds"
},
"taskmaster.mcp.healthCheckIntervalMs": {
"type": "number",
"default": 15000,
"minimum": 5000,
"maximum": 60000,
"description": "Health check interval in milliseconds"
},
"taskmaster.mcp.requestTimeoutMs": {
"type": "number",
"default": 300000,
"minimum": 30000,
"maximum": 600000,
"description": "MCP request timeout in milliseconds (default: 5 minutes)"
},
"taskmaster.ui.autoRefresh": {
"type": "boolean",
"default": true,
"description": "Automatically refresh tasks from the server"
},
"taskmaster.ui.refreshIntervalMs": {
"type": "number",
"default": 10000,
"minimum": 1000,
"maximum": 300000,
"description": "Auto-refresh interval in milliseconds"
},
"taskmaster.ui.theme": {
"type": "string",
"enum": ["auto", "light", "dark"],
"default": "auto",
"description": "UI theme preference"
},
"taskmaster.ui.showCompletedTasks": {
"type": "boolean",
"default": true,
"description": "Show completed tasks in the Kanban board"
},
"taskmaster.ui.taskDisplayLimit": {
"type": "number",
"default": 100,
"minimum": 1,
"maximum": 1000,
"description": "Maximum number of tasks to display"
},
"taskmaster.ui.showPriority": {
"type": "boolean",
"default": true,
"description": "Show task priority indicators"
},
"taskmaster.ui.showTaskIds": {
"type": "boolean",
"default": true,
"description": "Show task IDs in the interface"
},
"taskmaster.performance.maxConcurrentRequests": {
"type": "number",
"default": 5,
"minimum": 1,
"maximum": 20,
"description": "Maximum number of concurrent MCP requests"
},
"taskmaster.performance.requestTimeoutMs": {
"type": "number",
"default": 30000,
"minimum": 1000,
"maximum": 300000,
"description": "Request timeout in milliseconds"
},
"taskmaster.performance.cacheTasksMs": {
"type": "number",
"default": 5000,
"minimum": 0,
"maximum": 60000,
"description": "Task cache duration in milliseconds"
},
"taskmaster.performance.lazyLoadThreshold": {
"type": "number",
"default": 50,
"minimum": 10,
"maximum": 500,
"description": "Number of tasks before enabling lazy loading"
},
"taskmaster.debug.enableLogging": {
"type": "boolean",
"default": true,
"description": "Enable debug logging"
},
"taskmaster.debug.logLevel": {
"type": "string",
"enum": ["error", "warn", "info", "debug"],
"default": "info",
"description": "Logging level"
},
"taskmaster.debug.enableConnectionMetrics": {
"type": "boolean",
"default": true,
"description": "Enable connection performance metrics"
},
"taskmaster.debug.saveEventLogs": {
"type": "boolean",
"default": false,
"description": "Save event logs to files"
},
"taskmaster.debug.maxEventLogSize": {
"type": "number",
"default": 1000,
"minimum": 10,
"maximum": 10000,
"description": "Maximum number of events to keep in memory"
}
}
}
}
}
```