This is page 8 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&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/tools/next-task.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tools/next-task.js
3 | * Tool to find the next task to work on based on dependencies and status
4 | */
5 |
6 | import { z } from 'zod';
7 | import {
8 | createErrorResponse,
9 | handleApiResult,
10 | withNormalizedProjectRoot
11 | } from './utils.js';
12 | import { nextTaskDirect } from '../core/task-master-core.js';
13 | import {
14 | resolveTasksPath,
15 | resolveComplexityReportPath
16 | } from '../core/utils/path-utils.js';
17 | import { resolveTag } from '../../../scripts/modules/utils.js';
18 |
19 | /**
20 | * Register the nextTask tool with the MCP server
21 | * @param {Object} server - FastMCP server instance
22 | */
23 | export function registerNextTaskTool(server) {
24 | server.addTool({
25 | name: 'next_task',
26 | description:
27 | 'Find the next task to work on based on dependencies and status',
28 | parameters: z.object({
29 | file: z.string().optional().describe('Absolute path to the tasks file'),
30 | complexityReport: z
31 | .string()
32 | .optional()
33 | .describe(
34 | 'Path to the complexity report file (relative to project root or absolute)'
35 | ),
36 | projectRoot: z
37 | .string()
38 | .describe('The directory of the project. Must be an absolute path.'),
39 | tag: z.string().optional().describe('Tag context to operate on')
40 | }),
41 | execute: withNormalizedProjectRoot(async (args, { log, session }) => {
42 | try {
43 | log.info(`Finding next task with args: ${JSON.stringify(args)}`);
44 | const resolvedTag = resolveTag({
45 | projectRoot: args.projectRoot,
46 | tag: args.tag
47 | });
48 |
49 | // Resolve the path to tasks.json using new path utilities
50 | let tasksJsonPath;
51 | try {
52 | tasksJsonPath = resolveTasksPath(args, session);
53 | } catch (error) {
54 | log.error(`Error finding tasks.json: ${error.message}`);
55 | return createErrorResponse(
56 | `Failed to find tasks.json: ${error.message}`
57 | );
58 | }
59 |
60 | // Resolve the path to complexity report (optional)
61 | let complexityReportPath;
62 | try {
63 | complexityReportPath = resolveComplexityReportPath(
64 | { ...args, tag: resolvedTag },
65 | session
66 | );
67 | } catch (error) {
68 | log.error(`Error finding complexity report: ${error.message}`);
69 | // This is optional, so we don't fail the operation
70 | complexityReportPath = null;
71 | }
72 |
73 | const result = await nextTaskDirect(
74 | {
75 | tasksJsonPath: tasksJsonPath,
76 | reportPath: complexityReportPath,
77 | projectRoot: args.projectRoot,
78 | tag: resolvedTag
79 | },
80 | log,
81 | { session }
82 | );
83 |
84 | log.info(`Next task result: ${result.success ? 'found' : 'none'}`);
85 | return handleApiResult(
86 | result,
87 | log,
88 | 'Error finding next task',
89 | undefined,
90 | args.projectRoot
91 | );
92 | } catch (error) {
93 | log.error(`Error finding next task: ${error.message}`);
94 | return createErrorResponse(error.message);
95 | }
96 | })
97 | });
98 | }
99 |
```
--------------------------------------------------------------------------------
/src/utils/asset-resolver.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Asset Resolver Module
3 | * Handles resolving paths to asset files in the package
4 | *
5 | * The public/assets folder is copied to dist/assets during build via tsup's publicDir,
6 | * so we can reliably find it relative to the bundled files.
7 | */
8 | import fs from 'fs';
9 | import path from 'path';
10 | import { fileURLToPath } from 'url';
11 |
12 | const __filename = fileURLToPath(import.meta.url);
13 | const __dirname = path.dirname(__filename);
14 |
15 | /**
16 | * Get the assets directory path
17 | * When bundled, assets are in dist/assets
18 | * When in development, assets are in public/assets
19 | * @returns {string} Path to the assets directory
20 | */
21 | export function getAssetsDir() {
22 | // Check multiple possible locations
23 | const possiblePaths = [
24 | // When running from dist (bundled) - assets are in dist/assets
25 | path.join(__dirname, 'assets'),
26 | path.join(__dirname, '..', 'assets'),
27 | // When running from source in development - now in public/assets
28 | path.join(__dirname, '..', '..', 'public', 'assets'),
29 | // When installed as npm package - assets at package root
30 | path.join(process.cwd(), 'assets'),
31 | // For npx usage - check node_modules
32 | path.join(
33 | process.cwd(),
34 | 'node_modules',
35 | 'task-master-ai',
36 | 'dist',
37 | 'assets'
38 | ),
39 | path.join(process.cwd(), 'node_modules', 'task-master-ai', 'assets')
40 | ];
41 |
42 | // Find the first existing assets directory
43 | for (const assetPath of possiblePaths) {
44 | if (fs.existsSync(assetPath)) {
45 | // Verify it's actually the assets directory by checking for known files
46 | const testFile = path.join(assetPath, 'rules', 'taskmaster.mdc');
47 | if (fs.existsSync(testFile)) {
48 | return assetPath;
49 | }
50 | }
51 | }
52 |
53 | // If no assets directory found, throw an error
54 | throw new Error(
55 | 'Assets directory not found. This is likely a packaging issue.'
56 | );
57 | }
58 |
59 | /**
60 | * Get path to a specific asset file
61 | * @param {string} relativePath - Path relative to assets directory
62 | * @returns {string} Full path to the asset file
63 | */
64 | export function getAssetPath(relativePath) {
65 | const assetsDir = getAssetsDir();
66 | return path.join(assetsDir, relativePath);
67 | }
68 |
69 | /**
70 | * Check if an asset file exists
71 | * @param {string} relativePath - Path relative to assets directory
72 | * @returns {boolean} True if the asset exists
73 | */
74 | export function assetExists(relativePath) {
75 | try {
76 | const assetPath = getAssetPath(relativePath);
77 | return fs.existsSync(assetPath);
78 | } catch (error) {
79 | return false;
80 | }
81 | }
82 |
83 | /**
84 | * Read an asset file
85 | * @param {string} relativePath - Path relative to assets directory
86 | * @param {string} encoding - File encoding (default: 'utf8')
87 | * @returns {string|Buffer} File contents
88 | */
89 | export function readAsset(relativePath, encoding = 'utf8') {
90 | const assetPath = getAssetPath(relativePath);
91 | return fs.readFileSync(assetPath, encoding);
92 | }
93 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/move-task.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Direct function wrapper for moveTask
3 | */
4 |
5 | import { moveTask } from '../../../../scripts/modules/task-manager.js';
6 | import { findTasksPath } from '../utils/path-utils.js';
7 | import {
8 | enableSilentMode,
9 | disableSilentMode
10 | } from '../../../../scripts/modules/utils.js';
11 |
12 | /**
13 | * Move a task or subtask to a new position
14 | * @param {Object} args - Function arguments
15 | * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file
16 | * @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2' or '5,6,7')
17 | * @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3' or '7,8,9')
18 | * @param {string} args.file - Alternative path to the tasks.json file
19 | * @param {string} args.projectRoot - Project root directory
20 | * @param {string} args.tag - Tag for the task (optional)
21 | * @param {boolean} args.generateFiles - Whether to regenerate task files after moving (default: true)
22 | * @param {Object} log - Logger object
23 | * @returns {Promise<{success: boolean, data?: Object, error?: Object}>}
24 | */
25 | export async function moveTaskDirect(args, log, context = {}) {
26 | const { session } = context;
27 | const { projectRoot, tag } = args;
28 |
29 | // Validate required parameters
30 | if (!args.sourceId) {
31 | return {
32 | success: false,
33 | error: {
34 | message: 'Source ID is required',
35 | code: 'MISSING_SOURCE_ID'
36 | }
37 | };
38 | }
39 |
40 | if (!args.destinationId) {
41 | return {
42 | success: false,
43 | error: {
44 | message: 'Destination ID is required',
45 | code: 'MISSING_DESTINATION_ID'
46 | }
47 | };
48 | }
49 |
50 | try {
51 | // Find tasks.json path if not provided
52 | let tasksPath = args.tasksJsonPath || args.file;
53 | if (!tasksPath) {
54 | if (!args.projectRoot) {
55 | return {
56 | success: false,
57 | error: {
58 | message:
59 | 'Project root is required if tasksJsonPath is not provided',
60 | code: 'MISSING_PROJECT_ROOT'
61 | }
62 | };
63 | }
64 | tasksPath = findTasksPath(args, log);
65 | }
66 |
67 | // Enable silent mode to prevent console output during MCP operation
68 | enableSilentMode();
69 |
70 | // Call the core moveTask function with file generation control
71 | const generateFiles = args.generateFiles !== false; // Default to true
72 | const result = await moveTask(
73 | tasksPath,
74 | args.sourceId,
75 | args.destinationId,
76 | generateFiles,
77 | {
78 | projectRoot,
79 | tag
80 | }
81 | );
82 |
83 | // Restore console output
84 | disableSilentMode();
85 |
86 | return {
87 | success: true,
88 | data: {
89 | ...result,
90 | message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`
91 | }
92 | };
93 | } catch (error) {
94 | // Restore console output in case of error
95 | disableSilentMode();
96 |
97 | log.error(`Failed to move task: ${error.message}`);
98 |
99 | return {
100 | success: false,
101 | error: {
102 | message: error.message,
103 | code: 'MOVE_TASK_ERROR'
104 | }
105 | };
106 | }
107 | }
108 |
```
--------------------------------------------------------------------------------
/.github/workflows/claude-dedupe-issues.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Claude Issue Dedupe
2 | # description: Automatically dedupe GitHub issues using Claude Code
3 |
4 | on:
5 | issues:
6 | types: [opened]
7 | workflow_dispatch:
8 | inputs:
9 | issue_number:
10 | description: "Issue number to process for duplicate detection"
11 | required: true
12 | type: string
13 |
14 | jobs:
15 | claude-dedupe-issues:
16 | runs-on: ubuntu-latest
17 | timeout-minutes: 10
18 | permissions:
19 | contents: read
20 | issues: write
21 |
22 | steps:
23 | - name: Checkout repository
24 | uses: actions/checkout@v4
25 |
26 | - name: Run Claude Code slash command
27 | uses: anthropics/claude-code-base-action@beta
28 | with:
29 | prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
30 | anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
31 | claude_env: |
32 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 |
34 | - name: Log duplicate comment event to Statsig
35 | if: always()
36 | env:
37 | STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }}
38 | run: |
39 | ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }}
40 | REPO=${{ github.repository }}
41 |
42 | if [ -z "$STATSIG_API_KEY" ]; then
43 | echo "STATSIG_API_KEY not found, skipping Statsig logging"
44 | exit 0
45 | fi
46 |
47 | # Prepare the event payload
48 | EVENT_PAYLOAD=$(jq -n \
49 | --arg issue_number "$ISSUE_NUMBER" \
50 | --arg repo "$REPO" \
51 | --arg triggered_by "${{ github.event_name }}" \
52 | '{
53 | events: [{
54 | eventName: "github_duplicate_comment_added",
55 | value: 1,
56 | metadata: {
57 | repository: $repo,
58 | issue_number: ($issue_number | tonumber),
59 | triggered_by: $triggered_by,
60 | workflow_run_id: "${{ github.run_id }}"
61 | },
62 | time: (now | floor | tostring)
63 | }]
64 | }')
65 |
66 | # Send to Statsig API
67 | echo "Logging duplicate comment event to Statsig for issue #${ISSUE_NUMBER}"
68 |
69 | RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://events.statsigapi.net/v1/log_event \
70 | -H "Content-Type: application/json" \
71 | -H "STATSIG-API-KEY: ${STATSIG_API_KEY}" \
72 | -d "$EVENT_PAYLOAD")
73 |
74 | HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
75 | BODY=$(echo "$RESPONSE" | head -n-1)
76 |
77 | if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 202 ]; then
78 | echo "Successfully logged duplicate comment event for issue #${ISSUE_NUMBER}"
79 | else
80 | echo "Failed to log duplicate comment event for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}"
81 | fi
82 |
```
--------------------------------------------------------------------------------
/tests/unit/ai-providers/mcp-components.test.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tests/unit/ai-providers/mcp-components.test.js
3 | * Unit tests for MCP AI SDK custom components
4 | */
5 |
6 | import { jest } from '@jest/globals';
7 |
8 | describe('MCP Custom SDK Components', () => {
9 | describe('Message Converter', () => {
10 | let messageConverter;
11 |
12 | beforeAll(async () => {
13 | const module = await import(
14 | '../../../mcp-server/src/custom-sdk/message-converter.js'
15 | );
16 | messageConverter = module;
17 | });
18 |
19 | describe('convertToMCPFormat', () => {
20 | it('should convert AI SDK messages to MCP format', () => {
21 | const input = [
22 | { role: 'system', content: 'You are a helpful assistant.' },
23 | { role: 'user', content: 'Hello!' }
24 | ];
25 |
26 | const result = messageConverter.convertToMCPFormat(input);
27 |
28 | expect(result).toBeDefined();
29 | expect(result.messages).toBeDefined();
30 | expect(Array.isArray(result.messages)).toBe(true);
31 | expect(result.systemPrompt).toBe('You are a helpful assistant.');
32 | expect(result.messages).toHaveLength(1);
33 | expect(result.messages[0].role).toBe('user');
34 | expect(result.messages[0].content.text).toBe('Hello!');
35 | });
36 | });
37 |
38 | describe('convertFromMCPFormat', () => {
39 | it('should convert MCP response to AI SDK format', () => {
40 | const input = {
41 | content: 'Hello! How can I help you?',
42 | usage: { inputTokens: 10, outputTokens: 8 }
43 | };
44 |
45 | const result = messageConverter.convertFromMCPFormat(input);
46 |
47 | expect(result).toBeDefined();
48 | expect(result.text).toBe('Hello! How can I help you?');
49 | expect(result.usage).toEqual({ inputTokens: 10, outputTokens: 8 });
50 | expect(result.finishReason).toBe('stop');
51 | expect(result.warnings).toBeDefined();
52 | });
53 | });
54 | });
55 |
56 | describe('Language Model', () => {
57 | let languageModel;
58 |
59 | beforeAll(async () => {
60 | const module = await import(
61 | '../../../mcp-server/src/custom-sdk/language-model.js'
62 | );
63 | languageModel = module;
64 | });
65 |
66 | it('should export MCPLanguageModel class', () => {
67 | expect(languageModel.MCPLanguageModel).toBeDefined();
68 | expect(typeof languageModel.MCPLanguageModel).toBe('function');
69 | });
70 | });
71 |
72 | describe('Error Handling', () => {
73 | let errors;
74 |
75 | beforeAll(async () => {
76 | const module = await import(
77 | '../../../mcp-server/src/custom-sdk/errors.js'
78 | );
79 | errors = module;
80 | });
81 |
82 | it('should export error classes', () => {
83 | expect(errors.MCPError).toBeDefined();
84 | expect(typeof errors.MCPError).toBe('function');
85 | });
86 | });
87 |
88 | describe('Index Module', () => {
89 | let index;
90 |
91 | beforeAll(async () => {
92 | const module = await import(
93 | '../../../mcp-server/src/custom-sdk/index.js'
94 | );
95 | index = module;
96 | });
97 |
98 | it('should export createMCP function', () => {
99 | expect(index.createMCP).toBeDefined();
100 | expect(typeof index.createMCP).toBe('function');
101 | });
102 | });
103 | });
104 |
```
--------------------------------------------------------------------------------
/tests/unit/mcp-providers/mcp-components.test.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tests/unit/mcp-providers/mcp-components.test.js
3 | * Unit tests for MCP AI SDK custom components
4 | */
5 |
6 | import { jest } from '@jest/globals';
7 |
8 | describe('MCP Custom SDK Components', () => {
9 | describe('Message Converter', () => {
10 | let messageConverter;
11 |
12 | beforeAll(async () => {
13 | const module = await import(
14 | '../../../mcp-server/src/custom-sdk/message-converter.js'
15 | );
16 | messageConverter = module;
17 | });
18 |
19 | describe('convertToMCPFormat', () => {
20 | it('should convert AI SDK messages to MCP format', () => {
21 | const input = [
22 | { role: 'system', content: 'You are a helpful assistant.' },
23 | { role: 'user', content: 'Hello!' }
24 | ];
25 |
26 | const result = messageConverter.convertToMCPFormat(input);
27 |
28 | expect(result).toBeDefined();
29 | expect(result.messages).toBeDefined();
30 | expect(Array.isArray(result.messages)).toBe(true);
31 | expect(result.systemPrompt).toBe('You are a helpful assistant.');
32 | expect(result.messages).toHaveLength(1);
33 | expect(result.messages[0].role).toBe('user');
34 | expect(result.messages[0].content.text).toBe('Hello!');
35 | });
36 | });
37 |
38 | describe('convertFromMCPFormat', () => {
39 | it('should convert MCP response to AI SDK format', () => {
40 | const input = {
41 | content: 'Hello! How can I help you?',
42 | usage: { inputTokens: 10, outputTokens: 8 }
43 | };
44 |
45 | const result = messageConverter.convertFromMCPFormat(input);
46 |
47 | expect(result).toBeDefined();
48 | expect(result.text).toBe('Hello! How can I help you?');
49 | expect(result.usage).toEqual({ inputTokens: 10, outputTokens: 8 });
50 | expect(result.finishReason).toBe('stop');
51 | expect(result.warnings).toBeDefined();
52 | });
53 | });
54 | });
55 |
56 | describe('Language Model', () => {
57 | let languageModel;
58 |
59 | beforeAll(async () => {
60 | const module = await import(
61 | '../../../mcp-server/src/custom-sdk/language-model.js'
62 | );
63 | languageModel = module;
64 | });
65 |
66 | it('should export MCPLanguageModel class', () => {
67 | expect(languageModel.MCPLanguageModel).toBeDefined();
68 | expect(typeof languageModel.MCPLanguageModel).toBe('function');
69 | });
70 | });
71 |
72 | describe('Error Handling', () => {
73 | let errors;
74 |
75 | beforeAll(async () => {
76 | const module = await import(
77 | '../../../mcp-server/src/custom-sdk/errors.js'
78 | );
79 | errors = module;
80 | });
81 |
82 | it('should export error classes', () => {
83 | expect(errors.MCPError).toBeDefined();
84 | expect(typeof errors.MCPError).toBe('function');
85 | });
86 | });
87 |
88 | describe('Index Module', () => {
89 | let index;
90 |
91 | beforeAll(async () => {
92 | const module = await import(
93 | '../../../mcp-server/src/custom-sdk/index.js'
94 | );
95 | index = module;
96 | });
97 |
98 | it('should export createMCP function', () => {
99 | expect(index.createMCP).toBeDefined();
100 | expect(typeof index.createMCP).toBe('function');
101 | });
102 | });
103 | });
104 |
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/remove-subtask.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tools/remove-subtask.js
3 | * Tool for removing subtasks from parent tasks
4 | */
5 |
6 | import { z } from 'zod';
7 | import {
8 | handleApiResult,
9 | createErrorResponse,
10 | withNormalizedProjectRoot
11 | } from './utils.js';
12 | import { removeSubtaskDirect } from '../core/task-master-core.js';
13 | import { findTasksPath } from '../core/utils/path-utils.js';
14 | import { resolveTag } from '../../../scripts/modules/utils.js';
15 |
16 | /**
17 | * Register the removeSubtask tool with the MCP server
18 | * @param {Object} server - FastMCP server instance
19 | */
20 | export function registerRemoveSubtaskTool(server) {
21 | server.addTool({
22 | name: 'remove_subtask',
23 | description: 'Remove a subtask from its parent task',
24 | parameters: z.object({
25 | id: z
26 | .string()
27 | .describe(
28 | "Subtask ID to remove in format 'parentId.subtaskId' (required)"
29 | ),
30 | convert: z
31 | .boolean()
32 | .optional()
33 | .describe(
34 | 'Convert the subtask to a standalone task instead of deleting it'
35 | ),
36 | file: z
37 | .string()
38 | .optional()
39 | .describe(
40 | 'Absolute path to the tasks file (default: tasks/tasks.json)'
41 | ),
42 | skipGenerate: z
43 | .boolean()
44 | .optional()
45 | .describe('Skip regenerating task files'),
46 | projectRoot: z
47 | .string()
48 | .describe('The directory of the project. Must be an absolute path.'),
49 | tag: z.string().optional().describe('Tag context to operate on')
50 | }),
51 | execute: withNormalizedProjectRoot(async (args, { log, session }) => {
52 | try {
53 | const resolvedTag = resolveTag({
54 | projectRoot: args.projectRoot,
55 | tag: args.tag
56 | });
57 | log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
58 |
59 | // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
60 | let tasksJsonPath;
61 | try {
62 | tasksJsonPath = findTasksPath(
63 | { projectRoot: args.projectRoot, file: args.file },
64 | log
65 | );
66 | } catch (error) {
67 | log.error(`Error finding tasks.json: ${error.message}`);
68 | return createErrorResponse(
69 | `Failed to find tasks.json: ${error.message}`
70 | );
71 | }
72 |
73 | const result = await removeSubtaskDirect(
74 | {
75 | tasksJsonPath: tasksJsonPath,
76 | id: args.id,
77 | convert: args.convert,
78 | skipGenerate: args.skipGenerate,
79 | projectRoot: args.projectRoot,
80 | tag: resolvedTag
81 | },
82 | log,
83 | { session }
84 | );
85 |
86 | if (result.success) {
87 | log.info(`Subtask removed successfully: ${result.data.message}`);
88 | } else {
89 | log.error(`Failed to remove subtask: ${result.error.message}`);
90 | }
91 |
92 | return handleApiResult(
93 | result,
94 | log,
95 | 'Error removing subtask',
96 | undefined,
97 | args.projectRoot
98 | );
99 | } catch (error) {
100 | log.error(`Error in removeSubtask tool: ${error.message}`);
101 | return createErrorResponse(error.message);
102 | }
103 | })
104 | });
105 | }
106 |
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/json-extractor.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Tests for JSON extraction utilities
3 | */
4 |
5 | import { describe, expect, it } from 'vitest';
6 | import { extractJson } from './json-extractor.js';
7 |
8 | describe('extractJson', () => {
9 | it('should extract JSON from markdown code blocks', () => {
10 | const text = '```json\n{"name": "test", "value": 42}\n```';
11 | const result = extractJson(text);
12 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
13 | });
14 |
15 | it('should extract JSON from generic code blocks', () => {
16 | const text = '```\n{"name": "test", "value": 42}\n```';
17 | const result = extractJson(text);
18 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
19 | });
20 |
21 | it('should remove JavaScript variable declarations', () => {
22 | const text = 'const result = {"name": "test", "value": 42};';
23 | const result = extractJson(text);
24 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
25 | });
26 |
27 | it('should handle let variable declarations', () => {
28 | const text = 'let data = {"name": "test", "value": 42};';
29 | const result = extractJson(text);
30 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
31 | });
32 |
33 | it('should handle var variable declarations', () => {
34 | const text = 'var config = {"name": "test", "value": 42};';
35 | const result = extractJson(text);
36 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
37 | });
38 |
39 | it('should extract JSON arrays', () => {
40 | const text = '[{"name": "test1"}, {"name": "test2"}]';
41 | const result = extractJson(text);
42 | expect(JSON.parse(result)).toEqual([{ name: 'test1' }, { name: 'test2' }]);
43 | });
44 |
45 | it('should convert JavaScript object literals to JSON', () => {
46 | const text = "{name: 'test', value: 42}";
47 | const result = extractJson(text);
48 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
49 | });
50 |
51 | it('should return valid JSON (canonical formatting)', () => {
52 | const text = '{"name": "test", "value": 42}';
53 | const result = extractJson(text);
54 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
55 | });
56 |
57 | it('should return original text when JSON parsing fails completely', () => {
58 | const text = 'This is not JSON at all';
59 | const result = extractJson(text);
60 | expect(result).toBe('This is not JSON at all');
61 | });
62 |
63 | it('should handle complex nested objects', () => {
64 | const text =
65 | '```json\n{\n "user": {\n "name": "John",\n "age": 30\n },\n "items": [1, 2, 3]\n}\n```';
66 | const result = extractJson(text);
67 | expect(JSON.parse(result)).toEqual({
68 | user: {
69 | name: 'John',
70 | age: 30
71 | },
72 | items: [1, 2, 3]
73 | });
74 | });
75 |
76 | it('should handle mixed quotes in object literals', () => {
77 | const text = `{name: "test", value: 'mixed quotes'}`;
78 | const result = extractJson(text);
79 | expect(JSON.parse(result)).toEqual({ name: 'test', value: 'mixed quotes' });
80 | });
81 | });
82 |
```
--------------------------------------------------------------------------------
/packages/claude-code-plugin/commands/analyze-complexity.md:
--------------------------------------------------------------------------------
```markdown
1 | Analyze task complexity and generate expansion recommendations.
2 |
3 | Arguments: $ARGUMENTS
4 |
5 | Perform deep analysis of task complexity across the project.
6 |
7 | ## Complexity Analysis
8 |
9 | Uses AI to analyze tasks and recommend which ones need breakdown.
10 |
11 | ## Execution Options
12 |
13 | ```bash
14 | task-master analyze-complexity [--research] [--threshold=5]
15 | ```
16 |
17 | ## Analysis Parameters
18 |
19 | - `--research` → Use research AI for deeper analysis
20 | - `--threshold=5` → Only flag tasks above complexity 5
21 | - Default: Analyze all pending tasks
22 |
23 | ## Analysis Process
24 |
25 | ### 1. **Task Evaluation**
26 | For each task, AI evaluates:
27 | - Technical complexity
28 | - Time requirements
29 | - Dependency complexity
30 | - Risk factors
31 | - Knowledge requirements
32 |
33 | ### 2. **Complexity Scoring**
34 | Assigns score 1-10 based on:
35 | - Implementation difficulty
36 | - Integration challenges
37 | - Testing requirements
38 | - Unknown factors
39 | - Technical debt risk
40 |
41 | ### 3. **Recommendations**
42 | For complex tasks:
43 | - Suggest expansion approach
44 | - Recommend subtask breakdown
45 | - Identify risk areas
46 | - Propose mitigation strategies
47 |
48 | ## Smart Analysis Features
49 |
50 | 1. **Pattern Recognition**
51 | - Similar task comparisons
52 | - Historical complexity accuracy
53 | - Team velocity consideration
54 | - Technology stack factors
55 |
56 | 2. **Contextual Factors**
57 | - Team expertise
58 | - Available resources
59 | - Timeline constraints
60 | - Business criticality
61 |
62 | 3. **Risk Assessment**
63 | - Technical risks
64 | - Timeline risks
65 | - Dependency risks
66 | - Knowledge gaps
67 |
68 | ## Output Format
69 |
70 | ```
71 | Task Complexity Analysis Report
72 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
73 |
74 | High Complexity Tasks (>7):
75 | 📍 #5 "Implement real-time sync" - Score: 9/10
76 | Factors: WebSocket complexity, state management, conflict resolution
77 | Recommendation: Expand into 5-7 subtasks
78 | Risks: Performance, data consistency
79 |
80 | 📍 #12 "Migrate database schema" - Score: 8/10
81 | Factors: Data migration, zero downtime, rollback strategy
82 | Recommendation: Expand into 4-5 subtasks
83 | Risks: Data loss, downtime
84 |
85 | Medium Complexity Tasks (5-7):
86 | 📍 #23 "Add export functionality" - Score: 6/10
87 | Consider expansion if timeline tight
88 |
89 | Low Complexity Tasks (<5):
90 | ✅ 15 tasks - No expansion needed
91 |
92 | Summary:
93 | - Expand immediately: 2 tasks
94 | - Consider expanding: 5 tasks
95 | - Keep as-is: 15 tasks
96 | ```
97 |
98 | ## Actionable Output
99 |
100 | For each high-complexity task:
101 | 1. Complexity score with reasoning
102 | 2. Specific expansion suggestions
103 | 3. Risk mitigation approaches
104 | 4. Recommended subtask structure
105 |
106 | ## Integration
107 |
108 | Results are:
109 | - Saved to `.taskmaster/reports/complexity-analysis.md`
110 | - Used by expand command
111 | - Inform sprint planning
112 | - Guide resource allocation
113 |
114 | ## Next Steps
115 |
116 | After analysis:
117 | ```
118 | /taskmaster:expand 5 # Expand specific task
119 | /taskmaster:expand-all # Expand all recommended
120 | /taskmaster:complexity-report # View detailed report
121 | ```
```
--------------------------------------------------------------------------------
/apps/docs/configuration.mdx:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: "Configuration"
3 | description: "Configure Task Master through environment variables in a .env file"
4 | ---
5 |
6 | ## Required Configuration
7 |
8 | <Note>
9 | Task Master requires an Anthropic API key to function. Add this to your `.env` file:
10 |
11 | ```bash
12 | ANTHROPIC_API_KEY=sk-ant-api03-your-api-key
13 | ```
14 |
15 | You can obtain an API key from the [Anthropic Console](https://console.anthropic.com/).
16 | </Note>
17 |
18 | ## Optional Configuration
19 |
20 | | Variable | Default Value | Description | Example |
21 | | --- | --- | --- | --- |
22 | | `MODEL` | `"claude-3-7-sonnet-20250219"` | Claude model to use | `MODEL=claude-3-opus-20240229` |
23 | | `MAX_TOKENS` | `"4000"` | Maximum tokens for responses | `MAX_TOKENS=8000` |
24 | | `TEMPERATURE` | `"0.7"` | Temperature for model responses | `TEMPERATURE=0.5` |
25 | | `DEBUG` | `"false"` | Enable debug logging | `DEBUG=true` |
26 | | `LOG_LEVEL` | `"info"` | Console output level | `LOG_LEVEL=debug` |
27 | | `DEFAULT_SUBTASKS` | `"3"` | Default subtask count | `DEFAULT_SUBTASKS=5` |
28 | | `DEFAULT_PRIORITY` | `"medium"` | Default priority | `DEFAULT_PRIORITY=high` |
29 | | `PROJECT_NAME` | `"MCP SaaS MVP"` | Project name in metadata | `PROJECT_NAME=My Awesome Project` |
30 | | `PROJECT_VERSION` | `"1.0.0"` | Version in metadata | `PROJECT_VERSION=2.1.0` |
31 | | `PERPLEXITY_API_KEY` | - | For research-backed features | `PERPLEXITY_API_KEY=pplx-...` |
32 | | `PERPLEXITY_MODEL` | `"sonar-medium-online"` | Perplexity model | `PERPLEXITY_MODEL=sonar-large-online` |
33 |
34 | ## TDD Workflow Configuration
35 |
36 | Additional options for autonomous TDD workflow:
37 |
38 | | Variable | Default | Description |
39 | | --- | --- | --- |
40 | | `TM_MAX_ATTEMPTS` | `3` | Max attempts per subtask before marking blocked |
41 | | `TM_AUTO_COMMIT` | `true` | Auto-commit after GREEN phase |
42 | | `TM_PROJECT_ROOT` | Current dir | Default project root |
43 |
44 | ## Example .env File
45 |
46 | ```
47 | # Required
48 | ANTHROPIC_API_KEY=sk-ant-api03-your-api-key
49 |
50 | # Optional - Claude Configuration
51 | MODEL=claude-3-7-sonnet-20250219
52 | MAX_TOKENS=4000
53 | TEMPERATURE=0.7
54 |
55 | # Optional - Perplexity API for Research
56 | PERPLEXITY_API_KEY=pplx-your-api-key
57 | PERPLEXITY_MODEL=sonar-medium-online
58 |
59 | # Optional - Project Info
60 | PROJECT_NAME=My Project
61 | PROJECT_VERSION=1.0.0
62 |
63 | # Optional - Application Configuration
64 | DEFAULT_SUBTASKS=3
65 | DEFAULT_PRIORITY=medium
66 | DEBUG=false
67 | LOG_LEVEL=info
68 |
69 | # TDD Workflow
70 | TM_MAX_ATTEMPTS=3
71 | TM_AUTO_COMMIT=true
72 | ```
73 |
74 | ## Troubleshooting
75 |
76 | ### If `task-master init` doesn't respond:
77 |
78 | Try running it with Node directly:
79 |
80 | ```bash
81 | node node_modules/claude-task-master/scripts/init.js
82 | ```
83 |
84 | Or clone the repository and run:
85 |
86 | ```bash
87 | git clone https://github.com/eyaltoledano/claude-task-master.git
88 | cd claude-task-master
89 | node scripts/init.js
90 | ```
91 |
92 | <Note>
93 | For advanced configuration options and detailed customization, see our [Advanced Configuration Guide] page.
94 | </Note>
95 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/modules/config/services/config-merger.service.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview Configuration Merger Service
3 | * Responsible for merging configurations from multiple sources with precedence
4 | */
5 |
6 | import type { PartialConfiguration } from '../../../common/interfaces/configuration.interface.js';
7 |
8 | /**
9 | * Configuration source with precedence
10 | */
11 | export interface ConfigSource {
12 | /** Source name for debugging */
13 | name: string;
14 | /** Configuration data from this source */
15 | config: PartialConfiguration;
16 | /** Precedence level (higher = more important) */
17 | precedence: number;
18 | }
19 |
20 | /**
21 | * Configuration precedence levels (higher number = higher priority)
22 | */
23 | export const CONFIG_PRECEDENCE = {
24 | DEFAULTS: 0,
25 | GLOBAL: 1, // Reserved for future implementation
26 | LOCAL: 2,
27 | ENVIRONMENT: 3
28 | } as const;
29 |
30 | /**
31 | * ConfigMerger handles merging configurations with precedence rules
32 | * Single responsibility: Configuration merging logic
33 | */
34 | export class ConfigMerger {
35 | private configSources: ConfigSource[] = [];
36 |
37 | /**
38 | * Add a configuration source
39 | */
40 | addSource(source: ConfigSource): void {
41 | this.configSources.push(source);
42 | }
43 |
44 | /**
45 | * Clear all configuration sources
46 | */
47 | clearSources(): void {
48 | this.configSources = [];
49 | }
50 |
51 | /**
52 | * Merge all configuration sources based on precedence
53 | */
54 | merge(): PartialConfiguration {
55 | // Sort sources by precedence (lowest first)
56 | const sortedSources = [...this.configSources].sort(
57 | (a, b) => a.precedence - b.precedence
58 | );
59 |
60 | // Merge from lowest to highest precedence
61 | let merged: PartialConfiguration = {};
62 | for (const source of sortedSources) {
63 | merged = this.deepMerge(merged, source.config);
64 | }
65 |
66 | return merged;
67 | }
68 |
69 | /**
70 | * Deep merge two configuration objects
71 | * Higher precedence values override lower ones
72 | */
73 | private deepMerge(target: any, source: any): any {
74 | if (!source) return target;
75 | if (!target) return source;
76 |
77 | const result = { ...target };
78 |
79 | for (const key in source) {
80 | if (source[key] === null || source[key] === undefined) {
81 | continue;
82 | }
83 |
84 | if (typeof source[key] === 'object' && !Array.isArray(source[key])) {
85 | result[key] = this.deepMerge(result[key] || {}, source[key]);
86 | } else {
87 | result[key] = source[key];
88 | }
89 | }
90 |
91 | return result;
92 | }
93 |
94 | /**
95 | * Get configuration sources for debugging
96 | */
97 | getSources(): ConfigSource[] {
98 | return [...this.configSources].sort((a, b) => b.precedence - a.precedence);
99 | }
100 |
101 | /**
102 | * Check if a source exists
103 | */
104 | hasSource(name: string): boolean {
105 | return this.configSources.some((source) => source.name === name);
106 | }
107 |
108 | /**
109 | * Remove a source by name
110 | */
111 | removeSource(name: string): boolean {
112 | const initialLength = this.configSources.length;
113 | this.configSources = this.configSources.filter(
114 | (source) => source.name !== name
115 | );
116 | return this.configSources.length < initialLength;
117 | }
118 | }
119 |
```
--------------------------------------------------------------------------------
/tests/unit/profiles/cline-integration.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { jest } from '@jest/globals';
2 | import fs from 'fs';
3 | import path from 'path';
4 | import os from 'os';
5 |
6 | // Mock external modules
7 | jest.mock('child_process', () => ({
8 | execSync: jest.fn()
9 | }));
10 |
11 | // Mock console methods
12 | jest.mock('console', () => ({
13 | log: jest.fn(),
14 | info: jest.fn(),
15 | warn: jest.fn(),
16 | error: jest.fn(),
17 | clear: jest.fn()
18 | }));
19 |
20 | describe('Cline Integration', () => {
21 | let tempDir;
22 |
23 | beforeEach(() => {
24 | jest.clearAllMocks();
25 |
26 | // Create a temporary directory for testing
27 | tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
28 |
29 | // Spy on fs methods
30 | jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
31 | jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
32 | if (filePath.toString().includes('.clinerules')) {
33 | return 'Existing cline rules content';
34 | }
35 | return '{}';
36 | });
37 | jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
38 | jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
39 | });
40 |
41 | afterEach(() => {
42 | // Clean up the temporary directory
43 | try {
44 | fs.rmSync(tempDir, { recursive: true, force: true });
45 | } catch (err) {
46 | console.error(`Error cleaning up: ${err.message}`);
47 | }
48 | });
49 |
50 | // Test function that simulates the createProjectStructure behavior for Cline files
51 | function mockCreateClineStructure() {
52 | // Create main .clinerules directory
53 | fs.mkdirSync(path.join(tempDir, '.clinerules'), { recursive: true });
54 |
55 | // Create rule files
56 | const ruleFiles = [
57 | 'dev_workflow.md',
58 | 'taskmaster.md',
59 | 'architecture.md',
60 | 'commands.md',
61 | 'dependencies.md'
62 | ];
63 |
64 | for (const ruleFile of ruleFiles) {
65 | fs.writeFileSync(
66 | path.join(tempDir, '.clinerules', ruleFile),
67 | `Content for ${ruleFile}`
68 | );
69 | }
70 | }
71 |
72 | test('creates all required .clinerules directories', () => {
73 | // Act
74 | mockCreateClineStructure();
75 |
76 | // Assert
77 | expect(fs.mkdirSync).toHaveBeenCalledWith(
78 | path.join(tempDir, '.clinerules'),
79 | { recursive: true }
80 | );
81 | });
82 |
83 | test('creates rule files for Cline', () => {
84 | // Act
85 | mockCreateClineStructure();
86 |
87 | // Assert - check rule files are created
88 | expect(fs.writeFileSync).toHaveBeenCalledWith(
89 | path.join(tempDir, '.clinerules', 'dev_workflow.md'),
90 | expect.any(String)
91 | );
92 | expect(fs.writeFileSync).toHaveBeenCalledWith(
93 | path.join(tempDir, '.clinerules', 'taskmaster.md'),
94 | expect.any(String)
95 | );
96 | expect(fs.writeFileSync).toHaveBeenCalledWith(
97 | path.join(tempDir, '.clinerules', 'architecture.md'),
98 | expect.any(String)
99 | );
100 | });
101 |
102 | test('does not create MCP configuration files', () => {
103 | // Act
104 | mockCreateClineStructure();
105 |
106 | // Assert - Cline doesn't use MCP configuration
107 | expect(fs.writeFileSync).not.toHaveBeenCalledWith(
108 | path.join(tempDir, '.clinerules', 'mcp.json'),
109 | expect.any(String)
110 | );
111 | });
112 | });
113 |
```
--------------------------------------------------------------------------------
/apps/cli/src/commands/models/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * @fileoverview Type definitions for model setup functionality
3 | */
4 |
5 | /**
6 | * Represents a model role in the system
7 | */
8 | export type ModelRole = 'main' | 'research' | 'fallback';
9 |
10 | /**
11 | * Custom provider option identifiers
12 | */
13 | export const CUSTOM_PROVIDER_IDS = {
14 | OPENROUTER: '__CUSTOM_OPENROUTER__',
15 | OLLAMA: '__CUSTOM_OLLAMA__',
16 | BEDROCK: '__CUSTOM_BEDROCK__',
17 | AZURE: '__CUSTOM_AZURE__',
18 | VERTEX: '__CUSTOM_VERTEX__',
19 | LMSTUDIO: '__CUSTOM_LMSTUDIO__',
20 | OPENAI_COMPATIBLE: '__CUSTOM_OPENAI_COMPATIBLE__'
21 | } as const;
22 |
23 | export type CustomProviderId =
24 | (typeof CUSTOM_PROVIDER_IDS)[keyof typeof CUSTOM_PROVIDER_IDS];
25 |
26 | /**
27 | * Special control values for model selection
28 | */
29 | export const CONTROL_VALUES = {
30 | CANCEL: '__CANCEL__',
31 | NO_CHANGE: '__NO_CHANGE__'
32 | } as const;
33 |
34 | /**
35 | * Model information for display
36 | */
37 | export interface ModelInfo {
38 | id: string;
39 | name?: string;
40 | provider: string;
41 | cost_per_1m_tokens?: {
42 | input: number;
43 | output: number;
44 | };
45 | allowed_roles: ModelRole[];
46 | }
47 |
48 | /**
49 | * Currently configured model for a role
50 | */
51 | export interface CurrentModel {
52 | modelId?: string;
53 | provider?: string;
54 | baseURL?: string;
55 | }
56 |
57 | /**
58 | * Current models configuration
59 | */
60 | export interface CurrentModels {
61 | main: CurrentModel | null;
62 | research: CurrentModel | null;
63 | fallback: CurrentModel | null;
64 | }
65 |
66 | /**
67 | * Model selection choice for inquirer prompts
68 | */
69 | export interface ModelChoice {
70 | name: string;
71 | value: { id: string; provider: string } | CustomProviderId | string | null;
72 | short?: string;
73 | type?: 'separator';
74 | }
75 |
76 | /**
77 | * Prompt data for a specific role
78 | */
79 | export interface PromptData {
80 | choices: (ModelChoice | any)[]; // any to accommodate Separator instances
81 | default: number;
82 | }
83 |
84 | /**
85 | * Result from model fetcher functions
86 | */
87 | export interface FetchResult<T> {
88 | success: boolean;
89 | data?: T;
90 | error?: string;
91 | }
92 |
93 | /**
94 | * OpenRouter model response
95 | */
96 | export interface OpenRouterModel {
97 | id: string;
98 | name?: string;
99 | description?: string;
100 | }
101 |
102 | /**
103 | * Ollama model response
104 | */
105 | export interface OllamaModel {
106 | model: string;
107 | name: string;
108 | modified_at?: string;
109 | }
110 |
111 | /**
112 | * Custom provider handler configuration
113 | */
114 | export interface CustomProviderConfig {
115 | id: CustomProviderId;
116 | name: string;
117 | provider: string;
118 | promptMessage: (role: ModelRole) => string;
119 | validate?: (modelId: string, baseURL?: string) => Promise<boolean>;
120 | checkEnvVars?: () => boolean;
121 | fetchModels?: () => Promise<FetchResult<unknown[]>>;
122 | requiresBaseURL?: boolean;
123 | defaultBaseURL?: string;
124 | }
125 |
126 | /**
127 | * Model setup options
128 | */
129 | export interface ModelSetupOptions {
130 | projectRoot: string;
131 | providerHint?: string;
132 | }
133 |
134 | /**
135 | * Model set result
136 | */
137 | export interface ModelSetResult {
138 | success: boolean;
139 | data?: {
140 | message: string;
141 | provider: string;
142 | modelId: string;
143 | warning?: string;
144 | };
145 | error?: {
146 | message: string;
147 | };
148 | }
149 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/__tests__/context-manager.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { jest } from '@jest/globals';
2 | import { ContextManager } from '../context-manager.js';
3 |
4 | describe('ContextManager', () => {
5 | let contextManager;
6 |
7 | beforeEach(() => {
8 | contextManager = new ContextManager({
9 | maxCacheSize: 10,
10 | ttl: 1000, // 1 second for testing
11 | maxContextSize: 1000
12 | });
13 | });
14 |
15 | describe('getContext', () => {
16 | it('should create a new context when not in cache', async () => {
17 | const context = await contextManager.getContext('test-id', {
18 | test: true
19 | });
20 | expect(context.id).toBe('test-id');
21 | expect(context.metadata.test).toBe(true);
22 | expect(contextManager.stats.misses).toBe(1);
23 | expect(contextManager.stats.hits).toBe(0);
24 | });
25 |
26 | it('should return cached context when available', async () => {
27 | // First call creates the context
28 | await contextManager.getContext('test-id', { test: true });
29 |
30 | // Second call should hit cache
31 | const context = await contextManager.getContext('test-id', {
32 | test: true
33 | });
34 | expect(context.id).toBe('test-id');
35 | expect(context.metadata.test).toBe(true);
36 | expect(contextManager.stats.hits).toBe(1);
37 | expect(contextManager.stats.misses).toBe(1);
38 | });
39 |
40 | it('should respect TTL settings', async () => {
41 | // Create context
42 | await contextManager.getContext('test-id', { test: true });
43 |
44 | // Wait for TTL to expire
45 | await new Promise((resolve) => setTimeout(resolve, 1100));
46 |
47 | // Should create new context
48 | await contextManager.getContext('test-id', { test: true });
49 | expect(contextManager.stats.misses).toBe(2);
50 | expect(contextManager.stats.hits).toBe(0);
51 | });
52 | });
53 |
54 | describe('updateContext', () => {
55 | it('should update existing context metadata', async () => {
56 | await contextManager.getContext('test-id', { initial: true });
57 | const updated = await contextManager.updateContext('test-id', {
58 | updated: true
59 | });
60 |
61 | expect(updated.metadata.initial).toBe(true);
62 | expect(updated.metadata.updated).toBe(true);
63 | });
64 | });
65 |
66 | describe('invalidateContext', () => {
67 | it('should remove context from cache', async () => {
68 | await contextManager.getContext('test-id', { test: true });
69 | contextManager.invalidateContext('test-id', { test: true });
70 |
71 | // Should be a cache miss
72 | await contextManager.getContext('test-id', { test: true });
73 | expect(contextManager.stats.invalidations).toBe(1);
74 | expect(contextManager.stats.misses).toBe(2);
75 | });
76 | });
77 |
78 | describe('getStats', () => {
79 | it('should return current cache statistics', async () => {
80 | await contextManager.getContext('test-id', { test: true });
81 | const stats = contextManager.getStats();
82 |
83 | expect(stats.hits).toBe(0);
84 | expect(stats.misses).toBe(1);
85 | expect(stats.invalidations).toBe(0);
86 | expect(stats.size).toBe(1);
87 | expect(stats.maxSize).toBe(10);
88 | expect(stats.ttl).toBe(1000);
89 | });
90 | });
91 | });
92 |
```
--------------------------------------------------------------------------------
/packages/ai-sdk-provider-grok-cli/src/grok-cli-provider.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Grok CLI provider implementation for AI SDK v5
3 | */
4 |
5 | import type { LanguageModelV2, ProviderV2 } from '@ai-sdk/provider';
6 | import { NoSuchModelError } from '@ai-sdk/provider';
7 | import { GrokCliLanguageModel } from './grok-cli-language-model.js';
8 | import type { GrokCliModelId, GrokCliSettings } from './types.js';
9 |
10 | /**
11 | * Grok CLI provider interface that extends the AI SDK's ProviderV2
12 | */
13 | export interface GrokCliProvider extends ProviderV2 {
14 | /**
15 | * Creates a language model instance for the specified model ID.
16 | * This is a shorthand for calling `languageModel()`.
17 | */
18 | (modelId: GrokCliModelId, settings?: GrokCliSettings): LanguageModelV2;
19 |
20 | /**
21 | * Creates a language model instance for text generation.
22 | */
23 | languageModel(
24 | modelId: GrokCliModelId,
25 | settings?: GrokCliSettings
26 | ): LanguageModelV2;
27 |
28 | /**
29 | * Alias for `languageModel()` to maintain compatibility with AI SDK patterns.
30 | */
31 | chat(modelId: GrokCliModelId, settings?: GrokCliSettings): LanguageModelV2;
32 |
33 | textEmbeddingModel(modelId: string): never;
34 | imageModel(modelId: string): never;
35 | }
36 |
37 | /**
38 | * Configuration options for creating a Grok CLI provider instance
39 | */
40 | export interface GrokCliProviderSettings {
41 | /**
42 | * Default settings to use for all models created by this provider.
43 | * Individual model settings will override these defaults.
44 | */
45 | defaultSettings?: GrokCliSettings;
46 | }
47 |
48 | /**
49 | * Creates a Grok CLI provider instance with the specified configuration.
50 | * The provider can be used to create language models for interacting with Grok models.
51 | */
52 | export function createGrokCli(
53 | options: GrokCliProviderSettings = {}
54 | ): GrokCliProvider {
55 | const createModel = (
56 | modelId: GrokCliModelId,
57 | settings: GrokCliSettings = {}
58 | ): LanguageModelV2 => {
59 | const mergedSettings = {
60 | ...options.defaultSettings,
61 | ...settings
62 | };
63 |
64 | return new GrokCliLanguageModel({
65 | id: modelId,
66 | settings: mergedSettings
67 | });
68 | };
69 |
70 | const provider = function (
71 | modelId: GrokCliModelId,
72 | settings?: GrokCliSettings
73 | ) {
74 | if (new.target) {
75 | throw new Error(
76 | 'The Grok CLI model function cannot be called with the new keyword.'
77 | );
78 | }
79 |
80 | return createModel(modelId, settings);
81 | };
82 |
83 | provider.languageModel = createModel;
84 | provider.chat = createModel; // Alias for languageModel
85 |
86 | // Add textEmbeddingModel method that throws NoSuchModelError
87 | provider.textEmbeddingModel = (modelId: string) => {
88 | throw new NoSuchModelError({
89 | modelId,
90 | modelType: 'textEmbeddingModel'
91 | });
92 | };
93 |
94 | provider.imageModel = (modelId: string) => {
95 | throw new NoSuchModelError({
96 | modelId,
97 | modelType: 'imageModel'
98 | });
99 | };
100 |
101 | return provider as GrokCliProvider;
102 | }
103 |
104 | /**
105 | * Default Grok CLI provider instance.
106 | * Pre-configured provider for quick usage without custom settings.
107 | */
108 | export const grokCli = createGrokCli();
109 |
```
--------------------------------------------------------------------------------
/packages/tm-core/src/common/utils/path-normalizer.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Path normalization utilities for global storage system.
3 | * Converts project paths to storage-safe directory names using base64url encoding.
4 | *
5 | * This provides a bijective (one-to-one) mapping that preserves all characters
6 | * and supports perfect round-trip conversion between paths and storage names.
7 | *
8 | * @module path-normalizer
9 | */
10 |
11 | /**
12 | * Normalizes a project path to a storage-safe directory name using base64url encoding.
13 | * This encoding is filesystem-safe (no slashes, backslashes, or special characters)
14 | * and fully reversible, preserving hyphens and all other characters in paths.
15 | *
16 | * @param {string} projectPath - The project path to normalize
17 | * @returns {string} The base64url-encoded path safe for use as a directory name
18 | *
19 | * @example
20 | * normalizeProjectPath('/Users/test/project') // returns base64url encoded string
21 | * normalizeProjectPath('C:\\Users\\test') // returns base64url encoded string
22 | * normalizeProjectPath('/projects/my-app') // returns base64url encoded string (hyphens preserved)
23 | */
24 | export function normalizeProjectPath(projectPath: string): string {
25 | if (!projectPath) {
26 | return '';
27 | }
28 |
29 | // Use base64url encoding: filesystem-safe and fully reversible
30 | return Buffer.from(projectPath, 'utf-8').toString('base64url');
31 | }
32 |
33 | /**
34 | * Denormalizes a storage directory name back to the original path.
35 | * Decodes base64url-encoded paths with perfect fidelity.
36 | *
37 | * @param {string} normalizedPath - The base64url-encoded path to decode
38 | * @returns {string} The original path with all characters preserved
39 | *
40 | * @example
41 | * denormalizeProjectPath(normalizeProjectPath('/Users/test/project')) // returns '/Users/test/project'
42 | * denormalizeProjectPath(normalizeProjectPath('/projects/my-app')) // returns '/projects/my-app'
43 | */
44 | export function denormalizeProjectPath(normalizedPath: string): string {
45 | if (!normalizedPath) {
46 | return '';
47 | }
48 |
49 | // Validate that input is valid base64url before attempting to decode
50 | if (!isValidNormalizedPath(normalizedPath)) {
51 | // Return original string for backward compatibility with non-base64url inputs
52 | return normalizedPath;
53 | }
54 |
55 | return Buffer.from(normalizedPath, 'base64url').toString('utf-8');
56 | }
57 |
58 | /**
59 | * Validates whether a path is in normalized (base64url) format.
60 | * Valid base64url strings contain only: A-Z, a-z, 0-9, -, _
61 | *
62 | * @param {string} path - The path to validate
63 | * @returns {boolean} True if the path is in normalized base64url format
64 | *
65 | * @example
66 | * isValidNormalizedPath('VXNlcnMvdGVzdC9wcm9qZWN0') // returns true (valid base64url)
67 | * isValidNormalizedPath('Users/test/project') // returns false (contains slashes)
68 | */
69 | export function isValidNormalizedPath(path: string): boolean {
70 | if (path === '') {
71 | return true;
72 | }
73 |
74 | // Check if path is valid base64url: only A-Z, a-z, 0-9, -, _
75 | return /^[A-Za-z0-9_-]*$/.test(path);
76 | }
77 |
```
--------------------------------------------------------------------------------
/CLAUDE_CODE_PLUGIN.md:
--------------------------------------------------------------------------------
```markdown
1 | # Taskmaster AI - Claude Code Marketplace
2 |
3 | This repository includes a Claude Code plugin marketplace in `.claude-plugin/marketplace.json`.
4 |
5 | ## Installation
6 |
7 | ### From GitHub (Public Repository)
8 |
9 | Once this repository is pushed to GitHub, users can install with:
10 |
11 | ```bash
12 | # Add the marketplace
13 | /plugin marketplace add eyaltoledano/claude-task-master
14 |
15 | # Install the plugin
16 | /plugin install taskmaster@taskmaster
17 | ```
18 |
19 | ### Local Development/Testing
20 |
21 | ```bash
22 | # From the project root directory
23 | cd /path/to/claude-task-master
24 |
25 | # Build the plugin first
26 | cd packages/claude-code-plugin
27 | npm run build
28 | cd ../..
29 |
30 | # In Claude Code
31 | /plugin marketplace add .
32 | /plugin install taskmaster@taskmaster
33 | ```
34 |
35 | ## Marketplace Structure
36 |
37 | ```
38 | claude-task-master/
39 | ├── .claude-plugin/
40 | │ └── marketplace.json # Marketplace manifest (at repo root)
41 | │
42 | ├── packages/claude-code-plugin/
43 | │ ├── src/build.ts # Build tooling
44 | │ └── [generated plugin files]
45 | │
46 | └── assets/claude/ # Plugin source files
47 | ├── commands/
48 | └── agents/
49 | ```
50 |
51 | ## Available Plugins
52 |
53 | ### taskmaster
54 |
55 | AI-powered task management system for ambitious development workflows.
56 |
57 | **Features:**
58 |
59 | - 49 slash commands for comprehensive task management
60 | - 3 specialized AI agents (orchestrator, executor, checker)
61 | - MCP server integration
62 | - Complexity analysis and auto-expansion
63 | - Dependency management and validation
64 | - Automated workflow capabilities
65 |
66 | **Quick Start:**
67 |
68 | ```bash
69 | /tm:init
70 | /tm:parse-prd
71 | /tm:next
72 | ```
73 |
74 | ## For Contributors
75 |
76 | ### Adding New Plugins
77 |
78 | To add more plugins to this marketplace:
79 |
80 | 1. **Update marketplace.json**:
81 |
82 | ```json
83 | {
84 | "plugins": [
85 | {
86 | "name": "new-plugin",
87 | "source": "./path/to/plugin",
88 | "description": "Plugin description",
89 | "version": "1.0.0"
90 | }
91 | ]
92 | }
93 | ```
94 |
95 | 2. **Commit and push** the changes
96 |
97 | 3. **Users update** with: `/plugin marketplace update taskmaster`
98 |
99 | ### Marketplace Versioning
100 |
101 | The marketplace version is tracked in `.claude-plugin/marketplace.json`:
102 |
103 | ```json
104 | {
105 | "metadata": {
106 | "version": "1.0.0"
107 | }
108 | }
109 | ```
110 |
111 | Increment the version when adding or updating plugins.
112 |
113 | ## Team Configuration
114 |
115 | Organizations can auto-install this marketplace for all team members by adding to `.claude/settings.json`:
116 |
117 | ```json
118 | {
119 | "extraKnownMarketplaces": {
120 | "task-master": {
121 | "source": {
122 | "source": "github",
123 | "repo": "eyaltoledano/claude-task-master"
124 | }
125 | }
126 | },
127 | "enabledPlugins": {
128 | "taskmaster": {
129 | "marketplace": "taskmaster"
130 | }
131 | }
132 | }
133 | ```
134 |
135 | Team members who trust the repository folder will automatically get the marketplace and plugins installed.
136 |
137 | ## Documentation
138 |
139 | - [Claude Code Plugin Docs](https://docs.claude.com/en/docs/claude-code/plugins)
140 | - [Marketplace Documentation](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces)
141 |
```
--------------------------------------------------------------------------------
/apps/extension/src/utils/logger.ts:
--------------------------------------------------------------------------------
```typescript
1 | import * as vscode from 'vscode';
2 |
3 | /**
4 | * Logger interface for dependency injection
5 | */
6 | export interface ILogger {
7 | log(message: string, ...args: any[]): void;
8 | error(message: string, ...args: any[]): void;
9 | warn(message: string, ...args: any[]): void;
10 | debug(message: string, ...args: any[]): void;
11 | show(): void;
12 | dispose(): void;
13 | }
14 |
15 | /**
16 | * Logger that outputs to VS Code's output channel instead of console
17 | * This prevents interference with MCP stdio communication
18 | */
19 | export class ExtensionLogger implements ILogger {
20 | private static instance: ExtensionLogger;
21 | private outputChannel: vscode.OutputChannel;
22 | private debugMode: boolean;
23 |
24 | private constructor() {
25 | this.outputChannel = vscode.window.createOutputChannel('TaskMaster');
26 | const config = vscode.workspace.getConfiguration('taskmaster');
27 | this.debugMode = config.get<boolean>('debug.enableLogging', true);
28 | }
29 |
30 | static getInstance(): ExtensionLogger {
31 | if (!ExtensionLogger.instance) {
32 | ExtensionLogger.instance = new ExtensionLogger();
33 | }
34 | return ExtensionLogger.instance;
35 | }
36 |
37 | log(message: string, ...args: any[]): void {
38 | if (!this.debugMode) {
39 | return;
40 | }
41 | const timestamp = new Date().toISOString();
42 | const formattedMessage = this.formatMessage(message, args);
43 | this.outputChannel.appendLine(`[${timestamp}] ${formattedMessage}`);
44 | }
45 |
46 | error(message: string, ...args: any[]): void {
47 | const timestamp = new Date().toISOString();
48 | const formattedMessage = this.formatMessage(message, args);
49 | this.outputChannel.appendLine(`[${timestamp}] ERROR: ${formattedMessage}`);
50 | }
51 |
52 | warn(message: string, ...args: any[]): void {
53 | if (!this.debugMode) {
54 | return;
55 | }
56 | const timestamp = new Date().toISOString();
57 | const formattedMessage = this.formatMessage(message, args);
58 | this.outputChannel.appendLine(`[${timestamp}] WARN: ${formattedMessage}`);
59 | }
60 |
61 | debug(message: string, ...args: any[]): void {
62 | if (!this.debugMode) {
63 | return;
64 | }
65 | const timestamp = new Date().toISOString();
66 | const formattedMessage = this.formatMessage(message, args);
67 | this.outputChannel.appendLine(`[${timestamp}] DEBUG: ${formattedMessage}`);
68 | }
69 |
70 | private formatMessage(message: string, args: any[]): string {
71 | if (args.length === 0) {
72 | return message;
73 | }
74 |
75 | // Convert objects to JSON for better readability
76 | const formattedArgs = args.map((arg) => {
77 | if (typeof arg === 'object' && arg !== null) {
78 | try {
79 | return JSON.stringify(arg, null, 2);
80 | } catch {
81 | return String(arg);
82 | }
83 | }
84 | return String(arg);
85 | });
86 |
87 | return `${message} ${formattedArgs.join(' ')}`;
88 | }
89 |
90 | show(): void {
91 | this.outputChannel.show();
92 | }
93 |
94 | dispose(): void {
95 | this.outputChannel.dispose();
96 | }
97 |
98 | setDebugMode(enabled: boolean): void {
99 | this.debugMode = enabled;
100 | }
101 | }
102 |
103 | // Export a singleton instance for convenience
104 | export const logger = ExtensionLogger.getInstance();
105 |
```
--------------------------------------------------------------------------------
/src/progress/progress-tracker-builder.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Configuration for progress tracker features
3 | */
4 | class TrackerConfig {
5 | constructor() {
6 | this.features = new Set();
7 | this.spinnerFrames = null;
8 | this.unitName = 'unit';
9 | this.totalUnits = 100;
10 | }
11 |
12 | addFeature(feature) {
13 | this.features.add(feature);
14 | }
15 |
16 | hasFeature(feature) {
17 | return this.features.has(feature);
18 | }
19 |
20 | getOptions() {
21 | return {
22 | numUnits: this.totalUnits,
23 | unitName: this.unitName,
24 | spinnerFrames: this.spinnerFrames,
25 | features: Array.from(this.features)
26 | };
27 | }
28 | }
29 |
30 | /**
31 | * Builder for creating configured progress trackers
32 | */
33 | export class ProgressTrackerBuilder {
34 | constructor() {
35 | this.config = new TrackerConfig();
36 | }
37 |
38 | withPercent() {
39 | this.config.addFeature('percent');
40 | return this;
41 | }
42 |
43 | withTokens() {
44 | this.config.addFeature('tokens');
45 | return this;
46 | }
47 |
48 | withTasks() {
49 | this.config.addFeature('tasks');
50 | return this;
51 | }
52 |
53 | withSpinner(messages) {
54 | if (!messages || !Array.isArray(messages)) {
55 | throw new Error('Spinner messages must be an array');
56 | }
57 | this.config.spinnerFrames = messages;
58 | return this;
59 | }
60 |
61 | withUnits(total, unitName = 'unit') {
62 | this.config.totalUnits = total;
63 | this.config.unitName = unitName;
64 | return this;
65 | }
66 |
67 | build() {
68 | return new ProgressTracker(this.config);
69 | }
70 | }
71 |
72 | /**
73 | * Base progress tracker with configurable features
74 | */
75 | class ProgressTracker {
76 | constructor(config) {
77 | this.config = config;
78 | this.isActive = false;
79 | this.current = 0;
80 | this.spinnerIndex = 0;
81 | this.startTime = null;
82 | }
83 |
84 | start() {
85 | this.isActive = true;
86 | this.startTime = Date.now();
87 | this.current = 0;
88 |
89 | if (this.config.spinnerFrames) {
90 | this._startSpinner();
91 | }
92 | }
93 |
94 | update(data = {}) {
95 | if (!this.isActive) return;
96 |
97 | if (data.current !== undefined) {
98 | this.current = data.current;
99 | }
100 |
101 | const progress = this._buildProgressData(data);
102 | return progress;
103 | }
104 |
105 | finish() {
106 | this.isActive = false;
107 |
108 | if (this.spinnerInterval) {
109 | clearInterval(this.spinnerInterval);
110 | this.spinnerInterval = null;
111 | }
112 |
113 | return this._buildSummary();
114 | }
115 |
116 | _startSpinner() {
117 | this.spinnerInterval = setInterval(() => {
118 | this.spinnerIndex =
119 | (this.spinnerIndex + 1) % this.config.spinnerFrames.length;
120 | }, 100);
121 | }
122 |
123 | _buildProgressData(data) {
124 | const progress = { ...data };
125 |
126 | if (this.config.hasFeature('percent')) {
127 | progress.percentage = Math.round(
128 | (this.current / this.config.totalUnits) * 100
129 | );
130 | }
131 |
132 | if (this.config.hasFeature('tasks')) {
133 | progress.tasks = `${this.current}/${this.config.totalUnits}`;
134 | }
135 |
136 | if (this.config.spinnerFrames) {
137 | progress.spinner = this.config.spinnerFrames[this.spinnerIndex];
138 | }
139 |
140 | return progress;
141 | }
142 |
143 | _buildSummary() {
144 | const elapsed = Date.now() - this.startTime;
145 | return {
146 | total: this.config.totalUnits,
147 | completed: this.current,
148 | elapsedMs: elapsed,
149 | features: Array.from(this.config.features)
150 | };
151 | }
152 | }
153 |
```
--------------------------------------------------------------------------------
/src/profiles/kiro.js:
--------------------------------------------------------------------------------
```javascript
1 | // Kiro profile for rule-transformer
2 | import { createProfile } from './base-profile.js';
3 | import fs from 'fs';
4 | import path from 'path';
5 | import { log } from '../../scripts/modules/utils.js';
6 |
7 | // Create and export kiro profile using the base factory
8 | export const kiroProfile = createProfile({
9 | name: 'kiro',
10 | displayName: 'Kiro',
11 | url: 'kiro.dev',
12 | docsUrl: 'kiro.dev/docs',
13 | profileDir: '.kiro',
14 | rulesDir: '.kiro/steering', // Kiro rules location (full path)
15 | mcpConfig: true,
16 | mcpConfigName: 'settings/mcp.json', // Create directly in settings subdirectory
17 | includeDefaultRules: true, // Include default rules to get all the standard files
18 | targetExtension: '.md',
19 | fileMap: {
20 | // Override specific mappings - the base profile will create:
21 | // 'rules/cursor_rules.mdc': 'kiro_rules.md'
22 | // 'rules/dev_workflow.mdc': 'dev_workflow.md'
23 | // 'rules/self_improve.mdc': 'self_improve.md'
24 | // 'rules/taskmaster.mdc': 'taskmaster.md'
25 | // We can add additional custom mappings here if needed
26 | 'rules/taskmaster_hooks_workflow.mdc': 'taskmaster_hooks_workflow.md'
27 | },
28 | customReplacements: [
29 | // Core Kiro directory structure changes
30 | { from: /\.cursor\/rules/g, to: '.kiro/steering' },
31 | { from: /\.cursor\/mcp\.json/g, to: '.kiro/settings/mcp.json' },
32 |
33 | // Fix any remaining kiro/rules references that might be created during transformation
34 | { from: /\.kiro\/rules/g, to: '.kiro/steering' },
35 |
36 | // Essential markdown link transformations for Kiro structure
37 | {
38 | from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g,
39 | to: '[$1](.kiro/steering/$2.md)'
40 | },
41 |
42 | // Kiro specific terminology
43 | { from: /rules directory/g, to: 'steering directory' },
44 | { from: /cursor rules/gi, to: 'Kiro steering files' },
45 |
46 | // Transform frontmatter to Kiro format
47 | // This regex matches the entire frontmatter block and replaces it
48 | {
49 | from: /^---\n(?:description:\s*[^\n]*\n)?(?:globs:\s*[^\n]*\n)?(?:alwaysApply:\s*true\n)?---/m,
50 | to: '---\ninclusion: always\n---'
51 | }
52 | ],
53 |
54 | // Add lifecycle hook to copy Kiro hooks
55 | onPostConvert: (projectRoot, assetsDir) => {
56 | const hooksSourceDir = path.join(assetsDir, 'kiro-hooks');
57 | const hooksTargetDir = path.join(projectRoot, '.kiro', 'hooks');
58 |
59 | // Create hooks directory if it doesn't exist
60 | if (!fs.existsSync(hooksTargetDir)) {
61 | fs.mkdirSync(hooksTargetDir, { recursive: true });
62 | }
63 |
64 | // Copy all .kiro.hook files
65 | if (fs.existsSync(hooksSourceDir)) {
66 | const hookFiles = fs
67 | .readdirSync(hooksSourceDir)
68 | .filter((f) => f.endsWith('.kiro.hook'));
69 |
70 | hookFiles.forEach((file) => {
71 | const sourcePath = path.join(hooksSourceDir, file);
72 | const targetPath = path.join(hooksTargetDir, file);
73 |
74 | fs.copyFileSync(sourcePath, targetPath);
75 | });
76 |
77 | if (hookFiles.length > 0) {
78 | log(
79 | 'info',
80 | `[Kiro] Installed ${hookFiles.length} Taskmaster hooks in .kiro/hooks/`
81 | );
82 | }
83 | }
84 | }
85 | });
86 |
```
--------------------------------------------------------------------------------
/apps/extension/src/services/notification-preferences.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Notification Preferences Service
3 | * Manages user preferences for notifications
4 | */
5 |
6 | import * as vscode from 'vscode';
7 | import { ErrorCategory, ErrorSeverity } from './error-handler';
8 |
9 | export enum NotificationLevel {
10 | ALL = 'all',
11 | ERRORS_ONLY = 'errors_only',
12 | CRITICAL_ONLY = 'critical_only',
13 | NONE = 'none'
14 | }
15 |
16 | interface NotificationRule {
17 | category: ErrorCategory;
18 | minSeverity: ErrorSeverity;
19 | enabled: boolean;
20 | }
21 |
22 | export class NotificationPreferences {
23 | private defaultRules: NotificationRule[] = [
24 | {
25 | category: ErrorCategory.MCP_CONNECTION,
26 | minSeverity: ErrorSeverity.HIGH,
27 | enabled: true
28 | },
29 | {
30 | category: ErrorCategory.CONFIGURATION,
31 | minSeverity: ErrorSeverity.MEDIUM,
32 | enabled: true
33 | },
34 | {
35 | category: ErrorCategory.TASK_LOADING,
36 | minSeverity: ErrorSeverity.HIGH,
37 | enabled: true
38 | },
39 | {
40 | category: ErrorCategory.NETWORK,
41 | minSeverity: ErrorSeverity.HIGH,
42 | enabled: true
43 | },
44 | {
45 | category: ErrorCategory.INTERNAL,
46 | minSeverity: ErrorSeverity.CRITICAL,
47 | enabled: true
48 | }
49 | ];
50 |
51 | /**
52 | * Check if a notification should be shown
53 | */
54 | shouldShowNotification(
55 | category: ErrorCategory,
56 | severity: ErrorSeverity
57 | ): boolean {
58 | // Get user's notification level preference
59 | const level = this.getNotificationLevel();
60 |
61 | if (level === NotificationLevel.NONE) {
62 | return false;
63 | }
64 |
65 | if (
66 | level === NotificationLevel.CRITICAL_ONLY &&
67 | severity !== ErrorSeverity.CRITICAL
68 | ) {
69 | return false;
70 | }
71 |
72 | if (
73 | level === NotificationLevel.ERRORS_ONLY &&
74 | severity !== ErrorSeverity.CRITICAL &&
75 | severity !== ErrorSeverity.HIGH
76 | ) {
77 | return false;
78 | }
79 |
80 | // Check category-specific rules
81 | const rule = this.defaultRules.find((r) => r.category === category);
82 | if (!rule || !rule.enabled) {
83 | return false;
84 | }
85 |
86 | // Check if severity meets minimum threshold
87 | return this.compareSeverity(severity, rule.minSeverity) >= 0;
88 | }
89 |
90 | /**
91 | * Get user's notification level preference
92 | */
93 | private getNotificationLevel(): NotificationLevel {
94 | const config = vscode.workspace.getConfiguration('taskmaster');
95 | return config.get<NotificationLevel>(
96 | 'notifications.level',
97 | NotificationLevel.ERRORS_ONLY
98 | );
99 | }
100 |
101 | /**
102 | * Compare severity levels
103 | */
104 | private compareSeverity(a: ErrorSeverity, b: ErrorSeverity): number {
105 | const severityOrder = {
106 | [ErrorSeverity.LOW]: 0,
107 | [ErrorSeverity.MEDIUM]: 1,
108 | [ErrorSeverity.HIGH]: 2,
109 | [ErrorSeverity.CRITICAL]: 3
110 | };
111 | return severityOrder[a] - severityOrder[b];
112 | }
113 |
114 | /**
115 | * Get toast notification duration based on severity
116 | */
117 | getToastDuration(severity: ErrorSeverity): number {
118 | switch (severity) {
119 | case ErrorSeverity.CRITICAL:
120 | return 10000; // 10 seconds
121 | case ErrorSeverity.HIGH:
122 | return 7000; // 7 seconds
123 | case ErrorSeverity.MEDIUM:
124 | return 5000; // 5 seconds
125 | case ErrorSeverity.LOW:
126 | return 3000; // 3 seconds
127 | }
128 | }
129 | }
130 |
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/expand-task.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tools/expand-task.js
3 | * Tool to expand a task into subtasks
4 | */
5 |
6 | import { z } from 'zod';
7 | import {
8 | handleApiResult,
9 | createErrorResponse,
10 | withNormalizedProjectRoot
11 | } from './utils.js';
12 | import { expandTaskDirect } from '../core/task-master-core.js';
13 | import {
14 | findTasksPath,
15 | findComplexityReportPath
16 | } from '../core/utils/path-utils.js';
17 | import { resolveTag } from '../../../scripts/modules/utils.js';
18 |
19 | /**
20 | * Register the expand-task tool with the MCP server
21 | * @param {Object} server - FastMCP server instance
22 | */
23 | export function registerExpandTaskTool(server) {
24 | server.addTool({
25 | name: 'expand_task',
26 | description: 'Expand a task into subtasks for detailed implementation',
27 | parameters: z.object({
28 | id: z.string().describe('ID of task to expand'),
29 | num: z.string().optional().describe('Number of subtasks to generate'),
30 | research: z
31 | .boolean()
32 | .optional()
33 | .default(false)
34 | .describe('Use research role for generation'),
35 | prompt: z
36 | .string()
37 | .optional()
38 | .describe('Additional context for subtask generation'),
39 | file: z
40 | .string()
41 | .optional()
42 | .describe(
43 | 'Path to the tasks file relative to project root (e.g., tasks/tasks.json)'
44 | ),
45 | projectRoot: z
46 | .string()
47 | .describe('The directory of the project. Must be an absolute path.'),
48 | force: z
49 | .boolean()
50 | .optional()
51 | .default(false)
52 | .describe('Force expansion even if subtasks exist'),
53 | tag: z.string().optional().describe('Tag context to operate on')
54 | }),
55 | execute: withNormalizedProjectRoot(async (args, { log, session }) => {
56 | try {
57 | log.info(`Starting expand-task with args: ${JSON.stringify(args)}`);
58 | const resolvedTag = resolveTag({
59 | projectRoot: args.projectRoot,
60 | tag: args.tag
61 | });
62 | // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
63 | let tasksJsonPath;
64 | try {
65 | tasksJsonPath = findTasksPath(
66 | { projectRoot: args.projectRoot, file: args.file },
67 | log
68 | );
69 | } catch (error) {
70 | log.error(`Error finding tasks.json: ${error.message}`);
71 | return createErrorResponse(
72 | `Failed to find tasks.json: ${error.message}`
73 | );
74 | }
75 |
76 | const complexityReportPath = findComplexityReportPath(
77 | { ...args, tag: resolvedTag },
78 | log
79 | );
80 |
81 | const result = await expandTaskDirect(
82 | {
83 | tasksJsonPath: tasksJsonPath,
84 | id: args.id,
85 | num: args.num,
86 | research: args.research,
87 | prompt: args.prompt,
88 | force: args.force,
89 | complexityReportPath,
90 | projectRoot: args.projectRoot,
91 | tag: resolvedTag
92 | },
93 | log,
94 | { session }
95 | );
96 |
97 | return handleApiResult(
98 | result,
99 | log,
100 | 'Error expanding task',
101 | undefined,
102 | args.projectRoot
103 | );
104 | } catch (error) {
105 | log.error(`Error in expand-task tool: ${error.message}`);
106 | return createErrorResponse(error.message);
107 | }
108 | })
109 | });
110 | }
111 |
```
--------------------------------------------------------------------------------
/apps/extension/src/utils/task-master-api/types/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * TaskMaster API Types
3 | * All type definitions for the TaskMaster API
4 | */
5 |
6 | // MCP Response Types
7 | export interface MCPTaskResponse {
8 | data?: {
9 | tasks?: Array<{
10 | id: number | string;
11 | title: string;
12 | description: string;
13 | status: string;
14 | priority: string;
15 | details?: string;
16 | testStrategy?: string;
17 | dependencies?: Array<number | string>;
18 | complexityScore?: number;
19 | subtasks?: Array<{
20 | id: number;
21 | title: string;
22 | description?: string;
23 | status: string;
24 | details?: string;
25 | dependencies?: Array<number | string>;
26 | }>;
27 | }>;
28 | tag?: {
29 | currentTag: string;
30 | availableTags: string[];
31 | };
32 | };
33 | version?: {
34 | version: string;
35 | name: string;
36 | };
37 | error?: string;
38 | }
39 |
40 | // Internal Task Interface
41 | export interface TaskMasterTask {
42 | id: string;
43 | title: string;
44 | description: string;
45 | status:
46 | | 'pending'
47 | | 'in-progress'
48 | | 'review'
49 | | 'done'
50 | | 'deferred'
51 | | 'cancelled';
52 | priority: 'high' | 'medium' | 'low';
53 | details?: string;
54 | testStrategy?: string;
55 | dependencies?: string[];
56 | complexityScore?: number;
57 | subtasks?: Array<{
58 | id: number;
59 | title: string;
60 | description?: string;
61 | status: string;
62 | details?: string;
63 | testStrategy?: string;
64 | dependencies?: Array<number | string>;
65 | }>;
66 | }
67 |
68 | // API Response Wrapper
69 | export interface TaskMasterApiResponse<T = any> {
70 | success: boolean;
71 | data?: T;
72 | error?: string;
73 | requestDuration?: number;
74 | }
75 |
76 | // API Configuration
77 | export interface TaskMasterApiConfig {
78 | timeout: number;
79 | retryAttempts: number;
80 | cacheDuration: number;
81 | projectRoot?: string;
82 | cache?: CacheConfig;
83 | }
84 |
85 | export interface CacheConfig {
86 | maxSize: number;
87 | enableBackgroundRefresh: boolean;
88 | refreshInterval: number;
89 | enableAnalytics: boolean;
90 | enablePrefetch: boolean;
91 | compressionEnabled: boolean;
92 | persistToDisk: boolean;
93 | }
94 |
95 | // Cache Types
96 | export interface CacheEntry {
97 | data: any;
98 | timestamp: number;
99 | accessCount: number;
100 | lastAccessed: number;
101 | size: number;
102 | ttl?: number;
103 | tags: string[];
104 | }
105 |
106 | export interface CacheAnalytics {
107 | hits: number;
108 | misses: number;
109 | evictions: number;
110 | refreshes: number;
111 | totalSize: number;
112 | averageAccessTime: number;
113 | hitRate: number;
114 | }
115 |
116 | // Method Options
117 | export interface GetTasksOptions {
118 | status?: string;
119 | withSubtasks?: boolean;
120 | tag?: string;
121 | projectRoot?: string;
122 | }
123 |
124 | export interface UpdateTaskStatusOptions {
125 | projectRoot?: string;
126 | }
127 |
128 | export interface UpdateTaskOptions {
129 | projectRoot?: string;
130 | append?: boolean;
131 | research?: boolean;
132 | }
133 |
134 | export interface UpdateSubtaskOptions {
135 | projectRoot?: string;
136 | research?: boolean;
137 | }
138 |
139 | export interface AddSubtaskOptions {
140 | projectRoot?: string;
141 | }
142 |
143 | export interface TaskUpdate {
144 | title?: string;
145 | description?: string;
146 | details?: string;
147 | priority?: 'high' | 'medium' | 'low';
148 | testStrategy?: string;
149 | dependencies?: string[];
150 | }
151 |
152 | export interface SubtaskData {
153 | title: string;
154 | description?: string;
155 | dependencies?: string[];
156 | status?: string;
157 | }
158 |
```
--------------------------------------------------------------------------------
/tests/helpers/tool-counts.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tool-counts.js
3 | * Shared helper for validating tool counts across tests and validation scripts
4 | */
5 |
6 | import {
7 | getToolCounts,
8 | getToolCategories
9 | } from '../../mcp-server/src/tools/tool-registry.js';
10 |
11 | /**
12 | * Expected tool counts - update these when tools are added/removed
13 | * These serve as the canonical source of truth for expected counts
14 | */
15 | export const EXPECTED_TOOL_COUNTS = {
16 | core: 7,
17 | standard: 15,
18 | total: 44
19 | };
20 |
21 | /**
22 | * Expected core tools list for validation
23 | */
24 | export const EXPECTED_CORE_TOOLS = [
25 | 'get_tasks',
26 | 'next_task',
27 | 'get_task',
28 | 'set_task_status',
29 | 'update_subtask',
30 | 'parse_prd',
31 | 'expand_task'
32 | ];
33 |
34 | /**
35 | * Validate that actual tool counts match expected counts
36 | * @returns {Object} Validation result with isValid flag and details
37 | */
38 | export function validateToolCounts() {
39 | const actual = getToolCounts();
40 | const expected = EXPECTED_TOOL_COUNTS;
41 |
42 | const isValid =
43 | actual.core === expected.core &&
44 | actual.standard === expected.standard &&
45 | actual.total === expected.total;
46 |
47 | return {
48 | isValid,
49 | actual,
50 | expected,
51 | differences: {
52 | core: actual.core - expected.core,
53 | standard: actual.standard - expected.standard,
54 | total: actual.total - expected.total
55 | }
56 | };
57 | }
58 |
59 | /**
60 | * Validate that tool categories have correct structure and content
61 | * @returns {Object} Validation result
62 | */
63 | export function validateToolStructure() {
64 | const categories = getToolCategories();
65 | const counts = getToolCounts();
66 |
67 | // Check that core tools are subset of standard tools
68 | const coreInStandard = categories.core.every((tool) =>
69 | categories.standard.includes(tool)
70 | );
71 |
72 | // Check that standard tools are subset of all tools
73 | const standardInAll = categories.standard.every((tool) =>
74 | categories.all.includes(tool)
75 | );
76 |
77 | // Check that expected core tools match actual
78 | const expectedCoreMatch =
79 | EXPECTED_CORE_TOOLS.every((tool) => categories.core.includes(tool)) &&
80 | categories.core.every((tool) => EXPECTED_CORE_TOOLS.includes(tool));
81 |
82 | // Check array lengths match counts
83 | const lengthsMatch =
84 | categories.core.length === counts.core &&
85 | categories.standard.length === counts.standard &&
86 | categories.all.length === counts.total;
87 |
88 | return {
89 | isValid:
90 | coreInStandard && standardInAll && expectedCoreMatch && lengthsMatch,
91 | details: {
92 | coreInStandard,
93 | standardInAll,
94 | expectedCoreMatch,
95 | lengthsMatch
96 | },
97 | categories,
98 | counts
99 | };
100 | }
101 |
102 | /**
103 | * Get a detailed report of all tool information
104 | * @returns {Object} Comprehensive tool information
105 | */
106 | export function getToolReport() {
107 | const counts = getToolCounts();
108 | const categories = getToolCategories();
109 | const validation = validateToolCounts();
110 | const structure = validateToolStructure();
111 |
112 | return {
113 | counts,
114 | categories,
115 | validation,
116 | structure,
117 | summary: {
118 | totalValid: validation.isValid && structure.isValid,
119 | countsValid: validation.isValid,
120 | structureValid: structure.isValid
121 | }
122 | };
123 | }
124 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/remove-dependency.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * Direct function wrapper for removeDependency
3 | */
4 |
5 | import { removeDependency } from '../../../../scripts/modules/dependency-manager.js';
6 | import {
7 | enableSilentMode,
8 | disableSilentMode
9 | } from '../../../../scripts/modules/utils.js';
10 |
11 | /**
12 | * Remove a dependency from a task
13 | * @param {Object} args - Function arguments
14 | * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
15 | * @param {string|number} args.id - Task ID to remove dependency from
16 | * @param {string|number} args.dependsOn - Task ID to remove as a dependency
17 | * @param {string} args.projectRoot - Project root path (for MCP/env fallback)
18 | * @param {string} args.tag - Tag for the task (optional)
19 | * @param {Object} log - Logger object
20 | * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
21 | */
22 | export async function removeDependencyDirect(args, log) {
23 | // Destructure expected args
24 | const { tasksJsonPath, id, dependsOn, projectRoot, tag } = args;
25 | try {
26 | log.info(`Removing dependency with args: ${JSON.stringify(args)}`);
27 |
28 | // Check if tasksJsonPath was provided
29 | if (!tasksJsonPath) {
30 | log.error('removeDependencyDirect called without tasksJsonPath');
31 | return {
32 | success: false,
33 | error: {
34 | code: 'MISSING_ARGUMENT',
35 | message: 'tasksJsonPath is required'
36 | }
37 | };
38 | }
39 |
40 | // Validate required parameters
41 | if (!id) {
42 | return {
43 | success: false,
44 | error: {
45 | code: 'INPUT_VALIDATION_ERROR',
46 | message: 'Task ID (id) is required'
47 | }
48 | };
49 | }
50 |
51 | if (!dependsOn) {
52 | return {
53 | success: false,
54 | error: {
55 | code: 'INPUT_VALIDATION_ERROR',
56 | message: 'Dependency ID (dependsOn) is required'
57 | }
58 | };
59 | }
60 |
61 | // Use provided path
62 | const tasksPath = tasksJsonPath;
63 |
64 | // Format IDs for the core function
65 | const taskId =
66 | id && id.includes && id.includes('.') ? id : parseInt(id, 10);
67 | const dependencyId =
68 | dependsOn && dependsOn.includes && dependsOn.includes('.')
69 | ? dependsOn
70 | : parseInt(dependsOn, 10);
71 |
72 | log.info(
73 | `Removing dependency: task ${taskId} no longer depends on ${dependencyId}`
74 | );
75 |
76 | // Enable silent mode to prevent console logs from interfering with JSON response
77 | enableSilentMode();
78 |
79 | // Call the core function using the provided tasksPath
80 | await removeDependency(tasksPath, taskId, dependencyId, {
81 | projectRoot,
82 | tag
83 | });
84 |
85 | // Restore normal logging
86 | disableSilentMode();
87 |
88 | return {
89 | success: true,
90 | data: {
91 | message: `Successfully removed dependency: Task ${taskId} no longer depends on ${dependencyId}`,
92 | taskId: taskId,
93 | dependencyId: dependencyId
94 | }
95 | };
96 | } catch (error) {
97 | // Make sure to restore normal logging even if there's an error
98 | disableSilentMode();
99 |
100 | log.error(`Error in removeDependencyDirect: ${error.message}`);
101 | return {
102 | success: false,
103 | error: {
104 | code: 'CORE_FUNCTION_ERROR',
105 | message: error.message
106 | }
107 | };
108 | }
109 | }
110 |
```
--------------------------------------------------------------------------------
/mcp-server/src/core/direct-functions/delete-tag.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * delete-tag.js
3 | * Direct function implementation for deleting a tag
4 | */
5 |
6 | import { deleteTag } from '../../../../scripts/modules/task-manager/tag-management.js';
7 | import {
8 | enableSilentMode,
9 | disableSilentMode
10 | } from '../../../../scripts/modules/utils.js';
11 | import { createLogWrapper } from '../../tools/utils.js';
12 |
13 | /**
14 | * Direct function wrapper for deleting a tag with error handling.
15 | *
16 | * @param {Object} args - Command arguments
17 | * @param {string} args.name - Name of the tag to delete
18 | * @param {boolean} [args.yes=false] - Skip confirmation prompts
19 | * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
20 | * @param {string} [args.projectRoot] - Project root path
21 | * @param {Object} log - Logger object
22 | * @param {Object} context - Additional context (session)
23 | * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
24 | */
25 | export async function deleteTagDirect(args, log, context = {}) {
26 | // Destructure expected args
27 | const { tasksJsonPath, name, yes = false, projectRoot } = args;
28 | const { session } = context;
29 |
30 | // Enable silent mode to prevent console logs from interfering with JSON response
31 | enableSilentMode();
32 |
33 | // Create logger wrapper using the utility
34 | const mcpLog = createLogWrapper(log);
35 |
36 | try {
37 | // Check if tasksJsonPath was provided
38 | if (!tasksJsonPath) {
39 | log.error('deleteTagDirect called without tasksJsonPath');
40 | disableSilentMode();
41 | return {
42 | success: false,
43 | error: {
44 | code: 'MISSING_ARGUMENT',
45 | message: 'tasksJsonPath is required'
46 | }
47 | };
48 | }
49 |
50 | // Check required parameters
51 | if (!name || typeof name !== 'string') {
52 | log.error('Missing required parameter: name');
53 | disableSilentMode();
54 | return {
55 | success: false,
56 | error: {
57 | code: 'MISSING_PARAMETER',
58 | message: 'Tag name is required and must be a string'
59 | }
60 | };
61 | }
62 |
63 | log.info(`Deleting tag: ${name}`);
64 |
65 | // Prepare options
66 | const options = {
67 | yes // For MCP, we always skip confirmation prompts
68 | };
69 |
70 | // Call the deleteTag function
71 | const result = await deleteTag(
72 | tasksJsonPath,
73 | name,
74 | options,
75 | {
76 | session,
77 | mcpLog,
78 | projectRoot
79 | },
80 | 'json' // outputFormat - use 'json' to suppress CLI UI
81 | );
82 |
83 | // Restore normal logging
84 | disableSilentMode();
85 |
86 | return {
87 | success: true,
88 | data: {
89 | tagName: result.tagName,
90 | deleted: result.deleted,
91 | tasksDeleted: result.tasksDeleted,
92 | wasCurrentTag: result.wasCurrentTag,
93 | switchedToMaster: result.switchedToMaster,
94 | message: `Successfully deleted tag "${result.tagName}"`
95 | }
96 | };
97 | } catch (error) {
98 | // Make sure to restore normal logging even if there's an error
99 | disableSilentMode();
100 |
101 | log.error(`Error in deleteTagDirect: ${error.message}`);
102 | return {
103 | success: false,
104 | error: {
105 | code: error.code || 'DELETE_TAG_ERROR',
106 | message: error.message
107 | }
108 | };
109 | }
110 | }
111 |
```
--------------------------------------------------------------------------------
/tests/unit/ai-providers/codex-cli.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { jest } from '@jest/globals';
2 |
3 | // Mock the ai module
4 | jest.unstable_mockModule('ai', () => ({
5 | generateObject: jest.fn(),
6 | generateText: jest.fn(),
7 | streamText: jest.fn()
8 | }));
9 |
10 | // Mock the codex-cli SDK module
11 | jest.unstable_mockModule('ai-sdk-provider-codex-cli', () => ({
12 | createCodexCli: jest.fn((options) => {
13 | const provider = (modelId, settings) => ({ id: modelId, settings });
14 | provider.languageModel = jest.fn((id, settings) => ({ id, settings }));
15 | provider.chat = provider.languageModel;
16 | return provider;
17 | })
18 | }));
19 |
20 | // Mock config getters
21 | jest.unstable_mockModule('../../../scripts/modules/config-manager.js', () => ({
22 | getCodexCliSettingsForCommand: jest.fn(() => ({ allowNpx: true })),
23 | getSupportedModelsForProvider: jest.fn(() => ['gpt-5', 'gpt-5-codex']),
24 | // Provide commonly imported getters to satisfy other module imports if any
25 | getDebugFlag: jest.fn(() => false),
26 | getLogLevel: jest.fn(() => 'info')
27 | }));
28 |
29 | // Mock base provider
30 | jest.unstable_mockModule('../../../src/ai-providers/base-provider.js', () => ({
31 | BaseAIProvider: class {
32 | constructor() {
33 | this.name = 'Base Provider';
34 | }
35 | handleError(_ctx, err) {
36 | throw err;
37 | }
38 | validateParams(params) {
39 | if (!params.modelId) throw new Error('Model ID is required');
40 | }
41 | validateMessages(msgs) {
42 | if (!Array.isArray(msgs)) throw new Error('Invalid messages array');
43 | }
44 | }
45 | }));
46 |
47 | const { CodexCliProvider } = await import(
48 | '../../../src/ai-providers/codex-cli.js'
49 | );
50 | const { createCodexCli } = await import('ai-sdk-provider-codex-cli');
51 | const { getCodexCliSettingsForCommand } = await import(
52 | '../../../scripts/modules/config-manager.js'
53 | );
54 |
55 | describe('CodexCliProvider', () => {
56 | let provider;
57 |
58 | beforeEach(() => {
59 | jest.clearAllMocks();
60 | provider = new CodexCliProvider();
61 | });
62 |
63 | it('sets provider name and supported models', () => {
64 | expect(provider.name).toBe('Codex CLI');
65 | expect(provider.supportedModels).toEqual(['gpt-5', 'gpt-5-codex']);
66 | });
67 |
68 | it('does not require API key', () => {
69 | expect(provider.isRequiredApiKey()).toBe(false);
70 | });
71 |
72 | it('creates client with merged default settings', async () => {
73 | const client = await provider.getClient({ commandName: 'parse-prd' });
74 | expect(client).toBeDefined();
75 | expect(createCodexCli).toHaveBeenCalledWith({
76 | defaultSettings: expect.objectContaining({ allowNpx: true })
77 | });
78 | expect(getCodexCliSettingsForCommand).toHaveBeenCalledWith('parse-prd');
79 | });
80 |
81 | it('injects OPENAI_API_KEY only when apiKey provided', async () => {
82 | const client = await provider.getClient({
83 | commandName: 'expand',
84 | apiKey: 'sk-test'
85 | });
86 | const call = createCodexCli.mock.calls[0][0];
87 | expect(call.defaultSettings.env.OPENAI_API_KEY).toBe('sk-test');
88 | // Ensure env is not set when apiKey not provided
89 | await provider.getClient({ commandName: 'expand' });
90 | const second = createCodexCli.mock.calls[1][0];
91 | expect(second.defaultSettings.env).toBeUndefined();
92 | });
93 | });
94 |
```
--------------------------------------------------------------------------------
/apps/docs/archive/cursor-setup.mdx:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | title: "Cursor AI Integration"
3 | description: "Learn how to set up and use Task Master with Cursor AI"
4 | ---
5 |
6 | ## Setting up Cursor AI Integration
7 |
8 | <Check>
9 | Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development.
10 | </Check>
11 |
12 | <AccordionGroup>
13 | <Accordion title="Using Cursor with MCP (Recommended)" icon="sparkles">
14 | 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:
15 |
16 | ```
17 | What tasks are available to work on next?
18 | Can you analyze the complexity of our tasks?
19 | I'd like to implement task 4. What does it involve?
20 | ```
21 | </Accordion>
22 | <Accordion title="Manual Cursor Setup">
23 | If you're not using MCP, you can still set up Cursor integration:
24 |
25 | <Steps>
26 | <Step title="After initializing your project, open it in Cursor">
27 | The `.cursor/rules/dev_workflow.mdc` file is automatically loaded by Cursor, providing the AI with knowledge about the task management system
28 | </Step>
29 | <Step title="Place your PRD document in the scripts/ directory (e.g., scripts/prd.txt)">
30 |
31 | </Step>
32 | <Step title="Open Cursor's AI chat and switch to Agent mode">
33 |
34 | </Step>
35 | </Steps>
36 | </Accordion>
37 | <Accordion title="Alternative MCP Setup in Cursor">
38 | <Steps>
39 | <Step title="Go to Cursor settings">
40 |
41 | </Step>
42 | <Step title="Navigate to the MCP section">
43 |
44 | </Step>
45 | <Step title="Click on 'Add New MCP Server'">
46 |
47 | </Step>
48 | <Step title="Configure with the following details:">
49 | - Name: "Task Master"
50 | - Type: "Command"
51 | - Command: "npx -y task-master-ai"
52 | </Step>
53 | <Step title="Save Settings">
54 |
55 | </Step>
56 | </Steps>
57 | Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience.
58 | </Accordion>
59 | </AccordionGroup>
60 |
61 | ## Initial Task Generation
62 |
63 | In Cursor's AI chat, instruct the agent to generate tasks from your PRD:
64 |
65 | ```
66 | Please use the task-master parse-prd command to generate tasks from my PRD. The PRD is located at scripts/prd.txt.
67 | ```
68 |
69 | The agent will execute:
70 |
71 | ```bash
72 | task-master parse-prd scripts/prd.txt
73 | ```
74 |
75 | This will:
76 |
77 | - Parse your PRD document
78 | - Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies
79 | - The agent will understand this process due to the Cursor rules
80 |
81 | ### Generate Individual Task Files
82 |
83 | Next, ask the agent to generate individual task files:
84 |
85 | ```
86 | Please generate individual task files from tasks.json
87 | ```
88 |
89 | The agent will execute:
90 |
91 | ```bash
92 | task-master generate
93 | ```
94 |
95 | This creates individual task files in the `tasks/` directory (e.g., `task_001.txt`, `task_002.txt`), making it easier to reference specific tasks.
96 |
```
--------------------------------------------------------------------------------
/tests/unit/profiles/rule-transformer-gemini.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { jest } from '@jest/globals';
2 | import { getRulesProfile } from '../../../src/utils/rule-transformer.js';
3 | import { geminiProfile } from '../../../src/profiles/gemini.js';
4 |
5 | describe('Rule Transformer - Gemini Profile', () => {
6 | test('should have correct profile configuration', () => {
7 | const geminiProfile = getRulesProfile('gemini');
8 |
9 | expect(geminiProfile).toBeDefined();
10 | expect(geminiProfile.profileName).toBe('gemini');
11 | expect(geminiProfile.displayName).toBe('Gemini');
12 | expect(geminiProfile.profileDir).toBe('.gemini');
13 | expect(geminiProfile.rulesDir).toBe('.');
14 | expect(geminiProfile.mcpConfig).toBe(true);
15 | expect(geminiProfile.mcpConfigName).toBe('settings.json');
16 | expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json');
17 | expect(geminiProfile.includeDefaultRules).toBe(false);
18 | expect(geminiProfile.fileMap).toEqual({
19 | 'AGENT.md': 'AGENTS.md',
20 | 'GEMINI.md': 'GEMINI.md'
21 | });
22 | });
23 |
24 | test('should have minimal profile implementation', () => {
25 | // Verify that gemini.js is minimal (no lifecycle functions)
26 | expect(geminiProfile.onAddRulesProfile).toBeUndefined();
27 | expect(geminiProfile.onRemoveRulesProfile).toBeUndefined();
28 | expect(geminiProfile.onPostConvertRulesProfile).toBeUndefined();
29 | });
30 |
31 | test('should use settings.json instead of mcp.json', () => {
32 | const geminiProfile = getRulesProfile('gemini');
33 | expect(geminiProfile.mcpConfigName).toBe('settings.json');
34 | expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json');
35 | });
36 |
37 | test('should not include default rules', () => {
38 | const geminiProfile = getRulesProfile('gemini');
39 | expect(geminiProfile.includeDefaultRules).toBe(false);
40 | });
41 |
42 | test('should have correct file mapping', () => {
43 | const geminiProfile = getRulesProfile('gemini');
44 | expect(geminiProfile.fileMap).toEqual({
45 | 'AGENT.md': 'AGENTS.md',
46 | 'GEMINI.md': 'GEMINI.md'
47 | });
48 | });
49 |
50 | test('should place AGENTS.md and GEMINI.md in root directory', () => {
51 | const geminiProfile = getRulesProfile('gemini');
52 | // rulesDir determines where fileMap files go
53 | expect(geminiProfile.rulesDir).toBe('.');
54 | // This means both AGENTS.md and GEMINI.md will be placed in the root
55 | // Both files are auto-loaded by Gemini CLI
56 | });
57 |
58 | test('should place settings.json in .gemini directory', () => {
59 | const geminiProfile = getRulesProfile('gemini');
60 | // profileDir + mcpConfigName determines MCP config location
61 | expect(geminiProfile.profileDir).toBe('.gemini');
62 | expect(geminiProfile.mcpConfigName).toBe('settings.json');
63 | expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json');
64 | });
65 |
66 | test('should have proper conversion config', () => {
67 | const geminiProfile = getRulesProfile('gemini');
68 | // Gemini should have the standard conversion config
69 | expect(geminiProfile.conversionConfig).toBeDefined();
70 | expect(geminiProfile.globalReplacements).toBeDefined();
71 | expect(Array.isArray(geminiProfile.globalReplacements)).toBe(true);
72 | });
73 | });
74 |
```
--------------------------------------------------------------------------------
/mcp-server/src/custom-sdk/json-extractor.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * @fileoverview Extract JSON from MCP response, handling markdown blocks and other formatting
3 | */
4 |
5 | /**
6 | * Extract JSON from MCP AI response
7 | * @param {string} text - The text to extract JSON from
8 | * @returns {string} - The extracted JSON string
9 | */
10 | export function extractJson(text) {
11 | // Remove markdown code blocks if present
12 | let jsonText = text.trim();
13 |
14 | // Remove ```json blocks
15 | jsonText = jsonText.replace(/^```json\s*/gm, '');
16 | jsonText = jsonText.replace(/^```\s*/gm, '');
17 | jsonText = jsonText.replace(/```\s*$/gm, '');
18 |
19 | // Remove common TypeScript/JavaScript patterns
20 | jsonText = jsonText.replace(/^const\s+\w+\s*=\s*/, ''); // Remove "const varName = "
21 | jsonText = jsonText.replace(/^let\s+\w+\s*=\s*/, ''); // Remove "let varName = "
22 | jsonText = jsonText.replace(/^var\s+\w+\s*=\s*/, ''); // Remove "var varName = "
23 | jsonText = jsonText.replace(/;?\s*$/, ''); // Remove trailing semicolons
24 |
25 | // Remove explanatory text before JSON (common with AI responses)
26 | jsonText = jsonText.replace(/^.*?(?=\{|\[)/s, '');
27 |
28 | // Remove explanatory text after JSON
29 | const lines = jsonText.split('\n');
30 | let jsonEndIndex = -1;
31 | let braceCount = 0;
32 | let inString = false;
33 | let escapeNext = false;
34 |
35 | // Find the end of the JSON by tracking braces
36 | for (let i = 0; i < jsonText.length; i++) {
37 | const char = jsonText[i];
38 |
39 | if (escapeNext) {
40 | escapeNext = false;
41 | continue;
42 | }
43 |
44 | if (char === '\\') {
45 | escapeNext = true;
46 | continue;
47 | }
48 |
49 | if (char === '"' && !escapeNext) {
50 | inString = !inString;
51 | continue;
52 | }
53 |
54 | if (!inString) {
55 | if (char === '{' || char === '[') {
56 | braceCount++;
57 | } else if (char === '}' || char === ']') {
58 | braceCount--;
59 | if (braceCount === 0) {
60 | jsonEndIndex = i;
61 | break;
62 | }
63 | }
64 | }
65 | }
66 |
67 | if (jsonEndIndex > -1) {
68 | jsonText = jsonText.substring(0, jsonEndIndex + 1);
69 | }
70 |
71 | // Try to extract JSON object or array if previous method didn't work
72 | if (jsonEndIndex === -1) {
73 | const objectMatch = jsonText.match(/{[\s\S]*}/);
74 | const arrayMatch = jsonText.match(/\[[\s\S]*\]/);
75 |
76 | if (objectMatch) {
77 | jsonText = objectMatch[0];
78 | } else if (arrayMatch) {
79 | jsonText = arrayMatch[0];
80 | }
81 | }
82 |
83 | // First try to parse as valid JSON
84 | try {
85 | JSON.parse(jsonText);
86 | return jsonText;
87 | } catch {
88 | // If it's not valid JSON, it might be a JavaScript object literal
89 | // Try to convert it to valid JSON
90 | try {
91 | // This is a simple conversion that handles basic cases
92 | // Replace unquoted keys with quoted keys
93 | const converted = jsonText
94 | .replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":')
95 | // Replace single quotes with double quotes
96 | .replace(/'/g, '"')
97 | // Handle trailing commas
98 | .replace(/,\s*([}\]])/g, '$1');
99 |
100 | // Validate the converted JSON
101 | JSON.parse(converted);
102 | return converted;
103 | } catch {
104 | // If all else fails, return the original text
105 | // The calling code will handle the error appropriately
106 | return text;
107 | }
108 | }
109 | }
110 |
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/update-subtask.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tools/update-subtask.js
3 | * Tool to append additional information to a specific subtask
4 | */
5 |
6 | import { z } from 'zod';
7 | import {
8 | handleApiResult,
9 | createErrorResponse,
10 | withNormalizedProjectRoot
11 | } from './utils.js';
12 | import { updateSubtaskByIdDirect } from '../core/task-master-core.js';
13 | import { findTasksPath } from '../core/utils/path-utils.js';
14 | import { resolveTag } from '../../../scripts/modules/utils.js';
15 |
16 | /**
17 | * Register the update-subtask tool with the MCP server
18 | * @param {Object} server - FastMCP server instance
19 | */
20 | export function registerUpdateSubtaskTool(server) {
21 | server.addTool({
22 | name: 'update_subtask',
23 | description:
24 | '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.',
25 | parameters: z.object({
26 | id: z
27 | .string()
28 | .describe(
29 | '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.'
30 | ),
31 | prompt: z.string().describe('Information to add to the subtask'),
32 | research: z
33 | .boolean()
34 | .optional()
35 | .describe('Use Perplexity AI for research-backed updates'),
36 | file: z.string().optional().describe('Absolute path to the tasks file'),
37 | projectRoot: z
38 | .string()
39 | .describe('The directory of the project. Must be an absolute path.'),
40 | tag: z.string().optional().describe('Tag context to operate on')
41 | }),
42 | execute: withNormalizedProjectRoot(async (args, { log, session }) => {
43 | const toolName = 'update_subtask';
44 |
45 | try {
46 | const resolvedTag = resolveTag({
47 | projectRoot: args.projectRoot,
48 | tag: args.tag
49 | });
50 | log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
51 |
52 | let tasksJsonPath;
53 | try {
54 | tasksJsonPath = findTasksPath(
55 | { projectRoot: args.projectRoot, file: args.file },
56 | log
57 | );
58 | } catch (error) {
59 | log.error(`${toolName}: Error finding tasks.json: ${error.message}`);
60 | return createErrorResponse(
61 | `Failed to find tasks.json: ${error.message}`
62 | );
63 | }
64 |
65 | const result = await updateSubtaskByIdDirect(
66 | {
67 | tasksJsonPath: tasksJsonPath,
68 | id: args.id,
69 | prompt: args.prompt,
70 | research: args.research,
71 | projectRoot: args.projectRoot,
72 | tag: resolvedTag
73 | },
74 | log,
75 | { session }
76 | );
77 |
78 | if (result.success) {
79 | log.info(`Successfully updated subtask with ID ${args.id}`);
80 | } else {
81 | log.error(
82 | `Failed to update subtask: ${result.error?.message || 'Unknown error'}`
83 | );
84 | }
85 |
86 | return handleApiResult(
87 | result,
88 | log,
89 | 'Error updating subtask',
90 | undefined,
91 | args.projectRoot
92 | );
93 | } catch (error) {
94 | log.error(
95 | `Critical error in ${toolName} tool execute: ${error.message}`
96 | );
97 | return createErrorResponse(
98 | `Internal tool error (${toolName}): ${error.message}`
99 | );
100 | }
101 | })
102 | });
103 | }
104 |
```
--------------------------------------------------------------------------------
/tests/unit/profiles/trae-integration.test.js:
--------------------------------------------------------------------------------
```javascript
1 | import { jest } from '@jest/globals';
2 | import fs from 'fs';
3 | import path from 'path';
4 | import os from 'os';
5 |
6 | // Mock external modules
7 | jest.mock('child_process', () => ({
8 | execSync: jest.fn()
9 | }));
10 |
11 | // Mock console methods
12 | jest.mock('console', () => ({
13 | log: jest.fn(),
14 | info: jest.fn(),
15 | warn: jest.fn(),
16 | error: jest.fn(),
17 | clear: jest.fn()
18 | }));
19 |
20 | describe('Trae Integration', () => {
21 | let tempDir;
22 |
23 | beforeEach(() => {
24 | jest.clearAllMocks();
25 |
26 | // Create a temporary directory for testing
27 | tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
28 |
29 | // Spy on fs methods
30 | jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
31 | jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
32 | if (filePath.toString().includes('.trae')) {
33 | return 'Existing trae rules content';
34 | }
35 | return '{}';
36 | });
37 | jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
38 | jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
39 | });
40 |
41 | afterEach(() => {
42 | // Clean up the temporary directory
43 | try {
44 | fs.rmSync(tempDir, { recursive: true, force: true });
45 | } catch (err) {
46 | console.error(`Error cleaning up: ${err.message}`);
47 | }
48 | });
49 |
50 | // Test function that simulates the createProjectStructure behavior for Trae files
51 | function mockCreateTraeStructure() {
52 | // Create main .trae directory
53 | fs.mkdirSync(path.join(tempDir, '.trae'), { recursive: true });
54 |
55 | // Create rules directory
56 | fs.mkdirSync(path.join(tempDir, '.trae', 'rules'), { recursive: true });
57 |
58 | // Create rule files
59 | const ruleFiles = [
60 | 'dev_workflow.md',
61 | 'taskmaster.md',
62 | 'architecture.md',
63 | 'commands.md',
64 | 'dependencies.md'
65 | ];
66 |
67 | for (const ruleFile of ruleFiles) {
68 | fs.writeFileSync(
69 | path.join(tempDir, '.trae', 'rules', ruleFile),
70 | `Content for ${ruleFile}`
71 | );
72 | }
73 | }
74 |
75 | test('creates all required .trae directories', () => {
76 | // Act
77 | mockCreateTraeStructure();
78 |
79 | // Assert
80 | expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.trae'), {
81 | recursive: true
82 | });
83 | expect(fs.mkdirSync).toHaveBeenCalledWith(
84 | path.join(tempDir, '.trae', 'rules'),
85 | { recursive: true }
86 | );
87 | });
88 |
89 | test('creates rule files for Trae', () => {
90 | // Act
91 | mockCreateTraeStructure();
92 |
93 | // Assert - check rule files are created
94 | expect(fs.writeFileSync).toHaveBeenCalledWith(
95 | path.join(tempDir, '.trae', 'rules', 'dev_workflow.md'),
96 | expect.any(String)
97 | );
98 | expect(fs.writeFileSync).toHaveBeenCalledWith(
99 | path.join(tempDir, '.trae', 'rules', 'taskmaster.md'),
100 | expect.any(String)
101 | );
102 | expect(fs.writeFileSync).toHaveBeenCalledWith(
103 | path.join(tempDir, '.trae', 'rules', 'architecture.md'),
104 | expect.any(String)
105 | );
106 | });
107 |
108 | test('does not create MCP configuration files', () => {
109 | // Act
110 | mockCreateTraeStructure();
111 |
112 | // Assert - Trae doesn't use MCP configuration
113 | expect(fs.writeFileSync).not.toHaveBeenCalledWith(
114 | path.join(tempDir, '.trae', 'mcp.json'),
115 | expect.any(String)
116 | );
117 | });
118 | });
119 |
```
--------------------------------------------------------------------------------
/mcp-server/src/tools/update.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * tools/update.js
3 | * Tool to update tasks based on new context/prompt
4 | */
5 |
6 | import { z } from 'zod';
7 | import {
8 | handleApiResult,
9 | createErrorResponse,
10 | withNormalizedProjectRoot
11 | } from './utils.js';
12 | import { updateTasksDirect } from '../core/task-master-core.js';
13 | import { findTasksPath } from '../core/utils/path-utils.js';
14 | import { resolveTag } from '../../../scripts/modules/utils.js';
15 |
16 | /**
17 | * Register the update tool with the MCP server
18 | * @param {Object} server - FastMCP server instance
19 | */
20 | export function registerUpdateTool(server) {
21 | server.addTool({
22 | name: 'update',
23 | description:
24 | "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.",
25 | parameters: z.object({
26 | from: z
27 | .string()
28 | .describe(
29 | "Task ID from which to start updating (inclusive). IMPORTANT: This tool uses 'from', not 'id'"
30 | ),
31 | prompt: z
32 | .string()
33 | .describe('Explanation of changes or new context to apply'),
34 | research: z
35 | .boolean()
36 | .optional()
37 | .describe('Use Perplexity AI for research-backed updates'),
38 | file: z
39 | .string()
40 | .optional()
41 | .describe('Path to the tasks file relative to project root'),
42 | projectRoot: z
43 | .string()
44 | .optional()
45 | .describe(
46 | 'The directory of the project. (Optional, usually from session)'
47 | ),
48 | tag: z.string().optional().describe('Tag context to operate on')
49 | }),
50 | execute: withNormalizedProjectRoot(async (args, { log, session }) => {
51 | const toolName = 'update';
52 | const { from, prompt, research, file, projectRoot, tag } = args;
53 |
54 | const resolvedTag = resolveTag({
55 | projectRoot: args.projectRoot,
56 | tag: args.tag
57 | });
58 |
59 | try {
60 | log.info(
61 | `Executing ${toolName} tool with normalized root: ${projectRoot}`
62 | );
63 |
64 | let tasksJsonPath;
65 | try {
66 | tasksJsonPath = findTasksPath({ projectRoot, file }, log);
67 | log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`);
68 | } catch (error) {
69 | log.error(`${toolName}: Error finding tasks.json: ${error.message}`);
70 | return createErrorResponse(
71 | `Failed to find tasks.json within project root '${projectRoot}': ${error.message}`
72 | );
73 | }
74 |
75 | const result = await updateTasksDirect(
76 | {
77 | tasksJsonPath: tasksJsonPath,
78 | from: from,
79 | prompt: prompt,
80 | research: research,
81 | projectRoot: projectRoot,
82 | tag: resolvedTag
83 | },
84 | log,
85 | { session }
86 | );
87 |
88 | log.info(
89 | `${toolName}: Direct function result: success=${result.success}`
90 | );
91 | return handleApiResult(
92 | result,
93 | log,
94 | 'Error updating tasks',
95 | undefined,
96 | args.projectRoot
97 | );
98 | } catch (error) {
99 | log.error(
100 | `Critical error in ${toolName} tool execute: ${error.message}`
101 | );
102 | return createErrorResponse(
103 | `Internal tool error (${toolName}): ${error.message}`
104 | );
105 | }
106 | })
107 | });
108 | }
109 |
```