This is page 6 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
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/use-tag.js:
--------------------------------------------------------------------------------
```javascript
/**
* use-tag.js
* Direct function implementation for switching to a tag
*/
import { useTag } from '../../../../scripts/modules/task-manager/tag-management.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for switching to a tag with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.name - Name of the tag to switch to
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
* @param {string} [args.projectRoot] - Project root path
* @param {Object} log - Logger object
* @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function useTagDirect(args, log, context = {}) {
// Destructure expected args
const { tasksJsonPath, name, projectRoot } = args;
const { session } = context;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
try {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('useTagDirect called without tasksJsonPath');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Check required parameters
if (!name || typeof name !== 'string') {
log.error('Missing required parameter: name');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'Tag name is required and must be a string'
}
};
}
log.info(`Switching to tag: ${name}`);
// Call the useTag function
const result = await useTag(
tasksJsonPath,
name,
{}, // options (empty for now)
{
session,
mcpLog,
projectRoot
},
'json' // outputFormat - use 'json' to suppress CLI UI
);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
tagName: result.currentTag,
switched: result.switched,
previousTag: result.previousTag,
taskCount: result.taskCount,
message: `Successfully switched to tag "${result.currentTag}"`
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in useTagDirect: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'USE_TAG_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/add-dependency.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/add-dependency.js
* Tool for adding a dependency to a task
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { addDependencyDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the addDependency tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerAddDependencyTool(server) {
server.addTool({
name: 'add_dependency',
description: 'Add a dependency relationship between two tasks',
parameters: z.object({
id: z.string().describe('ID of task that will depend on another task'),
dependsOn: z
.string()
.describe('ID of task that will become a dependency'),
file: z
.string()
.optional()
.describe(
'Absolute path to the tasks file (default: tasks/tasks.json)'
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(
`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct function with the resolved path
const result = await addDependencyDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
dependsOn: args.dependsOn,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log
// Remove context object
);
// Log result
if (result.success) {
log.info(`Successfully added dependency: ${result.data.message}`);
} else {
log.error(`Failed to add dependency: ${result.error.message}`);
}
// Use handleApiResult to format the response
return handleApiResult(
result,
log,
'Error adding dependency',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in addDependency tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/clear-subtasks.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/clear-subtasks.js
* Tool for clearing subtasks from parent tasks
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { clearSubtasksDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the clearSubtasks tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerClearSubtasksTool(server) {
server.addTool({
name: 'clear_subtasks',
description: 'Clear subtasks from specified tasks',
parameters: z
.object({
id: z
.string()
.optional()
.describe('Task IDs (comma-separated) to clear subtasks from'),
all: z.boolean().optional().describe('Clear subtasks from all tasks'),
file: z
.string()
.optional()
.describe(
'Absolute path to the tasks file (default: tasks/tasks.json)'
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
})
.refine((data) => data.id || data.all, {
message: "Either 'id' or 'all' parameter must be provided",
path: ['id', 'all']
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await clearSubtasksDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
all: args.all,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
if (result.success) {
log.info(`Subtasks cleared successfully: ${result.data.message}`);
} else {
log.error(`Failed to clear subtasks: ${result.error.message}`);
}
return handleApiResult(
result,
log,
'Error clearing subtasks',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in clearSubtasks tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/tests/unit/scripts/modules/utils-tag-aware-paths.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* Test for getTagAwareFilePath utility function
* Tests the fix for Issue #850
*/
import { getTagAwareFilePath } from '../../../../scripts/modules/utils.js';
import path from 'path';
describe('getTagAwareFilePath utility function', () => {
const projectRoot = '/test/project';
const basePath = '.taskmaster/reports/task-complexity-report.json';
it('should return base path for master tag', () => {
const result = getTagAwareFilePath(basePath, 'master', projectRoot);
const expected = path.join(projectRoot, basePath);
expect(result).toBe(expected);
});
it('should return base path for null tag', () => {
const result = getTagAwareFilePath(basePath, null, projectRoot);
const expected = path.join(projectRoot, basePath);
expect(result).toBe(expected);
});
it('should return base path for undefined tag', () => {
const result = getTagAwareFilePath(basePath, undefined, projectRoot);
const expected = path.join(projectRoot, basePath);
expect(result).toBe(expected);
});
it('should return tag-specific path for non-master tag', () => {
const tag = 'feature-branch';
const result = getTagAwareFilePath(basePath, tag, projectRoot);
const expected = path.join(
projectRoot,
'.taskmaster/reports/task-complexity-report_feature-branch.json'
);
expect(result).toBe(expected);
});
it('should handle different file extensions', () => {
const csvBasePath = '.taskmaster/reports/export.csv';
const tag = 'dev-branch';
const result = getTagAwareFilePath(csvBasePath, tag, projectRoot);
const expected = path.join(
projectRoot,
'.taskmaster/reports/export_dev-branch.csv'
);
expect(result).toBe(expected);
});
it('should handle paths without extensions', () => {
const noExtPath = '.taskmaster/reports/summary';
const tag = 'test-tag';
const result = getTagAwareFilePath(noExtPath, tag, projectRoot);
// Since there's no extension, it should append the tag
const expected = path.join(
projectRoot,
'.taskmaster/reports/summary_test-tag'
);
expect(result).toBe(expected);
});
it('should use default project root when not provided', () => {
const tag = 'feature-tag';
const result = getTagAwareFilePath(basePath, tag);
const expected = path.join(
'.',
'.taskmaster/reports/task-complexity-report_feature-tag.json'
);
expect(result).toBe(expected);
});
it('should handle complex tag names with special characters', () => {
const tag = 'feature-user-auth-v2';
const result = getTagAwareFilePath(basePath, tag, projectRoot);
const expected = path.join(
projectRoot,
'.taskmaster/reports/task-complexity-report_feature-user-auth-v2.json'
);
expect(result).toBe(expected);
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/scope-down.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/scope-down.js
* Tool to scope down task complexity
*/
import { z } from 'zod';
import {
createErrorResponse,
handleApiResult,
withNormalizedProjectRoot
} from './utils.js';
import { scopeDownDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the scopeDown tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerScopeDownTool(server) {
server.addTool({
name: 'scope_down_task',
description: 'Decrease the complexity of one or more tasks using AI',
parameters: z.object({
id: z
.string()
.describe(
'Comma-separated list of task IDs to scope down (e.g., "1,3,5")'
),
strength: z
.string()
.optional()
.describe(
'Strength level: light, regular, or heavy (default: regular)'
),
prompt: z
.string()
.optional()
.describe('Custom prompt for specific scoping adjustments'),
file: z
.string()
.optional()
.describe('Path to the tasks file (default: tasks/tasks.json)'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on'),
research: z
.boolean()
.optional()
.describe('Whether to use research capabilities for scoping')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(`Starting scope-down with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct function
const result = await scopeDownDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
strength: args.strength,
prompt: args.prompt,
research: args.research,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
return handleApiResult(
result,
log,
'Error scoping down task',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in scope-down tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/execution/services/executor-service.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Service for managing task execution
*/
import { getLogger } from '../../../common/logger/index.js';
import type { Task } from '../../../common/types/index.js';
import { ExecutorFactory } from '../executors/executor-factory.js';
import type {
ExecutionResult,
ExecutorOptions,
ExecutorType,
ITaskExecutor
} from '../types.js';
export interface ExecutorServiceOptions {
projectRoot: string;
defaultExecutor?: ExecutorType;
executorConfig?: Record<string, any>;
}
export class ExecutorService {
private logger = getLogger('ExecutorService');
private projectRoot: string;
private defaultExecutor?: ExecutorType;
private executorConfig: Record<string, any>;
private currentExecutor?: ITaskExecutor;
constructor(options: ExecutorServiceOptions) {
this.projectRoot = options.projectRoot;
this.defaultExecutor = options.defaultExecutor;
this.executorConfig = options.executorConfig || {};
}
/**
* Execute a task
*/
async executeTask(
task: Task,
executorType?: ExecutorType
): Promise<ExecutionResult> {
try {
// Determine executor type
const type =
executorType ||
this.defaultExecutor ||
(await ExecutorFactory.getDefaultExecutor(this.projectRoot));
if (!type) {
return {
success: false,
taskId: task.id,
executorType: 'claude',
error:
'No executor available. Please install Claude CLI or specify an executor type.',
startTime: new Date().toISOString()
};
}
// Create executor
const executorOptions: ExecutorOptions = {
type,
projectRoot: this.projectRoot,
config: this.executorConfig
};
this.currentExecutor = ExecutorFactory.create(executorOptions);
// Check if executor is available
const isAvailable = await this.currentExecutor.isAvailable();
if (!isAvailable) {
return {
success: false,
taskId: task.id,
executorType: type,
error: `Executor ${type} is not available or not configured properly`,
startTime: new Date().toISOString()
};
}
// Execute the task
this.logger.info(`Starting task ${task.id} with ${type} executor`);
const result = await this.currentExecutor.execute(task);
return result;
} catch (error: any) {
this.logger.error(`Failed to execute task ${task.id}:`, error);
return {
success: false,
taskId: task.id,
executorType: executorType || 'claude',
error: error.message || 'Unknown error occurred',
startTime: new Date().toISOString()
};
}
}
/**
* Stop the current task execution
*/
async stopCurrentTask(): Promise<void> {
if (this.currentExecutor && this.currentExecutor.stop) {
await this.currentExecutor.stop();
this.currentExecutor = undefined;
}
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/next-task.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/next-task.js
* Tool to find the next task to work on based on dependencies and status
*/
import { z } from 'zod';
import {
createErrorResponse,
handleApiResult,
withNormalizedProjectRoot
} from './utils.js';
import { nextTaskDirect } from '../core/task-master-core.js';
import {
resolveTasksPath,
resolveComplexityReportPath
} from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the nextTask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerNextTaskTool(server) {
server.addTool({
name: 'next_task',
description:
'Find the next task to work on based on dependencies and status',
parameters: z.object({
file: z.string().optional().describe('Absolute path to the tasks file'),
complexityReport: z
.string()
.optional()
.describe(
'Path to the complexity report file (relative to project root or absolute)'
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Resolve the path to tasks.json using new path utilities
let tasksJsonPath;
try {
tasksJsonPath = resolveTasksPath(args, session);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Resolve the path to complexity report (optional)
let complexityReportPath;
try {
complexityReportPath = resolveComplexityReportPath(
{ ...args, tag: resolvedTag },
session
);
} catch (error) {
log.error(`Error finding complexity report: ${error.message}`);
// This is optional, so we don't fail the operation
complexityReportPath = null;
}
const result = await nextTaskDirect(
{
tasksJsonPath: tasksJsonPath,
reportPath: complexityReportPath,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
log.info(`Next task result: ${result.success ? 'found' : 'none'}`);
return handleApiResult(
result,
log,
'Error finding next task',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error finding next task: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/src/utils/asset-resolver.js:
--------------------------------------------------------------------------------
```javascript
/**
* Asset Resolver Module
* Handles resolving paths to asset files in the package
*
* The public/assets folder is copied to dist/assets during build via tsup's publicDir,
* so we can reliably find it relative to the bundled files.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Get the assets directory path
* When bundled, assets are in dist/assets
* When in development, assets are in public/assets
* @returns {string} Path to the assets directory
*/
export function getAssetsDir() {
// Check multiple possible locations
const possiblePaths = [
// When running from dist (bundled) - assets are in dist/assets
path.join(__dirname, 'assets'),
path.join(__dirname, '..', 'assets'),
// When running from source in development - now in public/assets
path.join(__dirname, '..', '..', 'public', 'assets'),
// When installed as npm package - assets at package root
path.join(process.cwd(), 'assets'),
// For npx usage - check node_modules
path.join(
process.cwd(),
'node_modules',
'task-master-ai',
'dist',
'assets'
),
path.join(process.cwd(), 'node_modules', 'task-master-ai', 'assets')
];
// Find the first existing assets directory
for (const assetPath of possiblePaths) {
if (fs.existsSync(assetPath)) {
// Verify it's actually the assets directory by checking for known files
const testFile = path.join(assetPath, 'rules', 'taskmaster.mdc');
if (fs.existsSync(testFile)) {
return assetPath;
}
}
}
// If no assets directory found, throw an error
throw new Error(
'Assets directory not found. This is likely a packaging issue.'
);
}
/**
* Get path to a specific asset file
* @param {string} relativePath - Path relative to assets directory
* @returns {string} Full path to the asset file
*/
export function getAssetPath(relativePath) {
const assetsDir = getAssetsDir();
return path.join(assetsDir, relativePath);
}
/**
* Check if an asset file exists
* @param {string} relativePath - Path relative to assets directory
* @returns {boolean} True if the asset exists
*/
export function assetExists(relativePath) {
try {
const assetPath = getAssetPath(relativePath);
return fs.existsSync(assetPath);
} catch (error) {
return false;
}
}
/**
* Read an asset file
* @param {string} relativePath - Path relative to assets directory
* @param {string} encoding - File encoding (default: 'utf8')
* @returns {string|Buffer} File contents
*/
export function readAsset(relativePath, encoding = 'utf8') {
const assetPath = getAssetPath(relativePath);
return fs.readFileSync(assetPath, encoding);
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/move-task.js:
--------------------------------------------------------------------------------
```javascript
/**
* Direct function wrapper for moveTask
*/
import { moveTask } from '../../../../scripts/modules/task-manager.js';
import { findTasksPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
/**
* Move a task or subtask to a new position
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file
* @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2' or '5,6,7')
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3' or '7,8,9')
* @param {string} args.file - Alternative path to the tasks.json file
* @param {string} args.projectRoot - Project root directory
* @param {string} args.tag - Tag for the task (optional)
* @param {boolean} args.generateFiles - Whether to regenerate task files after moving (default: true)
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: Object}>}
*/
export async function moveTaskDirect(args, log, context = {}) {
const { session } = context;
const { projectRoot, tag } = args;
// Validate required parameters
if (!args.sourceId) {
return {
success: false,
error: {
message: 'Source ID is required',
code: 'MISSING_SOURCE_ID'
}
};
}
if (!args.destinationId) {
return {
success: false,
error: {
message: 'Destination ID is required',
code: 'MISSING_DESTINATION_ID'
}
};
}
try {
// Find tasks.json path if not provided
let tasksPath = args.tasksJsonPath || args.file;
if (!tasksPath) {
if (!args.projectRoot) {
return {
success: false,
error: {
message:
'Project root is required if tasksJsonPath is not provided',
code: 'MISSING_PROJECT_ROOT'
}
};
}
tasksPath = findTasksPath(args, log);
}
// Enable silent mode to prevent console output during MCP operation
enableSilentMode();
// Call the core moveTask function with file generation control
const generateFiles = args.generateFiles !== false; // Default to true
const result = await moveTask(
tasksPath,
args.sourceId,
args.destinationId,
generateFiles,
{
projectRoot,
tag
}
);
// Restore console output
disableSilentMode();
return {
success: true,
data: {
...result,
message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`
}
};
} catch (error) {
// Restore console output in case of error
disableSilentMode();
log.error(`Failed to move task: ${error.message}`);
return {
success: false,
error: {
message: error.message,
code: 'MOVE_TASK_ERROR'
}
};
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/claude-dedupe-issues.yml:
--------------------------------------------------------------------------------
```yaml
name: Claude Issue Dedupe
# description: Automatically dedupe GitHub issues using Claude Code
on:
issues:
types: [opened]
workflow_dispatch:
inputs:
issue_number:
description: "Issue number to process for duplicate detection"
required: true
type: string
jobs:
claude-dedupe-issues:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run Claude Code slash command
uses: anthropics/claude-code-base-action@beta
with:
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_env: |
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Log duplicate comment event to Statsig
if: always()
env:
STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
run: |
ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
REPO=${{ github.repository }}
if [ -z "$STATSIG_API_KEY" ]; then
echo "STATSIG_API_KEY not found, skipping Statsig logging"
exit 0
fi
# Prepare the event payload
EVENT_PAYLOAD=$(jq -n \
--arg issue_number "$ISSUE_NUMBER" \
--arg repo "$REPO" \
--arg triggered_by "${{ github.event_name }}" \
'{
events: [{
eventName: "github_duplicate_comment_added",
value: 1,
metadata: {
repository: $repo,
issue_number: ($issue_number | tonumber),
triggered_by: $triggered_by,
workflow_run_id: "${{ github.run_id }}"
},
time: (now | floor | tostring)
}]
}')
# Send to Statsig API
echo "Logging duplicate comment event to Statsig for issue #${ISSUE_NUMBER}"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://events.statsigapi.net/v1/log_event \
-H "Content-Type: application/json" \
-H "STATSIG-API-KEY: ${STATSIG_API_KEY}" \
-d "$EVENT_PAYLOAD")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | head -n-1)
if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 202 ]; then
echo "Successfully logged duplicate comment event for issue #${ISSUE_NUMBER}"
else
echo "Failed to log duplicate comment event for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
fi
```
--------------------------------------------------------------------------------
/tests/unit/ai-providers/mcp-components.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* tests/unit/ai-providers/mcp-components.test.js
* Unit tests for MCP AI SDK custom components
*/
import { jest } from '@jest/globals';
describe('MCP Custom SDK Components', () => {
describe('Message Converter', () => {
let messageConverter;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/message-converter.js'
);
messageConverter = module;
});
describe('convertToMCPFormat', () => {
it('should convert AI SDK messages to MCP format', () => {
const input = [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Hello!' }
];
const result = messageConverter.convertToMCPFormat(input);
expect(result).toBeDefined();
expect(result.messages).toBeDefined();
expect(Array.isArray(result.messages)).toBe(true);
expect(result.systemPrompt).toBe('You are a helpful assistant.');
expect(result.messages).toHaveLength(1);
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content.text).toBe('Hello!');
});
});
describe('convertFromMCPFormat', () => {
it('should convert MCP response to AI SDK format', () => {
const input = {
content: 'Hello! How can I help you?',
usage: { inputTokens: 10, outputTokens: 8 }
};
const result = messageConverter.convertFromMCPFormat(input);
expect(result).toBeDefined();
expect(result.text).toBe('Hello! How can I help you?');
expect(result.usage).toEqual({ inputTokens: 10, outputTokens: 8 });
expect(result.finishReason).toBe('stop');
expect(result.warnings).toBeDefined();
});
});
});
describe('Language Model', () => {
let languageModel;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/language-model.js'
);
languageModel = module;
});
it('should export MCPLanguageModel class', () => {
expect(languageModel.MCPLanguageModel).toBeDefined();
expect(typeof languageModel.MCPLanguageModel).toBe('function');
});
});
describe('Error Handling', () => {
let errors;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/errors.js'
);
errors = module;
});
it('should export error classes', () => {
expect(errors.MCPError).toBeDefined();
expect(typeof errors.MCPError).toBe('function');
});
});
describe('Index Module', () => {
let index;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/index.js'
);
index = module;
});
it('should export createMCP function', () => {
expect(index.createMCP).toBeDefined();
expect(typeof index.createMCP).toBe('function');
});
});
});
```
--------------------------------------------------------------------------------
/tests/unit/mcp-providers/mcp-components.test.js:
--------------------------------------------------------------------------------
```javascript
/**
* tests/unit/mcp-providers/mcp-components.test.js
* Unit tests for MCP AI SDK custom components
*/
import { jest } from '@jest/globals';
describe('MCP Custom SDK Components', () => {
describe('Message Converter', () => {
let messageConverter;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/message-converter.js'
);
messageConverter = module;
});
describe('convertToMCPFormat', () => {
it('should convert AI SDK messages to MCP format', () => {
const input = [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Hello!' }
];
const result = messageConverter.convertToMCPFormat(input);
expect(result).toBeDefined();
expect(result.messages).toBeDefined();
expect(Array.isArray(result.messages)).toBe(true);
expect(result.systemPrompt).toBe('You are a helpful assistant.');
expect(result.messages).toHaveLength(1);
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content.text).toBe('Hello!');
});
});
describe('convertFromMCPFormat', () => {
it('should convert MCP response to AI SDK format', () => {
const input = {
content: 'Hello! How can I help you?',
usage: { inputTokens: 10, outputTokens: 8 }
};
const result = messageConverter.convertFromMCPFormat(input);
expect(result).toBeDefined();
expect(result.text).toBe('Hello! How can I help you?');
expect(result.usage).toEqual({ inputTokens: 10, outputTokens: 8 });
expect(result.finishReason).toBe('stop');
expect(result.warnings).toBeDefined();
});
});
});
describe('Language Model', () => {
let languageModel;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/language-model.js'
);
languageModel = module;
});
it('should export MCPLanguageModel class', () => {
expect(languageModel.MCPLanguageModel).toBeDefined();
expect(typeof languageModel.MCPLanguageModel).toBe('function');
});
});
describe('Error Handling', () => {
let errors;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/errors.js'
);
errors = module;
});
it('should export error classes', () => {
expect(errors.MCPError).toBeDefined();
expect(typeof errors.MCPError).toBe('function');
});
});
describe('Index Module', () => {
let index;
beforeAll(async () => {
const module = await import(
'../../../mcp-server/src/custom-sdk/index.js'
);
index = module;
});
it('should export createMCP function', () => {
expect(index.createMCP).toBeDefined();
expect(typeof index.createMCP).toBe('function');
});
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/remove-subtask.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/remove-subtask.js
* Tool for removing subtasks from parent tasks
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { removeSubtaskDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the removeSubtask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerRemoveSubtaskTool(server) {
server.addTool({
name: 'remove_subtask',
description: 'Remove a subtask from its parent task',
parameters: z.object({
id: z
.string()
.describe(
"Subtask ID to remove in format 'parentId.subtaskId' (required)"
),
convert: z
.boolean()
.optional()
.describe(
'Convert the subtask to a standalone task instead of deleting it'
),
file: z
.string()
.optional()
.describe(
'Absolute path to the tasks file (default: tasks/tasks.json)'
),
skipGenerate: z
.boolean()
.optional()
.describe('Skip regenerating task files'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await removeSubtaskDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
convert: args.convert,
skipGenerate: args.skipGenerate,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
if (result.success) {
log.info(`Subtask removed successfully: ${result.data.message}`);
} else {
log.error(`Failed to remove subtask: ${result.error.message}`);
}
return handleApiResult(
result,
log,
'Error removing subtask',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in removeSubtask tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/json-extractor.test.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Tests for JSON extraction utilities
*/
import { describe, expect, it } from 'vitest';
import { extractJson } from './json-extractor.js';
describe('extractJson', () => {
it('should extract JSON from markdown code blocks', () => {
const text = '```json\n{"name": "test", "value": 42}\n```';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should extract JSON from generic code blocks', () => {
const text = '```\n{"name": "test", "value": 42}\n```';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should remove JavaScript variable declarations', () => {
const text = 'const result = {"name": "test", "value": 42};';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should handle let variable declarations', () => {
const text = 'let data = {"name": "test", "value": 42};';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should handle var variable declarations', () => {
const text = 'var config = {"name": "test", "value": 42};';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should extract JSON arrays', () => {
const text = '[{"name": "test1"}, {"name": "test2"}]';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual([{ name: 'test1' }, { name: 'test2' }]);
});
it('should convert JavaScript object literals to JSON', () => {
const text = "{name: 'test', value: 42}";
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should return valid JSON (canonical formatting)', () => {
const text = '{"name": "test", "value": 42}';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
});
it('should return original text when JSON parsing fails completely', () => {
const text = 'This is not JSON at all';
const result = extractJson(text);
expect(result).toBe('This is not JSON at all');
});
it('should handle complex nested objects', () => {
const text =
'```json\n{\n "user": {\n "name": "John",\n "age": 30\n },\n "items": [1, 2, 3]\n}\n```';
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({
user: {
name: 'John',
age: 30
},
items: [1, 2, 3]
});
});
it('should handle mixed quotes in object literals', () => {
const text = `{name: "test", value: 'mixed quotes'}`;
const result = extractJson(text);
expect(JSON.parse(result)).toEqual({ name: 'test', value: 'mixed quotes' });
});
});
```
--------------------------------------------------------------------------------
/packages/claude-code-plugin/commands/analyze-complexity.md:
--------------------------------------------------------------------------------
```markdown
Analyze task complexity and generate expansion recommendations.
Arguments: $ARGUMENTS
Perform deep analysis of task complexity across the project.
## Complexity Analysis
Uses AI to analyze tasks and recommend which ones need breakdown.
## Execution Options
```bash
task-master analyze-complexity [--research] [--threshold=5]
```
## Analysis Parameters
- `--research` → Use research AI for deeper analysis
- `--threshold=5` → Only flag tasks above complexity 5
- Default: Analyze all pending tasks
## Analysis Process
### 1. **Task Evaluation**
For each task, AI evaluates:
- Technical complexity
- Time requirements
- Dependency complexity
- Risk factors
- Knowledge requirements
### 2. **Complexity Scoring**
Assigns score 1-10 based on:
- Implementation difficulty
- Integration challenges
- Testing requirements
- Unknown factors
- Technical debt risk
### 3. **Recommendations**
For complex tasks:
- Suggest expansion approach
- Recommend subtask breakdown
- Identify risk areas
- Propose mitigation strategies
## Smart Analysis Features
1. **Pattern Recognition**
- Similar task comparisons
- Historical complexity accuracy
- Team velocity consideration
- Technology stack factors
2. **Contextual Factors**
- Team expertise
- Available resources
- Timeline constraints
- Business criticality
3. **Risk Assessment**
- Technical risks
- Timeline risks
- Dependency risks
- Knowledge gaps
## Output Format
```
Task Complexity Analysis Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
High Complexity Tasks (>7):
📍 #5 "Implement real-time sync" - Score: 9/10
Factors: WebSocket complexity, state management, conflict resolution
Recommendation: Expand into 5-7 subtasks
Risks: Performance, data consistency
📍 #12 "Migrate database schema" - Score: 8/10
Factors: Data migration, zero downtime, rollback strategy
Recommendation: Expand into 4-5 subtasks
Risks: Data loss, downtime
Medium Complexity Tasks (5-7):
📍 #23 "Add export functionality" - Score: 6/10
Consider expansion if timeline tight
Low Complexity Tasks (<5):
✅ 15 tasks - No expansion needed
Summary:
- Expand immediately: 2 tasks
- Consider expanding: 5 tasks
- Keep as-is: 15 tasks
```
## Actionable Output
For each high-complexity task:
1. Complexity score with reasoning
2. Specific expansion suggestions
3. Risk mitigation approaches
4. Recommended subtask structure
## Integration
Results are:
- Saved to `.taskmaster/reports/complexity-analysis.md`
- Used by expand command
- Inform sprint planning
- Guide resource allocation
## Next Steps
After analysis:
```
/taskmaster:expand 5 # Expand specific task
/taskmaster:expand-all # Expand all recommended
/taskmaster:complexity-report # View detailed report
```
```
--------------------------------------------------------------------------------
/apps/docs/configuration.mdx:
--------------------------------------------------------------------------------
```markdown
---
title: "Configuration"
description: "Configure Task Master through environment variables in a .env file"
---
## Required Configuration
<Note>
Task Master requires an Anthropic API key to function. Add this to your `.env` file:
```bash
ANTHROPIC_API_KEY=sk-ant-api03-your-api-key
```
You can obtain an API key from the [Anthropic Console](https://console.anthropic.com/).
</Note>
## Optional Configuration
| Variable | Default Value | Description | Example |
| --- | --- | --- | --- |
| `MODEL` | `"claude-3-7-sonnet-20250219"` | Claude model to use | `MODEL=claude-3-opus-20240229` |
| `MAX_TOKENS` | `"4000"` | Maximum tokens for responses | `MAX_TOKENS=8000` |
| `TEMPERATURE` | `"0.7"` | Temperature for model responses | `TEMPERATURE=0.5` |
| `DEBUG` | `"false"` | Enable debug logging | `DEBUG=true` |
| `LOG_LEVEL` | `"info"` | Console output level | `LOG_LEVEL=debug` |
| `DEFAULT_SUBTASKS` | `"3"` | Default subtask count | `DEFAULT_SUBTASKS=5` |
| `DEFAULT_PRIORITY` | `"medium"` | Default priority | `DEFAULT_PRIORITY=high` |
| `PROJECT_NAME` | `"MCP SaaS MVP"` | Project name in metadata | `PROJECT_NAME=My Awesome Project` |
| `PROJECT_VERSION` | `"1.0.0"` | Version in metadata | `PROJECT_VERSION=2.1.0` |
| `PERPLEXITY_API_KEY` | - | For research-backed features | `PERPLEXITY_API_KEY=pplx-...` |
| `PERPLEXITY_MODEL` | `"sonar-medium-online"` | Perplexity model | `PERPLEXITY_MODEL=sonar-large-online` |
## TDD Workflow Configuration
Additional options for autonomous TDD workflow:
| Variable | Default | Description |
| --- | --- | --- |
| `TM_MAX_ATTEMPTS` | `3` | Max attempts per subtask before marking blocked |
| `TM_AUTO_COMMIT` | `true` | Auto-commit after GREEN phase |
| `TM_PROJECT_ROOT` | Current dir | Default project root |
## Example .env File
```
# Required
ANTHROPIC_API_KEY=sk-ant-api03-your-api-key
# Optional - Claude Configuration
MODEL=claude-3-7-sonnet-20250219
MAX_TOKENS=4000
TEMPERATURE=0.7
# Optional - Perplexity API for Research
PERPLEXITY_API_KEY=pplx-your-api-key
PERPLEXITY_MODEL=sonar-medium-online
# Optional - Project Info
PROJECT_NAME=My Project
PROJECT_VERSION=1.0.0
# Optional - Application Configuration
DEFAULT_SUBTASKS=3
DEFAULT_PRIORITY=medium
DEBUG=false
LOG_LEVEL=info
# TDD Workflow
TM_MAX_ATTEMPTS=3
TM_AUTO_COMMIT=true
```
## Troubleshooting
### If `task-master init` doesn't respond:
Try running it with Node directly:
```bash
node node_modules/claude-task-master/scripts/init.js
```
Or clone the repository and run:
```bash
git clone https://github.com/eyaltoledano/claude-task-master.git
cd claude-task-master
node scripts/init.js
```
<Note>
For advanced configuration options and detailed customization, see our [Advanced Configuration Guide] page.
</Note>
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/config-merger.service.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Configuration Merger Service
* Responsible for merging configurations from multiple sources with precedence
*/
import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
/**
* Configuration source with precedence
*/
export interface ConfigSource {
/** Source name for debugging */
name: string;
/** Configuration data from this source */
config: PartialConfiguration;
/** Precedence level (higher = more important) */
precedence: number;
}
/**
* Configuration precedence levels (higher number = higher priority)
*/
export const CONFIG_PRECEDENCE = {
DEFAULTS: 0,
GLOBAL: 1, // Reserved for future implementation
LOCAL: 2,
ENVIRONMENT: 3
} as const;
/**
* ConfigMerger handles merging configurations with precedence rules
* Single responsibility: Configuration merging logic
*/
export class ConfigMerger {
private configSources: ConfigSource[] = [];
/**
* Add a configuration source
*/
addSource(source: ConfigSource): void {
this.configSources.push(source);
}
/**
* Clear all configuration sources
*/
clearSources(): void {
this.configSources = [];
}
/**
* Merge all configuration sources based on precedence
*/
merge(): PartialConfiguration {
// Sort sources by precedence (lowest first)
const sortedSources = [...this.configSources].sort(
(a, b) => a.precedence - b.precedence
);
// Merge from lowest to highest precedence
let merged: PartialConfiguration = {};
for (const source of sortedSources) {
merged = this.deepMerge(merged, source.config);
}
return merged;
}
/**
* Deep merge two configuration objects
* Higher precedence values override lower ones
*/
private deepMerge(target: any, source: any): any {
if (!source) return target;
if (!target) return source;
const result = { ...target };
for (const key in source) {
if (source[key] === null || source[key] === undefined) {
continue;
}
if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = this.deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
/**
* Get configuration sources for debugging
*/
getSources(): ConfigSource[] {
return [...this.configSources].sort((a, b) => b.precedence - a.precedence);
}
/**
* Check if a source exists
*/
hasSource(name: string): boolean {
return this.configSources.some((source) => source.name === name);
}
/**
* Remove a source by name
*/
removeSource(name: string): boolean {
const initialLength = this.configSources.length;
this.configSources = this.configSources.filter(
(source) => source.name !== name
);
return this.configSources.length < initialLength;
}
}
```
--------------------------------------------------------------------------------
/tests/unit/profiles/cline-integration.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
// Mock external modules
jest.mock('child_process', () => ({
execSync: jest.fn()
}));
// Mock console methods
jest.mock('console', () => ({
log: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
clear: jest.fn()
}));
describe('Cline Integration', () => {
let tempDir;
beforeEach(() => {
jest.clearAllMocks();
// Create a temporary directory for testing
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
// Spy on fs methods
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
if (filePath.toString().includes('.clinerules')) {
return 'Existing cline rules content';
}
return '{}';
});
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
});
afterEach(() => {
// Clean up the temporary directory
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch (err) {
console.error(`Error cleaning up: ${err.message}`);
}
});
// Test function that simulates the createProjectStructure behavior for Cline files
function mockCreateClineStructure() {
// Create main .clinerules directory
fs.mkdirSync(path.join(tempDir, '.clinerules'), { recursive: true });
// Create rule files
const ruleFiles = [
'dev_workflow.md',
'taskmaster.md',
'architecture.md',
'commands.md',
'dependencies.md'
];
for (const ruleFile of ruleFiles) {
fs.writeFileSync(
path.join(tempDir, '.clinerules', ruleFile),
`Content for ${ruleFile}`
);
}
}
test('creates all required .clinerules directories', () => {
// Act
mockCreateClineStructure();
// Assert
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.clinerules'),
{ recursive: true }
);
});
test('creates rule files for Cline', () => {
// Act
mockCreateClineStructure();
// Assert - check rule files are created
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.clinerules', 'dev_workflow.md'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.clinerules', 'taskmaster.md'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.clinerules', 'architecture.md'),
expect.any(String)
);
});
test('does not create MCP configuration files', () => {
// Act
mockCreateClineStructure();
// Assert - Cline doesn't use MCP configuration
expect(fs.writeFileSync).not.toHaveBeenCalledWith(
path.join(tempDir, '.clinerules', 'mcp.json'),
expect.any(String)
);
});
});
```
--------------------------------------------------------------------------------
/apps/cli/src/commands/models/types.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Type definitions for model setup functionality
*/
/**
* Represents a model role in the system
*/
export type ModelRole = 'main' | 'research' | 'fallback';
/**
* Custom provider option identifiers
*/
export const CUSTOM_PROVIDER_IDS = {
OPENROUTER: '__CUSTOM_OPENROUTER__',
OLLAMA: '__CUSTOM_OLLAMA__',
BEDROCK: '__CUSTOM_BEDROCK__',
AZURE: '__CUSTOM_AZURE__',
VERTEX: '__CUSTOM_VERTEX__',
LMSTUDIO: '__CUSTOM_LMSTUDIO__',
OPENAI_COMPATIBLE: '__CUSTOM_OPENAI_COMPATIBLE__'
} as const;
export type CustomProviderId =
(typeof CUSTOM_PROVIDER_IDS)[keyof typeof CUSTOM_PROVIDER_IDS];
/**
* Special control values for model selection
*/
export const CONTROL_VALUES = {
CANCEL: '__CANCEL__',
NO_CHANGE: '__NO_CHANGE__'
} as const;
/**
* Model information for display
*/
export interface ModelInfo {
id: string;
name?: string;
provider: string;
cost_per_1m_tokens?: {
input: number;
output: number;
};
allowed_roles: ModelRole[];
}
/**
* Currently configured model for a role
*/
export interface CurrentModel {
modelId?: string;
provider?: string;
baseURL?: string;
}
/**
* Current models configuration
*/
export interface CurrentModels {
main: CurrentModel | null;
research: CurrentModel | null;
fallback: CurrentModel | null;
}
/**
* Model selection choice for inquirer prompts
*/
export interface ModelChoice {
name: string;
value: { id: string; provider: string } | CustomProviderId | string | null;
short?: string;
type?: 'separator';
}
/**
* Prompt data for a specific role
*/
export interface PromptData {
choices: (ModelChoice | any)[]; // any to accommodate Separator instances
default: number;
}
/**
* Result from model fetcher functions
*/
export interface FetchResult<T> {
success: boolean;
data?: T;
error?: string;
}
/**
* OpenRouter model response
*/
export interface OpenRouterModel {
id: string;
name?: string;
description?: string;
}
/**
* Ollama model response
*/
export interface OllamaModel {
model: string;
name: string;
modified_at?: string;
}
/**
* Custom provider handler configuration
*/
export interface CustomProviderConfig {
id: CustomProviderId;
name: string;
provider: string;
promptMessage: (role: ModelRole) => string;
validate?: (modelId: string, baseURL?: string) => Promise<boolean>;
checkEnvVars?: () => boolean;
fetchModels?: () => Promise<FetchResult<unknown[]>>;
requiresBaseURL?: boolean;
defaultBaseURL?: string;
}
/**
* Model setup options
*/
export interface ModelSetupOptions {
projectRoot: string;
providerHint?: string;
}
/**
* Model set result
*/
export interface ModelSetResult {
success: boolean;
data?: {
message: string;
provider: string;
modelId: string;
warning?: string;
};
error?: {
message: string;
};
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/__tests__/context-manager.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
import { ContextManager } from '../context-manager.js';
describe('ContextManager', () => {
let contextManager;
beforeEach(() => {
contextManager = new ContextManager({
maxCacheSize: 10,
ttl: 1000, // 1 second for testing
maxContextSize: 1000
});
});
describe('getContext', () => {
it('should create a new context when not in cache', async () => {
const context = await contextManager.getContext('test-id', {
test: true
});
expect(context.id).toBe('test-id');
expect(context.metadata.test).toBe(true);
expect(contextManager.stats.misses).toBe(1);
expect(contextManager.stats.hits).toBe(0);
});
it('should return cached context when available', async () => {
// First call creates the context
await contextManager.getContext('test-id', { test: true });
// Second call should hit cache
const context = await contextManager.getContext('test-id', {
test: true
});
expect(context.id).toBe('test-id');
expect(context.metadata.test).toBe(true);
expect(contextManager.stats.hits).toBe(1);
expect(contextManager.stats.misses).toBe(1);
});
it('should respect TTL settings', async () => {
// Create context
await contextManager.getContext('test-id', { test: true });
// Wait for TTL to expire
await new Promise((resolve) => setTimeout(resolve, 1100));
// Should create new context
await contextManager.getContext('test-id', { test: true });
expect(contextManager.stats.misses).toBe(2);
expect(contextManager.stats.hits).toBe(0);
});
});
describe('updateContext', () => {
it('should update existing context metadata', async () => {
await contextManager.getContext('test-id', { initial: true });
const updated = await contextManager.updateContext('test-id', {
updated: true
});
expect(updated.metadata.initial).toBe(true);
expect(updated.metadata.updated).toBe(true);
});
});
describe('invalidateContext', () => {
it('should remove context from cache', async () => {
await contextManager.getContext('test-id', { test: true });
contextManager.invalidateContext('test-id', { test: true });
// Should be a cache miss
await contextManager.getContext('test-id', { test: true });
expect(contextManager.stats.invalidations).toBe(1);
expect(contextManager.stats.misses).toBe(2);
});
});
describe('getStats', () => {
it('should return current cache statistics', async () => {
await contextManager.getContext('test-id', { test: true });
const stats = contextManager.getStats();
expect(stats.hits).toBe(0);
expect(stats.misses).toBe(1);
expect(stats.invalidations).toBe(0);
expect(stats.size).toBe(1);
expect(stats.maxSize).toBe(10);
expect(stats.ttl).toBe(1000);
});
});
});
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/grok-cli-provider.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Grok CLI provider implementation for AI SDK v5
*/
import type { LanguageModelV2, ProviderV2 } from '@ai-sdk/provider';
import { NoSuchModelError } from '@ai-sdk/provider';
import { GrokCliLanguageModel } from './grok-cli-language-model.js';
import type { GrokCliModelId, GrokCliSettings } from './types.js';
/**
* Grok CLI provider interface that extends the AI SDK's ProviderV2
*/
export interface GrokCliProvider extends ProviderV2 {
/**
* Creates a language model instance for the specified model ID.
* This is a shorthand for calling `languageModel()`.
*/
(modelId: GrokCliModelId, settings?: GrokCliSettings): LanguageModelV2;
/**
* Creates a language model instance for text generation.
*/
languageModel(
modelId: GrokCliModelId,
settings?: GrokCliSettings
): LanguageModelV2;
/**
* Alias for `languageModel()` to maintain compatibility with AI SDK patterns.
*/
chat(modelId: GrokCliModelId, settings?: GrokCliSettings): LanguageModelV2;
textEmbeddingModel(modelId: string): never;
imageModel(modelId: string): never;
}
/**
* Configuration options for creating a Grok CLI provider instance
*/
export interface GrokCliProviderSettings {
/**
* Default settings to use for all models created by this provider.
* Individual model settings will override these defaults.
*/
defaultSettings?: GrokCliSettings;
}
/**
* Creates a Grok CLI provider instance with the specified configuration.
* The provider can be used to create language models for interacting with Grok models.
*/
export function createGrokCli(
options: GrokCliProviderSettings = {}
): GrokCliProvider {
const createModel = (
modelId: GrokCliModelId,
settings: GrokCliSettings = {}
): LanguageModelV2 => {
const mergedSettings = {
...options.defaultSettings,
...settings
};
return new GrokCliLanguageModel({
id: modelId,
settings: mergedSettings
});
};
const provider = function (
modelId: GrokCliModelId,
settings?: GrokCliSettings
) {
if (new.target) {
throw new Error(
'The Grok CLI model function cannot be called with the new keyword.'
);
}
return createModel(modelId, settings);
};
provider.languageModel = createModel;
provider.chat = createModel; // Alias for languageModel
// Add textEmbeddingModel method that throws NoSuchModelError
provider.textEmbeddingModel = (modelId: string) => {
throw new NoSuchModelError({
modelId,
modelType: 'textEmbeddingModel'
});
};
provider.imageModel = (modelId: string) => {
throw new NoSuchModelError({
modelId,
modelType: 'imageModel'
});
};
return provider as GrokCliProvider;
}
/**
* Default Grok CLI provider instance.
* Pre-configured provider for quick usage without custom settings.
*/
export const grokCli = createGrokCli();
```
--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/path-normalizer.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Path normalization utilities for global storage system.
* Converts project paths to storage-safe directory names using base64url encoding.
*
* This provides a bijective (one-to-one) mapping that preserves all characters
* and supports perfect round-trip conversion between paths and storage names.
*
* @module path-normalizer
*/
/**
* Normalizes a project path to a storage-safe directory name using base64url encoding.
* This encoding is filesystem-safe (no slashes, backslashes, or special characters)
* and fully reversible, preserving hyphens and all other characters in paths.
*
* @param {string} projectPath - The project path to normalize
* @returns {string} The base64url-encoded path safe for use as a directory name
*
* @example
* normalizeProjectPath('/Users/test/project') // returns base64url encoded string
* normalizeProjectPath('C:\\Users\\test') // returns base64url encoded string
* normalizeProjectPath('/projects/my-app') // returns base64url encoded string (hyphens preserved)
*/
export function normalizeProjectPath(projectPath: string): string {
if (!projectPath) {
return '';
}
// Use base64url encoding: filesystem-safe and fully reversible
return Buffer.from(projectPath, 'utf-8').toString('base64url');
}
/**
* Denormalizes a storage directory name back to the original path.
* Decodes base64url-encoded paths with perfect fidelity.
*
* @param {string} normalizedPath - The base64url-encoded path to decode
* @returns {string} The original path with all characters preserved
*
* @example
* denormalizeProjectPath(normalizeProjectPath('/Users/test/project')) // returns '/Users/test/project'
* denormalizeProjectPath(normalizeProjectPath('/projects/my-app')) // returns '/projects/my-app'
*/
export function denormalizeProjectPath(normalizedPath: string): string {
if (!normalizedPath) {
return '';
}
// Validate that input is valid base64url before attempting to decode
if (!isValidNormalizedPath(normalizedPath)) {
// Return original string for backward compatibility with non-base64url inputs
return normalizedPath;
}
return Buffer.from(normalizedPath, 'base64url').toString('utf-8');
}
/**
* Validates whether a path is in normalized (base64url) format.
* Valid base64url strings contain only: A-Z, a-z, 0-9, -, _
*
* @param {string} path - The path to validate
* @returns {boolean} True if the path is in normalized base64url format
*
* @example
* isValidNormalizedPath('VXNlcnMvdGVzdC9wcm9qZWN0') // returns true (valid base64url)
* isValidNormalizedPath('Users/test/project') // returns false (contains slashes)
*/
export function isValidNormalizedPath(path: string): boolean {
if (path === '') {
return true;
}
// Check if path is valid base64url: only A-Z, a-z, 0-9, -, _
return /^[A-Za-z0-9_-]*$/.test(path);
}
```
--------------------------------------------------------------------------------
/CLAUDE_CODE_PLUGIN.md:
--------------------------------------------------------------------------------
```markdown
# Taskmaster AI - Claude Code Marketplace
This repository includes a Claude Code plugin marketplace in `.claude-plugin/marketplace.json`.
## Installation
### From GitHub (Public Repository)
Once this repository is pushed to GitHub, users can install with:
```bash
# Add the marketplace
/plugin marketplace add eyaltoledano/claude-task-master
# Install the plugin
/plugin install taskmaster@taskmaster
```
### Local Development/Testing
```bash
# From the project root directory
cd /path/to/claude-task-master
# Build the plugin first
cd packages/claude-code-plugin
npm run build
cd ../..
# In Claude Code
/plugin marketplace add .
/plugin install taskmaster@taskmaster
```
## Marketplace Structure
```
claude-task-master/
├── .claude-plugin/
│ └── marketplace.json # Marketplace manifest (at repo root)
│
├── packages/claude-code-plugin/
│ ├── src/build.ts # Build tooling
│ └── [generated plugin files]
│
└── assets/claude/ # Plugin source files
├── commands/
└── agents/
```
## Available Plugins
### taskmaster
AI-powered task management system for ambitious development workflows.
**Features:**
- 49 slash commands for comprehensive task management
- 3 specialized AI agents (orchestrator, executor, checker)
- MCP server integration
- Complexity analysis and auto-expansion
- Dependency management and validation
- Automated workflow capabilities
**Quick Start:**
```bash
/tm:init
/tm:parse-prd
/tm:next
```
## For Contributors
### Adding New Plugins
To add more plugins to this marketplace:
1. **Update marketplace.json**:
```json
{
"plugins": [
{
"name": "new-plugin",
"source": "./path/to/plugin",
"description": "Plugin description",
"version": "1.0.0"
}
]
}
```
2. **Commit and push** the changes
3. **Users update** with: `/plugin marketplace update taskmaster`
### Marketplace Versioning
The marketplace version is tracked in `.claude-plugin/marketplace.json`:
```json
{
"metadata": {
"version": "1.0.0"
}
}
```
Increment the version when adding or updating plugins.
## Team Configuration
Organizations can auto-install this marketplace for all team members by adding to `.claude/settings.json`:
```json
{
"extraKnownMarketplaces": {
"task-master": {
"source": {
"source": "github",
"repo": "eyaltoledano/claude-task-master"
}
}
},
"enabledPlugins": {
"taskmaster": {
"marketplace": "taskmaster"
}
}
}
```
Team members who trust the repository folder will automatically get the marketplace and plugins installed.
## Documentation
- [Claude Code Plugin Docs](https://docs.claude.com/en/docs/claude-code/plugins)
- [Marketplace Documentation](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces)
```
--------------------------------------------------------------------------------
/apps/extension/src/utils/logger.ts:
--------------------------------------------------------------------------------
```typescript
import * as vscode from 'vscode';
/**
* Logger interface for dependency injection
*/
export interface ILogger {
log(message: string, ...args: any[]): void;
error(message: string, ...args: any[]): void;
warn(message: string, ...args: any[]): void;
debug(message: string, ...args: any[]): void;
show(): void;
dispose(): void;
}
/**
* Logger that outputs to VS Code's output channel instead of console
* This prevents interference with MCP stdio communication
*/
export class ExtensionLogger implements ILogger {
private static instance: ExtensionLogger;
private outputChannel: vscode.OutputChannel;
private debugMode: boolean;
private constructor() {
this.outputChannel = vscode.window.createOutputChannel('TaskMaster');
const config = vscode.workspace.getConfiguration('taskmaster');
this.debugMode = config.get<boolean>('debug.enableLogging', true);
}
static getInstance(): ExtensionLogger {
if (!ExtensionLogger.instance) {
ExtensionLogger.instance = new ExtensionLogger();
}
return ExtensionLogger.instance;
}
log(message: string, ...args: any[]): void {
if (!this.debugMode) {
return;
}
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] ${formattedMessage}`);
}
error(message: string, ...args: any[]): void {
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] ERROR: ${formattedMessage}`);
}
warn(message: string, ...args: any[]): void {
if (!this.debugMode) {
return;
}
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] WARN: ${formattedMessage}`);
}
debug(message: string, ...args: any[]): void {
if (!this.debugMode) {
return;
}
const timestamp = new Date().toISOString();
const formattedMessage = this.formatMessage(message, args);
this.outputChannel.appendLine(`[${timestamp}] DEBUG: ${formattedMessage}`);
}
private formatMessage(message: string, args: any[]): string {
if (args.length === 0) {
return message;
}
// Convert objects to JSON for better readability
const formattedArgs = args.map((arg) => {
if (typeof arg === 'object' && arg !== null) {
try {
return JSON.stringify(arg, null, 2);
} catch {
return String(arg);
}
}
return String(arg);
});
return `${message} ${formattedArgs.join(' ')}`;
}
show(): void {
this.outputChannel.show();
}
dispose(): void {
this.outputChannel.dispose();
}
setDebugMode(enabled: boolean): void {
this.debugMode = enabled;
}
}
// Export a singleton instance for convenience
export const logger = ExtensionLogger.getInstance();
```
--------------------------------------------------------------------------------
/src/progress/progress-tracker-builder.js:
--------------------------------------------------------------------------------
```javascript
/**
* Configuration for progress tracker features
*/
class TrackerConfig {
constructor() {
this.features = new Set();
this.spinnerFrames = null;
this.unitName = 'unit';
this.totalUnits = 100;
}
addFeature(feature) {
this.features.add(feature);
}
hasFeature(feature) {
return this.features.has(feature);
}
getOptions() {
return {
numUnits: this.totalUnits,
unitName: this.unitName,
spinnerFrames: this.spinnerFrames,
features: Array.from(this.features)
};
}
}
/**
* Builder for creating configured progress trackers
*/
export class ProgressTrackerBuilder {
constructor() {
this.config = new TrackerConfig();
}
withPercent() {
this.config.addFeature('percent');
return this;
}
withTokens() {
this.config.addFeature('tokens');
return this;
}
withTasks() {
this.config.addFeature('tasks');
return this;
}
withSpinner(messages) {
if (!messages || !Array.isArray(messages)) {
throw new Error('Spinner messages must be an array');
}
this.config.spinnerFrames = messages;
return this;
}
withUnits(total, unitName = 'unit') {
this.config.totalUnits = total;
this.config.unitName = unitName;
return this;
}
build() {
return new ProgressTracker(this.config);
}
}
/**
* Base progress tracker with configurable features
*/
class ProgressTracker {
constructor(config) {
this.config = config;
this.isActive = false;
this.current = 0;
this.spinnerIndex = 0;
this.startTime = null;
}
start() {
this.isActive = true;
this.startTime = Date.now();
this.current = 0;
if (this.config.spinnerFrames) {
this._startSpinner();
}
}
update(data = {}) {
if (!this.isActive) return;
if (data.current !== undefined) {
this.current = data.current;
}
const progress = this._buildProgressData(data);
return progress;
}
finish() {
this.isActive = false;
if (this.spinnerInterval) {
clearInterval(this.spinnerInterval);
this.spinnerInterval = null;
}
return this._buildSummary();
}
_startSpinner() {
this.spinnerInterval = setInterval(() => {
this.spinnerIndex =
(this.spinnerIndex + 1) % this.config.spinnerFrames.length;
}, 100);
}
_buildProgressData(data) {
const progress = { ...data };
if (this.config.hasFeature('percent')) {
progress.percentage = Math.round(
(this.current / this.config.totalUnits) * 100
);
}
if (this.config.hasFeature('tasks')) {
progress.tasks = `${this.current}/${this.config.totalUnits}`;
}
if (this.config.spinnerFrames) {
progress.spinner = this.config.spinnerFrames[this.spinnerIndex];
}
return progress;
}
_buildSummary() {
const elapsed = Date.now() - this.startTime;
return {
total: this.config.totalUnits,
completed: this.current,
elapsedMs: elapsed,
features: Array.from(this.config.features)
};
}
}
```
--------------------------------------------------------------------------------
/src/profiles/kiro.js:
--------------------------------------------------------------------------------
```javascript
// Kiro profile for rule-transformer
import { createProfile } from './base-profile.js';
import fs from 'fs';
import path from 'path';
import { log } from '../../scripts/modules/utils.js';
// Create and export kiro profile using the base factory
export const kiroProfile = createProfile({
name: 'kiro',
displayName: 'Kiro',
url: 'kiro.dev',
docsUrl: 'kiro.dev/docs',
profileDir: '.kiro',
rulesDir: '.kiro/steering', // Kiro rules location (full path)
mcpConfig: true,
mcpConfigName: 'settings/mcp.json', // Create directly in settings subdirectory
includeDefaultRules: true, // Include default rules to get all the standard files
targetExtension: '.md',
fileMap: {
// Override specific mappings - the base profile will create:
// 'rules/cursor_rules.mdc': 'kiro_rules.md'
// 'rules/dev_workflow.mdc': 'dev_workflow.md'
// 'rules/self_improve.mdc': 'self_improve.md'
// 'rules/taskmaster.mdc': 'taskmaster.md'
// We can add additional custom mappings here if needed
'rules/taskmaster_hooks_workflow.mdc': 'taskmaster_hooks_workflow.md'
},
customReplacements: [
// Core Kiro directory structure changes
{ from: /\.cursor\/rules/g, to: '.kiro/steering' },
{ from: /\.cursor\/mcp\.json/g, to: '.kiro/settings/mcp.json' },
// Fix any remaining kiro/rules references that might be created during transformation
{ from: /\.kiro\/rules/g, to: '.kiro/steering' },
// Essential markdown link transformations for Kiro structure
{
from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
to: '[$1](.kiro/steering/$2.md)'
},
// Kiro specific terminology
{ from: /rules directory/g, to: 'steering directory' },
{ from: /cursor rules/gi, to: 'Kiro steering files' },
// Transform frontmatter to Kiro format
// This regex matches the entire frontmatter block and replaces it
{
from: /^---\n(?:description:\s*[^\n]*\n)?(?:globs:\s*[^\n]*\n)?(?:alwaysApply:\s*true\n)?---/m,
to: '---\ninclusion: always\n---'
}
],
// Add lifecycle hook to copy Kiro hooks
onPostConvert: (projectRoot, assetsDir) => {
const hooksSourceDir = path.join(assetsDir, 'kiro-hooks');
const hooksTargetDir = path.join(projectRoot, '.kiro', 'hooks');
// Create hooks directory if it doesn't exist
if (!fs.existsSync(hooksTargetDir)) {
fs.mkdirSync(hooksTargetDir, { recursive: true });
}
// Copy all .kiro.hook files
if (fs.existsSync(hooksSourceDir)) {
const hookFiles = fs
.readdirSync(hooksSourceDir)
.filter((f) => f.endsWith('.kiro.hook'));
hookFiles.forEach((file) => {
const sourcePath = path.join(hooksSourceDir, file);
const targetPath = path.join(hooksTargetDir, file);
fs.copyFileSync(sourcePath, targetPath);
});
if (hookFiles.length > 0) {
log(
'info',
`[Kiro] Installed ${hookFiles.length} Taskmaster hooks in .kiro/hooks/`
);
}
}
}
});
```
--------------------------------------------------------------------------------
/apps/extension/src/services/notification-preferences.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Notification Preferences Service
* Manages user preferences for notifications
*/
import * as vscode from 'vscode';
import { ErrorCategory, ErrorSeverity } from './error-handler';
export enum NotificationLevel {
ALL = 'all',
ERRORS_ONLY = 'errors_only',
CRITICAL_ONLY = 'critical_only',
NONE = 'none'
}
interface NotificationRule {
category: ErrorCategory;
minSeverity: ErrorSeverity;
enabled: boolean;
}
export class NotificationPreferences {
private defaultRules: NotificationRule[] = [
{
category: ErrorCategory.MCP_CONNECTION,
minSeverity: ErrorSeverity.HIGH,
enabled: true
},
{
category: ErrorCategory.CONFIGURATION,
minSeverity: ErrorSeverity.MEDIUM,
enabled: true
},
{
category: ErrorCategory.TASK_LOADING,
minSeverity: ErrorSeverity.HIGH,
enabled: true
},
{
category: ErrorCategory.NETWORK,
minSeverity: ErrorSeverity.HIGH,
enabled: true
},
{
category: ErrorCategory.INTERNAL,
minSeverity: ErrorSeverity.CRITICAL,
enabled: true
}
];
/**
* Check if a notification should be shown
*/
shouldShowNotification(
category: ErrorCategory,
severity: ErrorSeverity
): boolean {
// Get user's notification level preference
const level = this.getNotificationLevel();
if (level === NotificationLevel.NONE) {
return false;
}
if (
level === NotificationLevel.CRITICAL_ONLY &&
severity !== ErrorSeverity.CRITICAL
) {
return false;
}
if (
level === NotificationLevel.ERRORS_ONLY &&
severity !== ErrorSeverity.CRITICAL &&
severity !== ErrorSeverity.HIGH
) {
return false;
}
// Check category-specific rules
const rule = this.defaultRules.find((r) => r.category === category);
if (!rule || !rule.enabled) {
return false;
}
// Check if severity meets minimum threshold
return this.compareSeverity(severity, rule.minSeverity) >= 0;
}
/**
* Get user's notification level preference
*/
private getNotificationLevel(): NotificationLevel {
const config = vscode.workspace.getConfiguration('taskmaster');
return config.get<NotificationLevel>(
'notifications.level',
NotificationLevel.ERRORS_ONLY
);
}
/**
* Compare severity levels
*/
private compareSeverity(a: ErrorSeverity, b: ErrorSeverity): number {
const severityOrder = {
[ErrorSeverity.LOW]: 0,
[ErrorSeverity.MEDIUM]: 1,
[ErrorSeverity.HIGH]: 2,
[ErrorSeverity.CRITICAL]: 3
};
return severityOrder[a] - severityOrder[b];
}
/**
* Get toast notification duration based on severity
*/
getToastDuration(severity: ErrorSeverity): number {
switch (severity) {
case ErrorSeverity.CRITICAL:
return 10000; // 10 seconds
case ErrorSeverity.HIGH:
return 7000; // 7 seconds
case ErrorSeverity.MEDIUM:
return 5000; // 5 seconds
case ErrorSeverity.LOW:
return 3000; // 3 seconds
}
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/expand-task.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/expand-task.js
* Tool to expand a task into subtasks
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { expandTaskDirect } from '../core/task-master-core.js';
import {
findTasksPath,
findComplexityReportPath
} from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the expand-task tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerExpandTaskTool(server) {
server.addTool({
name: 'expand_task',
description: 'Expand a task into subtasks for detailed implementation',
parameters: z.object({
id: z.string().describe('ID of task to expand'),
num: z.string().optional().describe('Number of subtasks to generate'),
research: z
.boolean()
.optional()
.default(false)
.describe('Use research role for generation'),
prompt: z
.string()
.optional()
.describe('Additional context for subtask generation'),
file: z
.string()
.optional()
.describe(
'Path to the tasks file relative to project root (e.g., tasks/tasks.json)'
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
force: z
.boolean()
.optional()
.default(false)
.describe('Force expansion even if subtasks exist'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
log.info(`Starting expand-task with args: ${JSON.stringify(args)}`);
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const complexityReportPath = findComplexityReportPath(
{ ...args, tag: resolvedTag },
log
);
const result = await expandTaskDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
num: args.num,
research: args.research,
prompt: args.prompt,
force: args.force,
complexityReportPath,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
return handleApiResult(
result,
log,
'Error expanding task',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in expand-task tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/apps/extension/src/utils/task-master-api/types/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* TaskMaster API Types
* All type definitions for the TaskMaster API
*/
// MCP Response Types
export interface MCPTaskResponse {
data?: {
tasks?: Array<{
id: number | string;
title: string;
description: string;
status: string;
priority: string;
details?: string;
testStrategy?: string;
dependencies?: Array<number | string>;
complexityScore?: number;
subtasks?: Array<{
id: number;
title: string;
description?: string;
status: string;
details?: string;
dependencies?: Array<number | string>;
}>;
}>;
tag?: {
currentTag: string;
availableTags: string[];
};
};
version?: {
version: string;
name: string;
};
error?: string;
}
// Internal Task Interface
export interface TaskMasterTask {
id: string;
title: string;
description: string;
status:
| 'pending'
| 'in-progress'
| 'review'
| 'done'
| 'deferred'
| 'cancelled';
priority: 'high' | 'medium' | 'low';
details?: string;
testStrategy?: string;
dependencies?: string[];
complexityScore?: number;
subtasks?: Array<{
id: number;
title: string;
description?: string;
status: string;
details?: string;
testStrategy?: string;
dependencies?: Array<number | string>;
}>;
}
// API Response Wrapper
export interface TaskMasterApiResponse<T = any> {
success: boolean;
data?: T;
error?: string;
requestDuration?: number;
}
// API Configuration
export interface TaskMasterApiConfig {
timeout: number;
retryAttempts: number;
cacheDuration: number;
projectRoot?: string;
cache?: CacheConfig;
}
export interface CacheConfig {
maxSize: number;
enableBackgroundRefresh: boolean;
refreshInterval: number;
enableAnalytics: boolean;
enablePrefetch: boolean;
compressionEnabled: boolean;
persistToDisk: boolean;
}
// Cache Types
export interface CacheEntry {
data: any;
timestamp: number;
accessCount: number;
lastAccessed: number;
size: number;
ttl?: number;
tags: string[];
}
export interface CacheAnalytics {
hits: number;
misses: number;
evictions: number;
refreshes: number;
totalSize: number;
averageAccessTime: number;
hitRate: number;
}
// Method Options
export interface GetTasksOptions {
status?: string;
withSubtasks?: boolean;
tag?: string;
projectRoot?: string;
}
export interface UpdateTaskStatusOptions {
projectRoot?: string;
}
export interface UpdateTaskOptions {
projectRoot?: string;
append?: boolean;
research?: boolean;
}
export interface UpdateSubtaskOptions {
projectRoot?: string;
research?: boolean;
}
export interface AddSubtaskOptions {
projectRoot?: string;
}
export interface TaskUpdate {
title?: string;
description?: string;
details?: string;
priority?: 'high' | 'medium' | 'low';
testStrategy?: string;
dependencies?: string[];
}
export interface SubtaskData {
title: string;
description?: string;
dependencies?: string[];
status?: string;
}
```
--------------------------------------------------------------------------------
/tests/helpers/tool-counts.js:
--------------------------------------------------------------------------------
```javascript
/**
* tool-counts.js
* Shared helper for validating tool counts across tests and validation scripts
*/
import {
getToolCounts,
getToolCategories
} from '../../mcp-server/src/tools/tool-registry.js';
/**
* Expected tool counts - update these when tools are added/removed
* These serve as the canonical source of truth for expected counts
*/
export const EXPECTED_TOOL_COUNTS = {
core: 7,
standard: 15,
total: 44
};
/**
* Expected core tools list for validation
*/
export const EXPECTED_CORE_TOOLS = [
'get_tasks',
'next_task',
'get_task',
'set_task_status',
'update_subtask',
'parse_prd',
'expand_task'
];
/**
* Validate that actual tool counts match expected counts
* @returns {Object} Validation result with isValid flag and details
*/
export function validateToolCounts() {
const actual = getToolCounts();
const expected = EXPECTED_TOOL_COUNTS;
const isValid =
actual.core === expected.core &&
actual.standard === expected.standard &&
actual.total === expected.total;
return {
isValid,
actual,
expected,
differences: {
core: actual.core - expected.core,
standard: actual.standard - expected.standard,
total: actual.total - expected.total
}
};
}
/**
* Validate that tool categories have correct structure and content
* @returns {Object} Validation result
*/
export function validateToolStructure() {
const categories = getToolCategories();
const counts = getToolCounts();
// Check that core tools are subset of standard tools
const coreInStandard = categories.core.every((tool) =>
categories.standard.includes(tool)
);
// Check that standard tools are subset of all tools
const standardInAll = categories.standard.every((tool) =>
categories.all.includes(tool)
);
// Check that expected core tools match actual
const expectedCoreMatch =
EXPECTED_CORE_TOOLS.every((tool) => categories.core.includes(tool)) &&
categories.core.every((tool) => EXPECTED_CORE_TOOLS.includes(tool));
// Check array lengths match counts
const lengthsMatch =
categories.core.length === counts.core &&
categories.standard.length === counts.standard &&
categories.all.length === counts.total;
return {
isValid:
coreInStandard && standardInAll && expectedCoreMatch && lengthsMatch,
details: {
coreInStandard,
standardInAll,
expectedCoreMatch,
lengthsMatch
},
categories,
counts
};
}
/**
* Get a detailed report of all tool information
* @returns {Object} Comprehensive tool information
*/
export function getToolReport() {
const counts = getToolCounts();
const categories = getToolCategories();
const validation = validateToolCounts();
const structure = validateToolStructure();
return {
counts,
categories,
validation,
structure,
summary: {
totalValid: validation.isValid && structure.isValid,
countsValid: validation.isValid,
structureValid: structure.isValid
}
};
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/remove-dependency.js:
--------------------------------------------------------------------------------
```javascript
/**
* Direct function wrapper for removeDependency
*/
import { removeDependency } from '../../../../scripts/modules/dependency-manager.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
/**
* Remove a dependency from a task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to remove dependency from
* @param {string|number} args.dependsOn - Task ID to remove as a dependency
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {string} args.tag - Tag for the task (optional)
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function removeDependencyDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, dependsOn, projectRoot, tag } = args;
try {
log.info(`Removing dependency with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeDependencyDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Validate required parameters
if (!id) {
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: 'Task ID (id) is required'
}
};
}
if (!dependsOn) {
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: 'Dependency ID (dependsOn) is required'
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Format IDs for the core function
const taskId =
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
const dependencyId =
dependsOn && dependsOn.includes && dependsOn.includes('.')
? dependsOn
: parseInt(dependsOn, 10);
log.info(
`Removing dependency: task ${taskId} no longer depends on ${dependencyId}`
);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function using the provided tasksPath
await removeDependency(tasksPath, taskId, dependencyId, {
projectRoot,
tag
});
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
message: `Successfully removed dependency: Task ${taskId} no longer depends on ${dependencyId}`,
taskId: taskId,
dependencyId: dependencyId
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in removeDependencyDirect: ${error.message}`);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/delete-tag.js:
--------------------------------------------------------------------------------
```javascript
/**
* delete-tag.js
* Direct function implementation for deleting a tag
*/
import { deleteTag } from '../../../../scripts/modules/task-manager/tag-management.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for deleting a tag with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.name - Name of the tag to delete
* @param {boolean} [args.yes=false] - Skip confirmation prompts
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
* @param {string} [args.projectRoot] - Project root path
* @param {Object} log - Logger object
* @param {Object} context - Additional context (session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function deleteTagDirect(args, log, context = {}) {
// Destructure expected args
const { tasksJsonPath, name, yes = false, projectRoot } = args;
const { session } = context;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
try {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('deleteTagDirect called without tasksJsonPath');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Check required parameters
if (!name || typeof name !== 'string') {
log.error('Missing required parameter: name');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'Tag name is required and must be a string'
}
};
}
log.info(`Deleting tag: ${name}`);
// Prepare options
const options = {
yes // For MCP, we always skip confirmation prompts
};
// Call the deleteTag function
const result = await deleteTag(
tasksJsonPath,
name,
options,
{
session,
mcpLog,
projectRoot
},
'json' // outputFormat - use 'json' to suppress CLI UI
);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
tagName: result.tagName,
deleted: result.deleted,
tasksDeleted: result.tasksDeleted,
wasCurrentTag: result.wasCurrentTag,
switchedToMaster: result.switchedToMaster,
message: `Successfully deleted tag "${result.tagName}"`
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in deleteTagDirect: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'DELETE_TAG_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/tests/unit/ai-providers/codex-cli.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
// Mock the ai module
jest.unstable_mockModule('ai', () => ({
generateObject: jest.fn(),
generateText: jest.fn(),
streamText: jest.fn()
}));
// Mock the codex-cli SDK module
jest.unstable_mockModule('ai-sdk-provider-codex-cli', () => ({
createCodexCli: jest.fn((options) => {
const provider = (modelId, settings) => ({ id: modelId, settings });
provider.languageModel = jest.fn((id, settings) => ({ id, settings }));
provider.chat = provider.languageModel;
return provider;
})
}));
// Mock config getters
jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
getCodexCliSettingsForCommand: jest.fn(() => ({ allowNpx: true })),
getSupportedModelsForProvider: jest.fn(() => ['gpt-5', 'gpt-5-codex']),
// Provide commonly imported getters to satisfy other module imports if any
getDebugFlag: jest.fn(() => false),
getLogLevel: jest.fn(() => 'info')
}));
// Mock base provider
jest.unstable_mockModule('../../../src/ai-providers/base-provider.js', () => ({
BaseAIProvider: class {
constructor() {
this.name = 'Base Provider';
}
handleError(_ctx, err) {
throw err;
}
validateParams(params) {
if (!params.modelId) throw new Error('Model ID is required');
}
validateMessages(msgs) {
if (!Array.isArray(msgs)) throw new Error('Invalid messages array');
}
}
}));
const { CodexCliProvider } = await import(
'../../../src/ai-providers/codex-cli.js'
);
const { createCodexCli } = await import('ai-sdk-provider-codex-cli');
const { getCodexCliSettingsForCommand } = await import(
'../../../scripts/modules/config-manager.js'
);
describe('CodexCliProvider', () => {
let provider;
beforeEach(() => {
jest.clearAllMocks();
provider = new CodexCliProvider();
});
it('sets provider name and supported models', () => {
expect(provider.name).toBe('Codex CLI');
expect(provider.supportedModels).toEqual(['gpt-5', 'gpt-5-codex']);
});
it('does not require API key', () => {
expect(provider.isRequiredApiKey()).toBe(false);
});
it('creates client with merged default settings', async () => {
const client = await provider.getClient({ commandName: 'parse-prd' });
expect(client).toBeDefined();
expect(createCodexCli).toHaveBeenCalledWith({
defaultSettings: expect.objectContaining({ allowNpx: true })
});
expect(getCodexCliSettingsForCommand).toHaveBeenCalledWith('parse-prd');
});
it('injects OPENAI_API_KEY only when apiKey provided', async () => {
const client = await provider.getClient({
commandName: 'expand',
apiKey: 'sk-test'
});
const call = createCodexCli.mock.calls[0][0];
expect(call.defaultSettings.env.OPENAI_API_KEY).toBe('sk-test');
// Ensure env is not set when apiKey not provided
await provider.getClient({ commandName: 'expand' });
const second = createCodexCli.mock.calls[1][0];
expect(second.defaultSettings.env).toBeUndefined();
});
});
```
--------------------------------------------------------------------------------
/apps/docs/archive/cursor-setup.mdx:
--------------------------------------------------------------------------------
```markdown
---
title: "Cursor AI Integration"
description: "Learn how to set up and use Task Master with Cursor AI"
---
## Setting up Cursor AI Integration
<Check>
Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development.
</Check>
<AccordionGroup>
<Accordion title="Using Cursor with MCP (Recommended)" icon="sparkles">
If you've already set up Task Master with MCP in Cursor, the integration is automatic. You can simply use natural language to interact with Task Master:
```
What tasks are available to work on next?
Can you analyze the complexity of our tasks?
I'd like to implement task 4. What does it involve?
```
</Accordion>
<Accordion title="Manual Cursor Setup">
If you're not using MCP, you can still set up Cursor integration:
<Steps>
<Step title="After initializing your project, open it in Cursor">
The `.cursor/rules/dev_workflow.mdc` file is automatically loaded by Cursor, providing the AI with knowledge about the task management system
</Step>
<Step title="Place your PRD document in the scripts/ directory (e.g., scripts/prd.txt)">
</Step>
<Step title="Open Cursor's AI chat and switch to Agent mode">
</Step>
</Steps>
</Accordion>
<Accordion title="Alternative MCP Setup in Cursor">
<Steps>
<Step title="Go to Cursor settings">
</Step>
<Step title="Navigate to the MCP section">
</Step>
<Step title="Click on 'Add New MCP Server'">
</Step>
<Step title="Configure with the following details:">
- Name: "Task Master"
- Type: "Command"
- Command: "npx -y task-master-ai"
</Step>
<Step title="Save Settings">
</Step>
</Steps>
Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience.
</Accordion>
</AccordionGroup>
## Initial Task Generation
In Cursor's AI chat, instruct the agent to generate tasks from your PRD:
```
Please use the task-master parse-prd command to generate tasks from my PRD. The PRD is located at scripts/prd.txt.
```
The agent will execute:
```bash
task-master parse-prd scripts/prd.txt
```
This will:
- Parse your PRD document
- Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies
- The agent will understand this process due to the Cursor rules
### Generate Individual Task Files
Next, ask the agent to generate individual task files:
```
Please generate individual task files from tasks.json
```
The agent will execute:
```bash
task-master generate
```
This creates individual task files in the `tasks/` directory (e.g., `task_001.txt`, `task_002.txt`), making it easier to reference specific tasks.
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-gemini.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
import { getRulesProfile } from '../../../src/utils/rule-transformer.js';
import { geminiProfile } from '../../../src/profiles/gemini.js';
describe('Rule Transformer - Gemini Profile', () => {
test('should have correct profile configuration', () => {
const geminiProfile = getRulesProfile('gemini');
expect(geminiProfile).toBeDefined();
expect(geminiProfile.profileName).toBe('gemini');
expect(geminiProfile.displayName).toBe('Gemini');
expect(geminiProfile.profileDir).toBe('.gemini');
expect(geminiProfile.rulesDir).toBe('.');
expect(geminiProfile.mcpConfig).toBe(true);
expect(geminiProfile.mcpConfigName).toBe('settings.json');
expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json');
expect(geminiProfile.includeDefaultRules).toBe(false);
expect(geminiProfile.fileMap).toEqual({
'AGENT.md': 'AGENTS.md',
'GEMINI.md': 'GEMINI.md'
});
});
test('should have minimal profile implementation', () => {
// Verify that gemini.js is minimal (no lifecycle functions)
expect(geminiProfile.onAddRulesProfile).toBeUndefined();
expect(geminiProfile.onRemoveRulesProfile).toBeUndefined();
expect(geminiProfile.onPostConvertRulesProfile).toBeUndefined();
});
test('should use settings.json instead of mcp.json', () => {
const geminiProfile = getRulesProfile('gemini');
expect(geminiProfile.mcpConfigName).toBe('settings.json');
expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json');
});
test('should not include default rules', () => {
const geminiProfile = getRulesProfile('gemini');
expect(geminiProfile.includeDefaultRules).toBe(false);
});
test('should have correct file mapping', () => {
const geminiProfile = getRulesProfile('gemini');
expect(geminiProfile.fileMap).toEqual({
'AGENT.md': 'AGENTS.md',
'GEMINI.md': 'GEMINI.md'
});
});
test('should place AGENTS.md and GEMINI.md in root directory', () => {
const geminiProfile = getRulesProfile('gemini');
// rulesDir determines where fileMap files go
expect(geminiProfile.rulesDir).toBe('.');
// This means both AGENTS.md and GEMINI.md will be placed in the root
// Both files are auto-loaded by Gemini CLI
});
test('should place settings.json in .gemini directory', () => {
const geminiProfile = getRulesProfile('gemini');
// profileDir + mcpConfigName determines MCP config location
expect(geminiProfile.profileDir).toBe('.gemini');
expect(geminiProfile.mcpConfigName).toBe('settings.json');
expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json');
});
test('should have proper conversion config', () => {
const geminiProfile = getRulesProfile('gemini');
// Gemini should have the standard conversion config
expect(geminiProfile.conversionConfig).toBeDefined();
expect(geminiProfile.globalReplacements).toBeDefined();
expect(Array.isArray(geminiProfile.globalReplacements)).toBe(true);
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/custom-sdk/json-extractor.js:
--------------------------------------------------------------------------------
```javascript
/**
* @fileoverview Extract JSON from MCP response, handling markdown blocks and other formatting
*/
/**
* Extract JSON from MCP AI response
* @param {string} text - The text to extract JSON from
* @returns {string} - The extracted JSON string
*/
export function extractJson(text) {
// Remove markdown code blocks if present
let jsonText = text.trim();
// Remove ```json blocks
jsonText = jsonText.replace(/^```json\s*/gm, '');
jsonText = jsonText.replace(/^```\s*/gm, '');
jsonText = jsonText.replace(/```\s*$/gm, '');
// Remove common TypeScript/JavaScript patterns
jsonText = jsonText.replace(/^const\s+\w+\s*=\s*/, ''); // Remove "const varName = "
jsonText = jsonText.replace(/^let\s+\w+\s*=\s*/, ''); // Remove "let varName = "
jsonText = jsonText.replace(/^var\s+\w+\s*=\s*/, ''); // Remove "var varName = "
jsonText = jsonText.replace(/;?\s*$/, ''); // Remove trailing semicolons
// Remove explanatory text before JSON (common with AI responses)
jsonText = jsonText.replace(/^.*?(?=\{|\[)/s, '');
// Remove explanatory text after JSON
const lines = jsonText.split('\n');
let jsonEndIndex = -1;
let braceCount = 0;
let inString = false;
let escapeNext = false;
// Find the end of the JSON by tracking braces
for (let i = 0; i < jsonText.length; i++) {
const char = jsonText[i];
if (escapeNext) {
escapeNext = false;
continue;
}
if (char === '\\') {
escapeNext = true;
continue;
}
if (char === '"' && !escapeNext) {
inString = !inString;
continue;
}
if (!inString) {
if (char === '{' || char === '[') {
braceCount++;
} else if (char === '}' || char === ']') {
braceCount--;
if (braceCount === 0) {
jsonEndIndex = i;
break;
}
}
}
}
if (jsonEndIndex > -1) {
jsonText = jsonText.substring(0, jsonEndIndex + 1);
}
// Try to extract JSON object or array if previous method didn't work
if (jsonEndIndex === -1) {
const objectMatch = jsonText.match(/{[\s\S]*}/);
const arrayMatch = jsonText.match(/\[[\s\S]*\]/);
if (objectMatch) {
jsonText = objectMatch[0];
} else if (arrayMatch) {
jsonText = arrayMatch[0];
}
}
// First try to parse as valid JSON
try {
JSON.parse(jsonText);
return jsonText;
} catch {
// If it's not valid JSON, it might be a JavaScript object literal
// Try to convert it to valid JSON
try {
// This is a simple conversion that handles basic cases
// Replace unquoted keys with quoted keys
const converted = jsonText
.replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":')
// Replace single quotes with double quotes
.replace(/'/g, '"')
// Handle trailing commas
.replace(/,\s*([}\]])/g, '$1');
// Validate the converted JSON
JSON.parse(converted);
return converted;
} catch {
// If all else fails, return the original text
// The calling code will handle the error appropriately
return text;
}
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/update-subtask.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/update-subtask.js
* Tool to append additional information to a specific subtask
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { updateSubtaskByIdDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the update-subtask tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerUpdateSubtaskTool(server) {
server.addTool({
name: 'update_subtask',
description:
'Appends timestamped information to a specific subtask without replacing existing content. If you just want to update the subtask status, use set_task_status instead.',
parameters: z.object({
id: z
.string()
.describe(
'ID of the subtask to update in format "parentId.subtaskId" (e.g., "5.2"). Parent ID is the ID of the task that contains the subtask.'
),
prompt: z.string().describe('Information to add to the subtask'),
research: z
.boolean()
.optional()
.describe('Use Perplexity AI for research-backed updates'),
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
const toolName = 'update_subtask';
try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath(
{ projectRoot: args.projectRoot, file: args.file },
log
);
} catch (error) {
log.error(`${toolName}: Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await updateSubtaskByIdDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
prompt: args.prompt,
research: args.research,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
if (result.success) {
log.info(`Successfully updated subtask with ID ${args.id}`);
} else {
log.error(
`Failed to update subtask: ${result.error?.message || 'Unknown error'}`
);
}
return handleApiResult(
result,
log,
'Error updating subtask',
undefined,
args.projectRoot
);
} catch (error) {
log.error(
`Critical error in ${toolName} tool execute: ${error.message}`
);
return createErrorResponse(
`Internal tool error (${toolName}): ${error.message}`
);
}
})
});
}
```
--------------------------------------------------------------------------------
/tests/unit/profiles/trae-integration.test.js:
--------------------------------------------------------------------------------
```javascript
import { jest } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import os from 'os';
// Mock external modules
jest.mock('child_process', () => ({
execSync: jest.fn()
}));
// Mock console methods
jest.mock('console', () => ({
log: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
clear: jest.fn()
}));
describe('Trae Integration', () => {
let tempDir;
beforeEach(() => {
jest.clearAllMocks();
// Create a temporary directory for testing
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
// Spy on fs methods
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
if (filePath.toString().includes('.trae')) {
return 'Existing trae rules content';
}
return '{}';
});
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
});
afterEach(() => {
// Clean up the temporary directory
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch (err) {
console.error(`Error cleaning up: ${err.message}`);
}
});
// Test function that simulates the createProjectStructure behavior for Trae files
function mockCreateTraeStructure() {
// Create main .trae directory
fs.mkdirSync(path.join(tempDir, '.trae'), { recursive: true });
// Create rules directory
fs.mkdirSync(path.join(tempDir, '.trae', 'rules'), { recursive: true });
// Create rule files
const ruleFiles = [
'dev_workflow.md',
'taskmaster.md',
'architecture.md',
'commands.md',
'dependencies.md'
];
for (const ruleFile of ruleFiles) {
fs.writeFileSync(
path.join(tempDir, '.trae', 'rules', ruleFile),
`Content for ${ruleFile}`
);
}
}
test('creates all required .trae directories', () => {
// Act
mockCreateTraeStructure();
// Assert
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.trae'), {
recursive: true
});
expect(fs.mkdirSync).toHaveBeenCalledWith(
path.join(tempDir, '.trae', 'rules'),
{ recursive: true }
);
});
test('creates rule files for Trae', () => {
// Act
mockCreateTraeStructure();
// Assert - check rule files are created
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.trae', 'rules', 'dev_workflow.md'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.trae', 'rules', 'taskmaster.md'),
expect.any(String)
);
expect(fs.writeFileSync).toHaveBeenCalledWith(
path.join(tempDir, '.trae', 'rules', 'architecture.md'),
expect.any(String)
);
});
test('does not create MCP configuration files', () => {
// Act
mockCreateTraeStructure();
// Assert - Trae doesn't use MCP configuration
expect(fs.writeFileSync).not.toHaveBeenCalledWith(
path.join(tempDir, '.trae', 'mcp.json'),
expect.any(String)
);
});
});
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/update.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/update.js
* Tool to update tasks based on new context/prompt
*/
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
withNormalizedProjectRoot
} from './utils.js';
import { updateTasksDirect } from '../core/task-master-core.js';
import { findTasksPath } from '../core/utils/path-utils.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the update tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerUpdateTool(server) {
server.addTool({
name: 'update',
description:
"Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt. Use 'update_task' instead for a single specific task or 'update_subtask' for subtasks.",
parameters: z.object({
from: z
.string()
.describe(
"Task ID from which to start updating (inclusive). IMPORTANT: This tool uses 'from', not 'id'"
),
prompt: z
.string()
.describe('Explanation of changes or new context to apply'),
research: z
.boolean()
.optional()
.describe('Use Perplexity AI for research-backed updates'),
file: z
.string()
.optional()
.describe('Path to the tasks file relative to project root'),
projectRoot: z
.string()
.optional()
.describe(
'The directory of the project. (Optional, usually from session)'
),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
const toolName = 'update';
const { from, prompt, research, file, projectRoot, tag } = args;
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
try {
log.info(
`Executing ${toolName} tool with normalized root: ${projectRoot}`
);
let tasksJsonPath;
try {
tasksJsonPath = findTasksPath({ projectRoot, file }, log);
log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`);
} catch (error) {
log.error(`${toolName}: Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json within project root '${projectRoot}': ${error.message}`
);
}
const result = await updateTasksDirect(
{
tasksJsonPath: tasksJsonPath,
from: from,
prompt: prompt,
research: research,
projectRoot: projectRoot,
tag: resolvedTag
},
log,
{ session }
);
log.info(
`${toolName}: Direct function result: success=${result.success}`
);
return handleApiResult(
result,
log,
'Error updating tasks',
undefined,
args.projectRoot
);
} catch (error) {
log.error(
`Critical error in ${toolName} tool execute: ${error.message}`
);
return createErrorResponse(
`Internal tool error (${toolName}): ${error.message}`
);
}
})
});
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/add-dependency.js:
--------------------------------------------------------------------------------
```javascript
/**
* add-dependency.js
* Direct function implementation for adding a dependency to a task
*/
import { addDependency } from '../../../../scripts/modules/dependency-manager.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for addDependency with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to add dependency to
* @param {string|number} args.dependsOn - Task ID that will become a dependency
* @param {string} args.tag - Tag for the task (optional)
* @param {string} args.projectRoot - Project root path (for MCP/env fallback)
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Result object with success status and data/error information
*/
export async function addDependencyDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, dependsOn, tag, projectRoot } = args;
try {
log.info(`Adding dependency with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addDependencyDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Validate required parameters
if (!id) {
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: 'Task ID (id) is required'
}
};
}
if (!dependsOn) {
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: 'Dependency ID (dependsOn) is required'
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Format IDs for the core function
const taskId =
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
const dependencyId =
dependsOn && dependsOn.includes && dependsOn.includes('.')
? dependsOn
: parseInt(dependsOn, 10);
log.info(
`Adding dependency: task ${taskId} will depend on ${dependencyId}`
);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Create context object
const context = { projectRoot, tag };
// Call the core function using the provided path
await addDependency(tasksPath, taskId, dependencyId, context);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
message: `Successfully added dependency: Task ${taskId} now depends on ${dependencyId}`,
taskId: taskId,
dependencyId: dependencyId
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in addDependencyDirect: ${error.message}`);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/complexity-report.js:
--------------------------------------------------------------------------------
```javascript
/**
* complexity-report.js
* Direct function implementation for displaying complexity analysis report
*/
import {
readComplexityReport,
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for displaying the complexity report with error handling and caching.
*
* @param {Object} args - Command arguments containing reportPath.
* @param {string} args.reportPath - Explicit path to the complexity report file.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Result object with success status and data/error information
*/
export async function complexityReportDirect(args, log) {
// Destructure expected args
const { reportPath } = args;
try {
log.info(`Getting complexity report with args: ${JSON.stringify(args)}`);
// Check if reportPath was provided
if (!reportPath) {
log.error('complexityReportDirect called without reportPath');
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' }
};
}
// Use the provided report path
log.info(`Looking for complexity report at: ${reportPath}`);
// Generate cache key based on report path
const cacheKey = `complexityReport:${reportPath}`;
// Define the core action function to read the report
const coreActionFn = async () => {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
const report = readComplexityReport(reportPath);
// Restore normal logging
disableSilentMode();
if (!report) {
log.warn(`No complexity report found at ${reportPath}`);
return {
success: false,
error: {
code: 'FILE_NOT_FOUND_ERROR',
message: `No complexity report found at ${reportPath}. Run 'analyze-complexity' first.`
}
};
}
return {
success: true,
data: {
report,
reportPath
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error reading complexity report: ${error.message}`);
return {
success: false,
error: {
code: 'READ_ERROR',
message: error.message
}
};
}
};
// Use the caching utility
try {
const result = await coreActionFn();
log.info('complexityReportDirect completed');
return result;
} catch (error) {
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Unexpected error during complexityReport: ${error.message}`);
return {
success: false,
error: {
code: 'UNEXPECTED_ERROR',
message: error.message
}
};
}
} catch (error) {
// Ensure silent mode is disabled if an outer error occurs
disableSilentMode();
log.error(`Error in complexityReportDirect: ${error.message}`);
return {
success: false,
error: {
code: 'UNEXPECTED_ERROR',
message: error.message
}
};
}
}
```
--------------------------------------------------------------------------------
/mcp-server/src/logger.js:
--------------------------------------------------------------------------------
```javascript
import chalk from 'chalk';
import { isSilentMode } from '../../scripts/modules/utils.js';
import { getLogLevel } from '../../scripts/modules/config-manager.js';
// Define log levels
const LOG_LEVELS = {
debug: 0,
info: 1,
warn: 2,
error: 3,
success: 4
};
// Get log level from config manager or default to info
const LOG_LEVEL = LOG_LEVELS[getLogLevel().toLowerCase()] ?? LOG_LEVELS.info;
/**
* Logs a message with the specified level
* @param {string} level - The log level (debug, info, warn, error, success)
* @param {...any} args - Arguments to log
*/
function log(level, ...args) {
// Skip logging if silent mode is enabled
if (isSilentMode()) {
return;
}
// Use text prefixes instead of emojis
const prefixes = {
debug: chalk.gray('[DEBUG]'),
info: chalk.blue('[INFO]'),
warn: chalk.yellow('[WARN]'),
error: chalk.red('[ERROR]'),
success: chalk.green('[SUCCESS]')
};
if (LOG_LEVELS[level] !== undefined && LOG_LEVELS[level] >= LOG_LEVEL) {
const prefix = prefixes[level] || '';
let coloredArgs = args;
try {
switch (level) {
case 'error':
coloredArgs = args.map((arg) =>
typeof arg === 'string' ? chalk.red(arg) : arg
);
break;
case 'warn':
coloredArgs = args.map((arg) =>
typeof arg === 'string' ? chalk.yellow(arg) : arg
);
break;
case 'success':
coloredArgs = args.map((arg) =>
typeof arg === 'string' ? chalk.green(arg) : arg
);
break;
case 'info':
coloredArgs = args.map((arg) =>
typeof arg === 'string' ? chalk.blue(arg) : arg
);
break;
case 'debug':
coloredArgs = args.map((arg) =>
typeof arg === 'string' ? chalk.gray(arg) : arg
);
break;
// default: use original args (no color)
}
} catch (colorError) {
// Fallback if chalk fails on an argument
// Use console.error here for internal logger errors, separate from normal logging
console.error('Internal Logger Error applying chalk color:', colorError);
coloredArgs = args;
}
// Revert to console.log - FastMCP's context logger (context.log)
// is responsible for directing logs correctly (e.g., to stderr)
// during tool execution without upsetting the client connection.
// Logs outside of tool execution (like startup) will go to stdout.
console.error(prefix, ...coloredArgs);
}
}
/**
* Create a logger object with methods for different log levels
* @returns {Object} Logger object with info, error, debug, warn, and success methods
*/
export function createLogger() {
const createLogMethod =
(level) =>
(...args) =>
log(level, ...args);
return {
debug: createLogMethod('debug'),
info: createLogMethod('info'),
warn: createLogMethod('warn'),
error: createLogMethod('error'),
success: createLogMethod('success'),
log: log // Also expose the raw log function
};
}
// Export a default logger instance
const logger = createLogger();
export default logger;
export { log, LOG_LEVELS };
```
--------------------------------------------------------------------------------
/apps/extension/src/webview/App.tsx:
--------------------------------------------------------------------------------
```typescript
/**
* Main App Component
*/
import React, { useReducer, useState, useEffect, useRef } from 'react';
import { VSCodeContext } from './contexts/VSCodeContext';
import { QueryProvider } from './providers/QueryProvider';
import { AppContent } from './components/AppContent';
import { ToastContainer } from './components/ToastContainer';
import { ErrorBoundary } from './components/ErrorBoundary';
import { appReducer, initialState } from './reducers/appReducer';
import { useWebviewHeight } from './hooks/useWebviewHeight';
import { useVSCodeMessages } from './hooks/useVSCodeMessages';
import {
showSuccessToast,
showInfoToast,
showWarningToast,
showErrorToast,
createToast
} from './utils/toast';
export const App: React.FC = () => {
const [state, dispatch] = useReducer(appReducer, initialState);
const [vscode] = useState(() => window.acquireVsCodeApi?.());
const availableHeight = useWebviewHeight();
const { sendMessage } = useVSCodeMessages(vscode, state, dispatch);
const hasInitialized = useRef(false);
// Initialize the webview
useEffect(() => {
if (hasInitialized.current) return;
hasInitialized.current = true;
if (!vscode) {
console.warn('⚠️ VS Code API not available - running in standalone mode');
dispatch({
type: 'SET_CONNECTION_STATUS',
payload: { isConnected: false, status: 'Standalone Mode' }
});
return;
}
console.log('🔄 Initializing webview...');
// Notify extension that webview is ready
vscode.postMessage({ type: 'ready' });
// React Query will handle task fetching, so we only need to load tags data
sendMessage({ type: 'getTags' })
.then((tagsData) => {
if (tagsData?.tags && tagsData?.currentTag) {
const tagNames = tagsData.tags.map((tag: any) => tag.name || tag);
dispatch({
type: 'SET_TAG_DATA',
payload: {
currentTag: tagsData.currentTag,
availableTags: tagNames
}
});
}
})
.catch((error) => {
console.error('❌ Failed to load tags:', error);
});
}, [vscode, sendMessage, dispatch]);
const contextValue = {
vscode,
state,
dispatch,
sendMessage,
availableHeight,
// Toast notification functions
showSuccessToast: showSuccessToast(dispatch),
showInfoToast: showInfoToast(dispatch),
showWarningToast: showWarningToast(dispatch),
showErrorToast: showErrorToast(dispatch)
};
return (
<QueryProvider>
<VSCodeContext.Provider value={contextValue}>
<ErrorBoundary
onError={(error) => {
// Handle React errors and show appropriate toast
dispatch({
type: 'ADD_TOAST',
payload: createToast(
'error',
'Component Error',
`A React component crashed: ${error.message}`,
10000
)
});
}}
>
<AppContent />
<ToastContainer
notifications={state.toastNotifications}
onDismiss={(id) => dispatch({ type: 'REMOVE_TOAST', payload: id })}
/>
</ErrorBoundary>
</VSCodeContext.Provider>
</QueryProvider>
);
};
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/research.js:
--------------------------------------------------------------------------------
```javascript
/**
* tools/research.js
* Tool to perform AI-powered research queries with project context
*/
import { z } from 'zod';
import {
createErrorResponse,
handleApiResult,
withNormalizedProjectRoot
} from './utils.js';
import { researchDirect } from '../core/task-master-core.js';
import { resolveTag } from '../../../scripts/modules/utils.js';
/**
* Register the research tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerResearchTool(server) {
server.addTool({
name: 'research',
description: 'Perform AI-powered research queries with project context',
parameters: z.object({
query: z.string().describe('Research query/prompt (required)'),
taskIds: z
.string()
.optional()
.describe(
'Comma-separated list of task/subtask IDs for context (e.g., "15,16.2,17")'
),
filePaths: z
.string()
.optional()
.describe(
'Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md")'
),
customContext: z
.string()
.optional()
.describe('Additional custom context text to include in the research'),
includeProjectTree: z
.boolean()
.optional()
.describe(
'Include project file tree structure in context (default: false)'
),
detailLevel: z
.enum(['low', 'medium', 'high'])
.optional()
.describe('Detail level for the research response (default: medium)'),
saveTo: z
.string()
.optional()
.describe(
'Automatically save research results to specified task/subtask ID (e.g., "15" or "15.2")'
),
saveToFile: z
.boolean()
.optional()
.describe(
'Save research results to .taskmaster/docs/research/ directory (default: false)'
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
tag: z.string().optional().describe('Tag context to operate on')
}),
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
try {
const resolvedTag = resolveTag({
projectRoot: args.projectRoot,
tag: args.tag
});
log.info(
`Starting research with query: "${args.query.substring(0, 100)}${args.query.length > 100 ? '...' : ''}"`
);
// Call the direct function
const result = await researchDirect(
{
query: args.query,
taskIds: args.taskIds,
filePaths: args.filePaths,
customContext: args.customContext,
includeProjectTree: args.includeProjectTree || false,
detailLevel: args.detailLevel || 'medium',
saveTo: args.saveTo,
saveToFile: args.saveToFile || false,
projectRoot: args.projectRoot,
tag: resolvedTag
},
log,
{ session }
);
return handleApiResult(
result,
log,
'Error performing research',
undefined,
args.projectRoot
);
} catch (error) {
log.error(`Error in research tool: ${error.message}`);
return createErrorResponse(error.message);
}
})
});
}
```
--------------------------------------------------------------------------------
/apps/mcp/src/tools/autopilot/finalize.tool.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview autopilot-finalize MCP tool
* Finalize and complete the workflow with working tree validation
*/
import { z } from 'zod';
import {
handleApiResult,
withNormalizedProjectRoot
} from '../../shared/utils.js';
import type { MCPContext } from '../../shared/types.js';
import { WorkflowService } from '@tm/core';
import type { FastMCP } from 'fastmcp';
const FinalizeSchema = z.object({
projectRoot: z
.string()
.describe('Absolute path to the project root directory')
});
type FinalizeArgs = z.infer<typeof FinalizeSchema>;
/**
* Register the autopilot_finalize tool with the MCP server
*/
export function registerAutopilotFinalizeTool(server: FastMCP) {
server.addTool({
name: 'autopilot_finalize',
description:
'Finalize and complete the workflow. Validates that all changes are committed and working tree is clean before marking workflow as complete.',
parameters: FinalizeSchema,
execute: withNormalizedProjectRoot(
async (args: FinalizeArgs, context: MCPContext) => {
const { projectRoot } = args;
try {
context.log.info(`Finalizing workflow in ${projectRoot}`);
const workflowService = new WorkflowService(projectRoot);
// Check if workflow exists
if (!(await workflowService.hasWorkflow())) {
return handleApiResult({
result: {
success: false,
error: {
message:
'No active workflow found. Start a workflow with autopilot_start'
}
},
log: context.log,
projectRoot
});
}
// Resume workflow
await workflowService.resumeWorkflow();
const currentStatus = workflowService.getStatus();
// Verify we're in FINALIZE phase
if (currentStatus.phase !== 'FINALIZE') {
return handleApiResult({
result: {
success: false,
error: {
message: `Cannot finalize: workflow is in ${currentStatus.phase} phase. Complete all subtasks first.`
}
},
log: context.log,
projectRoot
});
}
// Finalize workflow (validates clean working tree)
const newStatus = await workflowService.finalizeWorkflow();
context.log.info('Workflow finalized successfully');
// Get next action
const nextAction = workflowService.getNextAction();
return handleApiResult({
result: {
success: true,
data: {
message: 'Workflow completed successfully',
...newStatus,
nextAction: nextAction.action,
nextSteps: nextAction.nextSteps
}
},
log: context.log,
projectRoot
});
} catch (error: any) {
context.log.error(`Error in autopilot-finalize: ${error.message}`);
if (error.stack) {
context.log.debug(error.stack);
}
return handleApiResult({
result: {
success: false,
error: {
message: `Failed to finalize workflow: ${error.message}`
}
},
log: context.log,
projectRoot
});
}
}
)
});
}
```
--------------------------------------------------------------------------------
/apps/docs/archive/Installation.mdx:
--------------------------------------------------------------------------------
```markdown
---
title: "Installation(2)"
description: "This guide walks you through setting up Task Master in your development environment."
---
## Initial Setup
<Tip>
MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor.
</Tip>
<AccordionGroup>
<Accordion title="Option 1: Using MCP (Recommended)" icon="sparkles">
<Steps>
<Step title="Add the MCP config to your editor">
<Link href="https://cursor.sh">Cursor</Link> recommended, but it works with other text editors
```json
{
"mcpServers": {
"taskmaster-ai": {
"command": "npx",
"args": ["-y", "task-master-ai"],
"env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",
"MODEL": "claude-3-7-sonnet-20250219",
"PERPLEXITY_MODEL": "sonar-pro",
"MAX_TOKENS": 128000,
"TEMPERATURE": 0.2,
"DEFAULT_SUBTASKS": 5,
"DEFAULT_PRIORITY": "medium"
}
}
}
}
```
</Step>
<Step title="Enable the MCP in your editor settings">
</Step>
<Step title="Prompt the AI to initialize Task Master">
> "Can you please initialize taskmaster-ai into my project?"
**The AI will:**
1. Create necessary project structure
2. Set up initial configuration files
3. Guide you through the rest of the process
4. Place your PRD document in the `scripts/` directory (e.g., `scripts/prd.txt`)
5. **Use natural language commands** to interact with Task Master:
> "Can you parse my PRD at scripts/prd.txt?"
>
> "What's the next task I should work on?"
>
> "Can you help me implement task 3?"
</Step>
</Steps>
</Accordion>
<Accordion title="Option 2: Manual Installation">
If you prefer to use the command line interface directly:
<Steps>
<Step title="Install">
<CodeGroup>
```bash Global
npm install -g task-master-ai
```
```bash Local
npm install task-master-ai
```
</CodeGroup>
</Step>
<Step title="Initialize a new project">
<CodeGroup>
```bash Global
task-master init
```
```bash Local
npx task-master-init
```
</CodeGroup>
</Step>
</Steps>
This will prompt you for project details and set up a new project with the necessary files and structure.
</Accordion>
</AccordionGroup>
## Common Commands
<Tip>
After setting up Task Master, you can use these commands (either via AI prompts or CLI)
</Tip>
```bash
# Parse a PRD and generate tasks
task-master parse-prd your-prd.txt
# List all tasks
task-master list
# Show the next task to work on
task-master next
# Generate task files
task-master generate
```
--------------------------------------------------------------------------------
/packages/tm-core/src/common/constants/paths.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Path constants for Task Master Core
* Defines all file paths and directory structure constants
*/
// .taskmaster directory structure paths
export const TASKMASTER_DIR = '.taskmaster';
export const TASKMASTER_TASKS_DIR = '.taskmaster/tasks';
export const TASKMASTER_DOCS_DIR = '.taskmaster/docs';
export const TASKMASTER_REPORTS_DIR = '.taskmaster/reports';
export const TASKMASTER_TEMPLATES_DIR = '.taskmaster/templates';
// Task Master configuration files
export const TASKMASTER_CONFIG_FILE = '.taskmaster/config.json';
export const TASKMASTER_STATE_FILE = '.taskmaster/state.json';
export const LEGACY_CONFIG_FILE = '.taskmasterconfig';
// Task Master report files
export const COMPLEXITY_REPORT_FILE =
'.taskmaster/reports/task-complexity-report.json';
export const LEGACY_COMPLEXITY_REPORT_FILE =
'scripts/task-complexity-report.json';
// Task Master PRD file paths
export const PRD_FILE = '.taskmaster/docs/prd.txt';
export const LEGACY_PRD_FILE = 'scripts/prd.txt';
// Task Master template files
export const EXAMPLE_PRD_FILE = '.taskmaster/templates/example_prd.txt';
export const LEGACY_EXAMPLE_PRD_FILE = 'scripts/example_prd.txt';
// Task Master task file paths
export const TASKMASTER_TASKS_FILE = '.taskmaster/tasks/tasks.json';
export const LEGACY_TASKS_FILE = 'tasks/tasks.json';
// General project files (not Task Master specific but commonly used)
export const ENV_EXAMPLE_FILE = '.env.example';
export const GITIGNORE_FILE = '.gitignore';
// Task file naming pattern
export const TASK_FILE_PREFIX = 'task_';
export const TASK_FILE_EXTENSION = '.txt';
/**
* Task Master specific markers (absolute highest priority)
* ONLY truly Task Master-specific markers that uniquely identify a Task Master project
*/
export const TASKMASTER_PROJECT_MARKERS = [
'.taskmaster', // Task Master directory
TASKMASTER_CONFIG_FILE, // .taskmaster/config.json
TASKMASTER_TASKS_FILE, // .taskmaster/tasks/tasks.json
LEGACY_CONFIG_FILE // .taskmasterconfig (legacy but still Task Master-specific)
] as const;
/**
* Other project markers (only checked if no Task Master markers found)
* Includes generic task files that could belong to any task runner/build system
*/
export const OTHER_PROJECT_MARKERS = [
LEGACY_TASKS_FILE, // tasks/tasks.json (NOT Task Master-specific)
'tasks.json', // Generic tasks file (NOT Task Master-specific)
'.git', // Git repository
'.svn', // SVN repository
'package.json', // Node.js project
'yarn.lock', // Yarn project
'package-lock.json', // npm project
'pnpm-lock.yaml', // pnpm project
'Cargo.toml', // Rust project
'go.mod', // Go project
'pyproject.toml', // Python project
'requirements.txt', // Python project
'Gemfile', // Ruby project
'composer.json' // PHP project
] as const;
/**
* All project markers combined (for backward compatibility)
* @deprecated Use TASKMASTER_PROJECT_MARKERS and OTHER_PROJECT_MARKERS separately
*/
export const PROJECT_MARKERS = [
...TASKMASTER_PROJECT_MARKERS,
...OTHER_PROJECT_MARKERS
] as const;
```
--------------------------------------------------------------------------------
/packages/tm-core/src/common/constants/index.ts:
--------------------------------------------------------------------------------
```typescript
/**
* @fileoverview Constants for Task Master Core
* Single source of truth for all constant values
*/
import type {
TaskComplexity,
TaskPriority,
TaskStatus
} from '../types/index.js';
// Import from root package.json (monorepo root) for version info
import packageJson from '../../../../../package.json' with { type: 'json' };
/**
* Task Master version from root package.json
* Centralized to avoid fragile relative paths throughout the codebase
*/
export const TASKMASTER_VERSION = packageJson.version || 'unknown';
/**
* Package name from root package.json
*/
export const PACKAGE_NAME = packageJson.name || 'task-master-ai';
/**
* Valid task status values
*/
export const TASK_STATUSES: readonly TaskStatus[] = [
'pending',
'in-progress',
'done',
'deferred',
'cancelled',
'blocked',
'review'
] as const;
/**
* Terminal complete statuses - tasks that are finished and satisfy dependencies
* These statuses indicate a task is in a final state and:
* - Should count toward completion percentage
* - Should be considered satisfied for dependency resolution
* - Should not be selected as "next task"
*
* Note: 'completed' is a workflow-specific alias for 'done' used in some contexts
*/
export const TERMINAL_COMPLETE_STATUSES: readonly TaskStatus[] = [
'done',
'completed',
'cancelled'
] as const;
/**
* Check if a task status represents a terminal complete state
*
* @param status - The task status to check
* @returns true if the status represents a completed/terminal task
*
* @example
* ```typescript
* isTaskComplete('done') // true
* isTaskComplete('completed') // true
* isTaskComplete('cancelled') // true
* isTaskComplete('pending') // false
* ```
*/
export function isTaskComplete(status: TaskStatus): boolean {
return TERMINAL_COMPLETE_STATUSES.includes(status);
}
/**
* Valid task priority values
*/
export const TASK_PRIORITIES: readonly TaskPriority[] = [
'low',
'medium',
'high',
'critical'
] as const;
/**
* Valid task complexity values
*/
export const TASK_COMPLEXITIES: readonly TaskComplexity[] = [
'simple',
'moderate',
'complex',
'very-complex'
] as const;
/**
* Valid output formats for task display
*/
export const OUTPUT_FORMATS = ['text', 'json', 'compact'] as const;
export type OutputFormat = (typeof OUTPUT_FORMATS)[number];
/**
* Status icons for display
*/
export const STATUS_ICONS: Record<TaskStatus, string> = {
done: '✓',
completed: '✓',
'in-progress': '►',
blocked: '⭕',
pending: '○',
deferred: '⏸',
cancelled: '✗',
review: '👁'
} as const;
/**
* Status colors for display (using chalk color names)
*/
export const STATUS_COLORS: Record<TaskStatus, string> = {
pending: 'yellow',
'in-progress': 'blue',
done: 'green',
deferred: 'gray',
cancelled: 'red',
blocked: 'magenta',
review: 'cyan',
completed: 'green'
} as const;
/**
* Provider constants - AI model providers
*/
export * from './providers.js';
/**
* Path constants - file paths and directory structure
*/
export * from './paths.js';
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/workflow/types.ts:
--------------------------------------------------------------------------------
```typescript
/**
* Workflow phase definitions
*/
export type WorkflowPhase =
| 'PREFLIGHT'
| 'BRANCH_SETUP'
| 'SUBTASK_LOOP'
| 'FINALIZE'
| 'COMPLETE';
/**
* TDD cycle phases within subtask loop
*/
export type TDDPhase = 'RED' | 'GREEN' | 'COMMIT';
/**
* Workflow state context
*/
export interface WorkflowContext {
taskId: string;
subtasks: SubtaskInfo[];
currentSubtaskIndex: number;
currentTDDPhase?: TDDPhase;
branchName?: string;
errors: WorkflowError[];
metadata: Record<string, unknown>;
lastTestResults?: TestResult;
}
/**
* Test result from test execution
*/
export interface TestResult {
total: number;
passed: number;
failed: number;
skipped: number;
phase: 'RED' | 'GREEN';
}
/**
* Subtask information
*/
export interface SubtaskInfo {
id: string;
title: string;
status: 'pending' | 'in-progress' | 'completed' | 'failed';
attempts: number;
maxAttempts?: number;
}
/**
* Workflow error information
*/
export interface WorkflowError {
phase: WorkflowPhase;
message: string;
timestamp: Date;
recoverable: boolean;
}
/**
* State machine state
*/
export interface WorkflowState {
phase: WorkflowPhase;
context: WorkflowContext;
}
/**
* State transition event types
*/
export type WorkflowEvent =
| { type: 'PREFLIGHT_COMPLETE' }
| { type: 'BRANCH_CREATED'; branchName: string }
| { type: 'SUBTASK_START'; subtaskId: string }
| { type: 'RED_PHASE_COMPLETE'; testResults?: TestResult }
| { type: 'GREEN_PHASE_COMPLETE'; testResults?: TestResult }
| { type: 'COMMIT_COMPLETE' }
| { type: 'SUBTASK_COMPLETE' }
| { type: 'ALL_SUBTASKS_COMPLETE' }
| { type: 'FINALIZE_COMPLETE' }
| { type: 'ERROR'; error: WorkflowError }
| { type: 'RETRY' }
| { type: 'ABORT' };
/**
* State transition definition
*/
export interface StateTransition {
from: WorkflowPhase;
to: WorkflowPhase;
event: WorkflowEvent['type'];
guard?: (context: WorkflowContext) => boolean;
}
/**
* State machine configuration
*/
export interface StateMachineConfig {
initialPhase: WorkflowPhase;
transitions: StateTransition[];
}
/**
* Workflow event listener
*/
export type WorkflowEventListener = (event: WorkflowEventData) => void;
/**
* Comprehensive event data for workflow events
*/
export interface WorkflowEventData {
type: WorkflowEventType;
timestamp: Date;
phase: WorkflowPhase;
tddPhase?: TDDPhase;
subtaskId?: string;
data?: Record<string, unknown>;
}
/**
* All possible workflow event types
*/
export type WorkflowEventType =
| 'workflow:started'
| 'workflow:completed'
| 'workflow:error'
| 'workflow:resumed'
| 'phase:entered'
| 'phase:exited'
| 'tdd:feature-already-implemented'
| 'tdd:red:started'
| 'tdd:red:completed'
| 'tdd:green:started'
| 'tdd:green:completed'
| 'tdd:commit:started'
| 'tdd:commit:completed'
| 'subtask:started'
| 'subtask:completed'
| 'subtask:failed'
| 'test:run'
| 'test:passed'
| 'test:failed'
| 'git:branch:created'
| 'git:commit:created'
| 'error:occurred'
| 'state:persisted'
| 'progress:updated'
| 'adapter:configured';
```