This is page 6 of 38. Use http://codebase.md/eyaltoledano/claude-task-master?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── README.md ├── .claude │ ├── agents │ │ ├── task-checker.md │ │ ├── task-executor.md │ │ └── task-orchestrator.md │ ├── commands │ │ ├── dedupe.md │ │ └── tm │ │ ├── add-dependency │ │ │ └── add-dependency.md │ │ ├── add-subtask │ │ │ ├── add-subtask.md │ │ │ └── convert-task-to-subtask.md │ │ ├── add-task │ │ │ └── add-task.md │ │ ├── analyze-complexity │ │ │ └── analyze-complexity.md │ │ ├── complexity-report │ │ │ └── complexity-report.md │ │ ├── expand │ │ │ ├── expand-all-tasks.md │ │ │ └── expand-task.md │ │ ├── fix-dependencies │ │ │ └── fix-dependencies.md │ │ ├── generate │ │ │ └── generate-tasks.md │ │ ├── help.md │ │ ├── init │ │ │ ├── init-project-quick.md │ │ │ └── init-project.md │ │ ├── learn.md │ │ ├── list │ │ │ ├── list-tasks-by-status.md │ │ │ ├── list-tasks-with-subtasks.md │ │ │ └── list-tasks.md │ │ ├── models │ │ │ ├── setup-models.md │ │ │ └── view-models.md │ │ ├── next │ │ │ └── next-task.md │ │ ├── parse-prd │ │ │ ├── parse-prd-with-research.md │ │ │ └── parse-prd.md │ │ ├── remove-dependency │ │ │ └── remove-dependency.md │ │ ├── remove-subtask │ │ │ └── remove-subtask.md │ │ ├── remove-subtasks │ │ │ ├── remove-all-subtasks.md │ │ │ └── remove-subtasks.md │ │ ├── remove-task │ │ │ └── remove-task.md │ │ ├── set-status │ │ │ ├── to-cancelled.md │ │ │ ├── to-deferred.md │ │ │ ├── to-done.md │ │ │ ├── to-in-progress.md │ │ │ ├── to-pending.md │ │ │ └── to-review.md │ │ ├── setup │ │ │ ├── install-taskmaster.md │ │ │ └── quick-install-taskmaster.md │ │ ├── show │ │ │ └── show-task.md │ │ ├── status │ │ │ └── project-status.md │ │ ├── sync-readme │ │ │ └── sync-readme.md │ │ ├── tm-main.md │ │ ├── update │ │ │ ├── update-single-task.md │ │ │ ├── update-task.md │ │ │ └── update-tasks-from-id.md │ │ ├── utils │ │ │ └── analyze-project.md │ │ ├── validate-dependencies │ │ │ └── validate-dependencies.md │ │ └── workflows │ │ ├── auto-implement-tasks.md │ │ ├── command-pipeline.md │ │ └── smart-workflow.md │ └── TM_COMMANDS_GUIDE.md ├── .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 │ └── 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 │ │ ├── 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 │ │ ├── test-prd.txt │ │ └── tm-core-phase-1.txt │ ├── reports │ │ ├── task-complexity-report_cc-kiro-hooks.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.txt ├── .vscode │ ├── extensions.json │ └── settings.json ├── apps │ ├── cli │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── commands │ │ │ │ ├── auth.command.ts │ │ │ │ ├── context.command.ts │ │ │ │ ├── list.command.ts │ │ │ │ ├── set-status.command.ts │ │ │ │ ├── show.command.ts │ │ │ │ └── start.command.ts │ │ │ ├── index.ts │ │ │ ├── ui │ │ │ │ ├── components │ │ │ │ │ ├── dashboard.component.ts │ │ │ │ │ ├── header.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── next-task.component.ts │ │ │ │ │ ├── suggested-steps.component.ts │ │ │ │ │ └── task-detail.component.ts │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ ├── auto-update.ts │ │ │ └── ui.ts │ │ └── tsconfig.json │ ├── 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 │ │ │ └── task-structure.mdx │ │ ├── CHANGELOG.md │ │ ├── docs.json │ │ ├── favicon.svg │ │ ├── getting-started │ │ │ ├── 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 │ │ ├── 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 ├── assets │ ├── .windsurfrules │ ├── AGENTS.md │ ├── claude │ │ ├── agents │ │ │ ├── task-checker.md │ │ │ ├── task-executor.md │ │ │ └── task-orchestrator.md │ │ ├── commands │ │ │ └── tm │ │ │ ├── add-dependency │ │ │ │ └── add-dependency.md │ │ │ ├── add-subtask │ │ │ │ ├── add-subtask.md │ │ │ │ └── convert-task-to-subtask.md │ │ │ ├── add-task │ │ │ │ └── add-task.md │ │ │ ├── analyze-complexity │ │ │ │ └── analyze-complexity.md │ │ │ ├── clear-subtasks │ │ │ │ ├── clear-all-subtasks.md │ │ │ │ └── clear-subtasks.md │ │ │ ├── complexity-report │ │ │ │ └── complexity-report.md │ │ │ ├── expand │ │ │ │ ├── expand-all-tasks.md │ │ │ │ └── expand-task.md │ │ │ ├── fix-dependencies │ │ │ │ └── fix-dependencies.md │ │ │ ├── generate │ │ │ │ └── generate-tasks.md │ │ │ ├── help.md │ │ │ ├── init │ │ │ │ ├── init-project-quick.md │ │ │ │ └── init-project.md │ │ │ ├── learn.md │ │ │ ├── list │ │ │ │ ├── list-tasks-by-status.md │ │ │ │ ├── list-tasks-with-subtasks.md │ │ │ │ └── list-tasks.md │ │ │ ├── models │ │ │ │ ├── setup-models.md │ │ │ │ └── view-models.md │ │ │ ├── next │ │ │ │ └── next-task.md │ │ │ ├── parse-prd │ │ │ │ ├── parse-prd-with-research.md │ │ │ │ └── parse-prd.md │ │ │ ├── remove-dependency │ │ │ │ └── remove-dependency.md │ │ │ ├── remove-subtask │ │ │ │ └── remove-subtask.md │ │ │ ├── remove-subtasks │ │ │ │ ├── remove-all-subtasks.md │ │ │ │ └── remove-subtasks.md │ │ │ ├── remove-task │ │ │ │ └── remove-task.md │ │ │ ├── set-status │ │ │ │ ├── to-cancelled.md │ │ │ │ ├── to-deferred.md │ │ │ │ ├── to-done.md │ │ │ │ ├── to-in-progress.md │ │ │ │ ├── to-pending.md │ │ │ │ └── to-review.md │ │ │ ├── setup │ │ │ │ ├── install-taskmaster.md │ │ │ │ └── quick-install-taskmaster.md │ │ │ ├── show │ │ │ │ └── show-task.md │ │ │ ├── status │ │ │ │ └── project-status.md │ │ │ ├── sync-readme │ │ │ │ └── sync-readme.md │ │ │ ├── tm-main.md │ │ │ ├── update │ │ │ │ ├── update-single-task.md │ │ │ │ ├── update-task.md │ │ │ │ └── update-tasks-from-id.md │ │ │ ├── utils │ │ │ │ └── analyze-project.md │ │ │ ├── validate-dependencies │ │ │ │ └── validate-dependencies.md │ │ │ └── workflows │ │ │ ├── auto-implement-tasks.md │ │ │ ├── command-pipeline.md │ │ │ └── smart-workflow.md │ │ └── TM_COMMANDS_GUIDE.md │ ├── config.json │ ├── env.example │ ├── example_prd.txt │ ├── 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.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 │ ├── CLI-COMMANDER-PATTERN.md │ ├── command-reference.md │ ├── configuration.md │ ├── contributor-docs │ │ └── testing-roo-integration.md │ ├── cross-tag-task-movement.md │ ├── examples │ │ └── claude-code-usage.md │ ├── examples.md │ ├── licensing.md │ ├── mcp-provider-guide.md │ ├── mcp-provider.md │ ├── migration-guide.md │ ├── models.md │ ├── providers │ │ └── gemini-cli.md │ ├── README.md │ ├── scripts │ │ └── models-json-to-markdown.js │ ├── task-structure.md │ └── tutorial.md ├── images │ └── 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 │ │ │ ├── list-tasks.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 │ │ │ ├── show-task.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 │ ├── get-task.js │ ├── get-tasks.js │ ├── index.js │ ├── initialize-project.js │ ├── list-tags.js │ ├── models.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.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 │ ├── build-config │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ └── tsdown.base.ts │ │ └── tsconfig.json │ └── tm-core │ ├── .gitignore │ ├── CHANGELOG.md │ ├── docs │ │ └── listTasks-architecture.md │ ├── package.json │ ├── POC-STATUS.md │ ├── README.md │ ├── src │ │ ├── auth │ │ │ ├── auth-manager.test.ts │ │ │ ├── auth-manager.ts │ │ │ ├── config.ts │ │ │ ├── credential-store.test.ts │ │ │ ├── credential-store.ts │ │ │ ├── index.ts │ │ │ ├── oauth-service.ts │ │ │ ├── supabase-session-storage.ts │ │ │ └── types.ts │ │ ├── clients │ │ │ ├── index.ts │ │ │ └── supabase-client.ts │ │ ├── config │ │ │ ├── config-manager.spec.ts │ │ │ ├── config-manager.ts │ │ │ ├── index.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 │ │ ├── constants │ │ │ └── index.ts │ │ ├── entities │ │ │ └── task.entity.ts │ │ ├── errors │ │ │ ├── index.ts │ │ │ └── task-master-error.ts │ │ ├── executors │ │ │ ├── base-executor.ts │ │ │ ├── claude-executor.ts │ │ │ ├── executor-factory.ts │ │ │ ├── executor-service.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── interfaces │ │ │ ├── ai-provider.interface.ts │ │ │ ├── configuration.interface.ts │ │ │ ├── index.ts │ │ │ └── storage.interface.ts │ │ ├── logger │ │ │ ├── factory.ts │ │ │ ├── index.ts │ │ │ └── logger.ts │ │ ├── mappers │ │ │ └── TaskMapper.ts │ │ ├── parser │ │ │ └── index.ts │ │ ├── providers │ │ │ ├── ai │ │ │ │ ├── base-provider.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── repositories │ │ │ ├── supabase-task-repository.ts │ │ │ └── task-repository.interface.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── organization.service.ts │ │ │ ├── task-execution-service.ts │ │ │ └── task-service.ts │ │ ├── storage │ │ │ ├── api-storage.ts │ │ │ ├── file-storage │ │ │ │ ├── file-operations.ts │ │ │ │ ├── file-storage.ts │ │ │ │ ├── format-handler.ts │ │ │ │ ├── index.ts │ │ │ │ └── path-resolver.ts │ │ │ ├── index.ts │ │ │ └── storage-factory.ts │ │ ├── subpath-exports.test.ts │ │ ├── task-master-core.ts │ │ ├── types │ │ │ ├── database.types.ts │ │ │ ├── index.ts │ │ │ └── legacy.ts │ │ └── utils │ │ ├── id-generator.ts │ │ └── index.ts │ ├── tests │ │ ├── integration │ │ │ └── list-tasks.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 │ ├── dev.js │ ├── init.js │ ├── modules │ │ ├── ai-services-unified.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 ├── src │ ├── ai-providers │ │ ├── anthropic.js │ │ ├── azure.js │ │ ├── base-provider.js │ │ ├── bedrock.js │ │ ├── claude-code.js │ │ ├── custom-sdk │ │ │ ├── claude-code │ │ │ │ ├── errors.js │ │ │ │ ├── index.js │ │ │ │ ├── json-extractor.js │ │ │ │ ├── language-model.js │ │ │ │ ├── message-converter.js │ │ │ │ └── types.js │ │ │ └── grok-cli │ │ │ ├── errors.js │ │ │ ├── index.js │ │ │ ├── json-extractor.js │ │ │ ├── language-model.js │ │ │ ├── message-converter.js │ │ │ └── types.js │ │ ├── gemini-cli.js │ │ ├── google-vertex.js │ │ ├── google.js │ │ ├── grok-cli.js │ │ ├── groq.js │ │ ├── index.js │ │ ├── ollama.js │ │ ├── openai.js │ │ ├── openrouter.js │ │ ├── perplexity.js │ │ └── xai.js │ ├── constants │ │ ├── commands.js │ │ ├── paths.js │ │ ├── profiles.js │ │ ├── providers.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 │ ├── 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 │ ├── fixture │ │ └── test-tasks.json │ ├── fixtures │ │ ├── .taskmasterconfig │ │ ├── sample-claude-response.js │ │ ├── sample-prd.txt │ │ └── sample-tasks.js │ ├── integration │ │ ├── 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 │ ├── 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 │ │ ├── claude-code.test.js │ │ ├── custom-sdk │ │ │ └── claude-code │ │ │ └── language-model.test.js │ │ ├── gemini-cli.test.js │ │ ├── mcp-components.test.js │ │ └── openai.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 │ ├── 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 │ ├── providers │ │ └── provider-registry.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 │ │ │ ├── move-task-cross-tag.test.js │ │ │ ├── move-task.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 ``` # Files -------------------------------------------------------------------------------- /apps/docs/capabilities/mcp.mdx: -------------------------------------------------------------------------------- ```markdown --- title: MCP Tools sidebarTitle: "MCP Tools" --- # MCP Tools This document provides an overview of the MCP (Machine-to-Machine Communication Protocol) interface for the Task Master application. The MCP interface is defined in the `mcp-server/` directory and exposes the application's core functionalities as a set of tools that can be called remotely. ## Core Concepts The MCP interface is built on top of the `fastmcp` library and registers a set of tools that correspond to the core functionalities of the Task Master application. These tools are defined in the `mcp-server/src/tools/` directory and are registered with the MCP server in `mcp-server/src/tools/index.js`. Each tool is defined with a name, a description, and a set of parameters that are validated using the `zod` library. The `execute` function of each tool calls the corresponding core logic function from `scripts/modules/task-manager.js`. ## Tool Categories The MCP tools can be categorized in the same way as the core functionalities: ### 1. Task and Subtask Management - **`add_task`**: Creates a new task. - **`add_subtask`**: Adds a subtask to a parent task. - **`remove_task`**: Removes one or more tasks or subtasks. - **`remove_subtask`**: Removes a subtask from its parent. - **`update_task`**: Updates a single task. - **`update_subtask`**: Appends information to a subtask. - **`update`**: Updates multiple tasks. - **`move_task`**: Moves a task or subtask. - **`clear_subtasks`**: Clears all subtasks from one or more tasks. ### 2. Task Information and Status - **`get_tasks`**: Lists all tasks. - **`get_task`**: Shows the details of a specific task. - **`next_task`**: Shows the next task to work on. - **`set_task_status`**: Sets the status of a task or subtask. ### 3. Task Analysis and Expansion - **`parse_prd`**: Parses a PRD to generate tasks. - **`expand_task`**: Expands a task into subtasks. - **`expand_all`**: Expands all eligible tasks. - **`analyze_project_complexity`**: Analyzes task complexity. - **`complexity_report`**: Displays the complexity analysis report. ### 4. Dependency Management - **`add_dependency`**: Adds a dependency to a task. - **`remove_dependency`**: Removes a dependency from a task. - **`validate_dependencies`**: Validates the dependencies of all tasks. - **`fix_dependencies`**: Fixes any invalid dependencies. ### 5. Project and Configuration - **`initialize_project`**: Initializes a new project. - **`generate`**: Generates individual task files. - **`models`**: Manages AI model configurations. - **`research`**: Performs AI-powered research. ### 6. Tag Management - **`add_tag`**: Creates a new tag. - **`delete_tag`**: Deletes a tag. - **`list_tags`**: Lists all tags. - **`use_tag`**: Switches to a different tag. - **`rename_tag`**: Renames a tag. - **`copy_tag`**: Copies a tag. ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/delete-tag.js: -------------------------------------------------------------------------------- ```javascript /** * delete-tag.js * Direct function implementation for deleting a tag */ import { deleteTag } from '../../../../scripts/modules/task-manager/tag-management.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; import { createLogWrapper } from '../../tools/utils.js'; /** * Direct function wrapper for deleting a tag with error handling. * * @param {Object} args - Command arguments * @param {string} args.name - Name of the tag to delete * @param {boolean} [args.yes=false] - Skip confirmation prompts * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool) * @param {string} [args.projectRoot] - Project root path * @param {Object} log - Logger object * @param {Object} context - Additional context (session) * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function deleteTagDirect(args, log, context = {}) { // Destructure expected args const { tasksJsonPath, name, yes = false, projectRoot } = args; const { session } = context; // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); // Create logger wrapper using the utility const mcpLog = createLogWrapper(log); try { // Check if tasksJsonPath was provided if (!tasksJsonPath) { log.error('deleteTagDirect called without tasksJsonPath'); disableSilentMode(); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } // Check required parameters if (!name || typeof name !== 'string') { log.error('Missing required parameter: name'); disableSilentMode(); return { success: false, error: { code: 'MISSING_PARAMETER', message: 'Tag name is required and must be a string' } }; } log.info(`Deleting tag: ${name}`); // Prepare options const options = { yes // For MCP, we always skip confirmation prompts }; // Call the deleteTag function const result = await deleteTag( tasksJsonPath, name, options, { session, mcpLog, projectRoot }, 'json' // outputFormat - use 'json' to suppress CLI UI ); // Restore normal logging disableSilentMode(); return { success: true, data: { tagName: result.tagName, deleted: result.deleted, tasksDeleted: result.tasksDeleted, wasCurrentTag: result.wasCurrentTag, switchedToMaster: result.switchedToMaster, message: `Successfully deleted tag "${result.tagName}"` } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in deleteTagDirect: ${error.message}`); return { success: false, error: { code: error.code || 'DELETE_TAG_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /tests/unit/scripts/modules/task-manager/update-task-by-id.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; jest.unstable_mockModule('fs', () => { const mockFs = { existsSync: jest.fn(() => true), writeFileSync: jest.fn(), readFileSync: jest.fn(), unlinkSync: jest.fn() }; return { default: mockFs, ...mockFs }; }); jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({ readJSON: jest.fn(), writeJSON: jest.fn(), log: jest.fn(), isSilentMode: jest.fn(() => false), findProjectRoot: jest.fn(() => '/project'), flattenTasksWithSubtasks: jest.fn(() => []), truncate: jest.fn((t) => t), isEmpty: jest.fn(() => false), resolveEnvVariable: jest.fn(), findTaskById: jest.fn(), getCurrentTag: jest.fn(() => 'master') })); jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({ getStatusWithColor: jest.fn((s) => s), startLoadingIndicator: jest.fn(() => ({ stop: jest.fn() })), stopLoadingIndicator: jest.fn(), displayAiUsageSummary: jest.fn() })); jest.unstable_mockModule( '../../../../../scripts/modules/task-manager/generate-task-files.js', () => ({ default: jest.fn().mockResolvedValue() }) ); jest.unstable_mockModule( '../../../../../scripts/modules/ai-services-unified.js', () => ({ generateTextService: jest .fn() .mockResolvedValue({ mainResult: { content: '{}' }, telemetryData: {} }) }) ); jest.unstable_mockModule( '../../../../../scripts/modules/config-manager.js', () => ({ getDebugFlag: jest.fn(() => false), isApiKeySet: jest.fn(() => true), hasCodebaseAnalysis: jest.fn(() => false) }) ); const { readJSON, log } = await import( '../../../../../scripts/modules/utils.js' ); const { default: updateTaskById } = await import( '../../../../../scripts/modules/task-manager/update-task-by-id.js' ); describe('updateTaskById validation', () => { beforeEach(() => { jest.clearAllMocks(); jest.spyOn(process, 'exit').mockImplementation(() => { throw new Error('process.exit called'); }); }); test('throws error if prompt is empty', async () => { await expect( updateTaskById( 'tasks/tasks.json', 1, '', false, { tag: 'master' }, 'json' ) ).rejects.toThrow('Prompt cannot be empty'); }); test('throws error if task file missing', async () => { const fs = await import('fs'); fs.existsSync.mockReturnValue(false); await expect( updateTaskById( 'tasks/tasks.json', 1, 'prompt', false, { tag: 'master' }, 'json' ) ).rejects.toThrow('Tasks file not found'); }); test('throws error when task ID not found', async () => { const fs = await import('fs'); fs.existsSync.mockReturnValue(true); readJSON.mockReturnValue({ tag: 'master', tasks: [] }); await expect( updateTaskById( 'tasks/tasks.json', 42, 'prompt', false, { tag: 'master' }, 'json' ) ).rejects.toThrow('Task with ID 42 not found'); expect(log).toHaveBeenCalled(); }); }); ``` -------------------------------------------------------------------------------- /apps/docs/archive/cursor-setup.mdx: -------------------------------------------------------------------------------- ```markdown --- title: "Cursor AI Integration" description: "Learn how to set up and use Task Master with Cursor AI" --- ## Setting up Cursor AI Integration <Check> Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development. </Check> <AccordionGroup> <Accordion title="Using Cursor with MCP (Recommended)" icon="sparkles"> If you've already set up Task Master with MCP in Cursor, the integration is automatic. You can simply use natural language to interact with Task Master: ``` What tasks are available to work on next? Can you analyze the complexity of our tasks? I'd like to implement task 4. What does it involve? ``` </Accordion> <Accordion title="Manual Cursor Setup"> If you're not using MCP, you can still set up Cursor integration: <Steps> <Step title="After initializing your project, open it in Cursor"> The `.cursor/rules/dev_workflow.mdc` file is automatically loaded by Cursor, providing the AI with knowledge about the task management system </Step> <Step title="Place your PRD document in the scripts/ directory (e.g., scripts/prd.txt)"> </Step> <Step title="Open Cursor's AI chat and switch to Agent mode"> </Step> </Steps> </Accordion> <Accordion title="Alternative MCP Setup in Cursor"> <Steps> <Step title="Go to Cursor settings"> </Step> <Step title="Navigate to the MCP section"> </Step> <Step title="Click on 'Add New MCP Server'"> </Step> <Step title="Configure with the following details:"> - Name: "Task Master" - Type: "Command" - Command: "npx -y task-master-ai" </Step> <Step title="Save Settings"> </Step> </Steps> Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience. </Accordion> </AccordionGroup> ## Initial Task Generation In Cursor's AI chat, instruct the agent to generate tasks from your PRD: ``` Please use the task-master parse-prd command to generate tasks from my PRD. The PRD is located at scripts/prd.txt. ``` The agent will execute: ```bash task-master parse-prd scripts/prd.txt ``` This will: - Parse your PRD document - Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies - The agent will understand this process due to the Cursor rules ### Generate Individual Task Files Next, ask the agent to generate individual task files: ``` Please generate individual task files from tasks.json ``` The agent will execute: ```bash task-master generate ``` This creates individual task files in the `tasks/` directory (e.g., `task_001.txt`, `task_002.txt`), making it easier to reference specific tasks. ``` -------------------------------------------------------------------------------- /mcp-server/src/custom-sdk/json-extractor.js: -------------------------------------------------------------------------------- ```javascript /** * @fileoverview Extract JSON from MCP response, handling markdown blocks and other formatting */ /** * Extract JSON from MCP AI response * @param {string} text - The text to extract JSON from * @returns {string} - The extracted JSON string */ export function extractJson(text) { // Remove markdown code blocks if present let jsonText = text.trim(); // Remove ```json blocks jsonText = jsonText.replace(/^```json\s*/gm, ''); jsonText = jsonText.replace(/^```\s*/gm, ''); jsonText = jsonText.replace(/```\s*$/gm, ''); // Remove common TypeScript/JavaScript patterns jsonText = jsonText.replace(/^const\s+\w+\s*=\s*/, ''); // Remove "const varName = " jsonText = jsonText.replace(/^let\s+\w+\s*=\s*/, ''); // Remove "let varName = " jsonText = jsonText.replace(/^var\s+\w+\s*=\s*/, ''); // Remove "var varName = " jsonText = jsonText.replace(/;?\s*$/, ''); // Remove trailing semicolons // Remove explanatory text before JSON (common with AI responses) jsonText = jsonText.replace(/^.*?(?=\{|\[)/s, ''); // Remove explanatory text after JSON const lines = jsonText.split('\n'); let jsonEndIndex = -1; let braceCount = 0; let inString = false; let escapeNext = false; // Find the end of the JSON by tracking braces for (let i = 0; i < jsonText.length; i++) { const char = jsonText[i]; if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (char === '"' && !escapeNext) { inString = !inString; continue; } if (!inString) { if (char === '{' || char === '[') { braceCount++; } else if (char === '}' || char === ']') { braceCount--; if (braceCount === 0) { jsonEndIndex = i; break; } } } } if (jsonEndIndex > -1) { jsonText = jsonText.substring(0, jsonEndIndex + 1); } // Try to extract JSON object or array if previous method didn't work if (jsonEndIndex === -1) { const objectMatch = jsonText.match(/{[\s\S]*}/); const arrayMatch = jsonText.match(/\[[\s\S]*\]/); if (objectMatch) { jsonText = objectMatch[0]; } else if (arrayMatch) { jsonText = arrayMatch[0]; } } // First try to parse as valid JSON try { JSON.parse(jsonText); return jsonText; } catch { // If it's not valid JSON, it might be a JavaScript object literal // Try to convert it to valid JSON try { // This is a simple conversion that handles basic cases // Replace unquoted keys with quoted keys const converted = jsonText .replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":') // Replace single quotes with double quotes .replace(/'/g, '"') // Handle trailing commas .replace(/,\s*([}\]])/g, '$1'); // Validate the converted JSON JSON.parse(converted); return converted; } catch { // If all else fails, return the original text // The calling code will handle the error appropriately return text; } } } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/update-subtask.js: -------------------------------------------------------------------------------- ```javascript /** * tools/update-subtask.js * Tool to append additional information to a specific subtask */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { updateSubtaskByIdDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the update-subtask tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerUpdateSubtaskTool(server) { server.addTool({ name: 'update_subtask', description: 'Appends timestamped information to a specific subtask without replacing existing content. If you just want to update the subtask status, use set_task_status instead.', parameters: z.object({ id: z .string() .describe( 'ID of the subtask to update in format "parentId.subtaskId" (e.g., "5.2"). Parent ID is the ID of the task that contains the subtask.' ), prompt: z.string().describe('Information to add to the subtask'), research: z .boolean() .optional() .describe('Use Perplexity AI for research-backed updates'), file: z.string().optional().describe('Absolute path to the tasks file'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { const toolName = 'update_subtask'; try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); log.info(`Updating subtask with args: ${JSON.stringify(args)}`); let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`${toolName}: Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const result = await updateSubtaskByIdDirect( { tasksJsonPath: tasksJsonPath, id: args.id, prompt: args.prompt, research: args.research, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); if (result.success) { log.info(`Successfully updated subtask with ID ${args.id}`); } else { log.error( `Failed to update subtask: ${result.error?.message || 'Unknown error'}` ); } return handleApiResult( result, log, 'Error updating subtask', undefined, args.projectRoot ); } catch (error) { log.error( `Critical error in ${toolName} tool execute: ${error.message}` ); return createErrorResponse( `Internal tool error (${toolName}): ${error.message}` ); } }) }); } ``` -------------------------------------------------------------------------------- /tests/unit/profiles/trae-integration.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import fs from 'fs'; import path from 'path'; import os from 'os'; // Mock external modules jest.mock('child_process', () => ({ execSync: jest.fn() })); // Mock console methods jest.mock('console', () => ({ log: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), clear: jest.fn() })); describe('Trae Integration', () => { let tempDir; beforeEach(() => { jest.clearAllMocks(); // Create a temporary directory for testing tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-')); // Spy on fs methods jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => { if (filePath.toString().includes('.trae')) { return 'Existing trae rules content'; } return '{}'; }); jest.spyOn(fs, 'existsSync').mockImplementation(() => false); jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {}); }); afterEach(() => { // Clean up the temporary directory try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (err) { console.error(`Error cleaning up: ${err.message}`); } }); // Test function that simulates the createProjectStructure behavior for Trae files function mockCreateTraeStructure() { // Create main .trae directory fs.mkdirSync(path.join(tempDir, '.trae'), { recursive: true }); // Create rules directory fs.mkdirSync(path.join(tempDir, '.trae', 'rules'), { recursive: true }); // Create rule files const ruleFiles = [ 'dev_workflow.md', 'taskmaster.md', 'architecture.md', 'commands.md', 'dependencies.md' ]; for (const ruleFile of ruleFiles) { fs.writeFileSync( path.join(tempDir, '.trae', 'rules', ruleFile), `Content for ${ruleFile}` ); } } test('creates all required .trae directories', () => { // Act mockCreateTraeStructure(); // Assert expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.trae'), { recursive: true }); expect(fs.mkdirSync).toHaveBeenCalledWith( path.join(tempDir, '.trae', 'rules'), { recursive: true } ); }); test('creates rule files for Trae', () => { // Act mockCreateTraeStructure(); // Assert - check rule files are created expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, '.trae', 'rules', 'dev_workflow.md'), expect.any(String) ); expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, '.trae', 'rules', 'taskmaster.md'), expect.any(String) ); expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, '.trae', 'rules', 'architecture.md'), expect.any(String) ); }); test('does not create MCP configuration files', () => { // Act mockCreateTraeStructure(); // Assert - Trae doesn't use MCP configuration expect(fs.writeFileSync).not.toHaveBeenCalledWith( path.join(tempDir, '.trae', 'mcp.json'), expect.any(String) ); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/update.js: -------------------------------------------------------------------------------- ```javascript /** * tools/update.js * Tool to update tasks based on new context/prompt */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { updateTasksDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the update tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerUpdateTool(server) { server.addTool({ name: 'update', description: "Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt. Use 'update_task' instead for a single specific task or 'update_subtask' for subtasks.", parameters: z.object({ from: z .string() .describe( "Task ID from which to start updating (inclusive). IMPORTANT: This tool uses 'from', not 'id'" ), prompt: z .string() .describe('Explanation of changes or new context to apply'), research: z .boolean() .optional() .describe('Use Perplexity AI for research-backed updates'), file: z .string() .optional() .describe('Path to the tasks file relative to project root'), projectRoot: z .string() .optional() .describe( 'The directory of the project. (Optional, usually from session)' ), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { const toolName = 'update'; const { from, prompt, research, file, projectRoot, tag } = args; const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); try { log.info( `Executing ${toolName} tool with normalized root: ${projectRoot}` ); let tasksJsonPath; try { tasksJsonPath = findTasksPath({ projectRoot, file }, log); log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`); } catch (error) { log.error(`${toolName}: Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json within project root '${projectRoot}': ${error.message}` ); } const result = await updateTasksDirect( { tasksJsonPath: tasksJsonPath, from: from, prompt: prompt, research: research, projectRoot: projectRoot, tag: resolvedTag }, log, { session } ); log.info( `${toolName}: Direct function result: success=${result.success}` ); return handleApiResult( result, log, 'Error updating tasks', undefined, args.projectRoot ); } catch (error) { log.error( `Critical error in ${toolName} tool execute: ${error.message}` ); return createErrorResponse( `Internal tool error (${toolName}): ${error.message}` ); } }) }); } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/add-dependency.js: -------------------------------------------------------------------------------- ```javascript /** * add-dependency.js * Direct function implementation for adding a dependency to a task */ import { addDependency } from '../../../../scripts/modules/dependency-manager.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; /** * Direct function wrapper for addDependency with error handling. * * @param {Object} args - Command arguments * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string|number} args.id - Task ID to add dependency to * @param {string|number} args.dependsOn - Task ID that will become a dependency * @param {string} args.tag - Tag for the task (optional) * @param {string} args.projectRoot - Project root path (for MCP/env fallback) * @param {Object} log - Logger object * @returns {Promise<Object>} - Result object with success status and data/error information */ export async function addDependencyDirect(args, log) { // Destructure expected args const { tasksJsonPath, id, dependsOn, tag, projectRoot } = args; try { log.info(`Adding dependency with args: ${JSON.stringify(args)}`); // Check if tasksJsonPath was provided if (!tasksJsonPath) { log.error('addDependencyDirect called without tasksJsonPath'); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } // Validate required parameters if (!id) { return { success: false, error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID (id) is required' } }; } if (!dependsOn) { return { success: false, error: { code: 'INPUT_VALIDATION_ERROR', message: 'Dependency ID (dependsOn) is required' } }; } // Use provided path const tasksPath = tasksJsonPath; // Format IDs for the core function const taskId = id && id.includes && id.includes('.') ? id : parseInt(id, 10); const dependencyId = dependsOn && dependsOn.includes && dependsOn.includes('.') ? dependsOn : parseInt(dependsOn, 10); log.info( `Adding dependency: task ${taskId} will depend on ${dependencyId}` ); // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); // Create context object const context = { projectRoot, tag }; // Call the core function using the provided path await addDependency(tasksPath, taskId, dependencyId, context); // Restore normal logging disableSilentMode(); return { success: true, data: { message: `Successfully added dependency: Task ${taskId} now depends on ${dependencyId}`, taskId: taskId, dependencyId: dependencyId } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in addDependencyDirect: ${error.message}`); return { success: false, error: { code: 'CORE_FUNCTION_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/complexity-report.js: -------------------------------------------------------------------------------- ```javascript /** * complexity-report.js * Direct function implementation for displaying complexity analysis report */ import { readComplexityReport, enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; /** * Direct function wrapper for displaying the complexity report with error handling and caching. * * @param {Object} args - Command arguments containing reportPath. * @param {string} args.reportPath - Explicit path to the complexity report file. * @param {Object} log - Logger object * @returns {Promise<Object>} - Result object with success status and data/error information */ export async function complexityReportDirect(args, log) { // Destructure expected args const { reportPath } = args; try { log.info(`Getting complexity report with args: ${JSON.stringify(args)}`); // Check if reportPath was provided if (!reportPath) { log.error('complexityReportDirect called without reportPath'); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' } }; } // Use the provided report path log.info(`Looking for complexity report at: ${reportPath}`); // Generate cache key based on report path const cacheKey = `complexityReport:${reportPath}`; // Define the core action function to read the report const coreActionFn = async () => { try { // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); const report = readComplexityReport(reportPath); // Restore normal logging disableSilentMode(); if (!report) { log.warn(`No complexity report found at ${reportPath}`); return { success: false, error: { code: 'FILE_NOT_FOUND_ERROR', message: `No complexity report found at ${reportPath}. Run 'analyze-complexity' first.` } }; } return { success: true, data: { report, reportPath } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error reading complexity report: ${error.message}`); return { success: false, error: { code: 'READ_ERROR', message: error.message } }; } }; // Use the caching utility try { const result = await coreActionFn(); log.info('complexityReportDirect completed'); return result; } catch (error) { // Ensure silent mode is disabled disableSilentMode(); log.error(`Unexpected error during complexityReport: ${error.message}`); return { success: false, error: { code: 'UNEXPECTED_ERROR', message: error.message } }; } } catch (error) { // Ensure silent mode is disabled if an outer error occurs disableSilentMode(); log.error(`Error in complexityReportDirect: ${error.message}`); return { success: false, error: { code: 'UNEXPECTED_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /mcp-server/src/logger.js: -------------------------------------------------------------------------------- ```javascript import chalk from 'chalk'; import { isSilentMode } from '../../scripts/modules/utils.js'; import { getLogLevel } from '../../scripts/modules/config-manager.js'; // Define log levels const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3, success: 4 }; // Get log level from config manager or default to info const LOG_LEVEL = LOG_LEVELS[getLogLevel().toLowerCase()] ?? LOG_LEVELS.info; /** * Logs a message with the specified level * @param {string} level - The log level (debug, info, warn, error, success) * @param {...any} args - Arguments to log */ function log(level, ...args) { // Skip logging if silent mode is enabled if (isSilentMode()) { return; } // Use text prefixes instead of emojis const prefixes = { debug: chalk.gray('[DEBUG]'), info: chalk.blue('[INFO]'), warn: chalk.yellow('[WARN]'), error: chalk.red('[ERROR]'), success: chalk.green('[SUCCESS]') }; if (LOG_LEVELS[level] !== undefined && LOG_LEVELS[level] >= LOG_LEVEL) { const prefix = prefixes[level] || ''; let coloredArgs = args; try { switch (level) { case 'error': coloredArgs = args.map((arg) => typeof arg === 'string' ? chalk.red(arg) : arg ); break; case 'warn': coloredArgs = args.map((arg) => typeof arg === 'string' ? chalk.yellow(arg) : arg ); break; case 'success': coloredArgs = args.map((arg) => typeof arg === 'string' ? chalk.green(arg) : arg ); break; case 'info': coloredArgs = args.map((arg) => typeof arg === 'string' ? chalk.blue(arg) : arg ); break; case 'debug': coloredArgs = args.map((arg) => typeof arg === 'string' ? chalk.gray(arg) : arg ); break; // default: use original args (no color) } } catch (colorError) { // Fallback if chalk fails on an argument // Use console.error here for internal logger errors, separate from normal logging console.error('Internal Logger Error applying chalk color:', colorError); coloredArgs = args; } // Revert to console.log - FastMCP's context logger (context.log) // is responsible for directing logs correctly (e.g., to stderr) // during tool execution without upsetting the client connection. // Logs outside of tool execution (like startup) will go to stdout. console.log(prefix, ...coloredArgs); } } /** * Create a logger object with methods for different log levels * @returns {Object} Logger object with info, error, debug, warn, and success methods */ export function createLogger() { const createLogMethod = (level) => (...args) => log(level, ...args); return { debug: createLogMethod('debug'), info: createLogMethod('info'), warn: createLogMethod('warn'), error: createLogMethod('error'), success: createLogMethod('success'), log: log // Also expose the raw log function }; } // Export a default logger instance const logger = createLogger(); export default logger; export { log, LOG_LEVELS }; ``` -------------------------------------------------------------------------------- /apps/extension/src/webview/App.tsx: -------------------------------------------------------------------------------- ```typescript /** * Main App Component */ import React, { useReducer, useState, useEffect, useRef } from 'react'; import { VSCodeContext } from './contexts/VSCodeContext'; import { QueryProvider } from './providers/QueryProvider'; import { AppContent } from './components/AppContent'; import { ToastContainer } from './components/ToastContainer'; import { ErrorBoundary } from './components/ErrorBoundary'; import { appReducer, initialState } from './reducers/appReducer'; import { useWebviewHeight } from './hooks/useWebviewHeight'; import { useVSCodeMessages } from './hooks/useVSCodeMessages'; import { showSuccessToast, showInfoToast, showWarningToast, showErrorToast, createToast } from './utils/toast'; export const App: React.FC = () => { const [state, dispatch] = useReducer(appReducer, initialState); const [vscode] = useState(() => window.acquireVsCodeApi?.()); const availableHeight = useWebviewHeight(); const { sendMessage } = useVSCodeMessages(vscode, state, dispatch); const hasInitialized = useRef(false); // Initialize the webview useEffect(() => { if (hasInitialized.current) return; hasInitialized.current = true; if (!vscode) { console.warn('⚠️ VS Code API not available - running in standalone mode'); dispatch({ type: 'SET_CONNECTION_STATUS', payload: { isConnected: false, status: 'Standalone Mode' } }); return; } console.log('🔄 Initializing webview...'); // Notify extension that webview is ready vscode.postMessage({ type: 'ready' }); // React Query will handle task fetching, so we only need to load tags data sendMessage({ type: 'getTags' }) .then((tagsData) => { if (tagsData?.tags && tagsData?.currentTag) { const tagNames = tagsData.tags.map((tag: any) => tag.name || tag); dispatch({ type: 'SET_TAG_DATA', payload: { currentTag: tagsData.currentTag, availableTags: tagNames } }); } }) .catch((error) => { console.error('❌ Failed to load tags:', error); }); }, [vscode, sendMessage, dispatch]); const contextValue = { vscode, state, dispatch, sendMessage, availableHeight, // Toast notification functions showSuccessToast: showSuccessToast(dispatch), showInfoToast: showInfoToast(dispatch), showWarningToast: showWarningToast(dispatch), showErrorToast: showErrorToast(dispatch) }; return ( <QueryProvider> <VSCodeContext.Provider value={contextValue}> <ErrorBoundary onError={(error) => { // Handle React errors and show appropriate toast dispatch({ type: 'ADD_TOAST', payload: createToast( 'error', 'Component Error', `A React component crashed: ${error.message}`, 10000 ) }); }} > <AppContent /> <ToastContainer notifications={state.toastNotifications} onDismiss={(id) => dispatch({ type: 'REMOVE_TOAST', payload: id })} /> </ErrorBoundary> </VSCodeContext.Provider> </QueryProvider> ); }; ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/research.js: -------------------------------------------------------------------------------- ```javascript /** * tools/research.js * Tool to perform AI-powered research queries with project context */ import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { researchDirect } from '../core/task-master-core.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the research tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerResearchTool(server) { server.addTool({ name: 'research', description: 'Perform AI-powered research queries with project context', parameters: z.object({ query: z.string().describe('Research query/prompt (required)'), taskIds: z .string() .optional() .describe( 'Comma-separated list of task/subtask IDs for context (e.g., "15,16.2,17")' ), filePaths: z .string() .optional() .describe( 'Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md")' ), customContext: z .string() .optional() .describe('Additional custom context text to include in the research'), includeProjectTree: z .boolean() .optional() .describe( 'Include project file tree structure in context (default: false)' ), detailLevel: z .enum(['low', 'medium', 'high']) .optional() .describe('Detail level for the research response (default: medium)'), saveTo: z .string() .optional() .describe( 'Automatically save research results to specified task/subtask ID (e.g., "15" or "15.2")' ), saveToFile: z .boolean() .optional() .describe( 'Save research results to .taskmaster/docs/research/ directory (default: false)' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); log.info( `Starting research with query: "${args.query.substring(0, 100)}${args.query.length > 100 ? '...' : ''}"` ); // Call the direct function const result = await researchDirect( { query: args.query, taskIds: args.taskIds, filePaths: args.filePaths, customContext: args.customContext, includeProjectTree: args.includeProjectTree || false, detailLevel: args.detailLevel || 'medium', saveTo: args.saveTo, saveToFile: args.saveToFile || false, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); return handleApiResult( result, log, 'Error performing research', undefined, args.projectRoot ); } catch (error) { log.error(`Error in research tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /apps/docs/archive/Installation.mdx: -------------------------------------------------------------------------------- ```markdown --- title: "Installation(2)" description: "This guide walks you through setting up Task Master in your development environment." --- ## Initial Setup <Tip> MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor. </Tip> <AccordionGroup> <Accordion title="Option 1: Using MCP (Recommended)" icon="sparkles"> <Steps> <Step title="Add the MCP config to your editor"> <Link href="https://cursor.sh">Cursor</Link> recommended, but it works with other text editors ```json { "mcpServers": { "taskmaster-ai": { "command": "npx", "args": ["-y", "task-master-ai"], "env": { "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE", "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE", "MODEL": "claude-3-7-sonnet-20250219", "PERPLEXITY_MODEL": "sonar-pro", "MAX_TOKENS": 128000, "TEMPERATURE": 0.2, "DEFAULT_SUBTASKS": 5, "DEFAULT_PRIORITY": "medium" } } } } ``` </Step> <Step title="Enable the MCP in your editor settings"> </Step> <Step title="Prompt the AI to initialize Task Master"> > "Can you please initialize taskmaster-ai into my project?" **The AI will:** 1. Create necessary project structure 2. Set up initial configuration files 3. Guide you through the rest of the process 4. Place your PRD document in the `scripts/` directory (e.g., `scripts/prd.txt`) 5. **Use natural language commands** to interact with Task Master: > "Can you parse my PRD at scripts/prd.txt?" > > "What's the next task I should work on?" > > "Can you help me implement task 3?" </Step> </Steps> </Accordion> <Accordion title="Option 2: Manual Installation"> If you prefer to use the command line interface directly: <Steps> <Step title="Install"> <CodeGroup> ```bash Global npm install -g task-master-ai ``` ```bash Local npm install task-master-ai ``` </CodeGroup> </Step> <Step title="Initialize a new project"> <CodeGroup> ```bash Global task-master init ``` ```bash Local npx task-master-init ``` </CodeGroup> </Step> </Steps> This will prompt you for project details and set up a new project with the necessary files and structure. </Accordion> </AccordionGroup> ## Common Commands <Tip> After setting up Task Master, you can use these commands (either via AI prompts or CLI) </Tip> ```bash # Parse a PRD and generate tasks task-master parse-prd your-prd.txt # List all tasks task-master list # Show the next task to work on task-master next # Generate task files task-master generate ``` -------------------------------------------------------------------------------- /tests/unit/ai-providers/claude-code.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; // Mock the claude-code SDK module jest.unstable_mockModule( '../../../src/ai-providers/custom-sdk/claude-code/index.js', () => ({ createClaudeCode: jest.fn(() => { const provider = (modelId, settings) => ({ // Mock language model id: modelId, settings }); provider.languageModel = jest.fn((id, settings) => ({ id, settings })); provider.chat = provider.languageModel; return provider; }) }) ); // Mock the base provider jest.unstable_mockModule('../../../src/ai-providers/base-provider.js', () => ({ BaseAIProvider: class { constructor() { this.name = 'Base Provider'; } handleError(context, error) { throw error; } } })); // Import after mocking const { ClaudeCodeProvider } = await import( '../../../src/ai-providers/claude-code.js' ); describe('ClaudeCodeProvider', () => { let provider; beforeEach(() => { provider = new ClaudeCodeProvider(); jest.clearAllMocks(); }); describe('constructor', () => { it('should set the provider name to Claude Code', () => { expect(provider.name).toBe('Claude Code'); }); }); describe('validateAuth', () => { it('should not throw an error (no API key required)', () => { expect(() => provider.validateAuth({})).not.toThrow(); }); it('should not require any parameters', () => { expect(() => provider.validateAuth()).not.toThrow(); }); it('should work with any params passed', () => { expect(() => provider.validateAuth({ apiKey: 'some-key', baseURL: 'https://example.com' }) ).not.toThrow(); }); }); describe('getClient', () => { it('should return a claude code client', () => { const client = provider.getClient({}); expect(client).toBeDefined(); expect(typeof client).toBe('function'); }); it('should create client without API key or base URL', () => { const client = provider.getClient({}); expect(client).toBeDefined(); }); it('should handle params even though they are not used', () => { const client = provider.getClient({ baseURL: 'https://example.com', apiKey: 'unused-key' }); expect(client).toBeDefined(); }); it('should have languageModel and chat methods', () => { const client = provider.getClient({}); expect(client.languageModel).toBeDefined(); expect(client.chat).toBeDefined(); expect(client.chat).toBe(client.languageModel); }); }); describe('error handling', () => { it('should handle client initialization errors', async () => { // Force an error by making createClaudeCode throw const { createClaudeCode } = await import( '../../../src/ai-providers/custom-sdk/claude-code/index.js' ); createClaudeCode.mockImplementationOnce(() => { throw new Error('Mock initialization error'); }); // Create a new provider instance to use the mocked createClaudeCode const errorProvider = new ClaudeCodeProvider(); expect(() => errorProvider.getClient({})).toThrow( 'Mock initialization error' ); }); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/expand-all.js: -------------------------------------------------------------------------------- ```javascript /** * tools/expand-all.js * Tool for expanding all pending tasks with subtasks */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { expandAllTasksDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the expandAll tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerExpandAllTool(server) { server.addTool({ name: 'expand_all', description: 'Expand all pending tasks into subtasks based on complexity or defaults', parameters: z.object({ num: z .string() .optional() .describe( 'Target number of subtasks per task (uses complexity/defaults otherwise)' ), research: z .boolean() .optional() .describe( 'Enable research-backed subtask generation (e.g., using Perplexity)' ), prompt: z .string() .optional() .describe( 'Additional context to guide subtask generation for all tasks' ), force: z .boolean() .optional() .describe( 'Force regeneration of subtasks for tasks that already have them' ), file: z .string() .optional() .describe( 'Absolute path to the tasks file in the /tasks folder inside the project root (default: tasks/tasks.json)' ), projectRoot: z .string() .optional() .describe( 'Absolute path to the project root directory (derived from session if possible)' ), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info( `Tool expand_all execution started with args: ${JSON.stringify(args)}` ); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); log.info(`Resolved tasks.json path: ${tasksJsonPath}`); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const result = await expandAllTasksDirect( { tasksJsonPath: tasksJsonPath, num: args.num, research: args.research, prompt: args.prompt, force: args.force, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); return handleApiResult( result, log, 'Error expanding all tasks', undefined, args.projectRoot ); } catch (error) { log.error( `Unexpected error in expand_all tool execute: ${error.message}` ); if (error.stack) { log.error(error.stack); } return createErrorResponse( `An unexpected error occurred: ${error.message}` ); } }) }); } ``` -------------------------------------------------------------------------------- /tests/integration/claude-code-optional.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; // Mock the base provider to avoid circular dependencies jest.unstable_mockModule('../../src/ai-providers/base-provider.js', () => ({ BaseAIProvider: class { constructor() { this.name = 'Base Provider'; } handleError(context, error) { throw error; } } })); // Mock the claude-code SDK to simulate it not being installed jest.unstable_mockModule('@anthropic-ai/claude-code', () => { throw new Error("Cannot find module '@anthropic-ai/claude-code'"); }); // Import after mocking const { ClaudeCodeProvider } = await import( '../../src/ai-providers/claude-code.js' ); describe('Claude Code Optional Dependency Integration', () => { describe('when @anthropic-ai/claude-code is not installed', () => { it('should allow provider instantiation', () => { // Provider should instantiate without error const provider = new ClaudeCodeProvider(); expect(provider).toBeDefined(); expect(provider.name).toBe('Claude Code'); }); it('should allow client creation', () => { const provider = new ClaudeCodeProvider(); // Client creation should work const client = provider.getClient({}); expect(client).toBeDefined(); expect(typeof client).toBe('function'); }); it('should fail with clear error when trying to use the model', async () => { const provider = new ClaudeCodeProvider(); const client = provider.getClient({}); const model = client('opus'); // The actual usage should fail with the lazy loading error await expect( model.doGenerate({ prompt: [{ role: 'user', content: 'Hello' }], mode: { type: 'regular' } }) ).rejects.toThrow( "Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider." ); }); it('should provide helpful error message for streaming', async () => { const provider = new ClaudeCodeProvider(); const client = provider.getClient({}); const model = client('sonnet'); await expect( model.doStream({ prompt: [{ role: 'user', content: 'Hello' }], mode: { type: 'regular' } }) ).rejects.toThrow( "Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider." ); }); }); describe('provider behavior', () => { it('should not require API key', () => { const provider = new ClaudeCodeProvider(); // Should not throw expect(() => provider.validateAuth()).not.toThrow(); expect(() => provider.validateAuth({ apiKey: null })).not.toThrow(); }); it('should work with ai-services-unified when provider is configured', async () => { // This tests that the provider can be selected but will fail appropriately // when the actual model is used const provider = new ClaudeCodeProvider(); expect(provider).toBeDefined(); // In real usage, ai-services-unified would: // 1. Get the provider instance (works) // 2. Call provider.getClient() (works) // 3. Create a model (works) // 4. Try to generate (fails with clear error) }); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/update-task.js: -------------------------------------------------------------------------------- ```javascript /** * tools/update-task.js * Tool to update a single task by ID with new information */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { updateTaskByIdDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the update-task tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerUpdateTaskTool(server) { server.addTool({ name: 'update_task', description: 'Updates a single task by ID with new information or context provided in the prompt.', parameters: z.object({ id: z .string() // ID can be number or string like "1.2" .describe( "ID of the task (e.g., '15') to update. Subtasks are supported using the update-subtask tool." ), prompt: z .string() .describe('New information or context to incorporate into the task'), research: z .boolean() .optional() .describe('Use Perplexity AI for research-backed updates'), append: z .boolean() .optional() .describe( 'Append timestamped information to task details instead of full update' ), file: z.string().optional().describe('Absolute path to the tasks file'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { const toolName = 'update_task'; try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); log.info( `Executing ${toolName} tool with args: ${JSON.stringify(args)}` ); let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`); } catch (error) { log.error(`${toolName}: Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // 3. Call Direct Function - Include projectRoot const result = await updateTaskByIdDirect( { tasksJsonPath: tasksJsonPath, id: args.id, prompt: args.prompt, research: args.research, append: args.append, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); // 4. Handle Result log.info( `${toolName}: Direct function result: success=${result.success}` ); return handleApiResult( result, log, 'Error updating task', undefined, args.projectRoot ); } catch (error) { log.error( `Critical error in ${toolName} tool execute: ${error.message}` ); return createErrorResponse( `Internal tool error (${toolName}): ${error.message}` ); } }) }); } ``` -------------------------------------------------------------------------------- /src/prompts/schemas/prompt-template.schema.json: -------------------------------------------------------------------------------- ```json { "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://github.com/eyaltoledano/claude-task-master/blob/main/src/prompts/schemas/prompt-template.schema.json", "version": "1.0.0", "title": "Task Master Prompt Template", "description": "Schema for Task Master AI prompt template files", "type": "object", "required": ["id", "version", "description", "prompts"], "properties": { "id": { "type": "string", "pattern": "^[a-z0-9-]+$", "description": "Unique identifier for the prompt template" }, "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "description": "Semantic version of the prompt template" }, "description": { "type": "string", "minLength": 1, "description": "Brief description of what this prompt does" }, "metadata": { "$ref": "#/definitions/metadata" }, "parameters": { "type": "object", "additionalProperties": { "$ref": "#/definitions/parameter" } }, "prompts": { "type": "object", "properties": { "default": { "$ref": "#/definitions/promptVariant" } }, "additionalProperties": { "$ref": "#/definitions/conditionalPromptVariant" } } }, "definitions": { "parameter": { "type": "object", "required": ["type", "description"], "properties": { "type": { "type": "string", "enum": ["string", "number", "boolean", "array", "object"] }, "description": { "type": "string", "minLength": 1 }, "required": { "type": "boolean", "default": false }, "default": { "description": "Default value for optional parameters" }, "enum": { "type": "array", "description": "Valid values for string parameters" }, "pattern": { "type": "string", "description": "Regular expression pattern for string validation" }, "minimum": { "type": "number", "description": "Minimum value for number parameters" }, "maximum": { "type": "number", "description": "Maximum value for number parameters" } } }, "promptVariant": { "type": "object", "required": ["system", "user"], "properties": { "system": { "type": "string", "minLength": 1 }, "user": { "type": "string", "minLength": 1 } } }, "conditionalPromptVariant": { "allOf": [ { "$ref": "#/definitions/promptVariant" }, { "type": "object", "properties": { "condition": { "type": "string", "description": "JavaScript expression for variant selection" } } } ] }, "metadata": { "type": "object", "properties": { "author": { "type": "string" }, "created": { "type": "string", "format": "date-time" }, "updated": { "type": "string", "format": "date-time" }, "tags": { "type": "array", "items": { "type": "string" } }, "category": { "type": "string", "enum": [ "task", "analysis", "research", "parsing", "update", "expansion" ] } } } } } ``` -------------------------------------------------------------------------------- /src/prompts/analyze-complexity.json: -------------------------------------------------------------------------------- ```json { "id": "analyze-complexity", "version": "1.0.0", "description": "Analyze task complexity and generate expansion recommendations", "metadata": { "author": "system", "created": "2024-01-01T00:00:00Z", "updated": "2024-01-01T00:00:00Z", "tags": ["analysis", "complexity", "expansion", "recommendations"] }, "parameters": { "tasks": { "type": "array", "required": true, "description": "Array of tasks to analyze" }, "gatheredContext": { "type": "string", "default": "", "description": "Additional project context" }, "threshold": { "type": "number", "default": 5, "min": 1, "max": 10, "description": "Complexity threshold for expansion recommendation" }, "useResearch": { "type": "boolean", "default": false, "description": "Use research mode for deeper analysis" }, "hasCodebaseAnalysis": { "type": "boolean", "default": false, "description": "Whether codebase analysis is available" }, "projectRoot": { "type": "string", "default": "", "description": "Project root path for context" } }, "prompts": { "default": { "system": "You are an expert software architect and project manager analyzing task complexity. Respond only with the requested valid JSON array.", "user": "{{#if hasCodebaseAnalysis}}## IMPORTANT: Codebase Analysis Required\n\nYou have access to powerful codebase analysis tools. Before analyzing task complexity:\n\n1. Use the Glob tool to explore the project structure and understand the codebase size\n2. Use the Grep tool to search for existing implementations related to each task\n3. Use the Read tool to examine key files that would be affected by these tasks\n4. Understand the current implementation state, patterns used, and technical debt\n\nBased on your codebase analysis:\n- Assess complexity based on ACTUAL code that needs to be modified/created\n- Consider existing abstractions and patterns that could simplify implementation\n- Identify tasks that require refactoring vs. greenfield development\n- Factor in dependencies between existing code and new features\n- Provide more accurate subtask recommendations based on real code structure\n\nProject Root: {{projectRoot}}\n\n{{/if}}Analyze the following tasks to determine their complexity (1-10 scale) and recommend the number of subtasks for expansion. Provide a brief reasoning and an initial expansion prompt for each.{{#if useResearch}} Consider current best practices, common implementation patterns, and industry standards in your analysis.{{/if}}\n\nTasks:\n{{{json tasks}}}\n{{#if gatheredContext}}\n\n# Project Context\n\n{{gatheredContext}}\n{{/if}}\n\nRespond ONLY with a valid JSON array matching the schema:\n[\n {\n \"taskId\": <number>,\n \"taskTitle\": \"<string>\",\n \"complexityScore\": <number 1-10>,\n \"recommendedSubtasks\": <number>,\n \"expansionPrompt\": \"<string>\",\n \"reasoning\": \"<string>\"\n },\n ...\n]\n\nDo not include any explanatory text, markdown formatting, or code block markers before or after the JSON array." } } } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/generate-task-files.js: -------------------------------------------------------------------------------- ```javascript /** * generate-task-files.js * Direct function implementation for generating task files from tasks.json */ import { generateTaskFiles } from '../../../../scripts/modules/task-manager.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; /** * Direct function wrapper for generateTaskFiles with error handling. * * @param {Object} args - Command arguments containing tasksJsonPath and outputDir. * @param {string} args.tasksJsonPath - Path to the tasks.json file. * @param {string} args.outputDir - Path to the output directory. * @param {string} args.projectRoot - Project root path (for MCP/env fallback) * @param {string} args.tag - Tag for the task (optional) * @param {Object} log - Logger object. * @returns {Promise<Object>} - Result object with success status and data/error information. */ export async function generateTaskFilesDirect(args, log) { // Destructure expected args const { tasksJsonPath, outputDir, projectRoot, tag } = args; try { log.info(`Generating task files with args: ${JSON.stringify(args)}`); // Check if paths were provided if (!tasksJsonPath) { const errorMessage = 'tasksJsonPath is required but was not provided.'; log.error(errorMessage); return { success: false, error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } if (!outputDir) { const errorMessage = 'outputDir is required but was not provided.'; log.error(errorMessage); return { success: false, error: { code: 'MISSING_ARGUMENT', message: errorMessage } }; } // Use the provided paths const tasksPath = tasksJsonPath; const resolvedOutputDir = outputDir; log.info(`Generating task files from ${tasksPath} to ${resolvedOutputDir}`); // Execute core generateTaskFiles function in a separate try/catch try { // Enable silent mode to prevent logs from being written to stdout enableSilentMode(); // Pass projectRoot and tag so the core respects context generateTaskFiles(tasksPath, resolvedOutputDir, { projectRoot, tag, mcpLog: log }); // Restore normal logging after task generation disableSilentMode(); } catch (genError) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in generateTaskFiles: ${genError.message}`); return { success: false, error: { code: 'GENERATE_FILES_ERROR', message: genError.message } }; } // Return success with file paths return { success: true, data: { message: `Successfully generated task files`, tasksPath: tasksPath, outputDir: resolvedOutputDir, taskFiles: 'Individual task files have been generated in the output directory' } }; } catch (error) { // Make sure to restore normal logging if an outer error occurs disableSilentMode(); log.error(`Error generating task files: ${error.message}`); return { success: false, error: { code: 'GENERATE_TASKS_ERROR', message: error.message || 'Unknown error generating task files' } }; } } ``` -------------------------------------------------------------------------------- /.github/workflows/extension-release.yml: -------------------------------------------------------------------------------- ```yaml name: Extension Release on: push: tags: - "extension@*" permissions: contents: write concurrency: extension-release-${{ github.ref }} jobs: publish-extension: runs-on: ubuntu-latest environment: extension-release steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Cache node_modules uses: actions/cache@v4 with: path: | node_modules */*/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install Extension Dependencies working-directory: apps/extension run: npm ci timeout-minutes: 5 - name: Type Check Extension working-directory: apps/extension run: npm run check-types env: FORCE_COLOR: 1 - name: Build Extension working-directory: apps/extension run: npm run build env: FORCE_COLOR: 1 - name: Package Extension working-directory: apps/extension run: npm run package env: FORCE_COLOR: 1 - name: Create VSIX Package working-directory: apps/extension/vsix-build run: npx vsce package --no-dependencies env: FORCE_COLOR: 1 - name: Get VSIX filename id: vsix-info working-directory: apps/extension/vsix-build run: | VSIX_FILE=$(find . -maxdepth 1 -name "*.vsix" -type f | head -n1 | xargs basename) if [ -z "$VSIX_FILE" ]; then echo "Error: No VSIX file found" exit 1 fi echo "vsix-filename=$VSIX_FILE" >> "$GITHUB_OUTPUT" echo "Found VSIX: $VSIX_FILE" - name: Publish to VS Code Marketplace working-directory: apps/extension/vsix-build run: npx vsce publish --packagePath "${{ steps.vsix-info.outputs.vsix-filename }}" env: VSCE_PAT: ${{ secrets.VSCE_PAT }} FORCE_COLOR: 1 - name: Install Open VSX CLI run: npm install -g ovsx - name: Publish to Open VSX Registry working-directory: apps/extension/vsix-build run: ovsx publish "${{ steps.vsix-info.outputs.vsix-filename }}" env: OVSX_PAT: ${{ secrets.OVSX_PAT }} FORCE_COLOR: 1 - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: name: extension-release-${{ github.ref_name }} path: | apps/extension/vsix-build/*.vsix apps/extension/dist/ retention-days: 90 notify-success: needs: publish-extension if: success() runs-on: ubuntu-latest steps: - name: Success Notification run: | echo "🎉 Extension ${{ github.ref_name }} successfully published!" echo "📦 Available on VS Code Marketplace" echo "🌍 Available on Open VSX Registry" echo "🏷️ GitHub release created: ${{ github.ref_name }}" ``` -------------------------------------------------------------------------------- /tests/unit/mcp/tools/__mocks__/move-task.js: -------------------------------------------------------------------------------- ```javascript /** * Mock for move-task module * Provides mock implementations for testing scenarios */ // Mock the moveTask function from the core module const mockMoveTask = jest .fn() .mockImplementation( async (tasksPath, sourceId, destinationId, generateFiles, options) => { // Simulate successful move operation return { success: true, sourceId, destinationId, message: `Successfully moved task ${sourceId} to ${destinationId}`, ...options }; } ); // Mock the moveTaskDirect function const mockMoveTaskDirect = jest .fn() .mockImplementation(async (args, log, context = {}) => { // Validate required parameters if (!args.sourceId) { return { success: false, error: { message: 'Source ID is required', code: 'MISSING_SOURCE_ID' } }; } if (!args.destinationId) { return { success: false, error: { message: 'Destination ID is required', code: 'MISSING_DESTINATION_ID' } }; } // Simulate successful move return { success: true, data: { sourceId: args.sourceId, destinationId: args.destinationId, message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`, tag: args.tag, projectRoot: args.projectRoot } }; }); // Mock the moveTaskCrossTagDirect function const mockMoveTaskCrossTagDirect = jest .fn() .mockImplementation(async (args, log, context = {}) => { // Validate required parameters if (!args.sourceIds) { return { success: false, error: { message: 'Source IDs are required', code: 'MISSING_SOURCE_IDS' } }; } if (!args.sourceTag) { return { success: false, error: { message: 'Source tag is required for cross-tag moves', code: 'MISSING_SOURCE_TAG' } }; } if (!args.targetTag) { return { success: false, error: { message: 'Target tag is required for cross-tag moves', code: 'MISSING_TARGET_TAG' } }; } if (args.sourceTag === args.targetTag) { return { success: false, error: { message: `Source and target tags are the same ("${args.sourceTag}")`, code: 'SAME_SOURCE_TARGET_TAG' } }; } // Simulate successful cross-tag move return { success: true, data: { sourceIds: args.sourceIds, sourceTag: args.sourceTag, targetTag: args.targetTag, message: `Successfully moved tasks ${args.sourceIds} from ${args.sourceTag} to ${args.targetTag}`, withDependencies: args.withDependencies || false, ignoreDependencies: args.ignoreDependencies || false } }; }); // Mock the registerMoveTaskTool function const mockRegisterMoveTaskTool = jest.fn().mockImplementation((server) => { // Simulate tool registration server.addTool({ name: 'move_task', description: 'Move a task or subtask to a new position', parameters: {}, execute: jest.fn() }); }); // Export the mock functions export { mockMoveTask, mockMoveTaskDirect, mockMoveTaskCrossTagDirect, mockRegisterMoveTaskTool }; // Default export for the main moveTask function export default mockMoveTask; ``` -------------------------------------------------------------------------------- /tests/integration/profiles/gemini-init-functionality.test.js: -------------------------------------------------------------------------------- ```javascript import fs from 'fs'; import path from 'path'; import { geminiProfile } from '../../../src/profiles/gemini.js'; describe('Gemini Profile Initialization Functionality', () => { let geminiProfileContent; beforeAll(() => { const geminiJsPath = path.join( process.cwd(), 'src', 'profiles', 'gemini.js' ); geminiProfileContent = fs.readFileSync(geminiJsPath, 'utf8'); }); test('gemini.js has correct profile configuration', () => { // Check for explicit, non-default values in the source file expect(geminiProfileContent).toContain("name: 'gemini'"); expect(geminiProfileContent).toContain("displayName: 'Gemini'"); expect(geminiProfileContent).toContain("url: 'codeassist.google'"); expect(geminiProfileContent).toContain( "docsUrl: 'github.com/google-gemini/gemini-cli'" ); expect(geminiProfileContent).toContain("profileDir: '.gemini'"); expect(geminiProfileContent).toContain("rulesDir: '.'"); // non-default expect(geminiProfileContent).toContain("mcpConfigName: 'settings.json'"); // non-default expect(geminiProfileContent).toContain('includeDefaultRules: false'); // non-default expect(geminiProfileContent).toContain("'AGENTS.md': 'GEMINI.md'"); // Check the final computed properties on the profile object expect(geminiProfile.profileName).toBe('gemini'); expect(geminiProfile.displayName).toBe('Gemini'); expect(geminiProfile.profileDir).toBe('.gemini'); expect(geminiProfile.rulesDir).toBe('.'); expect(geminiProfile.mcpConfig).toBe(true); // computed from mcpConfigName expect(geminiProfile.mcpConfigName).toBe('settings.json'); expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json'); // computed expect(geminiProfile.includeDefaultRules).toBe(false); expect(geminiProfile.fileMap['AGENTS.md']).toBe('GEMINI.md'); }); test('gemini.js has no lifecycle functions', () => { // Gemini profile should not have any lifecycle functions expect(geminiProfileContent).not.toContain('function onAddRulesProfile'); expect(geminiProfileContent).not.toContain('function onRemoveRulesProfile'); expect(geminiProfileContent).not.toContain( 'function onPostConvertRulesProfile' ); expect(geminiProfileContent).not.toContain('onAddRulesProfile:'); expect(geminiProfileContent).not.toContain('onRemoveRulesProfile:'); expect(geminiProfileContent).not.toContain('onPostConvertRulesProfile:'); }); test('gemini.js uses custom MCP config name', () => { // Gemini uses settings.json instead of mcp.json expect(geminiProfileContent).toContain("mcpConfigName: 'settings.json'"); // Should not contain mcp.json as a config value (comments are OK) expect(geminiProfileContent).not.toMatch( /mcpConfigName:\s*['"]mcp\.json['"]/ ); }); test('gemini.js has minimal implementation', () => { // Verify the profile is minimal (no extra functions or logic) const lines = geminiProfileContent.split('\n'); const nonEmptyLines = lines.filter((line) => line.trim().length > 0); // Should be around 16 lines (import, export, and profile definition) expect(nonEmptyLines.length).toBeLessThan(20); }); }); ``` -------------------------------------------------------------------------------- /packages/tm-core/src/config/services/config-loader.service.ts: -------------------------------------------------------------------------------- ```typescript /** * @fileoverview Configuration Loader Service * Responsible for loading configuration from various file sources */ import { promises as fs } from 'node:fs'; import path from 'node:path'; import type { PartialConfiguration } from '../../interfaces/configuration.interface.js'; import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.js'; import { ERROR_CODES, TaskMasterError } from '../../errors/task-master-error.js'; /** * ConfigLoader handles loading configuration from files * Single responsibility: File-based configuration loading */ export class ConfigLoader { private localConfigPath: string; private globalConfigPath: string; constructor(projectRoot: string) { this.localConfigPath = path.join(projectRoot, '.taskmaster', 'config.json'); this.globalConfigPath = path.join( process.env.HOME || '', '.taskmaster', 'config.json' ); } /** * Get default configuration values */ getDefaultConfig(): PartialConfiguration { return { models: { main: DEFAULT_CONFIG_VALUES.MODELS.MAIN, fallback: DEFAULT_CONFIG_VALUES.MODELS.FALLBACK }, storage: { type: DEFAULT_CONFIG_VALUES.STORAGE.TYPE, encoding: DEFAULT_CONFIG_VALUES.STORAGE.ENCODING, enableBackup: false, maxBackups: DEFAULT_CONFIG_VALUES.STORAGE.MAX_BACKUPS, enableCompression: false, atomicOperations: true }, version: DEFAULT_CONFIG_VALUES.VERSION }; } /** * Load local project configuration */ async loadLocalConfig(): Promise<PartialConfiguration | null> { try { const configData = await fs.readFile(this.localConfigPath, 'utf-8'); return JSON.parse(configData); } catch (error: any) { if (error.code === 'ENOENT') { // File doesn't exist, return null console.debug('No local config.json found, using defaults'); return null; } throw new TaskMasterError( 'Failed to load local configuration', ERROR_CODES.CONFIG_ERROR, { configPath: this.localConfigPath }, error ); } } /** * Load global user configuration * @future-implementation Full implementation pending */ async loadGlobalConfig(): Promise<PartialConfiguration | null> { // TODO: Implement in future PR // For now, return null to indicate no global config return null; // Future implementation: // try { // const configData = await fs.readFile(this.globalConfigPath, 'utf-8'); // return JSON.parse(configData); // } catch (error: any) { // if (error.code === 'ENOENT') { // return null; // } // throw new TaskMasterError( // 'Failed to load global configuration', // ERROR_CODES.CONFIG_ERROR, // { configPath: this.globalConfigPath }, // error // ); // } } /** * Check if local config exists */ async hasLocalConfig(): Promise<boolean> { try { await fs.access(this.localConfigPath); return true; } catch { return false; } } /** * Check if global config exists */ async hasGlobalConfig(): Promise<boolean> { try { await fs.access(this.globalConfigPath); return true; } catch { return false; } } } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/parse-prd.js: -------------------------------------------------------------------------------- ```javascript /** * tools/parsePRD.js * Tool to parse PRD document and generate tasks */ import { z } from 'zod'; import { handleApiResult, withNormalizedProjectRoot, createErrorResponse, checkProgressCapability } from './utils.js'; import { parsePRDDirect } from '../core/task-master-core.js'; import { PRD_FILE, TASKMASTER_DOCS_DIR, TASKMASTER_TASKS_FILE } from '../../../src/constants/paths.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the parse_prd tool * @param {Object} server - FastMCP server instance */ export function registerParsePRDTool(server) { server.addTool({ name: 'parse_prd', description: `Parse a Product Requirements Document (PRD) text file to automatically generate initial tasks. Reinitializing the project is not necessary to run this tool. It is recommended to run parse-prd after initializing the project and creating/importing a prd.txt file in the project root's ${TASKMASTER_DOCS_DIR} directory.`, parameters: z.object({ input: z .string() .optional() .default(PRD_FILE) .describe('Absolute path to the PRD document file (.txt, .md, etc.)'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on'), output: z .string() .optional() .describe( `Output path for tasks.json file (default: ${TASKMASTER_TASKS_FILE})` ), numTasks: z .string() .optional() .describe( 'Approximate number of top-level tasks to generate (default: 10). As the agent, if you have enough information, ensure to enter a number of tasks that would logically scale with project complexity. Setting to 0 will allow Taskmaster to determine the appropriate number of tasks based on the complexity of the PRD. Avoid entering numbers above 50 due to context window limitations.' ), force: z .boolean() .optional() .default(false) .describe('Overwrite existing output file without prompting.'), research: z .boolean() .optional() .describe( 'Enable Taskmaster to use the research role for potentially more informed task generation. Requires appropriate API key.' ), append: z .boolean() .optional() .describe('Append generated tasks to existing file.') }), execute: withNormalizedProjectRoot( async (args, { log, session, reportProgress }) => { try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); const progressCapability = checkProgressCapability( reportProgress, log ); const result = await parsePRDDirect( { ...args, tag: resolvedTag }, log, { session, reportProgress: progressCapability } ); return handleApiResult( result, log, 'Error parsing PRD', undefined, args.projectRoot ); } catch (error) { log.error(`Error in parse_prd: ${error.message}`); return createErrorResponse(`Failed to parse PRD: ${error.message}`); } } ) }); } ``` -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- ```yaml name: Pre-Release (RC) on: workflow_dispatch: # Allows manual triggering from GitHub UI/API concurrency: pre-release-${{ github.ref_name }} jobs: rc: runs-on: ubuntu-latest # Only allow pre-releases on non-main branches if: github.ref != 'refs/heads/main' environment: extension-release steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 20 cache: "npm" - name: Cache node_modules uses: actions/cache@v4 with: path: | node_modules */*/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install dependencies run: npm ci timeout-minutes: 2 - name: Enter RC mode (if not already in RC mode) run: | # Check if we're in pre-release mode with the "rc" tag if [ -f .changeset/pre.json ]; then MODE=$(jq -r '.mode' .changeset/pre.json 2>/dev/null || echo '') TAG=$(jq -r '.tag' .changeset/pre.json 2>/dev/null || echo '') if [ "$MODE" = "exit" ]; then echo "Pre-release mode is in 'exit' state, re-entering RC mode..." npx changeset pre enter rc elif [ "$MODE" = "pre" ] && [ "$TAG" != "rc" ]; then echo "In pre-release mode but with wrong tag ($TAG), switching to RC..." npx changeset pre exit npx changeset pre enter rc elif [ "$MODE" = "pre" ] && [ "$TAG" = "rc" ]; then echo "Already in RC pre-release mode" else echo "Unknown mode state: $MODE, entering RC mode..." npx changeset pre enter rc fi else echo "No pre.json found, entering RC mode..." npx changeset pre enter rc fi - name: Version RC packages run: npx changeset version env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Run format run: npm run format env: FORCE_COLOR: 1 - name: Build packages run: npm run turbo:build env: NODE_ENV: production FORCE_COLOR: 1 TM_PUBLIC_BASE_DOMAIN: ${{ secrets.TM_PUBLIC_BASE_DOMAIN }} TM_PUBLIC_SUPABASE_URL: ${{ secrets.TM_PUBLIC_SUPABASE_URL }} TM_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.TM_PUBLIC_SUPABASE_ANON_KEY }} - name: Create Release Candidate Pull Request or Publish Release Candidate to npm uses: changesets/action@v1 with: publish: npx changeset publish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Commit & Push changes uses: actions-js/push@master with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: ${{ github.ref }} message: "chore: rc version bump" ``` -------------------------------------------------------------------------------- /apps/extension/src/webview/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- ```typescript /** * Error Boundary Component */ import React from 'react'; interface ErrorBoundaryState { hasError: boolean; error?: Error; errorInfo?: React.ErrorInfo; } interface ErrorBoundaryProps { children: React.ReactNode; onError?: (error: Error, errorInfo: React.ErrorInfo) => void; } export class ErrorBoundary extends React.Component< ErrorBoundaryProps, ErrorBoundaryState > { constructor(props: ErrorBoundaryProps) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error('React Error Boundary caught:', error, errorInfo); // Log to extension if (this.props.onError) { this.props.onError(error, errorInfo); } // Send error to extension for centralized handling if (window.acquireVsCodeApi) { const vscode = window.acquireVsCodeApi(); vscode.postMessage({ type: 'reactError', data: { message: error.message, stack: error.stack, componentStack: errorInfo.componentStack, timestamp: Date.now() } }); } } render() { if (this.state.hasError) { return ( <div className="min-h-screen flex items-center justify-center bg-vscode-background"> <div className="max-w-md mx-auto text-center p-6"> <div className="w-16 h-16 mx-auto mb-4 text-red-400"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.732 19c-.77.833.192 2.5 1.732 2.5z" /> </svg> </div> <h2 className="text-xl font-semibold text-vscode-foreground mb-2"> Something went wrong </h2> <p className="text-vscode-foreground/70 mb-4"> The Task Master Kanban board encountered an unexpected error. </p> <div className="space-y-2"> <button onClick={() => this.setState({ hasError: false, error: undefined, errorInfo: undefined }) } className="w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md transition-colors" > Try Again </button> <button onClick={() => window.location.reload()} className="w-full px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md transition-colors" > Reload Extension </button> </div> {this.state.error && ( <details className="mt-4 text-left"> <summary className="text-sm text-vscode-foreground/50 cursor-pointer"> Error Details </summary> <pre className="mt-2 text-xs text-vscode-foreground/70 bg-vscode-input/30 p-2 rounded overflow-auto max-h-32"> {this.state.error.message} {this.state.error.stack && `\n\n${this.state.error.stack}`} </pre> </details> )} </div> </div> ); } return this.props.children; } } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/rename-tag.js: -------------------------------------------------------------------------------- ```javascript /** * rename-tag.js * Direct function implementation for renaming a tag */ import { renameTag } from '../../../../scripts/modules/task-manager/tag-management.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; import { createLogWrapper } from '../../tools/utils.js'; /** * Direct function wrapper for renaming a tag with error handling. * * @param {Object} args - Command arguments * @param {string} args.oldName - Current name of the tag to rename * @param {string} args.newName - New name for the tag * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool) * @param {string} [args.projectRoot] - Project root path * @param {Object} log - Logger object * @param {Object} context - Additional context (session) * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function renameTagDirect(args, log, context = {}) { // Destructure expected args const { tasksJsonPath, oldName, newName, projectRoot } = args; const { session } = context; // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); // Create logger wrapper using the utility const mcpLog = createLogWrapper(log); try { // Check if tasksJsonPath was provided if (!tasksJsonPath) { log.error('renameTagDirect called without tasksJsonPath'); disableSilentMode(); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } // Check required parameters if (!oldName || typeof oldName !== 'string') { log.error('Missing required parameter: oldName'); disableSilentMode(); return { success: false, error: { code: 'MISSING_PARAMETER', message: 'Old tag name is required and must be a string' } }; } if (!newName || typeof newName !== 'string') { log.error('Missing required parameter: newName'); disableSilentMode(); return { success: false, error: { code: 'MISSING_PARAMETER', message: 'New tag name is required and must be a string' } }; } log.info(`Renaming tag from "${oldName}" to "${newName}"`); // Call the renameTag function const result = await renameTag( tasksJsonPath, oldName, newName, {}, // options (empty for now) { session, mcpLog, projectRoot }, 'json' // outputFormat - use 'json' to suppress CLI UI ); // Restore normal logging disableSilentMode(); return { success: true, data: { oldName: result.oldName, newName: result.newName, renamed: result.renamed, taskCount: result.taskCount, wasCurrentTag: result.wasCurrentTag, message: `Successfully renamed tag from "${result.oldName}" to "${result.newName}"` } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in renameTagDirect: ${error.message}`); return { success: false, error: { code: error.code || 'RENAME_TAG_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /apps/extension/src/webview/types/index.ts: -------------------------------------------------------------------------------- ```typescript /** * Shared types for the webview application */ export interface TaskMasterTask { id: string; title: string; description: string; status: 'pending' | 'in-progress' | 'done' | 'deferred' | 'review'; priority: 'high' | 'medium' | 'low'; dependencies?: string[]; details?: string; testStrategy?: string; subtasks?: TaskMasterTask[]; complexityScore?: number; } export interface TaskUpdates { title?: string; description?: string; details?: string; priority?: TaskMasterTask['priority']; testStrategy?: string; dependencies?: string[]; } export interface WebviewMessage { type: string; requestId?: string; data?: any; success?: boolean; [key: string]: any; } export interface ToastNotification { id: string; type: 'success' | 'info' | 'warning' | 'error'; title: string; message: string; duration?: number; } export interface AppState { tasks: TaskMasterTask[]; loading: boolean; error?: string; requestId: number; isConnected: boolean; connectionStatus: string; editingTask?: { taskId: string | null; editData?: TaskMasterTask }; polling: { isActive: boolean; errorCount: number; lastUpdate?: number; isUserInteracting: boolean; isOfflineMode: boolean; reconnectAttempts: number; maxReconnectAttempts: number; lastSuccessfulConnection?: number; connectionStatus: 'online' | 'offline' | 'reconnecting'; }; toastNotifications: ToastNotification[]; currentView: 'kanban' | 'task-details' | 'config'; selectedTaskId?: string; // Tag-related state currentTag: string; availableTags: string[]; } export type AppAction = | { type: 'SET_TASKS'; payload: TaskMasterTask[] } | { type: 'SET_LOADING'; payload: boolean } | { type: 'SET_ERROR'; payload: string } | { type: 'CLEAR_ERROR' } | { type: 'INCREMENT_REQUEST_ID' } | { type: 'UPDATE_TASK_STATUS'; payload: { taskId: string; newStatus: TaskMasterTask['status'] }; } | { type: 'UPDATE_TASK_CONTENT'; payload: { taskId: string; updates: TaskUpdates }; } | { type: 'SET_CONNECTION_STATUS'; payload: { isConnected: boolean; status: string }; } | { type: 'SET_EDITING_TASK'; payload: { taskId: string | null; editData?: TaskMasterTask }; } | { type: 'SET_POLLING_STATUS'; payload: { isActive: boolean; errorCount?: number }; } | { type: 'SET_USER_INTERACTING'; payload: boolean } | { type: 'TASKS_UPDATED_FROM_POLLING'; payload: TaskMasterTask[] } | { type: 'SET_NETWORK_STATUS'; payload: { isOfflineMode: boolean; connectionStatus: 'online' | 'offline' | 'reconnecting'; reconnectAttempts?: number; maxReconnectAttempts?: number; lastSuccessfulConnection?: number; }; } | { type: 'LOAD_CACHED_TASKS'; payload: TaskMasterTask[] } | { type: 'ADD_TOAST'; payload: ToastNotification } | { type: 'REMOVE_TOAST'; payload: string } | { type: 'CLEAR_ALL_TOASTS' } | { type: 'NAVIGATE_TO_TASK'; payload: string } | { type: 'NAVIGATE_TO_KANBAN' } | { type: 'NAVIGATE_TO_CONFIG' } | { type: 'SET_CURRENT_TAG'; payload: string } | { type: 'SET_AVAILABLE_TAGS'; payload: string[] } | { type: 'SET_TAG_DATA'; payload: { currentTag: string; availableTags: string[] }; }; ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/add-task.js: -------------------------------------------------------------------------------- ```javascript /** * tools/add-task.js * Tool to add a new task using AI */ import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { addTaskDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the addTask tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerAddTaskTool(server) { server.addTool({ name: 'add_task', description: 'Add a new task using AI', parameters: z.object({ prompt: z .string() .optional() .describe( 'Description of the task to add (required if not using manual fields)' ), title: z .string() .optional() .describe('Task title (for manual task creation)'), description: z .string() .optional() .describe('Task description (for manual task creation)'), details: z .string() .optional() .describe('Implementation details (for manual task creation)'), testStrategy: z .string() .optional() .describe('Test strategy (for manual task creation)'), dependencies: z .string() .optional() .describe('Comma-separated list of task IDs this task depends on'), priority: z .string() .optional() .describe('Task priority (high, medium, low)'), file: z .string() .optional() .describe('Path to the tasks file (default: tasks/tasks.json)'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on'), research: z .boolean() .optional() .describe('Whether to use research capabilities for task creation') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Starting add-task with args: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // Call the direct functionP const result = await addTaskDirect( { tasksJsonPath: tasksJsonPath, prompt: args.prompt, title: args.title, description: args.description, details: args.details, testStrategy: args.testStrategy, dependencies: args.dependencies, priority: args.priority, research: args.research, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); return handleApiResult( result, log, 'Error adding task', undefined, args.projectRoot ); } catch (error) { log.error(`Error in add-task tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /tests/integration/profiles/rules-files-inclusion.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import fs from 'fs'; import path from 'path'; import os from 'os'; import { execSync } from 'child_process'; describe('Rules Files Inclusion in Package', () => { // This test verifies that the required rules files are included in the final package test('package.json includes dist/** in the "files" array for bundled files', () => { // Read the package.json file const packageJsonPath = path.join(process.cwd(), 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // Check if dist/** is included in the files array (which contains bundled output including assets) expect(packageJson.files).toContain('dist/**'); }); test('source rules files exist in assets/rules directory', () => { // Verify that the actual rules files exist const rulesDir = path.join(process.cwd(), 'assets', 'rules'); expect(fs.existsSync(rulesDir)).toBe(true); // Check for the 4 files that currently exist const expectedFiles = [ 'dev_workflow.mdc', 'taskmaster.mdc', 'self_improve.mdc', 'cursor_rules.mdc' ]; expectedFiles.forEach((file) => { const filePath = path.join(rulesDir, file); expect(fs.existsSync(filePath)).toBe(true); }); }); test('roo.js profile contains logic for Roo directory creation and file copying', () => { // Read the roo.js profile file const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js'); const rooJsContent = fs.readFileSync(rooJsPath, 'utf8'); // Check for the main handler function expect( rooJsContent.includes('onAddRulesProfile(targetDir, assetsDir)') ).toBe(true); // Check for general recursive copy of assets/roocode expect( rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)') ).toBe(true); // Check for updated path handling expect(rooJsContent.includes("path.join(assetsDir, 'roocode')")).toBe(true); // Check for .roomodes file copying logic (source and destination paths) expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe( true ); expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe( true ); // Check for mode-specific rule file copying logic expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true); expect( rooJsContent.includes( 'path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)' ) ).toBe(true); expect( rooJsContent.includes( "path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)" ) ).toBe(true); // Check for import of ROO_MODES from profiles.js expect( rooJsContent.includes( "import { ROO_MODES } from '../constants/profiles.js'" ) ).toBe(true); // Verify mode variable is used in the template strings (this confirms modes are being processed) expect(rooJsContent.includes('rules-${mode}')).toBe(true); expect(rooJsContent.includes('${mode}-rules')).toBe(true); }); test('source Roo files exist in assets directory', () => { // Verify that the source files for Roo integration exist expect( fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roo')) ).toBe(true); expect( fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roomodes')) ).toBe(true); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/get-tasks.js: -------------------------------------------------------------------------------- ```javascript /** * tools/get-tasks.js * Tool to get all tasks from Task Master */ import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { listTasksDirect } from '../core/task-master-core.js'; import { resolveTasksPath, resolveComplexityReportPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the getTasks tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerListTasksTool(server) { server.addTool({ name: 'get_tasks', description: 'Get all tasks from Task Master, optionally filtering by status and including subtasks.', parameters: z.object({ status: z .string() .optional() .describe( "Filter tasks by status (e.g., 'pending', 'done') or multiple statuses separated by commas (e.g., 'blocked,deferred')" ), withSubtasks: z .boolean() .optional() .describe( 'Include subtasks nested within their parent tasks in the response' ), file: z .string() .optional() .describe( 'Path to the tasks file (relative to project root or absolute)' ), complexityReport: z .string() .optional() .describe( 'Path to the complexity report file (relative to project root or absolute)' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Getting tasks with filters: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Resolve the path to tasks.json using new path utilities let tasksJsonPath; try { tasksJsonPath = resolveTasksPath(args, log); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // Resolve the path to complexity report let complexityReportPath; try { complexityReportPath = resolveComplexityReportPath( { ...args, tag: resolvedTag }, session ); } catch (error) { log.error(`Error finding complexity report: ${error.message}`); // This is optional, so we don't fail the operation complexityReportPath = null; } const result = await listTasksDirect( { tasksJsonPath: tasksJsonPath, status: args.status, withSubtasks: args.withSubtasks, reportPath: complexityReportPath, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); log.info( `Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks` ); return handleApiResult( result, log, 'Error getting tasks', undefined, args.projectRoot ); } catch (error) { log.error(`Error getting tasks: ${error.message}`); return createErrorResponse(error.message); } }) }); } // We no longer need the formatTasksResponse function as we're returning raw JSON data ``` -------------------------------------------------------------------------------- /tests/unit/profiles/codex-integration.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import fs from 'fs'; import path from 'path'; import os from 'os'; // Mock external modules jest.mock('child_process', () => ({ execSync: jest.fn() })); // Mock console methods jest.mock('console', () => ({ log: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), clear: jest.fn() })); describe('Codex Profile Integration', () => { let tempDir; beforeEach(() => { jest.clearAllMocks(); // Create a temporary directory for testing tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-')); // Spy on fs methods jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => { if (filePath.toString().includes('AGENTS.md')) { return 'Sample AGENTS.md content for Codex integration'; } return '{}'; }); jest.spyOn(fs, 'existsSync').mockImplementation(() => false); jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {}); }); afterEach(() => { // Clean up the temporary directory try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (err) { console.error(`Error cleaning up: ${err.message}`); } }); // Test function that simulates the Codex profile file copying behavior function mockCreateCodexStructure() { // Codex profile copies AGENTS.md to AGENTS.md in project root (same name) const sourceContent = 'Sample AGENTS.md content for Codex integration'; fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), sourceContent); } test('creates AGENTS.md file in project root', () => { // Act mockCreateCodexStructure(); // Assert expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, 'AGENTS.md'), 'Sample AGENTS.md content for Codex integration' ); }); test('does not create any profile directories', () => { // Act mockCreateCodexStructure(); // Assert - Codex profile should not create any directories // Only the temp directory creation calls should exist const mkdirCalls = fs.mkdirSync.mock.calls.filter( (call) => !call[0].includes('task-master-test-') ); expect(mkdirCalls).toHaveLength(0); }); test('does not create MCP configuration files', () => { // Act mockCreateCodexStructure(); // Assert - Codex profile should not create any MCP config files const writeFileCalls = fs.writeFileSync.mock.calls; const mcpConfigCalls = writeFileCalls.filter( (call) => call[0].toString().includes('mcp.json') || call[0].toString().includes('mcp_settings.json') ); expect(mcpConfigCalls).toHaveLength(0); }); test('only creates the target integration guide file', () => { // Act mockCreateCodexStructure(); // Assert - Should only create AGENTS.md const writeFileCalls = fs.writeFileSync.mock.calls; expect(writeFileCalls).toHaveLength(1); expect(writeFileCalls[0][0]).toBe(path.join(tempDir, 'AGENTS.md')); }); test('uses the same filename as source (AGENTS.md)', () => { // Act mockCreateCodexStructure(); // Assert - Codex should keep the same filename unlike Claude which renames it const writeFileCalls = fs.writeFileSync.mock.calls; expect(writeFileCalls[0][0]).toContain('AGENTS.md'); expect(writeFileCalls[0][0]).not.toContain('CLAUDE.md'); }); }); ``` -------------------------------------------------------------------------------- /tests/unit/progress/base-progress-tracker.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; // Mock cli-progress factory before importing BaseProgressTracker jest.unstable_mockModule( '../../../src/progress/cli-progress-factory.js', () => ({ newMultiBar: jest.fn(() => ({ create: jest.fn(() => ({ update: jest.fn() })), stop: jest.fn() })) }) ); const { newMultiBar } = await import( '../../../src/progress/cli-progress-factory.js' ); const { BaseProgressTracker } = await import( '../../../src/progress/base-progress-tracker.js' ); describe('BaseProgressTracker', () => { let tracker; let mockMultiBar; let mockProgressBar; let mockTimeTokensBar; beforeEach(() => { jest.clearAllMocks(); jest.useFakeTimers(); // Setup mocks mockProgressBar = { update: jest.fn() }; mockTimeTokensBar = { update: jest.fn() }; mockMultiBar = { create: jest .fn() .mockReturnValueOnce(mockTimeTokensBar) .mockReturnValueOnce(mockProgressBar), stop: jest.fn() }; newMultiBar.mockReturnValue(mockMultiBar); tracker = new BaseProgressTracker({ numUnits: 10, unitName: 'task' }); }); afterEach(() => { jest.useRealTimers(); }); describe('cleanup', () => { it('should stop and clear timer interval', () => { tracker.start(); expect(tracker._timerInterval).toBeTruthy(); tracker.cleanup(); expect(tracker._timerInterval).toBeNull(); }); it('should stop and null multibar reference', () => { tracker.start(); expect(tracker.multibar).toBeTruthy(); tracker.cleanup(); expect(mockMultiBar.stop).toHaveBeenCalled(); expect(tracker.multibar).toBeNull(); }); it('should null progress bar references', () => { tracker.start(); expect(tracker.timeTokensBar).toBeTruthy(); expect(tracker.progressBar).toBeTruthy(); tracker.cleanup(); expect(tracker.timeTokensBar).toBeNull(); expect(tracker.progressBar).toBeNull(); }); it('should set finished state', () => { tracker.start(); expect(tracker.isStarted).toBe(true); expect(tracker.isFinished).toBe(false); tracker.cleanup(); expect(tracker.isStarted).toBe(false); expect(tracker.isFinished).toBe(true); }); it('should handle cleanup when multibar.stop throws error', () => { tracker.start(); mockMultiBar.stop.mockImplementation(() => { throw new Error('Stop failed'); }); expect(() => tracker.cleanup()).not.toThrow(); expect(tracker.multibar).toBeNull(); }); it('should be safe to call multiple times', () => { tracker.start(); tracker.cleanup(); tracker.cleanup(); tracker.cleanup(); expect(mockMultiBar.stop).toHaveBeenCalledTimes(1); }); it('should be safe to call without starting', () => { expect(() => tracker.cleanup()).not.toThrow(); expect(tracker.multibar).toBeNull(); }); }); describe('stop vs cleanup', () => { it('stop should call cleanup and null multibar reference', () => { tracker.start(); tracker.stop(); // stop() now calls cleanup() which nulls the multibar expect(tracker.multibar).toBeNull(); expect(tracker.isFinished).toBe(true); }); it('cleanup should null multibar preventing getSummary', () => { tracker.start(); tracker.cleanup(); expect(tracker.multibar).toBeNull(); expect(tracker.isFinished).toBe(true); }); }); }); ``` -------------------------------------------------------------------------------- /.taskmaster/reports/task-complexity-report_test-prd-tag.json: -------------------------------------------------------------------------------- ```json { "meta": { "generatedAt": "2025-06-13T06:52:00.611Z", "tasksAnalyzed": 5, "totalTasks": 5, "analysisCount": 5, "thresholdScore": 5, "projectName": "Taskmaster", "usedResearch": true }, "complexityAnalysis": [ { "taskId": 1, "taskTitle": "Setup Project Repository and Node.js Environment", "complexityScore": 4, "recommendedSubtasks": 6, "expansionPrompt": "Break down the setup process into subtasks such as initializing npm, creating directory structure, installing dependencies, configuring package.json, adding configuration files, and setting up the main entry point.", "reasoning": "This task involves several standard setup steps that are well-defined and sequential, with low algorithmic complexity but moderate procedural detail. Each step is independent and can be assigned as a subtask, making the overall complexity moderate." }, { "taskId": 2, "taskTitle": "Implement Core Functionality and CLI Interface", "complexityScore": 7, "recommendedSubtasks": 7, "expansionPrompt": "Expand into subtasks for implementing main logic, designing CLI commands, creating the CLI entry point, integrating business logic, adding error handling, formatting output, and ensuring CLI executability.", "reasoning": "This task requires both application logic and user interface (CLI) development, including error handling and integration. The need to coordinate between core logic and CLI, plus ensuring usability, increases complexity and warrants detailed subtasking." }, { "taskId": 3, "taskTitle": "Implement Testing Suite and Validation", "complexityScore": 6, "recommendedSubtasks": 6, "expansionPrompt": "Divide into subtasks for configuring Jest, writing unit tests, writing integration tests, testing CLI commands, setting up coverage reporting, and preparing test fixtures/mocks.", "reasoning": "Comprehensive testing involves multiple types of tests and configuration steps. While each is straightforward, the breadth of coverage and need for automation and validation increases the overall complexity." }, { "taskId": 4, "taskTitle": "Setup Node.js Project with CLI Interface", "complexityScore": 5, "recommendedSubtasks": 7, "expansionPrompt": "Break down into subtasks for npm initialization, package.json setup, directory structure creation, dependency installation, CLI entry point creation, package.json bin configuration, and CLI executability.", "reasoning": "This task combines project setup with initial CLI implementation. While each step is standard, the integration of CLI elements adds a layer of complexity beyond a basic setup." }, { "taskId": 5, "taskTitle": "Implement Core Functionality with Testing", "complexityScore": 8, "recommendedSubtasks": 8, "expansionPrompt": "Expand into subtasks for implementing each feature (A, B, C), setting up the testing framework, writing tests for each feature, integrating CLI with core logic, and adding coverage reporting.", "reasoning": "This task requires simultaneous development of multiple features, integration with CLI, and comprehensive testing. The coordination and depth required for both implementation and validation make it the most complex among the listed tasks." } ] } ``` -------------------------------------------------------------------------------- /mcp-server/src/index.js: -------------------------------------------------------------------------------- ```javascript import { FastMCP } from 'fastmcp'; import path from 'path'; import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import fs from 'fs'; import logger from './logger.js'; import { registerTaskMasterTools } from './tools/index.js'; import ProviderRegistry from '../../src/provider-registry/index.js'; import { MCPProvider } from './providers/mcp-provider.js'; import packageJson from '../../package.json' with { type: 'json' }; // Load environment variables dotenv.config(); // Constants const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** * Main MCP server class that integrates with Task Master */ class TaskMasterMCPServer { constructor() { this.options = { name: 'Task Master MCP Server', version: packageJson.version }; this.server = new FastMCP(this.options); this.initialized = false; // Bind methods this.init = this.init.bind(this); this.start = this.start.bind(this); this.stop = this.stop.bind(this); // Setup logging this.logger = logger; } /** * Initialize the MCP server with necessary tools and routes */ async init() { if (this.initialized) return; // Pass the manager instance to the tool registration function registerTaskMasterTools(this.server, this.asyncManager); this.initialized = true; return this; } /** * Start the MCP server */ async start() { if (!this.initialized) { await this.init(); } this.server.on('connect', (event) => { event.session.server.sendLoggingMessage({ data: { context: event.session.context, message: `MCP Server connected: ${event.session.name}` }, level: 'info' }); this.registerRemoteProvider(event.session); }); // Start the FastMCP server with increased timeout await this.server.start({ transportType: 'stdio', timeout: 120000 // 2 minutes timeout (in milliseconds) }); return this; } /** * Register both MCP providers with the provider registry */ registerRemoteProvider(session) { // Check if the server has at least one session if (session) { // Make sure session has required capabilities if (!session.clientCapabilities || !session.clientCapabilities.sampling) { session.server.sendLoggingMessage({ data: { context: session.context, message: `MCP session missing required sampling capabilities, providers not registered` }, level: 'info' }); return; } // Register MCP provider with the Provider Registry // Register the unified MCP provider const mcpProvider = new MCPProvider(); mcpProvider.setSession(session); // Register provider with the registry const providerRegistry = ProviderRegistry.getInstance(); providerRegistry.registerProvider('mcp', mcpProvider); session.server.sendLoggingMessage({ data: { context: session.context, message: `MCP Server connected` }, level: 'info' }); } else { session.server.sendLoggingMessage({ data: { context: session.context, message: `No MCP sessions available, providers not registered` }, level: 'warn' }); } } /** * Stop the MCP server */ async stop() { if (this.server) { await this.server.stop(); } } } export default TaskMasterMCPServer; ``` -------------------------------------------------------------------------------- /src/ai-providers/custom-sdk/claude-code/errors.js: -------------------------------------------------------------------------------- ```javascript /** * @fileoverview Error handling utilities for Claude Code provider */ import { APICallError, LoadAPIKeyError } from '@ai-sdk/provider'; /** * @typedef {import('./types.js').ClaudeCodeErrorMetadata} ClaudeCodeErrorMetadata */ /** * Create an API call error with Claude Code specific metadata * @param {Object} params - Error parameters * @param {string} params.message - Error message * @param {string} [params.code] - Error code * @param {number} [params.exitCode] - Process exit code * @param {string} [params.stderr] - Standard error output * @param {string} [params.promptExcerpt] - Excerpt of the prompt * @param {boolean} [params.isRetryable=false] - Whether the error is retryable * @returns {APICallError} */ export function createAPICallError({ message, code, exitCode, stderr, promptExcerpt, isRetryable = false }) { /** @type {ClaudeCodeErrorMetadata} */ const metadata = { code, exitCode, stderr, promptExcerpt }; return new APICallError({ message, isRetryable, url: 'claude-code-cli://command', requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined, data: metadata }); } /** * Create an authentication error * @param {Object} params - Error parameters * @param {string} params.message - Error message * @returns {LoadAPIKeyError} */ export function createAuthenticationError({ message }) { return new LoadAPIKeyError({ message: message || 'Authentication failed. Please ensure Claude Code CLI is properly authenticated.' }); } /** * Create a timeout error * @param {Object} params - Error parameters * @param {string} params.message - Error message * @param {string} [params.promptExcerpt] - Excerpt of the prompt * @param {number} params.timeoutMs - Timeout in milliseconds * @returns {APICallError} */ export function createTimeoutError({ message, promptExcerpt, timeoutMs }) { // Store timeoutMs in metadata for potential use by error handlers /** @type {ClaudeCodeErrorMetadata & { timeoutMs: number }} */ const metadata = { code: 'TIMEOUT', promptExcerpt, timeoutMs }; return new APICallError({ message, isRetryable: true, url: 'claude-code-cli://command', requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : undefined, data: metadata }); } /** * Check if an error is an authentication error * @param {unknown} error - Error to check * @returns {boolean} */ export function isAuthenticationError(error) { if (error instanceof LoadAPIKeyError) return true; if ( error instanceof APICallError && /** @type {ClaudeCodeErrorMetadata} */ (error.data)?.exitCode === 401 ) return true; return false; } /** * Check if an error is a timeout error * @param {unknown} error - Error to check * @returns {boolean} */ export function isTimeoutError(error) { if ( error instanceof APICallError && /** @type {ClaudeCodeErrorMetadata} */ (error.data)?.code === 'TIMEOUT' ) return true; return false; } /** * Get error metadata from an error * @param {unknown} error - Error to extract metadata from * @returns {ClaudeCodeErrorMetadata|undefined} */ export function getErrorMetadata(error) { if (error instanceof APICallError && error.data) { return /** @type {ClaudeCodeErrorMetadata} */ (error.data); } return undefined; } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/list-tasks.js: -------------------------------------------------------------------------------- ```javascript /** * list-tasks.js * Direct function implementation for listing tasks */ import { listTasks } from '../../../../scripts/modules/task-manager.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; /** * Direct function wrapper for listTasks with error handling and caching. * * @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly). * @param {string} args.tasksJsonPath - Path to the tasks.json file. * @param {string} args.reportPath - Path to the report file. * @param {string} args.status - Status of the task. * @param {boolean} args.withSubtasks - Whether to include subtasks. * @param {string} args.projectRoot - Project root path (for MCP/env fallback) * @param {string} args.tag - Tag for the task (optional) * @param {Object} log - Logger object. * @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }. */ export async function listTasksDirect(args, log, context = {}) { // Destructure the explicit tasksJsonPath from args const { tasksJsonPath, reportPath, status, withSubtasks, projectRoot, tag } = args; const { session } = context; if (!tasksJsonPath) { log.error('listTasksDirect called without tasksJsonPath'); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } // Use the explicit tasksJsonPath for cache key const statusFilter = status || 'all'; const withSubtasksFilter = withSubtasks || false; // Define the action function to be executed on cache miss const coreListTasksAction = async () => { try { // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); log.info( `Executing core listTasks function for path: ${tasksJsonPath}, filter: ${statusFilter}, subtasks: ${withSubtasksFilter}` ); // Pass the explicit tasksJsonPath to the core function const resultData = listTasks( tasksJsonPath, statusFilter, reportPath, withSubtasksFilter, 'json', { projectRoot, session, tag } ); if (!resultData || !resultData.tasks) { log.error('Invalid or empty response from listTasks core function'); return { success: false, error: { code: 'INVALID_CORE_RESPONSE', message: 'Invalid or empty response from listTasks core function' } }; } log.info( `Core listTasks function retrieved ${resultData.tasks.length} tasks` ); // Restore normal logging disableSilentMode(); return { success: true, data: resultData }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Core listTasks function failed: ${error.message}`); return { success: false, error: { code: 'LIST_TASKS_CORE_ERROR', message: error.message || 'Failed to list tasks' } }; } }; try { const result = await coreListTasksAction(); log.info('listTasksDirect completed'); return result; } catch (error) { log.error(`Unexpected error during listTasks: ${error.message}`); console.error(error.stack); return { success: false, error: { code: 'UNEXPECTED_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /.github/scripts/check-pre-release-mode.mjs: -------------------------------------------------------------------------------- ``` #!/usr/bin/env node import { readFileSync, existsSync } from 'node:fs'; import { join, dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Get context from command line argument or environment const context = process.argv[2] || process.env.GITHUB_WORKFLOW || 'manual'; function findRootDir(startDir) { let currentDir = resolve(startDir); while (currentDir !== '/') { if (existsSync(join(currentDir, 'package.json'))) { try { const pkg = JSON.parse( readFileSync(join(currentDir, 'package.json'), 'utf8') ); if (pkg.name === 'task-master-ai' || pkg.repository) { return currentDir; } } catch {} } currentDir = dirname(currentDir); } throw new Error('Could not find root directory'); } function checkPreReleaseMode() { console.log('🔍 Checking if branch is in pre-release mode...'); const rootDir = findRootDir(__dirname); const preJsonPath = join(rootDir, '.changeset', 'pre.json'); // Check if pre.json exists if (!existsSync(preJsonPath)) { console.log('✅ Not in active pre-release mode - safe to proceed'); process.exit(0); } try { // Read and parse pre.json const preJsonContent = readFileSync(preJsonPath, 'utf8'); const preJson = JSON.parse(preJsonContent); // Check if we're in active pre-release mode if (preJson.mode === 'pre') { console.error('❌ ERROR: This branch is in active pre-release mode!'); console.error(''); // Provide context-specific error messages if (context === 'Release Check' || context === 'pull_request') { console.error( 'Pre-release mode must be exited before merging to main.' ); console.error(''); console.error( 'To fix this, run the following commands in your branch:' ); console.error(' npx changeset pre exit'); console.error(' git add -u'); console.error(' git commit -m "chore: exit pre-release mode"'); console.error(' git push'); console.error(''); console.error('Then update this pull request.'); } else if (context === 'Release' || context === 'main') { console.error( 'Pre-release mode should only be used on feature branches, not main.' ); console.error(''); console.error('To fix this, run the following commands locally:'); console.error(' npx changeset pre exit'); console.error(' git add -u'); console.error(' git commit -m "chore: exit pre-release mode"'); console.error(' git push origin main'); console.error(''); console.error('Then re-run this workflow.'); } else { console.error('Pre-release mode must be exited before proceeding.'); console.error(''); console.error('To fix this, run the following commands:'); console.error(' npx changeset pre exit'); console.error(' git add -u'); console.error(' git commit -m "chore: exit pre-release mode"'); console.error(' git push'); } process.exit(1); } console.log('✅ Not in active pre-release mode - safe to proceed'); process.exit(0); } catch (error) { console.error(`❌ ERROR: Unable to parse .changeset/pre.json – aborting.`); console.error(`Error details: ${error.message}`); process.exit(1); } } // Run the check checkPreReleaseMode(); ``` -------------------------------------------------------------------------------- /apps/extension/src/services/config-service.ts: -------------------------------------------------------------------------------- ```typescript /** * Config Service * Manages Task Master config.json file operations */ import * as path from 'path'; import * as fs from 'fs/promises'; import * as vscode from 'vscode'; import type { ExtensionLogger } from '../utils/logger'; export interface TaskMasterConfigJson { anthropicApiKey?: string; perplexityApiKey?: string; openaiApiKey?: string; googleApiKey?: string; xaiApiKey?: string; openrouterApiKey?: string; mistralApiKey?: string; debug?: boolean; models?: { main?: string; research?: string; fallback?: string; }; } export class ConfigService { private configCache: TaskMasterConfigJson | null = null; private lastReadTime = 0; private readonly CACHE_DURATION = 5000; // 5 seconds constructor(private logger: ExtensionLogger) {} /** * Read Task Master config.json from the workspace */ async readConfig(): Promise<TaskMasterConfigJson | null> { // Check cache first if ( this.configCache && Date.now() - this.lastReadTime < this.CACHE_DURATION ) { return this.configCache; } try { const workspaceRoot = this.getWorkspaceRoot(); if (!workspaceRoot) { this.logger.warn('No workspace folder found'); return null; } const configPath = path.join(workspaceRoot, '.taskmaster', 'config.json'); try { const configContent = await fs.readFile(configPath, 'utf-8'); const config = JSON.parse(configContent) as TaskMasterConfigJson; // Cache the result this.configCache = config; this.lastReadTime = Date.now(); this.logger.debug('Successfully read Task Master config', { hasModels: !!config.models, debug: config.debug }); return config; } catch (error) { if ((error as any).code === 'ENOENT') { this.logger.debug('Task Master config.json not found'); } else { this.logger.error('Failed to read Task Master config', error); } return null; } } catch (error) { this.logger.error('Error accessing Task Master config', error); return null; } } /** * Get safe config for display (with sensitive data masked) */ async getSafeConfig(): Promise<Record<string, any> | null> { const config = await this.readConfig(); if (!config) { return null; } // Create a safe copy with masked API keys const safeConfig: Record<string, any> = { ...config }; // Mask all API keys const apiKeyFields = [ 'anthropicApiKey', 'perplexityApiKey', 'openaiApiKey', 'googleApiKey', 'xaiApiKey', 'openrouterApiKey', 'mistralApiKey' ]; for (const field of apiKeyFields) { if (safeConfig[field]) { safeConfig[field] = this.maskApiKey(safeConfig[field]); } } return safeConfig; } /** * Mask API key for display * Shows only the last 4 characters for better security */ private maskApiKey(key: string): string { if (key.length <= 4) { return '****'; } const visibleChars = 4; const maskedLength = key.length - visibleChars; return ( '*'.repeat(Math.min(maskedLength, 12)) + key.substring(key.length - visibleChars) ); } /** * Clear cache */ clearCache(): void { this.configCache = null; this.lastReadTime = 0; } /** * Get workspace root path */ private getWorkspaceRoot(): string | undefined { return vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; } } ``` -------------------------------------------------------------------------------- /src/progress/cli-progress-factory.js: -------------------------------------------------------------------------------- ```javascript import cliProgress from 'cli-progress'; /** * Default configuration for progress bars * Extracted to avoid duplication and provide single source of truth */ const DEFAULT_CONFIG = { clearOnComplete: false, stopOnComplete: true, hideCursor: true, barsize: 40 // Standard terminal width for progress bar }; /** * Available presets for progress bar styling * Makes it easy to see what options are available */ const PRESETS = { shades_classic: cliProgress.Presets.shades_classic, shades_grey: cliProgress.Presets.shades_grey, rect: cliProgress.Presets.rect, legacy: cliProgress.Presets.legacy }; /** * Factory class for creating CLI progress bars * Provides a consistent interface for creating both single and multi-bar instances */ export class ProgressBarFactory { constructor(defaultOptions = {}, defaultPreset = PRESETS.shades_classic) { this.defaultOptions = { ...DEFAULT_CONFIG, ...defaultOptions }; this.defaultPreset = defaultPreset; } /** * Creates a new single progress bar * @param {Object} opts - Custom options to override defaults * @param {Object} preset - Progress bar preset for styling * @returns {cliProgress.SingleBar} Configured single progress bar instance */ createSingleBar(opts = {}, preset = null) { const config = this._mergeConfig(opts); const barPreset = preset || this.defaultPreset; return new cliProgress.SingleBar(config, barPreset); } /** * Creates a new multi-bar container * @param {Object} opts - Custom options to override defaults * @param {Object} preset - Progress bar preset for styling * @returns {cliProgress.MultiBar} Configured multi-bar instance */ createMultiBar(opts = {}, preset = null) { const config = this._mergeConfig(opts); const barPreset = preset || this.defaultPreset; return new cliProgress.MultiBar(config, barPreset); } /** * Merges custom options with defaults * @private * @param {Object} customOpts - Custom options to merge * @returns {Object} Merged configuration */ _mergeConfig(customOpts) { return { ...this.defaultOptions, ...customOpts }; } /** * Updates the default configuration * @param {Object} options - New default options */ setDefaultOptions(options) { this.defaultOptions = { ...this.defaultOptions, ...options }; } /** * Updates the default preset * @param {Object} preset - New default preset */ setDefaultPreset(preset) { this.defaultPreset = preset; } } // Create a default factory instance for backward compatibility const defaultFactory = new ProgressBarFactory(); /** * Legacy function for creating a single progress bar * @deprecated Use ProgressBarFactory.createSingleBar() instead * @param {Object} opts - Progress bar options * @returns {cliProgress.SingleBar} Single progress bar instance */ export function newSingle(opts = {}) { return defaultFactory.createSingleBar(opts); } /** * Legacy function for creating a multi-bar * @deprecated Use ProgressBarFactory.createMultiBar() instead * @param {Object} opts - Progress bar options * @returns {cliProgress.MultiBar} Multi-bar instance */ export function newMultiBar(opts = {}) { return defaultFactory.createMultiBar(opts); } // Export presets for easy access export { PRESETS }; // Export the factory class as default export default ProgressBarFactory; ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/add-subtask.js: -------------------------------------------------------------------------------- ```javascript /** * tools/add-subtask.js * Tool for adding subtasks to existing tasks */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { addSubtaskDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the addSubtask tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerAddSubtaskTool(server) { server.addTool({ name: 'add_subtask', description: 'Add a subtask to an existing task', parameters: z.object({ id: z.string().describe('Parent task ID (required)'), taskId: z .string() .optional() .describe('Existing task ID to convert to subtask'), title: z .string() .optional() .describe('Title for the new subtask (when creating a new subtask)'), description: z .string() .optional() .describe('Description for the new subtask'), details: z .string() .optional() .describe('Implementation details for the new subtask'), status: z .string() .optional() .describe("Status for the new subtask (default: 'pending')"), dependencies: z .string() .optional() .describe('Comma-separated list of dependency IDs for the new subtask'), file: z .string() .optional() .describe( 'Absolute path to the tasks file (default: tasks/tasks.json)' ), skipGenerate: z .boolean() .optional() .describe('Skip regenerating task files'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); log.info(`Adding subtask with args: ${JSON.stringify(args)}`); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const result = await addSubtaskDirect( { tasksJsonPath: tasksJsonPath, id: args.id, taskId: args.taskId, title: args.title, description: args.description, details: args.details, status: args.status, dependencies: args.dependencies, skipGenerate: args.skipGenerate, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); if (result.success) { log.info(`Subtask added successfully: ${result.data.message}`); } else { log.error(`Failed to add subtask: ${result.error.message}`); } return handleApiResult( result, log, 'Error adding subtask', undefined, args.projectRoot ); } catch (error) { log.error(`Error in addSubtask tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /src/provider-registry/index.js: -------------------------------------------------------------------------------- ```javascript /** * Provider Registry - Singleton for managing AI providers * * This module implements a singleton registry that allows dynamic registration * of AI providers at runtime, while maintaining compatibility with the existing * static PROVIDERS object in ai-services-unified.js. */ // Singleton instance let instance = null; /** * Provider Registry class - Manages dynamic provider registration */ class ProviderRegistry { constructor() { // Private provider map this._providers = new Map(); // Flag to track initialization this._initialized = false; } /** * Get the singleton instance * @returns {ProviderRegistry} The singleton instance */ static getInstance() { if (!instance) { instance = new ProviderRegistry(); } return instance; } /** * Initialize the registry * @returns {ProviderRegistry} The singleton instance */ initialize() { if (this._initialized) { return this; } this._initialized = true; return this; } /** * Register a provider with the registry * @param {string} providerName - The name of the provider * @param {object} provider - The provider instance * @param {object} options - Additional options for registration * @returns {ProviderRegistry} The singleton instance for chaining */ registerProvider(providerName, provider, options = {}) { if (!providerName || typeof providerName !== 'string') { throw new Error('Provider name must be a non-empty string'); } if (!provider) { throw new Error('Provider instance is required'); } // Validate that provider implements the required interface if ( typeof provider.generateText !== 'function' || typeof provider.streamText !== 'function' || typeof provider.generateObject !== 'function' ) { throw new Error('Provider must implement BaseAIProvider interface'); } // Add provider to the registry this._providers.set(providerName, { instance: provider, options, registeredAt: new Date() }); return this; } /** * Check if a provider exists in the registry * @param {string} providerName - The name of the provider * @returns {boolean} True if the provider exists */ hasProvider(providerName) { return this._providers.has(providerName); } /** * Get a provider from the registry * @param {string} providerName - The name of the provider * @returns {object|null} The provider instance or null if not found */ getProvider(providerName) { const providerEntry = this._providers.get(providerName); return providerEntry ? providerEntry.instance : null; } /** * Get all registered providers * @returns {Map} Map of all registered providers */ getAllProviders() { return new Map(this._providers); } /** * Remove a provider from the registry * @param {string} providerName - The name of the provider * @returns {boolean} True if the provider was removed */ unregisterProvider(providerName) { if (this._providers.has(providerName)) { this._providers.delete(providerName); return true; } return false; } /** * Reset the registry (primarily for testing) */ reset() { this._providers.clear(); this._initialized = false; } } ProviderRegistry.getInstance().initialize(); // Ensure singleton is initialized on import // Export singleton getter export default ProviderRegistry; ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/copy-tag.js: -------------------------------------------------------------------------------- ```javascript /** * copy-tag.js * Direct function implementation for copying a tag */ import { copyTag } from '../../../../scripts/modules/task-manager/tag-management.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; import { createLogWrapper } from '../../tools/utils.js'; /** * Direct function wrapper for copying a tag with error handling. * * @param {Object} args - Command arguments * @param {string} args.sourceName - Name of the source tag to copy from * @param {string} args.targetName - Name of the new tag to create * @param {string} [args.description] - Optional description for the new tag * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool) * @param {string} [args.projectRoot] - Project root path * @param {Object} log - Logger object * @param {Object} context - Additional context (session) * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function copyTagDirect(args, log, context = {}) { // Destructure expected args const { tasksJsonPath, sourceName, targetName, description, projectRoot } = args; const { session } = context; // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); // Create logger wrapper using the utility const mcpLog = createLogWrapper(log); try { // Check if tasksJsonPath was provided if (!tasksJsonPath) { log.error('copyTagDirect called without tasksJsonPath'); disableSilentMode(); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } // Check required parameters if (!sourceName || typeof sourceName !== 'string') { log.error('Missing required parameter: sourceName'); disableSilentMode(); return { success: false, error: { code: 'MISSING_PARAMETER', message: 'Source tag name is required and must be a string' } }; } if (!targetName || typeof targetName !== 'string') { log.error('Missing required parameter: targetName'); disableSilentMode(); return { success: false, error: { code: 'MISSING_PARAMETER', message: 'Target tag name is required and must be a string' } }; } log.info(`Copying tag from "${sourceName}" to "${targetName}"`); // Prepare options const options = { description }; // Call the copyTag function const result = await copyTag( tasksJsonPath, sourceName, targetName, options, { session, mcpLog, projectRoot }, 'json' // outputFormat - use 'json' to suppress CLI UI ); // Restore normal logging disableSilentMode(); return { success: true, data: { sourceName: result.sourceName, targetName: result.targetName, copied: result.copied, tasksCopied: result.tasksCopied, description: result.description, message: `Successfully copied tag from "${result.sourceName}" to "${result.targetName}"` } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in copyTagDirect: ${error.message}`); return { success: false, error: { code: error.code || 'COPY_TAG_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /scripts/modules/task-manager/parse-prd/parse-prd-config.js: -------------------------------------------------------------------------------- ```javascript /** * Configuration classes and schemas for PRD parsing */ import { z } from 'zod'; import { TASK_PRIORITY_OPTIONS } from '../../../../src/constants/task-priority.js'; import { getCurrentTag, isSilentMode, log } from '../../utils.js'; import { Duration } from '../../../../src/utils/timeout-manager.js'; import { hasCodebaseAnalysis } from '../../config-manager.js'; // ============================================================================ // SCHEMAS // ============================================================================ // Define the Zod schema for a SINGLE task object export const prdSingleTaskSchema = z.object({ id: z.number(), title: z.string().min(1), description: z.string().min(1), details: z.string(), testStrategy: z.string(), priority: z.enum(TASK_PRIORITY_OPTIONS), dependencies: z.array(z.number()), status: z.string() }); // Define the Zod schema for the ENTIRE expected AI response object export const prdResponseSchema = z.object({ tasks: z.array(prdSingleTaskSchema), metadata: z.object({ projectName: z.string(), totalTasks: z.number(), sourceFile: z.string(), generatedAt: z.string() }) }); // ============================================================================ // CONFIGURATION CLASSES // ============================================================================ /** * Configuration object for PRD parsing */ export class PrdParseConfig { constructor(prdPath, tasksPath, numTasks, options = {}) { this.prdPath = prdPath; this.tasksPath = tasksPath; this.numTasks = numTasks; this.force = options.force || false; this.append = options.append || false; this.research = options.research || false; this.reportProgress = options.reportProgress; this.mcpLog = options.mcpLog; this.session = options.session; this.projectRoot = options.projectRoot; this.tag = options.tag; this.streamingTimeout = options.streamingTimeout || Duration.seconds(180).milliseconds; // Derived values this.targetTag = this.tag || getCurrentTag(this.projectRoot) || 'master'; this.isMCP = !!this.mcpLog; this.outputFormat = this.isMCP && !this.reportProgress ? 'json' : 'text'; // Feature flag: Temporarily disable streaming, use generateObject instead // TODO: Re-enable streaming once issues are resolved const ENABLE_STREAMING = false; this.useStreaming = ENABLE_STREAMING && (typeof this.reportProgress === 'function' || this.outputFormat === 'text'); } /** * Check if codebase analysis is available (Claude Code or Gemini CLI) */ hasCodebaseAnalysis() { return hasCodebaseAnalysis(this.research, this.projectRoot, this.session); } } /** * Logging configuration and utilities */ export class LoggingConfig { constructor(mcpLog, reportProgress) { this.isMCP = !!mcpLog; this.outputFormat = this.isMCP && !reportProgress ? 'json' : 'text'; this.logFn = mcpLog || { info: (...args) => log('info', ...args), warn: (...args) => log('warn', ...args), error: (...args) => log('error', ...args), debug: (...args) => log('debug', ...args), success: (...args) => log('success', ...args) }; } report(message, level = 'info') { if (this.logFn && typeof this.logFn[level] === 'function') { this.logFn[level](message); } else if (!isSilentMode() && this.outputFormat === 'text') { log(level, message); } } } ``` -------------------------------------------------------------------------------- /.github/workflows/extension-ci.yml: -------------------------------------------------------------------------------- ```yaml name: Extension CI on: push: branches: - main - next paths: - 'apps/extension/**' - '.github/workflows/extension-ci.yml' pull_request: branches: - main - next paths: - 'apps/extension/**' - '.github/workflows/extension-ci.yml' permissions: contents: read jobs: setup: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 20 - name: Cache node_modules uses: actions/cache@v4 with: path: | node_modules */*/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install Extension Dependencies working-directory: apps/extension run: npm ci timeout-minutes: 5 typecheck: needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Restore node_modules uses: actions/cache@v4 with: path: | node_modules */*/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install if cache miss working-directory: apps/extension run: npm ci timeout-minutes: 3 - name: Type Check Extension working-directory: apps/extension run: npm run check-types env: FORCE_COLOR: 1 build: needs: setup runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Restore node_modules uses: actions/cache@v4 with: path: | node_modules */*/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Install if cache miss working-directory: apps/extension run: npm ci timeout-minutes: 3 - name: Build Extension working-directory: apps/extension run: npm run build env: FORCE_COLOR: 1 - name: Package Extension working-directory: apps/extension run: npm run package env: FORCE_COLOR: 1 - name: Verify Package Contents working-directory: apps/extension run: | echo "Checking vsix-build contents..." ls -la vsix-build/ echo "Checking dist contents..." ls -la vsix-build/dist/ echo "Checking package.json exists..." test -f vsix-build/package.json - name: Create VSIX Package (Test) working-directory: apps/extension/vsix-build run: npx vsce package --no-dependencies env: FORCE_COLOR: 1 - name: Upload Extension Artifact uses: actions/upload-artifact@v4 with: name: extension-package path: | apps/extension/vsix-build/*.vsix apps/extension/dist/ retention-days: 30 ```