This is page 8 of 52. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── README.md ├── .claude │ ├── 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 -------------------------------------------------------------------------------- /mcp-server/src/custom-sdk/json-extractor.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * @fileoverview Extract JSON from MCP response, handling markdown blocks and other formatting 3 | */ 4 | 5 | /** 6 | * Extract JSON from MCP AI response 7 | * @param {string} text - The text to extract JSON from 8 | * @returns {string} - The extracted JSON string 9 | */ 10 | export function extractJson(text) { 11 | // Remove markdown code blocks if present 12 | let jsonText = text.trim(); 13 | 14 | // Remove ```json blocks 15 | jsonText = jsonText.replace(/^```json\s*/gm, ''); 16 | jsonText = jsonText.replace(/^```\s*/gm, ''); 17 | jsonText = jsonText.replace(/```\s*$/gm, ''); 18 | 19 | // Remove common TypeScript/JavaScript patterns 20 | jsonText = jsonText.replace(/^const\s+\w+\s*=\s*/, ''); // Remove "const varName = " 21 | jsonText = jsonText.replace(/^let\s+\w+\s*=\s*/, ''); // Remove "let varName = " 22 | jsonText = jsonText.replace(/^var\s+\w+\s*=\s*/, ''); // Remove "var varName = " 23 | jsonText = jsonText.replace(/;?\s*$/, ''); // Remove trailing semicolons 24 | 25 | // Remove explanatory text before JSON (common with AI responses) 26 | jsonText = jsonText.replace(/^.*?(?=\{|\[)/s, ''); 27 | 28 | // Remove explanatory text after JSON 29 | const lines = jsonText.split('\n'); 30 | let jsonEndIndex = -1; 31 | let braceCount = 0; 32 | let inString = false; 33 | let escapeNext = false; 34 | 35 | // Find the end of the JSON by tracking braces 36 | for (let i = 0; i < jsonText.length; i++) { 37 | const char = jsonText[i]; 38 | 39 | if (escapeNext) { 40 | escapeNext = false; 41 | continue; 42 | } 43 | 44 | if (char === '\\') { 45 | escapeNext = true; 46 | continue; 47 | } 48 | 49 | if (char === '"' && !escapeNext) { 50 | inString = !inString; 51 | continue; 52 | } 53 | 54 | if (!inString) { 55 | if (char === '{' || char === '[') { 56 | braceCount++; 57 | } else if (char === '}' || char === ']') { 58 | braceCount--; 59 | if (braceCount === 0) { 60 | jsonEndIndex = i; 61 | break; 62 | } 63 | } 64 | } 65 | } 66 | 67 | if (jsonEndIndex > -1) { 68 | jsonText = jsonText.substring(0, jsonEndIndex + 1); 69 | } 70 | 71 | // Try to extract JSON object or array if previous method didn't work 72 | if (jsonEndIndex === -1) { 73 | const objectMatch = jsonText.match(/{[\s\S]*}/); 74 | const arrayMatch = jsonText.match(/\[[\s\S]*\]/); 75 | 76 | if (objectMatch) { 77 | jsonText = objectMatch[0]; 78 | } else if (arrayMatch) { 79 | jsonText = arrayMatch[0]; 80 | } 81 | } 82 | 83 | // First try to parse as valid JSON 84 | try { 85 | JSON.parse(jsonText); 86 | return jsonText; 87 | } catch { 88 | // If it's not valid JSON, it might be a JavaScript object literal 89 | // Try to convert it to valid JSON 90 | try { 91 | // This is a simple conversion that handles basic cases 92 | // Replace unquoted keys with quoted keys 93 | const converted = jsonText 94 | .replace(/([{,]\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:/g, '$1"$2":') 95 | // Replace single quotes with double quotes 96 | .replace(/'/g, '"') 97 | // Handle trailing commas 98 | .replace(/,\s*([}\]])/g, '$1'); 99 | 100 | // Validate the converted JSON 101 | JSON.parse(converted); 102 | return converted; 103 | } catch { 104 | // If all else fails, return the original text 105 | // The calling code will handle the error appropriately 106 | return text; 107 | } 108 | } 109 | } 110 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/update-subtask.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/update-subtask.js 3 | * Tool to append additional information to a specific subtask 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | handleApiResult, 9 | createErrorResponse, 10 | withNormalizedProjectRoot 11 | } from './utils.js'; 12 | import { updateSubtaskByIdDirect } from '../core/task-master-core.js'; 13 | import { findTasksPath } from '../core/utils/path-utils.js'; 14 | import { resolveTag } from '../../../scripts/modules/utils.js'; 15 | 16 | /** 17 | * Register the update-subtask tool with the MCP server 18 | * @param {Object} server - FastMCP server instance 19 | */ 20 | export function registerUpdateSubtaskTool(server) { 21 | server.addTool({ 22 | name: 'update_subtask', 23 | description: 24 | 'Appends timestamped information to a specific subtask without replacing existing content. If you just want to update the subtask status, use set_task_status instead.', 25 | parameters: z.object({ 26 | id: z 27 | .string() 28 | .describe( 29 | 'ID of the subtask to update in format "parentId.subtaskId" (e.g., "5.2"). Parent ID is the ID of the task that contains the subtask.' 30 | ), 31 | prompt: z.string().describe('Information to add to the subtask'), 32 | research: z 33 | .boolean() 34 | .optional() 35 | .describe('Use Perplexity AI for research-backed updates'), 36 | file: z.string().optional().describe('Absolute path to the tasks file'), 37 | projectRoot: z 38 | .string() 39 | .describe('The directory of the project. Must be an absolute path.'), 40 | tag: z.string().optional().describe('Tag context to operate on') 41 | }), 42 | execute: withNormalizedProjectRoot(async (args, { log, session }) => { 43 | const toolName = 'update_subtask'; 44 | 45 | try { 46 | const resolvedTag = resolveTag({ 47 | projectRoot: args.projectRoot, 48 | tag: args.tag 49 | }); 50 | log.info(`Updating subtask with args: ${JSON.stringify(args)}`); 51 | 52 | let tasksJsonPath; 53 | try { 54 | tasksJsonPath = findTasksPath( 55 | { projectRoot: args.projectRoot, file: args.file }, 56 | log 57 | ); 58 | } catch (error) { 59 | log.error(`${toolName}: Error finding tasks.json: ${error.message}`); 60 | return createErrorResponse( 61 | `Failed to find tasks.json: ${error.message}` 62 | ); 63 | } 64 | 65 | const result = await updateSubtaskByIdDirect( 66 | { 67 | tasksJsonPath: tasksJsonPath, 68 | id: args.id, 69 | prompt: args.prompt, 70 | research: args.research, 71 | projectRoot: args.projectRoot, 72 | tag: resolvedTag 73 | }, 74 | log, 75 | { session } 76 | ); 77 | 78 | if (result.success) { 79 | log.info(`Successfully updated subtask with ID ${args.id}`); 80 | } else { 81 | log.error( 82 | `Failed to update subtask: ${result.error?.message || 'Unknown error'}` 83 | ); 84 | } 85 | 86 | return handleApiResult( 87 | result, 88 | log, 89 | 'Error updating subtask', 90 | undefined, 91 | args.projectRoot 92 | ); 93 | } catch (error) { 94 | log.error( 95 | `Critical error in ${toolName} tool execute: ${error.message}` 96 | ); 97 | return createErrorResponse( 98 | `Internal tool error (${toolName}): ${error.message}` 99 | ); 100 | } 101 | }) 102 | }); 103 | } 104 | ``` -------------------------------------------------------------------------------- /tests/unit/profiles/trae-integration.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { jest } from '@jest/globals'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import os from 'os'; 5 | 6 | // Mock external modules 7 | jest.mock('child_process', () => ({ 8 | execSync: jest.fn() 9 | })); 10 | 11 | // Mock console methods 12 | jest.mock('console', () => ({ 13 | log: jest.fn(), 14 | info: jest.fn(), 15 | warn: jest.fn(), 16 | error: jest.fn(), 17 | clear: jest.fn() 18 | })); 19 | 20 | describe('Trae Integration', () => { 21 | let tempDir; 22 | 23 | beforeEach(() => { 24 | jest.clearAllMocks(); 25 | 26 | // Create a temporary directory for testing 27 | tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-')); 28 | 29 | // Spy on fs methods 30 | jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); 31 | jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => { 32 | if (filePath.toString().includes('.trae')) { 33 | return 'Existing trae rules content'; 34 | } 35 | return '{}'; 36 | }); 37 | jest.spyOn(fs, 'existsSync').mockImplementation(() => false); 38 | jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {}); 39 | }); 40 | 41 | afterEach(() => { 42 | // Clean up the temporary directory 43 | try { 44 | fs.rmSync(tempDir, { recursive: true, force: true }); 45 | } catch (err) { 46 | console.error(`Error cleaning up: ${err.message}`); 47 | } 48 | }); 49 | 50 | // Test function that simulates the createProjectStructure behavior for Trae files 51 | function mockCreateTraeStructure() { 52 | // Create main .trae directory 53 | fs.mkdirSync(path.join(tempDir, '.trae'), { recursive: true }); 54 | 55 | // Create rules directory 56 | fs.mkdirSync(path.join(tempDir, '.trae', 'rules'), { recursive: true }); 57 | 58 | // Create rule files 59 | const ruleFiles = [ 60 | 'dev_workflow.md', 61 | 'taskmaster.md', 62 | 'architecture.md', 63 | 'commands.md', 64 | 'dependencies.md' 65 | ]; 66 | 67 | for (const ruleFile of ruleFiles) { 68 | fs.writeFileSync( 69 | path.join(tempDir, '.trae', 'rules', ruleFile), 70 | `Content for ${ruleFile}` 71 | ); 72 | } 73 | } 74 | 75 | test('creates all required .trae directories', () => { 76 | // Act 77 | mockCreateTraeStructure(); 78 | 79 | // Assert 80 | expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.trae'), { 81 | recursive: true 82 | }); 83 | expect(fs.mkdirSync).toHaveBeenCalledWith( 84 | path.join(tempDir, '.trae', 'rules'), 85 | { recursive: true } 86 | ); 87 | }); 88 | 89 | test('creates rule files for Trae', () => { 90 | // Act 91 | mockCreateTraeStructure(); 92 | 93 | // Assert - check rule files are created 94 | expect(fs.writeFileSync).toHaveBeenCalledWith( 95 | path.join(tempDir, '.trae', 'rules', 'dev_workflow.md'), 96 | expect.any(String) 97 | ); 98 | expect(fs.writeFileSync).toHaveBeenCalledWith( 99 | path.join(tempDir, '.trae', 'rules', 'taskmaster.md'), 100 | expect.any(String) 101 | ); 102 | expect(fs.writeFileSync).toHaveBeenCalledWith( 103 | path.join(tempDir, '.trae', 'rules', 'architecture.md'), 104 | expect.any(String) 105 | ); 106 | }); 107 | 108 | test('does not create MCP configuration files', () => { 109 | // Act 110 | mockCreateTraeStructure(); 111 | 112 | // Assert - Trae doesn't use MCP configuration 113 | expect(fs.writeFileSync).not.toHaveBeenCalledWith( 114 | path.join(tempDir, '.trae', 'mcp.json'), 115 | expect.any(String) 116 | ); 117 | }); 118 | }); 119 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/update.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/update.js 3 | * Tool to update tasks based on new context/prompt 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | handleApiResult, 9 | createErrorResponse, 10 | withNormalizedProjectRoot 11 | } from './utils.js'; 12 | import { updateTasksDirect } from '../core/task-master-core.js'; 13 | import { findTasksPath } from '../core/utils/path-utils.js'; 14 | import { resolveTag } from '../../../scripts/modules/utils.js'; 15 | 16 | /** 17 | * Register the update tool with the MCP server 18 | * @param {Object} server - FastMCP server instance 19 | */ 20 | export function registerUpdateTool(server) { 21 | server.addTool({ 22 | name: 'update', 23 | description: 24 | "Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt. Use 'update_task' instead for a single specific task or 'update_subtask' for subtasks.", 25 | parameters: z.object({ 26 | from: z 27 | .string() 28 | .describe( 29 | "Task ID from which to start updating (inclusive). IMPORTANT: This tool uses 'from', not 'id'" 30 | ), 31 | prompt: z 32 | .string() 33 | .describe('Explanation of changes or new context to apply'), 34 | research: z 35 | .boolean() 36 | .optional() 37 | .describe('Use Perplexity AI for research-backed updates'), 38 | file: z 39 | .string() 40 | .optional() 41 | .describe('Path to the tasks file relative to project root'), 42 | projectRoot: z 43 | .string() 44 | .optional() 45 | .describe( 46 | 'The directory of the project. (Optional, usually from session)' 47 | ), 48 | tag: z.string().optional().describe('Tag context to operate on') 49 | }), 50 | execute: withNormalizedProjectRoot(async (args, { log, session }) => { 51 | const toolName = 'update'; 52 | const { from, prompt, research, file, projectRoot, tag } = args; 53 | 54 | const resolvedTag = resolveTag({ 55 | projectRoot: args.projectRoot, 56 | tag: args.tag 57 | }); 58 | 59 | try { 60 | log.info( 61 | `Executing ${toolName} tool with normalized root: ${projectRoot}` 62 | ); 63 | 64 | let tasksJsonPath; 65 | try { 66 | tasksJsonPath = findTasksPath({ projectRoot, file }, log); 67 | log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`); 68 | } catch (error) { 69 | log.error(`${toolName}: Error finding tasks.json: ${error.message}`); 70 | return createErrorResponse( 71 | `Failed to find tasks.json within project root '${projectRoot}': ${error.message}` 72 | ); 73 | } 74 | 75 | const result = await updateTasksDirect( 76 | { 77 | tasksJsonPath: tasksJsonPath, 78 | from: from, 79 | prompt: prompt, 80 | research: research, 81 | projectRoot: projectRoot, 82 | tag: resolvedTag 83 | }, 84 | log, 85 | { session } 86 | ); 87 | 88 | log.info( 89 | `${toolName}: Direct function result: success=${result.success}` 90 | ); 91 | return handleApiResult( 92 | result, 93 | log, 94 | 'Error updating tasks', 95 | undefined, 96 | args.projectRoot 97 | ); 98 | } catch (error) { 99 | log.error( 100 | `Critical error in ${toolName} tool execute: ${error.message}` 101 | ); 102 | return createErrorResponse( 103 | `Internal tool error (${toolName}): ${error.message}` 104 | ); 105 | } 106 | }) 107 | }); 108 | } 109 | ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/add-dependency.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * add-dependency.js 3 | * Direct function implementation for adding a dependency to a task 4 | */ 5 | 6 | import { addDependency } from '../../../../scripts/modules/dependency-manager.js'; 7 | import { 8 | enableSilentMode, 9 | disableSilentMode 10 | } from '../../../../scripts/modules/utils.js'; 11 | 12 | /** 13 | * Direct function wrapper for addDependency with error handling. 14 | * 15 | * @param {Object} args - Command arguments 16 | * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. 17 | * @param {string|number} args.id - Task ID to add dependency to 18 | * @param {string|number} args.dependsOn - Task ID that will become a dependency 19 | * @param {string} args.tag - Tag for the task (optional) 20 | * @param {string} args.projectRoot - Project root path (for MCP/env fallback) 21 | * @param {Object} log - Logger object 22 | * @returns {Promise<Object>} - Result object with success status and data/error information 23 | */ 24 | export async function addDependencyDirect(args, log) { 25 | // Destructure expected args 26 | const { tasksJsonPath, id, dependsOn, tag, projectRoot } = args; 27 | try { 28 | log.info(`Adding dependency with args: ${JSON.stringify(args)}`); 29 | 30 | // Check if tasksJsonPath was provided 31 | if (!tasksJsonPath) { 32 | log.error('addDependencyDirect called without tasksJsonPath'); 33 | return { 34 | success: false, 35 | error: { 36 | code: 'MISSING_ARGUMENT', 37 | message: 'tasksJsonPath is required' 38 | } 39 | }; 40 | } 41 | 42 | // Validate required parameters 43 | if (!id) { 44 | return { 45 | success: false, 46 | error: { 47 | code: 'INPUT_VALIDATION_ERROR', 48 | message: 'Task ID (id) is required' 49 | } 50 | }; 51 | } 52 | 53 | if (!dependsOn) { 54 | return { 55 | success: false, 56 | error: { 57 | code: 'INPUT_VALIDATION_ERROR', 58 | message: 'Dependency ID (dependsOn) is required' 59 | } 60 | }; 61 | } 62 | 63 | // Use provided path 64 | const tasksPath = tasksJsonPath; 65 | 66 | // Format IDs for the core function 67 | const taskId = 68 | id && id.includes && id.includes('.') ? id : parseInt(id, 10); 69 | const dependencyId = 70 | dependsOn && dependsOn.includes && dependsOn.includes('.') 71 | ? dependsOn 72 | : parseInt(dependsOn, 10); 73 | 74 | log.info( 75 | `Adding dependency: task ${taskId} will depend on ${dependencyId}` 76 | ); 77 | 78 | // Enable silent mode to prevent console logs from interfering with JSON response 79 | enableSilentMode(); 80 | 81 | // Create context object 82 | const context = { projectRoot, tag }; 83 | 84 | // Call the core function using the provided path 85 | await addDependency(tasksPath, taskId, dependencyId, context); 86 | 87 | // Restore normal logging 88 | disableSilentMode(); 89 | 90 | return { 91 | success: true, 92 | data: { 93 | message: `Successfully added dependency: Task ${taskId} now depends on ${dependencyId}`, 94 | taskId: taskId, 95 | dependencyId: dependencyId 96 | } 97 | }; 98 | } catch (error) { 99 | // Make sure to restore normal logging even if there's an error 100 | disableSilentMode(); 101 | 102 | log.error(`Error in addDependencyDirect: ${error.message}`); 103 | return { 104 | success: false, 105 | error: { 106 | code: 'CORE_FUNCTION_ERROR', 107 | message: error.message 108 | } 109 | }; 110 | } 111 | } 112 | ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/complexity-report.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * complexity-report.js 3 | * Direct function implementation for displaying complexity analysis report 4 | */ 5 | 6 | import { 7 | readComplexityReport, 8 | enableSilentMode, 9 | disableSilentMode 10 | } from '../../../../scripts/modules/utils.js'; 11 | 12 | /** 13 | * Direct function wrapper for displaying the complexity report with error handling and caching. 14 | * 15 | * @param {Object} args - Command arguments containing reportPath. 16 | * @param {string} args.reportPath - Explicit path to the complexity report file. 17 | * @param {Object} log - Logger object 18 | * @returns {Promise<Object>} - Result object with success status and data/error information 19 | */ 20 | export async function complexityReportDirect(args, log) { 21 | // Destructure expected args 22 | const { reportPath } = args; 23 | try { 24 | log.info(`Getting complexity report with args: ${JSON.stringify(args)}`); 25 | 26 | // Check if reportPath was provided 27 | if (!reportPath) { 28 | log.error('complexityReportDirect called without reportPath'); 29 | return { 30 | success: false, 31 | error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' } 32 | }; 33 | } 34 | 35 | // Use the provided report path 36 | log.info(`Looking for complexity report at: ${reportPath}`); 37 | 38 | // Generate cache key based on report path 39 | const cacheKey = `complexityReport:${reportPath}`; 40 | 41 | // Define the core action function to read the report 42 | const coreActionFn = async () => { 43 | try { 44 | // Enable silent mode to prevent console logs from interfering with JSON response 45 | enableSilentMode(); 46 | 47 | const report = readComplexityReport(reportPath); 48 | 49 | // Restore normal logging 50 | disableSilentMode(); 51 | 52 | if (!report) { 53 | log.warn(`No complexity report found at ${reportPath}`); 54 | return { 55 | success: false, 56 | error: { 57 | code: 'FILE_NOT_FOUND_ERROR', 58 | message: `No complexity report found at ${reportPath}. Run 'analyze-complexity' first.` 59 | } 60 | }; 61 | } 62 | 63 | return { 64 | success: true, 65 | data: { 66 | report, 67 | reportPath 68 | } 69 | }; 70 | } catch (error) { 71 | // Make sure to restore normal logging even if there's an error 72 | disableSilentMode(); 73 | 74 | log.error(`Error reading complexity report: ${error.message}`); 75 | return { 76 | success: false, 77 | error: { 78 | code: 'READ_ERROR', 79 | message: error.message 80 | } 81 | }; 82 | } 83 | }; 84 | 85 | // Use the caching utility 86 | try { 87 | const result = await coreActionFn(); 88 | log.info('complexityReportDirect completed'); 89 | return result; 90 | } catch (error) { 91 | // Ensure silent mode is disabled 92 | disableSilentMode(); 93 | 94 | log.error(`Unexpected error during complexityReport: ${error.message}`); 95 | return { 96 | success: false, 97 | error: { 98 | code: 'UNEXPECTED_ERROR', 99 | message: error.message 100 | } 101 | }; 102 | } 103 | } catch (error) { 104 | // Ensure silent mode is disabled if an outer error occurs 105 | disableSilentMode(); 106 | 107 | log.error(`Error in complexityReportDirect: ${error.message}`); 108 | return { 109 | success: false, 110 | error: { 111 | code: 'UNEXPECTED_ERROR', 112 | message: error.message 113 | } 114 | }; 115 | } 116 | } 117 | ``` -------------------------------------------------------------------------------- /mcp-server/src/logger.js: -------------------------------------------------------------------------------- ```javascript 1 | import chalk from 'chalk'; 2 | import { isSilentMode } from '../../scripts/modules/utils.js'; 3 | import { getLogLevel } from '../../scripts/modules/config-manager.js'; 4 | 5 | // Define log levels 6 | const LOG_LEVELS = { 7 | debug: 0, 8 | info: 1, 9 | warn: 2, 10 | error: 3, 11 | success: 4 12 | }; 13 | 14 | // Get log level from config manager or default to info 15 | const LOG_LEVEL = LOG_LEVELS[getLogLevel().toLowerCase()] ?? LOG_LEVELS.info; 16 | 17 | /** 18 | * Logs a message with the specified level 19 | * @param {string} level - The log level (debug, info, warn, error, success) 20 | * @param {...any} args - Arguments to log 21 | */ 22 | function log(level, ...args) { 23 | // Skip logging if silent mode is enabled 24 | if (isSilentMode()) { 25 | return; 26 | } 27 | 28 | // Use text prefixes instead of emojis 29 | const prefixes = { 30 | debug: chalk.gray('[DEBUG]'), 31 | info: chalk.blue('[INFO]'), 32 | warn: chalk.yellow('[WARN]'), 33 | error: chalk.red('[ERROR]'), 34 | success: chalk.green('[SUCCESS]') 35 | }; 36 | 37 | if (LOG_LEVELS[level] !== undefined && LOG_LEVELS[level] >= LOG_LEVEL) { 38 | const prefix = prefixes[level] || ''; 39 | let coloredArgs = args; 40 | 41 | try { 42 | switch (level) { 43 | case 'error': 44 | coloredArgs = args.map((arg) => 45 | typeof arg === 'string' ? chalk.red(arg) : arg 46 | ); 47 | break; 48 | case 'warn': 49 | coloredArgs = args.map((arg) => 50 | typeof arg === 'string' ? chalk.yellow(arg) : arg 51 | ); 52 | break; 53 | case 'success': 54 | coloredArgs = args.map((arg) => 55 | typeof arg === 'string' ? chalk.green(arg) : arg 56 | ); 57 | break; 58 | case 'info': 59 | coloredArgs = args.map((arg) => 60 | typeof arg === 'string' ? chalk.blue(arg) : arg 61 | ); 62 | break; 63 | case 'debug': 64 | coloredArgs = args.map((arg) => 65 | typeof arg === 'string' ? chalk.gray(arg) : arg 66 | ); 67 | break; 68 | // default: use original args (no color) 69 | } 70 | } catch (colorError) { 71 | // Fallback if chalk fails on an argument 72 | // Use console.error here for internal logger errors, separate from normal logging 73 | console.error('Internal Logger Error applying chalk color:', colorError); 74 | coloredArgs = args; 75 | } 76 | 77 | // Revert to console.log - FastMCP's context logger (context.log) 78 | // is responsible for directing logs correctly (e.g., to stderr) 79 | // during tool execution without upsetting the client connection. 80 | // Logs outside of tool execution (like startup) will go to stdout. 81 | console.log(prefix, ...coloredArgs); 82 | } 83 | } 84 | 85 | /** 86 | * Create a logger object with methods for different log levels 87 | * @returns {Object} Logger object with info, error, debug, warn, and success methods 88 | */ 89 | export function createLogger() { 90 | const createLogMethod = 91 | (level) => 92 | (...args) => 93 | log(level, ...args); 94 | 95 | return { 96 | debug: createLogMethod('debug'), 97 | info: createLogMethod('info'), 98 | warn: createLogMethod('warn'), 99 | error: createLogMethod('error'), 100 | success: createLogMethod('success'), 101 | log: log // Also expose the raw log function 102 | }; 103 | } 104 | 105 | // Export a default logger instance 106 | const logger = createLogger(); 107 | 108 | export default logger; 109 | export { log, LOG_LEVELS }; 110 | ``` -------------------------------------------------------------------------------- /apps/extension/src/webview/App.tsx: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Main App Component 3 | */ 4 | 5 | import React, { useReducer, useState, useEffect, useRef } from 'react'; 6 | import { VSCodeContext } from './contexts/VSCodeContext'; 7 | import { QueryProvider } from './providers/QueryProvider'; 8 | import { AppContent } from './components/AppContent'; 9 | import { ToastContainer } from './components/ToastContainer'; 10 | import { ErrorBoundary } from './components/ErrorBoundary'; 11 | import { appReducer, initialState } from './reducers/appReducer'; 12 | import { useWebviewHeight } from './hooks/useWebviewHeight'; 13 | import { useVSCodeMessages } from './hooks/useVSCodeMessages'; 14 | import { 15 | showSuccessToast, 16 | showInfoToast, 17 | showWarningToast, 18 | showErrorToast, 19 | createToast 20 | } from './utils/toast'; 21 | 22 | export const App: React.FC = () => { 23 | const [state, dispatch] = useReducer(appReducer, initialState); 24 | const [vscode] = useState(() => window.acquireVsCodeApi?.()); 25 | const availableHeight = useWebviewHeight(); 26 | const { sendMessage } = useVSCodeMessages(vscode, state, dispatch); 27 | const hasInitialized = useRef(false); 28 | 29 | // Initialize the webview 30 | useEffect(() => { 31 | if (hasInitialized.current) return; 32 | hasInitialized.current = true; 33 | 34 | if (!vscode) { 35 | console.warn('⚠️ VS Code API not available - running in standalone mode'); 36 | dispatch({ 37 | type: 'SET_CONNECTION_STATUS', 38 | payload: { isConnected: false, status: 'Standalone Mode' } 39 | }); 40 | return; 41 | } 42 | 43 | console.log('🔄 Initializing webview...'); 44 | 45 | // Notify extension that webview is ready 46 | vscode.postMessage({ type: 'ready' }); 47 | 48 | // React Query will handle task fetching, so we only need to load tags data 49 | sendMessage({ type: 'getTags' }) 50 | .then((tagsData) => { 51 | if (tagsData?.tags && tagsData?.currentTag) { 52 | const tagNames = tagsData.tags.map((tag: any) => tag.name || tag); 53 | dispatch({ 54 | type: 'SET_TAG_DATA', 55 | payload: { 56 | currentTag: tagsData.currentTag, 57 | availableTags: tagNames 58 | } 59 | }); 60 | } 61 | }) 62 | .catch((error) => { 63 | console.error('❌ Failed to load tags:', error); 64 | }); 65 | }, [vscode, sendMessage, dispatch]); 66 | 67 | const contextValue = { 68 | vscode, 69 | state, 70 | dispatch, 71 | sendMessage, 72 | availableHeight, 73 | // Toast notification functions 74 | showSuccessToast: showSuccessToast(dispatch), 75 | showInfoToast: showInfoToast(dispatch), 76 | showWarningToast: showWarningToast(dispatch), 77 | showErrorToast: showErrorToast(dispatch) 78 | }; 79 | 80 | return ( 81 | <QueryProvider> 82 | <VSCodeContext.Provider value={contextValue}> 83 | <ErrorBoundary 84 | onError={(error) => { 85 | // Handle React errors and show appropriate toast 86 | dispatch({ 87 | type: 'ADD_TOAST', 88 | payload: createToast( 89 | 'error', 90 | 'Component Error', 91 | `A React component crashed: ${error.message}`, 92 | 10000 93 | ) 94 | }); 95 | }} 96 | > 97 | <AppContent /> 98 | <ToastContainer 99 | notifications={state.toastNotifications} 100 | onDismiss={(id) => dispatch({ type: 'REMOVE_TOAST', payload: id })} 101 | /> 102 | </ErrorBoundary> 103 | </VSCodeContext.Provider> 104 | </QueryProvider> 105 | ); 106 | }; 107 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/research.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/research.js 3 | * Tool to perform AI-powered research queries with project context 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | createErrorResponse, 9 | handleApiResult, 10 | withNormalizedProjectRoot 11 | } from './utils.js'; 12 | import { researchDirect } from '../core/task-master-core.js'; 13 | import { resolveTag } from '../../../scripts/modules/utils.js'; 14 | 15 | /** 16 | * Register the research tool with the MCP server 17 | * @param {Object} server - FastMCP server instance 18 | */ 19 | export function registerResearchTool(server) { 20 | server.addTool({ 21 | name: 'research', 22 | description: 'Perform AI-powered research queries with project context', 23 | 24 | parameters: z.object({ 25 | query: z.string().describe('Research query/prompt (required)'), 26 | taskIds: z 27 | .string() 28 | .optional() 29 | .describe( 30 | 'Comma-separated list of task/subtask IDs for context (e.g., "15,16.2,17")' 31 | ), 32 | filePaths: z 33 | .string() 34 | .optional() 35 | .describe( 36 | 'Comma-separated list of file paths for context (e.g., "src/api.js,docs/readme.md")' 37 | ), 38 | customContext: z 39 | .string() 40 | .optional() 41 | .describe('Additional custom context text to include in the research'), 42 | includeProjectTree: z 43 | .boolean() 44 | .optional() 45 | .describe( 46 | 'Include project file tree structure in context (default: false)' 47 | ), 48 | detailLevel: z 49 | .enum(['low', 'medium', 'high']) 50 | .optional() 51 | .describe('Detail level for the research response (default: medium)'), 52 | saveTo: z 53 | .string() 54 | .optional() 55 | .describe( 56 | 'Automatically save research results to specified task/subtask ID (e.g., "15" or "15.2")' 57 | ), 58 | saveToFile: z 59 | .boolean() 60 | .optional() 61 | .describe( 62 | 'Save research results to .taskmaster/docs/research/ directory (default: false)' 63 | ), 64 | projectRoot: z 65 | .string() 66 | .describe('The directory of the project. Must be an absolute path.'), 67 | tag: z.string().optional().describe('Tag context to operate on') 68 | }), 69 | execute: withNormalizedProjectRoot(async (args, { log, session }) => { 70 | try { 71 | const resolvedTag = resolveTag({ 72 | projectRoot: args.projectRoot, 73 | tag: args.tag 74 | }); 75 | log.info( 76 | `Starting research with query: "${args.query.substring(0, 100)}${args.query.length > 100 ? '...' : ''}"` 77 | ); 78 | 79 | // Call the direct function 80 | const result = await researchDirect( 81 | { 82 | query: args.query, 83 | taskIds: args.taskIds, 84 | filePaths: args.filePaths, 85 | customContext: args.customContext, 86 | includeProjectTree: args.includeProjectTree || false, 87 | detailLevel: args.detailLevel || 'medium', 88 | saveTo: args.saveTo, 89 | saveToFile: args.saveToFile || false, 90 | projectRoot: args.projectRoot, 91 | tag: resolvedTag 92 | }, 93 | log, 94 | { session } 95 | ); 96 | 97 | return handleApiResult( 98 | result, 99 | log, 100 | 'Error performing research', 101 | undefined, 102 | args.projectRoot 103 | ); 104 | } catch (error) { 105 | log.error(`Error in research tool: ${error.message}`); 106 | return createErrorResponse(error.message); 107 | } 108 | }) 109 | }); 110 | } 111 | ``` -------------------------------------------------------------------------------- /apps/docs/archive/Installation.mdx: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Installation(2)" 3 | description: "This guide walks you through setting up Task Master in your development environment." 4 | --- 5 | 6 | ## Initial Setup 7 | 8 | <Tip> 9 | MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor. 10 | </Tip> 11 | 12 | <AccordionGroup> 13 | <Accordion title="Option 1: Using MCP (Recommended)" icon="sparkles"> 14 | <Steps> 15 | <Step title="Add the MCP config to your editor"> 16 | <Link href="https://cursor.sh">Cursor</Link> recommended, but it works with other text editors 17 | ```json 18 | { 19 | "mcpServers": { 20 | "taskmaster-ai": { 21 | "command": "npx", 22 | "args": ["-y", "task-master-ai"], 23 | "env": { 24 | "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE", 25 | "PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE", 26 | "MODEL": "claude-3-7-sonnet-20250219", 27 | "PERPLEXITY_MODEL": "sonar-pro", 28 | "MAX_TOKENS": 128000, 29 | "TEMPERATURE": 0.2, 30 | "DEFAULT_SUBTASKS": 5, 31 | "DEFAULT_PRIORITY": "medium" 32 | } 33 | } 34 | } 35 | } 36 | ``` 37 | </Step> 38 | <Step title="Enable the MCP in your editor settings"> 39 | 40 | </Step> 41 | <Step title="Prompt the AI to initialize Task Master"> 42 | > "Can you please initialize taskmaster-ai into my project?" 43 | 44 | **The AI will:** 45 | 46 | 1. Create necessary project structure 47 | 2. Set up initial configuration files 48 | 3. Guide you through the rest of the process 49 | 4. Place your PRD document in the `scripts/` directory (e.g., `scripts/prd.txt`) 50 | 5. **Use natural language commands** to interact with Task Master: 51 | 52 | > "Can you parse my PRD at scripts/prd.txt?" 53 | > 54 | > "What's the next task I should work on?" 55 | > 56 | > "Can you help me implement task 3?" 57 | </Step> 58 | </Steps> 59 | </Accordion> 60 | <Accordion title="Option 2: Manual Installation"> 61 | If you prefer to use the command line interface directly: 62 | 63 | <Steps> 64 | <Step title="Install"> 65 | <CodeGroup> 66 | 67 | ```bash Global 68 | npm install -g task-master-ai 69 | ``` 70 | 71 | 72 | ```bash Local 73 | npm install task-master-ai 74 | ``` 75 | 76 | </CodeGroup> 77 | </Step> 78 | <Step title="Initialize a new project"> 79 | <CodeGroup> 80 | 81 | ```bash Global 82 | task-master init 83 | ``` 84 | 85 | 86 | ```bash Local 87 | npx task-master-init 88 | ``` 89 | 90 | </CodeGroup> 91 | </Step> 92 | </Steps> 93 | This will prompt you for project details and set up a new project with the necessary files and structure. 94 | </Accordion> 95 | </AccordionGroup> 96 | 97 | ## Common Commands 98 | 99 | <Tip> 100 | After setting up Task Master, you can use these commands (either via AI prompts or CLI) 101 | </Tip> 102 | 103 | ```bash 104 | # Parse a PRD and generate tasks 105 | task-master parse-prd your-prd.txt 106 | 107 | # List all tasks 108 | task-master list 109 | 110 | # Show the next task to work on 111 | task-master next 112 | 113 | # Generate task files 114 | task-master generate 115 | ``` -------------------------------------------------------------------------------- /tests/unit/ai-providers/claude-code.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { jest } from '@jest/globals'; 2 | 3 | // Mock the claude-code SDK module 4 | jest.unstable_mockModule( 5 | '../../../src/ai-providers/custom-sdk/claude-code/index.js', 6 | () => ({ 7 | createClaudeCode: jest.fn(() => { 8 | const provider = (modelId, settings) => ({ 9 | // Mock language model 10 | id: modelId, 11 | settings 12 | }); 13 | provider.languageModel = jest.fn((id, settings) => ({ id, settings })); 14 | provider.chat = provider.languageModel; 15 | return provider; 16 | }) 17 | }) 18 | ); 19 | 20 | // Mock the base provider 21 | jest.unstable_mockModule('../../../src/ai-providers/base-provider.js', () => ({ 22 | BaseAIProvider: class { 23 | constructor() { 24 | this.name = 'Base Provider'; 25 | } 26 | handleError(context, error) { 27 | throw error; 28 | } 29 | } 30 | })); 31 | 32 | // Import after mocking 33 | const { ClaudeCodeProvider } = await import( 34 | '../../../src/ai-providers/claude-code.js' 35 | ); 36 | 37 | describe('ClaudeCodeProvider', () => { 38 | let provider; 39 | 40 | beforeEach(() => { 41 | provider = new ClaudeCodeProvider(); 42 | jest.clearAllMocks(); 43 | }); 44 | 45 | describe('constructor', () => { 46 | it('should set the provider name to Claude Code', () => { 47 | expect(provider.name).toBe('Claude Code'); 48 | }); 49 | }); 50 | 51 | describe('validateAuth', () => { 52 | it('should not throw an error (no API key required)', () => { 53 | expect(() => provider.validateAuth({})).not.toThrow(); 54 | }); 55 | 56 | it('should not require any parameters', () => { 57 | expect(() => provider.validateAuth()).not.toThrow(); 58 | }); 59 | 60 | it('should work with any params passed', () => { 61 | expect(() => 62 | provider.validateAuth({ 63 | apiKey: 'some-key', 64 | baseURL: 'https://example.com' 65 | }) 66 | ).not.toThrow(); 67 | }); 68 | }); 69 | 70 | describe('getClient', () => { 71 | it('should return a claude code client', () => { 72 | const client = provider.getClient({}); 73 | expect(client).toBeDefined(); 74 | expect(typeof client).toBe('function'); 75 | }); 76 | 77 | it('should create client without API key or base URL', () => { 78 | const client = provider.getClient({}); 79 | expect(client).toBeDefined(); 80 | }); 81 | 82 | it('should handle params even though they are not used', () => { 83 | const client = provider.getClient({ 84 | baseURL: 'https://example.com', 85 | apiKey: 'unused-key' 86 | }); 87 | expect(client).toBeDefined(); 88 | }); 89 | 90 | it('should have languageModel and chat methods', () => { 91 | const client = provider.getClient({}); 92 | expect(client.languageModel).toBeDefined(); 93 | expect(client.chat).toBeDefined(); 94 | expect(client.chat).toBe(client.languageModel); 95 | }); 96 | }); 97 | 98 | describe('error handling', () => { 99 | it('should handle client initialization errors', async () => { 100 | // Force an error by making createClaudeCode throw 101 | const { createClaudeCode } = await import( 102 | '../../../src/ai-providers/custom-sdk/claude-code/index.js' 103 | ); 104 | createClaudeCode.mockImplementationOnce(() => { 105 | throw new Error('Mock initialization error'); 106 | }); 107 | 108 | // Create a new provider instance to use the mocked createClaudeCode 109 | const errorProvider = new ClaudeCodeProvider(); 110 | expect(() => errorProvider.getClient({})).toThrow( 111 | 'Mock initialization error' 112 | ); 113 | }); 114 | }); 115 | }); 116 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/expand-all.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/expand-all.js 3 | * Tool for expanding all pending tasks with subtasks 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | handleApiResult, 9 | createErrorResponse, 10 | withNormalizedProjectRoot 11 | } from './utils.js'; 12 | import { expandAllTasksDirect } from '../core/task-master-core.js'; 13 | import { findTasksPath } from '../core/utils/path-utils.js'; 14 | import { resolveTag } from '../../../scripts/modules/utils.js'; 15 | 16 | /** 17 | * Register the expandAll tool with the MCP server 18 | * @param {Object} server - FastMCP server instance 19 | */ 20 | export function registerExpandAllTool(server) { 21 | server.addTool({ 22 | name: 'expand_all', 23 | description: 24 | 'Expand all pending tasks into subtasks based on complexity or defaults', 25 | parameters: z.object({ 26 | num: z 27 | .string() 28 | .optional() 29 | .describe( 30 | 'Target number of subtasks per task (uses complexity/defaults otherwise)' 31 | ), 32 | research: z 33 | .boolean() 34 | .optional() 35 | .describe( 36 | 'Enable research-backed subtask generation (e.g., using Perplexity)' 37 | ), 38 | prompt: z 39 | .string() 40 | .optional() 41 | .describe( 42 | 'Additional context to guide subtask generation for all tasks' 43 | ), 44 | force: z 45 | .boolean() 46 | .optional() 47 | .describe( 48 | 'Force regeneration of subtasks for tasks that already have them' 49 | ), 50 | file: z 51 | .string() 52 | .optional() 53 | .describe( 54 | 'Absolute path to the tasks file in the /tasks folder inside the project root (default: tasks/tasks.json)' 55 | ), 56 | projectRoot: z 57 | .string() 58 | .optional() 59 | .describe( 60 | 'Absolute path to the project root directory (derived from session if possible)' 61 | ), 62 | tag: z.string().optional().describe('Tag context to operate on') 63 | }), 64 | execute: withNormalizedProjectRoot(async (args, { log, session }) => { 65 | try { 66 | log.info( 67 | `Tool expand_all execution started with args: ${JSON.stringify(args)}` 68 | ); 69 | 70 | const resolvedTag = resolveTag({ 71 | projectRoot: args.projectRoot, 72 | tag: args.tag 73 | }); 74 | let tasksJsonPath; 75 | try { 76 | tasksJsonPath = findTasksPath( 77 | { projectRoot: args.projectRoot, file: args.file }, 78 | log 79 | ); 80 | log.info(`Resolved tasks.json path: ${tasksJsonPath}`); 81 | } catch (error) { 82 | log.error(`Error finding tasks.json: ${error.message}`); 83 | return createErrorResponse( 84 | `Failed to find tasks.json: ${error.message}` 85 | ); 86 | } 87 | 88 | const result = await expandAllTasksDirect( 89 | { 90 | tasksJsonPath: tasksJsonPath, 91 | num: args.num, 92 | research: args.research, 93 | prompt: args.prompt, 94 | force: args.force, 95 | projectRoot: args.projectRoot, 96 | tag: resolvedTag 97 | }, 98 | log, 99 | { session } 100 | ); 101 | 102 | return handleApiResult( 103 | result, 104 | log, 105 | 'Error expanding all tasks', 106 | undefined, 107 | args.projectRoot 108 | ); 109 | } catch (error) { 110 | log.error( 111 | `Unexpected error in expand_all tool execute: ${error.message}` 112 | ); 113 | if (error.stack) { 114 | log.error(error.stack); 115 | } 116 | return createErrorResponse( 117 | `An unexpected error occurred: ${error.message}` 118 | ); 119 | } 120 | }) 121 | }); 122 | } 123 | ``` -------------------------------------------------------------------------------- /tests/integration/claude-code-optional.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { jest } from '@jest/globals'; 2 | 3 | // Mock the base provider to avoid circular dependencies 4 | jest.unstable_mockModule('../../src/ai-providers/base-provider.js', () => ({ 5 | BaseAIProvider: class { 6 | constructor() { 7 | this.name = 'Base Provider'; 8 | } 9 | handleError(context, error) { 10 | throw error; 11 | } 12 | } 13 | })); 14 | 15 | // Mock the claude-code SDK to simulate it not being installed 16 | jest.unstable_mockModule('@anthropic-ai/claude-code', () => { 17 | throw new Error("Cannot find module '@anthropic-ai/claude-code'"); 18 | }); 19 | 20 | // Import after mocking 21 | const { ClaudeCodeProvider } = await import( 22 | '../../src/ai-providers/claude-code.js' 23 | ); 24 | 25 | describe('Claude Code Optional Dependency Integration', () => { 26 | describe('when @anthropic-ai/claude-code is not installed', () => { 27 | it('should allow provider instantiation', () => { 28 | // Provider should instantiate without error 29 | const provider = new ClaudeCodeProvider(); 30 | expect(provider).toBeDefined(); 31 | expect(provider.name).toBe('Claude Code'); 32 | }); 33 | 34 | it('should allow client creation', () => { 35 | const provider = new ClaudeCodeProvider(); 36 | // Client creation should work 37 | const client = provider.getClient({}); 38 | expect(client).toBeDefined(); 39 | expect(typeof client).toBe('function'); 40 | }); 41 | 42 | it('should fail with clear error when trying to use the model', async () => { 43 | const provider = new ClaudeCodeProvider(); 44 | const client = provider.getClient({}); 45 | const model = client('opus'); 46 | 47 | // The actual usage should fail with the lazy loading error 48 | await expect( 49 | model.doGenerate({ 50 | prompt: [{ role: 'user', content: 'Hello' }], 51 | mode: { type: 'regular' } 52 | }) 53 | ).rejects.toThrow( 54 | "Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider." 55 | ); 56 | }); 57 | 58 | it('should provide helpful error message for streaming', async () => { 59 | const provider = new ClaudeCodeProvider(); 60 | const client = provider.getClient({}); 61 | const model = client('sonnet'); 62 | 63 | await expect( 64 | model.doStream({ 65 | prompt: [{ role: 'user', content: 'Hello' }], 66 | mode: { type: 'regular' } 67 | }) 68 | ).rejects.toThrow( 69 | "Claude Code SDK is not installed. Please install '@anthropic-ai/claude-code' to use the claude-code provider." 70 | ); 71 | }); 72 | }); 73 | 74 | describe('provider behavior', () => { 75 | it('should not require API key', () => { 76 | const provider = new ClaudeCodeProvider(); 77 | // Should not throw 78 | expect(() => provider.validateAuth()).not.toThrow(); 79 | expect(() => provider.validateAuth({ apiKey: null })).not.toThrow(); 80 | }); 81 | 82 | it('should work with ai-services-unified when provider is configured', async () => { 83 | // This tests that the provider can be selected but will fail appropriately 84 | // when the actual model is used 85 | const provider = new ClaudeCodeProvider(); 86 | expect(provider).toBeDefined(); 87 | 88 | // In real usage, ai-services-unified would: 89 | // 1. Get the provider instance (works) 90 | // 2. Call provider.getClient() (works) 91 | // 3. Create a model (works) 92 | // 4. Try to generate (fails with clear error) 93 | }); 94 | }); 95 | }); 96 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/update-task.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/update-task.js 3 | * Tool to update a single task by ID with new information 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | handleApiResult, 9 | createErrorResponse, 10 | withNormalizedProjectRoot 11 | } from './utils.js'; 12 | import { updateTaskByIdDirect } from '../core/task-master-core.js'; 13 | import { findTasksPath } from '../core/utils/path-utils.js'; 14 | import { resolveTag } from '../../../scripts/modules/utils.js'; 15 | 16 | /** 17 | * Register the update-task tool with the MCP server 18 | * @param {Object} server - FastMCP server instance 19 | */ 20 | export function registerUpdateTaskTool(server) { 21 | server.addTool({ 22 | name: 'update_task', 23 | description: 24 | 'Updates a single task by ID with new information or context provided in the prompt.', 25 | parameters: z.object({ 26 | id: z 27 | .string() // ID can be number or string like "1.2" 28 | .describe( 29 | "ID of the task (e.g., '15') to update. Subtasks are supported using the update-subtask tool." 30 | ), 31 | prompt: z 32 | .string() 33 | .describe('New information or context to incorporate into the task'), 34 | research: z 35 | .boolean() 36 | .optional() 37 | .describe('Use Perplexity AI for research-backed updates'), 38 | append: z 39 | .boolean() 40 | .optional() 41 | .describe( 42 | 'Append timestamped information to task details instead of full update' 43 | ), 44 | file: z.string().optional().describe('Absolute path to the tasks file'), 45 | projectRoot: z 46 | .string() 47 | .describe('The directory of the project. Must be an absolute path.'), 48 | tag: z.string().optional().describe('Tag context to operate on') 49 | }), 50 | execute: withNormalizedProjectRoot(async (args, { log, session }) => { 51 | const toolName = 'update_task'; 52 | try { 53 | const resolvedTag = resolveTag({ 54 | projectRoot: args.projectRoot, 55 | tag: args.tag 56 | }); 57 | log.info( 58 | `Executing ${toolName} tool with args: ${JSON.stringify(args)}` 59 | ); 60 | 61 | let tasksJsonPath; 62 | try { 63 | tasksJsonPath = findTasksPath( 64 | { projectRoot: args.projectRoot, file: args.file }, 65 | log 66 | ); 67 | log.info(`${toolName}: Resolved tasks path: ${tasksJsonPath}`); 68 | } catch (error) { 69 | log.error(`${toolName}: Error finding tasks.json: ${error.message}`); 70 | return createErrorResponse( 71 | `Failed to find tasks.json: ${error.message}` 72 | ); 73 | } 74 | 75 | // 3. Call Direct Function - Include projectRoot 76 | const result = await updateTaskByIdDirect( 77 | { 78 | tasksJsonPath: tasksJsonPath, 79 | id: args.id, 80 | prompt: args.prompt, 81 | research: args.research, 82 | append: args.append, 83 | projectRoot: args.projectRoot, 84 | tag: resolvedTag 85 | }, 86 | log, 87 | { session } 88 | ); 89 | 90 | // 4. Handle Result 91 | log.info( 92 | `${toolName}: Direct function result: success=${result.success}` 93 | ); 94 | return handleApiResult( 95 | result, 96 | log, 97 | 'Error updating task', 98 | undefined, 99 | args.projectRoot 100 | ); 101 | } catch (error) { 102 | log.error( 103 | `Critical error in ${toolName} tool execute: ${error.message}` 104 | ); 105 | return createErrorResponse( 106 | `Internal tool error (${toolName}): ${error.message}` 107 | ); 108 | } 109 | }) 110 | }); 111 | } 112 | ``` -------------------------------------------------------------------------------- /src/prompts/schemas/prompt-template.schema.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/eyaltoledano/claude-task-master/blob/main/src/prompts/schemas/prompt-template.schema.json", 4 | "version": "1.0.0", 5 | "title": "Task Master Prompt Template", 6 | "description": "Schema for Task Master AI prompt template files", 7 | "type": "object", 8 | "required": ["id", "version", "description", "prompts"], 9 | "properties": { 10 | "id": { 11 | "type": "string", 12 | "pattern": "^[a-z0-9-]+$", 13 | "description": "Unique identifier for the prompt template" 14 | }, 15 | "version": { 16 | "type": "string", 17 | "pattern": "^\\d+\\.\\d+\\.\\d+$", 18 | "description": "Semantic version of the prompt template" 19 | }, 20 | "description": { 21 | "type": "string", 22 | "minLength": 1, 23 | "description": "Brief description of what this prompt does" 24 | }, 25 | "metadata": { 26 | "$ref": "#/definitions/metadata" 27 | }, 28 | "parameters": { 29 | "type": "object", 30 | "additionalProperties": { 31 | "$ref": "#/definitions/parameter" 32 | } 33 | }, 34 | "prompts": { 35 | "type": "object", 36 | "properties": { 37 | "default": { 38 | "$ref": "#/definitions/promptVariant" 39 | } 40 | }, 41 | "additionalProperties": { 42 | "$ref": "#/definitions/conditionalPromptVariant" 43 | } 44 | } 45 | }, 46 | "definitions": { 47 | "parameter": { 48 | "type": "object", 49 | "required": ["type", "description"], 50 | "properties": { 51 | "type": { 52 | "type": "string", 53 | "enum": ["string", "number", "boolean", "array", "object"] 54 | }, 55 | "description": { 56 | "type": "string", 57 | "minLength": 1 58 | }, 59 | "required": { 60 | "type": "boolean", 61 | "default": false 62 | }, 63 | "default": { 64 | "description": "Default value for optional parameters" 65 | }, 66 | "enum": { 67 | "type": "array", 68 | "description": "Valid values for string parameters" 69 | }, 70 | "pattern": { 71 | "type": "string", 72 | "description": "Regular expression pattern for string validation" 73 | }, 74 | "minimum": { 75 | "type": "number", 76 | "description": "Minimum value for number parameters" 77 | }, 78 | "maximum": { 79 | "type": "number", 80 | "description": "Maximum value for number parameters" 81 | } 82 | } 83 | }, 84 | "promptVariant": { 85 | "type": "object", 86 | "required": ["system", "user"], 87 | "properties": { 88 | "system": { 89 | "type": "string", 90 | "minLength": 1 91 | }, 92 | "user": { 93 | "type": "string", 94 | "minLength": 1 95 | } 96 | } 97 | }, 98 | "conditionalPromptVariant": { 99 | "allOf": [ 100 | { "$ref": "#/definitions/promptVariant" }, 101 | { 102 | "type": "object", 103 | "properties": { 104 | "condition": { 105 | "type": "string", 106 | "description": "JavaScript expression for variant selection" 107 | } 108 | } 109 | } 110 | ] 111 | }, 112 | "metadata": { 113 | "type": "object", 114 | "properties": { 115 | "author": { "type": "string" }, 116 | "created": { "type": "string", "format": "date-time" }, 117 | "updated": { "type": "string", "format": "date-time" }, 118 | "tags": { 119 | "type": "array", 120 | "items": { "type": "string" } 121 | }, 122 | "category": { 123 | "type": "string", 124 | "enum": [ 125 | "task", 126 | "analysis", 127 | "research", 128 | "parsing", 129 | "update", 130 | "expansion" 131 | ] 132 | } 133 | } 134 | } 135 | } 136 | } 137 | ``` -------------------------------------------------------------------------------- /src/prompts/analyze-complexity.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "id": "analyze-complexity", 3 | "version": "1.0.0", 4 | "description": "Analyze task complexity and generate expansion recommendations", 5 | "metadata": { 6 | "author": "system", 7 | "created": "2024-01-01T00:00:00Z", 8 | "updated": "2024-01-01T00:00:00Z", 9 | "tags": ["analysis", "complexity", "expansion", "recommendations"] 10 | }, 11 | "parameters": { 12 | "tasks": { 13 | "type": "array", 14 | "required": true, 15 | "description": "Array of tasks to analyze" 16 | }, 17 | "gatheredContext": { 18 | "type": "string", 19 | "default": "", 20 | "description": "Additional project context" 21 | }, 22 | "threshold": { 23 | "type": "number", 24 | "default": 5, 25 | "min": 1, 26 | "max": 10, 27 | "description": "Complexity threshold for expansion recommendation" 28 | }, 29 | "useResearch": { 30 | "type": "boolean", 31 | "default": false, 32 | "description": "Use research mode for deeper analysis" 33 | }, 34 | "hasCodebaseAnalysis": { 35 | "type": "boolean", 36 | "default": false, 37 | "description": "Whether codebase analysis is available" 38 | }, 39 | "projectRoot": { 40 | "type": "string", 41 | "default": "", 42 | "description": "Project root path for context" 43 | } 44 | }, 45 | "prompts": { 46 | "default": { 47 | "system": "You are an expert software architect and project manager analyzing task complexity. Respond only with the requested valid JSON array.", 48 | "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." 49 | } 50 | } 51 | } 52 | ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/generate-task-files.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * generate-task-files.js 3 | * Direct function implementation for generating task files from tasks.json 4 | */ 5 | 6 | import { generateTaskFiles } from '../../../../scripts/modules/task-manager.js'; 7 | import { 8 | enableSilentMode, 9 | disableSilentMode 10 | } from '../../../../scripts/modules/utils.js'; 11 | 12 | /** 13 | * Direct function wrapper for generateTaskFiles with error handling. 14 | * 15 | * @param {Object} args - Command arguments containing tasksJsonPath and outputDir. 16 | * @param {string} args.tasksJsonPath - Path to the tasks.json file. 17 | * @param {string} args.outputDir - Path to the output directory. 18 | * @param {string} args.projectRoot - Project root path (for MCP/env fallback) 19 | * @param {string} args.tag - Tag for the task (optional) 20 | * @param {Object} log - Logger object. 21 | * @returns {Promise<Object>} - Result object with success status and data/error information. 22 | */ 23 | export async function generateTaskFilesDirect(args, log) { 24 | // Destructure expected args 25 | const { tasksJsonPath, outputDir, projectRoot, tag } = args; 26 | try { 27 | log.info(`Generating task files with args: ${JSON.stringify(args)}`); 28 | 29 | // Check if paths were provided 30 | if (!tasksJsonPath) { 31 | const errorMessage = 'tasksJsonPath is required but was not provided.'; 32 | log.error(errorMessage); 33 | return { 34 | success: false, 35 | error: { code: 'MISSING_ARGUMENT', message: errorMessage } 36 | }; 37 | } 38 | if (!outputDir) { 39 | const errorMessage = 'outputDir is required but was not provided.'; 40 | log.error(errorMessage); 41 | return { 42 | success: false, 43 | error: { code: 'MISSING_ARGUMENT', message: errorMessage } 44 | }; 45 | } 46 | 47 | // Use the provided paths 48 | const tasksPath = tasksJsonPath; 49 | const resolvedOutputDir = outputDir; 50 | 51 | log.info(`Generating task files from ${tasksPath} to ${resolvedOutputDir}`); 52 | 53 | // Execute core generateTaskFiles function in a separate try/catch 54 | try { 55 | // Enable silent mode to prevent logs from being written to stdout 56 | enableSilentMode(); 57 | 58 | // Pass projectRoot and tag so the core respects context 59 | generateTaskFiles(tasksPath, resolvedOutputDir, { 60 | projectRoot, 61 | tag, 62 | mcpLog: log 63 | }); 64 | 65 | // Restore normal logging after task generation 66 | disableSilentMode(); 67 | } catch (genError) { 68 | // Make sure to restore normal logging even if there's an error 69 | disableSilentMode(); 70 | 71 | log.error(`Error in generateTaskFiles: ${genError.message}`); 72 | return { 73 | success: false, 74 | error: { code: 'GENERATE_FILES_ERROR', message: genError.message } 75 | }; 76 | } 77 | 78 | // Return success with file paths 79 | return { 80 | success: true, 81 | data: { 82 | message: `Successfully generated task files`, 83 | tasksPath: tasksPath, 84 | outputDir: resolvedOutputDir, 85 | taskFiles: 86 | 'Individual task files have been generated in the output directory' 87 | } 88 | }; 89 | } catch (error) { 90 | // Make sure to restore normal logging if an outer error occurs 91 | disableSilentMode(); 92 | 93 | log.error(`Error generating task files: ${error.message}`); 94 | return { 95 | success: false, 96 | error: { 97 | code: 'GENERATE_TASKS_ERROR', 98 | message: error.message || 'Unknown error generating task files' 99 | } 100 | }; 101 | } 102 | } 103 | ``` -------------------------------------------------------------------------------- /.github/workflows/extension-release.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Extension Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "extension@*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | concurrency: extension-release-${{ github.ref }} 12 | 13 | jobs: 14 | publish-extension: 15 | runs-on: ubuntu-latest 16 | environment: extension-release 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: 20 23 | 24 | - name: Cache node_modules 25 | uses: actions/cache@v4 26 | with: 27 | path: | 28 | node_modules 29 | */*/node_modules 30 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 31 | restore-keys: | 32 | ${{ runner.os }}-node- 33 | 34 | - name: Install Extension Dependencies 35 | working-directory: apps/extension 36 | run: npm ci 37 | timeout-minutes: 5 38 | 39 | - name: Type Check Extension 40 | working-directory: apps/extension 41 | run: npm run check-types 42 | env: 43 | FORCE_COLOR: 1 44 | 45 | - name: Build Extension 46 | working-directory: apps/extension 47 | run: npm run build 48 | env: 49 | FORCE_COLOR: 1 50 | 51 | - name: Package Extension 52 | working-directory: apps/extension 53 | run: npm run package 54 | env: 55 | FORCE_COLOR: 1 56 | 57 | - name: Create VSIX Package 58 | working-directory: apps/extension/vsix-build 59 | run: npx vsce package --no-dependencies 60 | env: 61 | FORCE_COLOR: 1 62 | 63 | - name: Get VSIX filename 64 | id: vsix-info 65 | working-directory: apps/extension/vsix-build 66 | run: | 67 | VSIX_FILE=$(find . -maxdepth 1 -name "*.vsix" -type f | head -n1 | xargs basename) 68 | if [ -z "$VSIX_FILE" ]; then 69 | echo "Error: No VSIX file found" 70 | exit 1 71 | fi 72 | echo "vsix-filename=$VSIX_FILE" >> "$GITHUB_OUTPUT" 73 | echo "Found VSIX: $VSIX_FILE" 74 | 75 | - name: Publish to VS Code Marketplace 76 | working-directory: apps/extension/vsix-build 77 | run: npx vsce publish --packagePath "${{ steps.vsix-info.outputs.vsix-filename }}" 78 | env: 79 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 80 | FORCE_COLOR: 1 81 | 82 | - name: Install Open VSX CLI 83 | run: npm install -g ovsx 84 | 85 | - name: Publish to Open VSX Registry 86 | working-directory: apps/extension/vsix-build 87 | run: ovsx publish "${{ steps.vsix-info.outputs.vsix-filename }}" 88 | env: 89 | OVSX_PAT: ${{ secrets.OVSX_PAT }} 90 | FORCE_COLOR: 1 91 | 92 | - name: Upload Build Artifacts 93 | uses: actions/upload-artifact@v4 94 | with: 95 | name: extension-release-${{ github.ref_name }} 96 | path: | 97 | apps/extension/vsix-build/*.vsix 98 | apps/extension/dist/ 99 | retention-days: 90 100 | 101 | notify-success: 102 | needs: publish-extension 103 | if: success() 104 | runs-on: ubuntu-latest 105 | steps: 106 | - name: Success Notification 107 | run: | 108 | echo "🎉 Extension ${{ github.ref_name }} successfully published!" 109 | echo "📦 Available on VS Code Marketplace" 110 | echo "🌍 Available on Open VSX Registry" 111 | echo "🏷️ GitHub release created: ${{ github.ref_name }}" ``` -------------------------------------------------------------------------------- /tests/unit/mcp/tools/__mocks__/move-task.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * Mock for move-task module 3 | * Provides mock implementations for testing scenarios 4 | */ 5 | 6 | // Mock the moveTask function from the core module 7 | const mockMoveTask = jest 8 | .fn() 9 | .mockImplementation( 10 | async (tasksPath, sourceId, destinationId, generateFiles, options) => { 11 | // Simulate successful move operation 12 | return { 13 | success: true, 14 | sourceId, 15 | destinationId, 16 | message: `Successfully moved task ${sourceId} to ${destinationId}`, 17 | ...options 18 | }; 19 | } 20 | ); 21 | 22 | // Mock the moveTaskDirect function 23 | const mockMoveTaskDirect = jest 24 | .fn() 25 | .mockImplementation(async (args, log, context = {}) => { 26 | // Validate required parameters 27 | if (!args.sourceId) { 28 | return { 29 | success: false, 30 | error: { 31 | message: 'Source ID is required', 32 | code: 'MISSING_SOURCE_ID' 33 | } 34 | }; 35 | } 36 | 37 | if (!args.destinationId) { 38 | return { 39 | success: false, 40 | error: { 41 | message: 'Destination ID is required', 42 | code: 'MISSING_DESTINATION_ID' 43 | } 44 | }; 45 | } 46 | 47 | // Simulate successful move 48 | return { 49 | success: true, 50 | data: { 51 | sourceId: args.sourceId, 52 | destinationId: args.destinationId, 53 | message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`, 54 | tag: args.tag, 55 | projectRoot: args.projectRoot 56 | } 57 | }; 58 | }); 59 | 60 | // Mock the moveTaskCrossTagDirect function 61 | const mockMoveTaskCrossTagDirect = jest 62 | .fn() 63 | .mockImplementation(async (args, log, context = {}) => { 64 | // Validate required parameters 65 | if (!args.sourceIds) { 66 | return { 67 | success: false, 68 | error: { 69 | message: 'Source IDs are required', 70 | code: 'MISSING_SOURCE_IDS' 71 | } 72 | }; 73 | } 74 | 75 | if (!args.sourceTag) { 76 | return { 77 | success: false, 78 | error: { 79 | message: 'Source tag is required for cross-tag moves', 80 | code: 'MISSING_SOURCE_TAG' 81 | } 82 | }; 83 | } 84 | 85 | if (!args.targetTag) { 86 | return { 87 | success: false, 88 | error: { 89 | message: 'Target tag is required for cross-tag moves', 90 | code: 'MISSING_TARGET_TAG' 91 | } 92 | }; 93 | } 94 | 95 | if (args.sourceTag === args.targetTag) { 96 | return { 97 | success: false, 98 | error: { 99 | message: `Source and target tags are the same ("${args.sourceTag}")`, 100 | code: 'SAME_SOURCE_TARGET_TAG' 101 | } 102 | }; 103 | } 104 | 105 | // Simulate successful cross-tag move 106 | return { 107 | success: true, 108 | data: { 109 | sourceIds: args.sourceIds, 110 | sourceTag: args.sourceTag, 111 | targetTag: args.targetTag, 112 | message: `Successfully moved tasks ${args.sourceIds} from ${args.sourceTag} to ${args.targetTag}`, 113 | withDependencies: args.withDependencies || false, 114 | ignoreDependencies: args.ignoreDependencies || false 115 | } 116 | }; 117 | }); 118 | 119 | // Mock the registerMoveTaskTool function 120 | const mockRegisterMoveTaskTool = jest.fn().mockImplementation((server) => { 121 | // Simulate tool registration 122 | server.addTool({ 123 | name: 'move_task', 124 | description: 'Move a task or subtask to a new position', 125 | parameters: {}, 126 | execute: jest.fn() 127 | }); 128 | }); 129 | 130 | // Export the mock functions 131 | export { 132 | mockMoveTask, 133 | mockMoveTaskDirect, 134 | mockMoveTaskCrossTagDirect, 135 | mockRegisterMoveTaskTool 136 | }; 137 | 138 | // Default export for the main moveTask function 139 | export default mockMoveTask; 140 | ``` -------------------------------------------------------------------------------- /tests/integration/profiles/gemini-init-functionality.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { geminiProfile } from '../../../src/profiles/gemini.js'; 4 | 5 | describe('Gemini Profile Initialization Functionality', () => { 6 | let geminiProfileContent; 7 | 8 | beforeAll(() => { 9 | const geminiJsPath = path.join( 10 | process.cwd(), 11 | 'src', 12 | 'profiles', 13 | 'gemini.js' 14 | ); 15 | geminiProfileContent = fs.readFileSync(geminiJsPath, 'utf8'); 16 | }); 17 | 18 | test('gemini.js has correct profile configuration', () => { 19 | // Check for explicit, non-default values in the source file 20 | expect(geminiProfileContent).toContain("name: 'gemini'"); 21 | expect(geminiProfileContent).toContain("displayName: 'Gemini'"); 22 | expect(geminiProfileContent).toContain("url: 'codeassist.google'"); 23 | expect(geminiProfileContent).toContain( 24 | "docsUrl: 'github.com/google-gemini/gemini-cli'" 25 | ); 26 | expect(geminiProfileContent).toContain("profileDir: '.gemini'"); 27 | expect(geminiProfileContent).toContain("rulesDir: '.'"); // non-default 28 | expect(geminiProfileContent).toContain("mcpConfigName: 'settings.json'"); // non-default 29 | expect(geminiProfileContent).toContain('includeDefaultRules: false'); // non-default 30 | expect(geminiProfileContent).toContain("'AGENTS.md': 'GEMINI.md'"); 31 | 32 | // Check the final computed properties on the profile object 33 | expect(geminiProfile.profileName).toBe('gemini'); 34 | expect(geminiProfile.displayName).toBe('Gemini'); 35 | expect(geminiProfile.profileDir).toBe('.gemini'); 36 | expect(geminiProfile.rulesDir).toBe('.'); 37 | expect(geminiProfile.mcpConfig).toBe(true); // computed from mcpConfigName 38 | expect(geminiProfile.mcpConfigName).toBe('settings.json'); 39 | expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json'); // computed 40 | expect(geminiProfile.includeDefaultRules).toBe(false); 41 | expect(geminiProfile.fileMap['AGENTS.md']).toBe('GEMINI.md'); 42 | }); 43 | 44 | test('gemini.js has no lifecycle functions', () => { 45 | // Gemini profile should not have any lifecycle functions 46 | expect(geminiProfileContent).not.toContain('function onAddRulesProfile'); 47 | expect(geminiProfileContent).not.toContain('function onRemoveRulesProfile'); 48 | expect(geminiProfileContent).not.toContain( 49 | 'function onPostConvertRulesProfile' 50 | ); 51 | expect(geminiProfileContent).not.toContain('onAddRulesProfile:'); 52 | expect(geminiProfileContent).not.toContain('onRemoveRulesProfile:'); 53 | expect(geminiProfileContent).not.toContain('onPostConvertRulesProfile:'); 54 | }); 55 | 56 | test('gemini.js uses custom MCP config name', () => { 57 | // Gemini uses settings.json instead of mcp.json 58 | expect(geminiProfileContent).toContain("mcpConfigName: 'settings.json'"); 59 | // Should not contain mcp.json as a config value (comments are OK) 60 | expect(geminiProfileContent).not.toMatch( 61 | /mcpConfigName:\s*['"]mcp\.json['"]/ 62 | ); 63 | }); 64 | 65 | test('gemini.js has minimal implementation', () => { 66 | // Verify the profile is minimal (no extra functions or logic) 67 | const lines = geminiProfileContent.split('\n'); 68 | const nonEmptyLines = lines.filter((line) => line.trim().length > 0); 69 | // Should be around 16 lines (import, export, and profile definition) 70 | expect(nonEmptyLines.length).toBeLessThan(20); 71 | }); 72 | }); 73 | ``` -------------------------------------------------------------------------------- /packages/tm-core/src/config/services/config-loader.service.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * @fileoverview Configuration Loader Service 3 | * Responsible for loading configuration from various file sources 4 | */ 5 | 6 | import { promises as fs } from 'node:fs'; 7 | import path from 'node:path'; 8 | import type { PartialConfiguration } from '../../interfaces/configuration.interface.js'; 9 | import { DEFAULT_CONFIG_VALUES } from '../../interfaces/configuration.interface.js'; 10 | import { 11 | ERROR_CODES, 12 | TaskMasterError 13 | } from '../../errors/task-master-error.js'; 14 | 15 | /** 16 | * ConfigLoader handles loading configuration from files 17 | * Single responsibility: File-based configuration loading 18 | */ 19 | export class ConfigLoader { 20 | private localConfigPath: string; 21 | private globalConfigPath: string; 22 | 23 | constructor(projectRoot: string) { 24 | this.localConfigPath = path.join(projectRoot, '.taskmaster', 'config.json'); 25 | this.globalConfigPath = path.join( 26 | process.env.HOME || '', 27 | '.taskmaster', 28 | 'config.json' 29 | ); 30 | } 31 | 32 | /** 33 | * Get default configuration values 34 | */ 35 | getDefaultConfig(): PartialConfiguration { 36 | return { 37 | models: { 38 | main: DEFAULT_CONFIG_VALUES.MODELS.MAIN, 39 | fallback: DEFAULT_CONFIG_VALUES.MODELS.FALLBACK 40 | }, 41 | storage: { 42 | type: DEFAULT_CONFIG_VALUES.STORAGE.TYPE, 43 | encoding: DEFAULT_CONFIG_VALUES.STORAGE.ENCODING, 44 | enableBackup: false, 45 | maxBackups: DEFAULT_CONFIG_VALUES.STORAGE.MAX_BACKUPS, 46 | enableCompression: false, 47 | atomicOperations: true 48 | }, 49 | version: DEFAULT_CONFIG_VALUES.VERSION 50 | }; 51 | } 52 | 53 | /** 54 | * Load local project configuration 55 | */ 56 | async loadLocalConfig(): Promise<PartialConfiguration | null> { 57 | try { 58 | const configData = await fs.readFile(this.localConfigPath, 'utf-8'); 59 | return JSON.parse(configData); 60 | } catch (error: any) { 61 | if (error.code === 'ENOENT') { 62 | // File doesn't exist, return null 63 | console.debug('No local config.json found, using defaults'); 64 | return null; 65 | } 66 | throw new TaskMasterError( 67 | 'Failed to load local configuration', 68 | ERROR_CODES.CONFIG_ERROR, 69 | { configPath: this.localConfigPath }, 70 | error 71 | ); 72 | } 73 | } 74 | 75 | /** 76 | * Load global user configuration 77 | * @future-implementation Full implementation pending 78 | */ 79 | async loadGlobalConfig(): Promise<PartialConfiguration | null> { 80 | // TODO: Implement in future PR 81 | // For now, return null to indicate no global config 82 | return null; 83 | 84 | // Future implementation: 85 | // try { 86 | // const configData = await fs.readFile(this.globalConfigPath, 'utf-8'); 87 | // return JSON.parse(configData); 88 | // } catch (error: any) { 89 | // if (error.code === 'ENOENT') { 90 | // return null; 91 | // } 92 | // throw new TaskMasterError( 93 | // 'Failed to load global configuration', 94 | // ERROR_CODES.CONFIG_ERROR, 95 | // { configPath: this.globalConfigPath }, 96 | // error 97 | // ); 98 | // } 99 | } 100 | 101 | /** 102 | * Check if local config exists 103 | */ 104 | async hasLocalConfig(): Promise<boolean> { 105 | try { 106 | await fs.access(this.localConfigPath); 107 | return true; 108 | } catch { 109 | return false; 110 | } 111 | } 112 | 113 | /** 114 | * Check if global config exists 115 | */ 116 | async hasGlobalConfig(): Promise<boolean> { 117 | try { 118 | await fs.access(this.globalConfigPath); 119 | return true; 120 | } catch { 121 | return false; 122 | } 123 | } 124 | } 125 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/parse-prd.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/parsePRD.js 3 | * Tool to parse PRD document and generate tasks 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | handleApiResult, 9 | withNormalizedProjectRoot, 10 | createErrorResponse, 11 | checkProgressCapability 12 | } from './utils.js'; 13 | import { parsePRDDirect } from '../core/task-master-core.js'; 14 | import { 15 | PRD_FILE, 16 | TASKMASTER_DOCS_DIR, 17 | TASKMASTER_TASKS_FILE 18 | } from '../../../src/constants/paths.js'; 19 | import { resolveTag } from '../../../scripts/modules/utils.js'; 20 | 21 | /** 22 | * Register the parse_prd tool 23 | * @param {Object} server - FastMCP server instance 24 | */ 25 | export function registerParsePRDTool(server) { 26 | server.addTool({ 27 | name: 'parse_prd', 28 | 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.`, 29 | 30 | parameters: z.object({ 31 | input: z 32 | .string() 33 | .optional() 34 | .default(PRD_FILE) 35 | .describe('Absolute path to the PRD document file (.txt, .md, etc.)'), 36 | projectRoot: z 37 | .string() 38 | .describe('The directory of the project. Must be an absolute path.'), 39 | tag: z.string().optional().describe('Tag context to operate on'), 40 | output: z 41 | .string() 42 | .optional() 43 | .describe( 44 | `Output path for tasks.json file (default: ${TASKMASTER_TASKS_FILE})` 45 | ), 46 | numTasks: z 47 | .string() 48 | .optional() 49 | .describe( 50 | '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.' 51 | ), 52 | force: z 53 | .boolean() 54 | .optional() 55 | .default(false) 56 | .describe('Overwrite existing output file without prompting.'), 57 | research: z 58 | .boolean() 59 | .optional() 60 | .describe( 61 | 'Enable Taskmaster to use the research role for potentially more informed task generation. Requires appropriate API key.' 62 | ), 63 | append: z 64 | .boolean() 65 | .optional() 66 | .describe('Append generated tasks to existing file.') 67 | }), 68 | execute: withNormalizedProjectRoot( 69 | async (args, { log, session, reportProgress }) => { 70 | try { 71 | const resolvedTag = resolveTag({ 72 | projectRoot: args.projectRoot, 73 | tag: args.tag 74 | }); 75 | const progressCapability = checkProgressCapability( 76 | reportProgress, 77 | log 78 | ); 79 | const result = await parsePRDDirect( 80 | { 81 | ...args, 82 | tag: resolvedTag 83 | }, 84 | log, 85 | { session, reportProgress: progressCapability } 86 | ); 87 | return handleApiResult( 88 | result, 89 | log, 90 | 'Error parsing PRD', 91 | undefined, 92 | args.projectRoot 93 | ); 94 | } catch (error) { 95 | log.error(`Error in parse_prd: ${error.message}`); 96 | return createErrorResponse(`Failed to parse PRD: ${error.message}`); 97 | } 98 | } 99 | ) 100 | }); 101 | } 102 | ``` -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: Pre-Release (RC) 2 | 3 | on: 4 | workflow_dispatch: # Allows manual triggering from GitHub UI/API 5 | 6 | concurrency: pre-release-${{ github.ref_name }} 7 | jobs: 8 | rc: 9 | runs-on: ubuntu-latest 10 | # Only allow pre-releases on non-main branches 11 | if: github.ref != 'refs/heads/main' 12 | environment: extension-release 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | cache: "npm" 22 | 23 | - name: Cache node_modules 24 | uses: actions/cache@v4 25 | with: 26 | path: | 27 | node_modules 28 | */*/node_modules 29 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 30 | restore-keys: | 31 | ${{ runner.os }}-node- 32 | 33 | - name: Install dependencies 34 | run: npm ci 35 | timeout-minutes: 2 36 | 37 | - name: Enter RC mode (if not already in RC mode) 38 | run: | 39 | # Check if we're in pre-release mode with the "rc" tag 40 | if [ -f .changeset/pre.json ]; then 41 | MODE=$(jq -r '.mode' .changeset/pre.json 2>/dev/null || echo '') 42 | TAG=$(jq -r '.tag' .changeset/pre.json 2>/dev/null || echo '') 43 | 44 | if [ "$MODE" = "exit" ]; then 45 | echo "Pre-release mode is in 'exit' state, re-entering RC mode..." 46 | npx changeset pre enter rc 47 | elif [ "$MODE" = "pre" ] && [ "$TAG" != "rc" ]; then 48 | echo "In pre-release mode but with wrong tag ($TAG), switching to RC..." 49 | npx changeset pre exit 50 | npx changeset pre enter rc 51 | elif [ "$MODE" = "pre" ] && [ "$TAG" = "rc" ]; then 52 | echo "Already in RC pre-release mode" 53 | else 54 | echo "Unknown mode state: $MODE, entering RC mode..." 55 | npx changeset pre enter rc 56 | fi 57 | else 58 | echo "No pre.json found, entering RC mode..." 59 | npx changeset pre enter rc 60 | fi 61 | 62 | - name: Version RC packages 63 | run: npx changeset version 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 67 | 68 | - name: Run format 69 | run: npm run format 70 | env: 71 | FORCE_COLOR: 1 72 | 73 | - name: Build packages 74 | run: npm run turbo:build 75 | env: 76 | NODE_ENV: production 77 | FORCE_COLOR: 1 78 | TM_PUBLIC_BASE_DOMAIN: ${{ secrets.TM_PUBLIC_BASE_DOMAIN }} 79 | TM_PUBLIC_SUPABASE_URL: ${{ secrets.TM_PUBLIC_SUPABASE_URL }} 80 | TM_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.TM_PUBLIC_SUPABASE_ANON_KEY }} 81 | 82 | - name: Create Release Candidate Pull Request or Publish Release Candidate to npm 83 | uses: changesets/action@v1 84 | with: 85 | publish: npx changeset publish 86 | env: 87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 88 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 89 | 90 | - name: Commit & Push changes 91 | uses: actions-js/push@master 92 | with: 93 | github_token: ${{ secrets.GITHUB_TOKEN }} 94 | branch: ${{ github.ref }} 95 | message: "chore: rc version bump" 96 | ``` -------------------------------------------------------------------------------- /apps/extension/src/webview/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Error Boundary Component 3 | */ 4 | 5 | import React from 'react'; 6 | 7 | interface ErrorBoundaryState { 8 | hasError: boolean; 9 | error?: Error; 10 | errorInfo?: React.ErrorInfo; 11 | } 12 | 13 | interface ErrorBoundaryProps { 14 | children: React.ReactNode; 15 | onError?: (error: Error, errorInfo: React.ErrorInfo) => void; 16 | } 17 | 18 | export class ErrorBoundary extends React.Component< 19 | ErrorBoundaryProps, 20 | ErrorBoundaryState 21 | > { 22 | constructor(props: ErrorBoundaryProps) { 23 | super(props); 24 | this.state = { hasError: false }; 25 | } 26 | 27 | static getDerivedStateFromError(error: Error): ErrorBoundaryState { 28 | return { hasError: true, error }; 29 | } 30 | 31 | componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { 32 | console.error('React Error Boundary caught:', error, errorInfo); 33 | 34 | // Log to extension 35 | if (this.props.onError) { 36 | this.props.onError(error, errorInfo); 37 | } 38 | 39 | // Send error to extension for centralized handling 40 | if (window.acquireVsCodeApi) { 41 | const vscode = window.acquireVsCodeApi(); 42 | vscode.postMessage({ 43 | type: 'reactError', 44 | data: { 45 | message: error.message, 46 | stack: error.stack, 47 | componentStack: errorInfo.componentStack, 48 | timestamp: Date.now() 49 | } 50 | }); 51 | } 52 | } 53 | 54 | render() { 55 | if (this.state.hasError) { 56 | return ( 57 | <div className="min-h-screen flex items-center justify-center bg-vscode-background"> 58 | <div className="max-w-md mx-auto text-center p-6"> 59 | <div className="w-16 h-16 mx-auto mb-4 text-red-400"> 60 | <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> 61 | <path 62 | strokeLinecap="round" 63 | strokeLinejoin="round" 64 | strokeWidth={2} 65 | 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" 66 | /> 67 | </svg> 68 | </div> 69 | <h2 className="text-xl font-semibold text-vscode-foreground mb-2"> 70 | Something went wrong 71 | </h2> 72 | <p className="text-vscode-foreground/70 mb-4"> 73 | The Task Master Kanban board encountered an unexpected error. 74 | </p> 75 | <div className="space-y-2"> 76 | <button 77 | onClick={() => 78 | this.setState({ 79 | hasError: false, 80 | error: undefined, 81 | errorInfo: undefined 82 | }) 83 | } 84 | className="w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md transition-colors" 85 | > 86 | Try Again 87 | </button> 88 | <button 89 | onClick={() => window.location.reload()} 90 | className="w-full px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded-md transition-colors" 91 | > 92 | Reload Extension 93 | </button> 94 | </div> 95 | {this.state.error && ( 96 | <details className="mt-4 text-left"> 97 | <summary className="text-sm text-vscode-foreground/50 cursor-pointer"> 98 | Error Details 99 | </summary> 100 | <pre className="mt-2 text-xs text-vscode-foreground/70 bg-vscode-input/30 p-2 rounded overflow-auto max-h-32"> 101 | {this.state.error.message} 102 | {this.state.error.stack && `\n\n${this.state.error.stack}`} 103 | </pre> 104 | </details> 105 | )} 106 | </div> 107 | </div> 108 | ); 109 | } 110 | 111 | return this.props.children; 112 | } 113 | } 114 | ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/rename-tag.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * rename-tag.js 3 | * Direct function implementation for renaming a tag 4 | */ 5 | 6 | import { renameTag } from '../../../../scripts/modules/task-manager/tag-management.js'; 7 | import { 8 | enableSilentMode, 9 | disableSilentMode 10 | } from '../../../../scripts/modules/utils.js'; 11 | import { createLogWrapper } from '../../tools/utils.js'; 12 | 13 | /** 14 | * Direct function wrapper for renaming a tag with error handling. 15 | * 16 | * @param {Object} args - Command arguments 17 | * @param {string} args.oldName - Current name of the tag to rename 18 | * @param {string} args.newName - New name for the tag 19 | * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool) 20 | * @param {string} [args.projectRoot] - Project root path 21 | * @param {Object} log - Logger object 22 | * @param {Object} context - Additional context (session) 23 | * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } 24 | */ 25 | export async function renameTagDirect(args, log, context = {}) { 26 | // Destructure expected args 27 | const { tasksJsonPath, oldName, newName, projectRoot } = args; 28 | const { session } = context; 29 | 30 | // Enable silent mode to prevent console logs from interfering with JSON response 31 | enableSilentMode(); 32 | 33 | // Create logger wrapper using the utility 34 | const mcpLog = createLogWrapper(log); 35 | 36 | try { 37 | // Check if tasksJsonPath was provided 38 | if (!tasksJsonPath) { 39 | log.error('renameTagDirect called without tasksJsonPath'); 40 | disableSilentMode(); 41 | return { 42 | success: false, 43 | error: { 44 | code: 'MISSING_ARGUMENT', 45 | message: 'tasksJsonPath is required' 46 | } 47 | }; 48 | } 49 | 50 | // Check required parameters 51 | if (!oldName || typeof oldName !== 'string') { 52 | log.error('Missing required parameter: oldName'); 53 | disableSilentMode(); 54 | return { 55 | success: false, 56 | error: { 57 | code: 'MISSING_PARAMETER', 58 | message: 'Old tag name is required and must be a string' 59 | } 60 | }; 61 | } 62 | 63 | if (!newName || typeof newName !== 'string') { 64 | log.error('Missing required parameter: newName'); 65 | disableSilentMode(); 66 | return { 67 | success: false, 68 | error: { 69 | code: 'MISSING_PARAMETER', 70 | message: 'New tag name is required and must be a string' 71 | } 72 | }; 73 | } 74 | 75 | log.info(`Renaming tag from "${oldName}" to "${newName}"`); 76 | 77 | // Call the renameTag function 78 | const result = await renameTag( 79 | tasksJsonPath, 80 | oldName, 81 | newName, 82 | {}, // options (empty for now) 83 | { 84 | session, 85 | mcpLog, 86 | projectRoot 87 | }, 88 | 'json' // outputFormat - use 'json' to suppress CLI UI 89 | ); 90 | 91 | // Restore normal logging 92 | disableSilentMode(); 93 | 94 | return { 95 | success: true, 96 | data: { 97 | oldName: result.oldName, 98 | newName: result.newName, 99 | renamed: result.renamed, 100 | taskCount: result.taskCount, 101 | wasCurrentTag: result.wasCurrentTag, 102 | message: `Successfully renamed tag from "${result.oldName}" to "${result.newName}"` 103 | } 104 | }; 105 | } catch (error) { 106 | // Make sure to restore normal logging even if there's an error 107 | disableSilentMode(); 108 | 109 | log.error(`Error in renameTagDirect: ${error.message}`); 110 | return { 111 | success: false, 112 | error: { 113 | code: error.code || 'RENAME_TAG_ERROR', 114 | message: error.message 115 | } 116 | }; 117 | } 118 | } 119 | ``` -------------------------------------------------------------------------------- /apps/extension/src/webview/types/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared types for the webview application 3 | */ 4 | 5 | export interface TaskMasterTask { 6 | id: string; 7 | title: string; 8 | description: string; 9 | status: 'pending' | 'in-progress' | 'done' | 'deferred' | 'review'; 10 | priority: 'high' | 'medium' | 'low'; 11 | dependencies?: string[]; 12 | details?: string; 13 | testStrategy?: string; 14 | subtasks?: TaskMasterTask[]; 15 | complexityScore?: number; 16 | } 17 | 18 | export interface TaskUpdates { 19 | title?: string; 20 | description?: string; 21 | details?: string; 22 | priority?: TaskMasterTask['priority']; 23 | testStrategy?: string; 24 | dependencies?: string[]; 25 | } 26 | 27 | export interface WebviewMessage { 28 | type: string; 29 | requestId?: string; 30 | data?: any; 31 | success?: boolean; 32 | [key: string]: any; 33 | } 34 | 35 | export interface ToastNotification { 36 | id: string; 37 | type: 'success' | 'info' | 'warning' | 'error'; 38 | title: string; 39 | message: string; 40 | duration?: number; 41 | } 42 | 43 | export interface AppState { 44 | tasks: TaskMasterTask[]; 45 | loading: boolean; 46 | error?: string; 47 | requestId: number; 48 | isConnected: boolean; 49 | connectionStatus: string; 50 | editingTask?: { taskId: string | null; editData?: TaskMasterTask }; 51 | polling: { 52 | isActive: boolean; 53 | errorCount: number; 54 | lastUpdate?: number; 55 | isUserInteracting: boolean; 56 | isOfflineMode: boolean; 57 | reconnectAttempts: number; 58 | maxReconnectAttempts: number; 59 | lastSuccessfulConnection?: number; 60 | connectionStatus: 'online' | 'offline' | 'reconnecting'; 61 | }; 62 | toastNotifications: ToastNotification[]; 63 | currentView: 'kanban' | 'task-details' | 'config'; 64 | selectedTaskId?: string; 65 | // Tag-related state 66 | currentTag: string; 67 | availableTags: string[]; 68 | } 69 | 70 | export type AppAction = 71 | | { type: 'SET_TASKS'; payload: TaskMasterTask[] } 72 | | { type: 'SET_LOADING'; payload: boolean } 73 | | { type: 'SET_ERROR'; payload: string } 74 | | { type: 'CLEAR_ERROR' } 75 | | { type: 'INCREMENT_REQUEST_ID' } 76 | | { 77 | type: 'UPDATE_TASK_STATUS'; 78 | payload: { taskId: string; newStatus: TaskMasterTask['status'] }; 79 | } 80 | | { 81 | type: 'UPDATE_TASK_CONTENT'; 82 | payload: { taskId: string; updates: TaskUpdates }; 83 | } 84 | | { 85 | type: 'SET_CONNECTION_STATUS'; 86 | payload: { isConnected: boolean; status: string }; 87 | } 88 | | { 89 | type: 'SET_EDITING_TASK'; 90 | payload: { taskId: string | null; editData?: TaskMasterTask }; 91 | } 92 | | { 93 | type: 'SET_POLLING_STATUS'; 94 | payload: { isActive: boolean; errorCount?: number }; 95 | } 96 | | { type: 'SET_USER_INTERACTING'; payload: boolean } 97 | | { type: 'TASKS_UPDATED_FROM_POLLING'; payload: TaskMasterTask[] } 98 | | { 99 | type: 'SET_NETWORK_STATUS'; 100 | payload: { 101 | isOfflineMode: boolean; 102 | connectionStatus: 'online' | 'offline' | 'reconnecting'; 103 | reconnectAttempts?: number; 104 | maxReconnectAttempts?: number; 105 | lastSuccessfulConnection?: number; 106 | }; 107 | } 108 | | { type: 'LOAD_CACHED_TASKS'; payload: TaskMasterTask[] } 109 | | { type: 'ADD_TOAST'; payload: ToastNotification } 110 | | { type: 'REMOVE_TOAST'; payload: string } 111 | | { type: 'CLEAR_ALL_TOASTS' } 112 | | { type: 'NAVIGATE_TO_TASK'; payload: string } 113 | | { type: 'NAVIGATE_TO_KANBAN' } 114 | | { type: 'NAVIGATE_TO_CONFIG' } 115 | | { type: 'SET_CURRENT_TAG'; payload: string } 116 | | { type: 'SET_AVAILABLE_TAGS'; payload: string[] } 117 | | { 118 | type: 'SET_TAG_DATA'; 119 | payload: { currentTag: string; availableTags: string[] }; 120 | }; 121 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/add-task.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/add-task.js 3 | * Tool to add a new task using AI 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | createErrorResponse, 9 | handleApiResult, 10 | withNormalizedProjectRoot 11 | } from './utils.js'; 12 | import { addTaskDirect } from '../core/task-master-core.js'; 13 | import { findTasksPath } from '../core/utils/path-utils.js'; 14 | import { resolveTag } from '../../../scripts/modules/utils.js'; 15 | 16 | /** 17 | * Register the addTask tool with the MCP server 18 | * @param {Object} server - FastMCP server instance 19 | */ 20 | export function registerAddTaskTool(server) { 21 | server.addTool({ 22 | name: 'add_task', 23 | description: 'Add a new task using AI', 24 | parameters: z.object({ 25 | prompt: z 26 | .string() 27 | .optional() 28 | .describe( 29 | 'Description of the task to add (required if not using manual fields)' 30 | ), 31 | title: z 32 | .string() 33 | .optional() 34 | .describe('Task title (for manual task creation)'), 35 | description: z 36 | .string() 37 | .optional() 38 | .describe('Task description (for manual task creation)'), 39 | details: z 40 | .string() 41 | .optional() 42 | .describe('Implementation details (for manual task creation)'), 43 | testStrategy: z 44 | .string() 45 | .optional() 46 | .describe('Test strategy (for manual task creation)'), 47 | dependencies: z 48 | .string() 49 | .optional() 50 | .describe('Comma-separated list of task IDs this task depends on'), 51 | priority: z 52 | .string() 53 | .optional() 54 | .describe('Task priority (high, medium, low)'), 55 | file: z 56 | .string() 57 | .optional() 58 | .describe('Path to the tasks file (default: tasks/tasks.json)'), 59 | projectRoot: z 60 | .string() 61 | .describe('The directory of the project. Must be an absolute path.'), 62 | tag: z.string().optional().describe('Tag context to operate on'), 63 | research: z 64 | .boolean() 65 | .optional() 66 | .describe('Whether to use research capabilities for task creation') 67 | }), 68 | execute: withNormalizedProjectRoot(async (args, { log, session }) => { 69 | try { 70 | log.info(`Starting add-task with args: ${JSON.stringify(args)}`); 71 | 72 | const resolvedTag = resolveTag({ 73 | projectRoot: args.projectRoot, 74 | tag: args.tag 75 | }); 76 | 77 | // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) 78 | let tasksJsonPath; 79 | try { 80 | tasksJsonPath = findTasksPath( 81 | { projectRoot: args.projectRoot, file: args.file }, 82 | log 83 | ); 84 | } catch (error) { 85 | log.error(`Error finding tasks.json: ${error.message}`); 86 | return createErrorResponse( 87 | `Failed to find tasks.json: ${error.message}` 88 | ); 89 | } 90 | 91 | // Call the direct functionP 92 | const result = await addTaskDirect( 93 | { 94 | tasksJsonPath: tasksJsonPath, 95 | prompt: args.prompt, 96 | title: args.title, 97 | description: args.description, 98 | details: args.details, 99 | testStrategy: args.testStrategy, 100 | dependencies: args.dependencies, 101 | priority: args.priority, 102 | research: args.research, 103 | projectRoot: args.projectRoot, 104 | tag: resolvedTag 105 | }, 106 | log, 107 | { session } 108 | ); 109 | 110 | return handleApiResult( 111 | result, 112 | log, 113 | 'Error adding task', 114 | undefined, 115 | args.projectRoot 116 | ); 117 | } catch (error) { 118 | log.error(`Error in add-task tool: ${error.message}`); 119 | return createErrorResponse(error.message); 120 | } 121 | }) 122 | }); 123 | } 124 | ``` -------------------------------------------------------------------------------- /tests/integration/profiles/rules-files-inclusion.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { jest } from '@jest/globals'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import os from 'os'; 5 | import { execSync } from 'child_process'; 6 | 7 | describe('Rules Files Inclusion in Package', () => { 8 | // This test verifies that the required rules files are included in the final package 9 | 10 | test('package.json includes dist/** in the "files" array for bundled files', () => { 11 | // Read the package.json file 12 | const packageJsonPath = path.join(process.cwd(), 'package.json'); 13 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); 14 | 15 | // Check if dist/** is included in the files array (which contains bundled output including assets) 16 | expect(packageJson.files).toContain('dist/**'); 17 | }); 18 | 19 | test('source rules files exist in assets/rules directory', () => { 20 | // Verify that the actual rules files exist 21 | const rulesDir = path.join(process.cwd(), 'assets', 'rules'); 22 | expect(fs.existsSync(rulesDir)).toBe(true); 23 | 24 | // Check for the 4 files that currently exist 25 | const expectedFiles = [ 26 | 'dev_workflow.mdc', 27 | 'taskmaster.mdc', 28 | 'self_improve.mdc', 29 | 'cursor_rules.mdc' 30 | ]; 31 | 32 | expectedFiles.forEach((file) => { 33 | const filePath = path.join(rulesDir, file); 34 | expect(fs.existsSync(filePath)).toBe(true); 35 | }); 36 | }); 37 | 38 | test('roo.js profile contains logic for Roo directory creation and file copying', () => { 39 | // Read the roo.js profile file 40 | const rooJsPath = path.join(process.cwd(), 'src', 'profiles', 'roo.js'); 41 | const rooJsContent = fs.readFileSync(rooJsPath, 'utf8'); 42 | 43 | // Check for the main handler function 44 | expect( 45 | rooJsContent.includes('onAddRulesProfile(targetDir, assetsDir)') 46 | ).toBe(true); 47 | 48 | // Check for general recursive copy of assets/roocode 49 | expect( 50 | rooJsContent.includes('copyRecursiveSync(sourceDir, targetDir)') 51 | ).toBe(true); 52 | 53 | // Check for updated path handling 54 | expect(rooJsContent.includes("path.join(assetsDir, 'roocode')")).toBe(true); 55 | 56 | // Check for .roomodes file copying logic (source and destination paths) 57 | expect(rooJsContent.includes("path.join(sourceDir, '.roomodes')")).toBe( 58 | true 59 | ); 60 | expect(rooJsContent.includes("path.join(targetDir, '.roomodes')")).toBe( 61 | true 62 | ); 63 | 64 | // Check for mode-specific rule file copying logic 65 | expect(rooJsContent.includes('for (const mode of ROO_MODES)')).toBe(true); 66 | expect( 67 | rooJsContent.includes( 68 | 'path.join(rooModesDir, `rules-${mode}`, `${mode}-rules`)' 69 | ) 70 | ).toBe(true); 71 | expect( 72 | rooJsContent.includes( 73 | "path.join(targetDir, '.roo', `rules-${mode}`, `${mode}-rules`)" 74 | ) 75 | ).toBe(true); 76 | 77 | // Check for import of ROO_MODES from profiles.js 78 | expect( 79 | rooJsContent.includes( 80 | "import { ROO_MODES } from '../constants/profiles.js'" 81 | ) 82 | ).toBe(true); 83 | 84 | // Verify mode variable is used in the template strings (this confirms modes are being processed) 85 | expect(rooJsContent.includes('rules-${mode}')).toBe(true); 86 | expect(rooJsContent.includes('${mode}-rules')).toBe(true); 87 | }); 88 | 89 | test('source Roo files exist in assets directory', () => { 90 | // Verify that the source files for Roo integration exist 91 | expect( 92 | fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roo')) 93 | ).toBe(true); 94 | expect( 95 | fs.existsSync(path.join(process.cwd(), 'assets', 'roocode', '.roomodes')) 96 | ).toBe(true); 97 | }); 98 | }); 99 | ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/get-tasks.js: -------------------------------------------------------------------------------- ```javascript 1 | /** 2 | * tools/get-tasks.js 3 | * Tool to get all tasks from Task Master 4 | */ 5 | 6 | import { z } from 'zod'; 7 | import { 8 | createErrorResponse, 9 | handleApiResult, 10 | withNormalizedProjectRoot 11 | } from './utils.js'; 12 | import { listTasksDirect } from '../core/task-master-core.js'; 13 | import { 14 | resolveTasksPath, 15 | resolveComplexityReportPath 16 | } from '../core/utils/path-utils.js'; 17 | 18 | import { resolveTag } from '../../../scripts/modules/utils.js'; 19 | 20 | /** 21 | * Register the getTasks tool with the MCP server 22 | * @param {Object} server - FastMCP server instance 23 | */ 24 | export function registerListTasksTool(server) { 25 | server.addTool({ 26 | name: 'get_tasks', 27 | description: 28 | 'Get all tasks from Task Master, optionally filtering by status and including subtasks.', 29 | parameters: z.object({ 30 | status: z 31 | .string() 32 | .optional() 33 | .describe( 34 | "Filter tasks by status (e.g., 'pending', 'done') or multiple statuses separated by commas (e.g., 'blocked,deferred')" 35 | ), 36 | withSubtasks: z 37 | .boolean() 38 | .optional() 39 | .describe( 40 | 'Include subtasks nested within their parent tasks in the response' 41 | ), 42 | file: z 43 | .string() 44 | .optional() 45 | .describe( 46 | 'Path to the tasks file (relative to project root or absolute)' 47 | ), 48 | complexityReport: z 49 | .string() 50 | .optional() 51 | .describe( 52 | 'Path to the complexity report file (relative to project root or absolute)' 53 | ), 54 | projectRoot: z 55 | .string() 56 | .describe('The directory of the project. Must be an absolute path.'), 57 | tag: z.string().optional().describe('Tag context to operate on') 58 | }), 59 | execute: withNormalizedProjectRoot(async (args, { log, session }) => { 60 | try { 61 | log.info(`Getting tasks with filters: ${JSON.stringify(args)}`); 62 | 63 | const resolvedTag = resolveTag({ 64 | projectRoot: args.projectRoot, 65 | tag: args.tag 66 | }); 67 | // Resolve the path to tasks.json using new path utilities 68 | let tasksJsonPath; 69 | try { 70 | tasksJsonPath = resolveTasksPath(args, log); 71 | } catch (error) { 72 | log.error(`Error finding tasks.json: ${error.message}`); 73 | return createErrorResponse( 74 | `Failed to find tasks.json: ${error.message}` 75 | ); 76 | } 77 | 78 | // Resolve the path to complexity report 79 | let complexityReportPath; 80 | try { 81 | complexityReportPath = resolveComplexityReportPath( 82 | { ...args, tag: resolvedTag }, 83 | session 84 | ); 85 | } catch (error) { 86 | log.error(`Error finding complexity report: ${error.message}`); 87 | // This is optional, so we don't fail the operation 88 | complexityReportPath = null; 89 | } 90 | 91 | const result = await listTasksDirect( 92 | { 93 | tasksJsonPath: tasksJsonPath, 94 | status: args.status, 95 | withSubtasks: args.withSubtasks, 96 | reportPath: complexityReportPath, 97 | projectRoot: args.projectRoot, 98 | tag: resolvedTag 99 | }, 100 | log, 101 | { session } 102 | ); 103 | 104 | log.info( 105 | `Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks` 106 | ); 107 | return handleApiResult( 108 | result, 109 | log, 110 | 'Error getting tasks', 111 | undefined, 112 | args.projectRoot 113 | ); 114 | } catch (error) { 115 | log.error(`Error getting tasks: ${error.message}`); 116 | return createErrorResponse(error.message); 117 | } 118 | }) 119 | }); 120 | } 121 | 122 | // We no longer need the formatTasksResponse function as we're returning raw JSON data 123 | ``` -------------------------------------------------------------------------------- /tests/unit/profiles/codex-integration.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { jest } from '@jest/globals'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import os from 'os'; 5 | 6 | // Mock external modules 7 | jest.mock('child_process', () => ({ 8 | execSync: jest.fn() 9 | })); 10 | 11 | // Mock console methods 12 | jest.mock('console', () => ({ 13 | log: jest.fn(), 14 | info: jest.fn(), 15 | warn: jest.fn(), 16 | error: jest.fn(), 17 | clear: jest.fn() 18 | })); 19 | 20 | describe('Codex Profile Integration', () => { 21 | let tempDir; 22 | 23 | beforeEach(() => { 24 | jest.clearAllMocks(); 25 | 26 | // Create a temporary directory for testing 27 | tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-')); 28 | 29 | // Spy on fs methods 30 | jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); 31 | jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => { 32 | if (filePath.toString().includes('AGENTS.md')) { 33 | return 'Sample AGENTS.md content for Codex integration'; 34 | } 35 | return '{}'; 36 | }); 37 | jest.spyOn(fs, 'existsSync').mockImplementation(() => false); 38 | jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {}); 39 | }); 40 | 41 | afterEach(() => { 42 | // Clean up the temporary directory 43 | try { 44 | fs.rmSync(tempDir, { recursive: true, force: true }); 45 | } catch (err) { 46 | console.error(`Error cleaning up: ${err.message}`); 47 | } 48 | }); 49 | 50 | // Test function that simulates the Codex profile file copying behavior 51 | function mockCreateCodexStructure() { 52 | // Codex profile copies AGENTS.md to AGENTS.md in project root (same name) 53 | const sourceContent = 'Sample AGENTS.md content for Codex integration'; 54 | fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), sourceContent); 55 | } 56 | 57 | test('creates AGENTS.md file in project root', () => { 58 | // Act 59 | mockCreateCodexStructure(); 60 | 61 | // Assert 62 | expect(fs.writeFileSync).toHaveBeenCalledWith( 63 | path.join(tempDir, 'AGENTS.md'), 64 | 'Sample AGENTS.md content for Codex integration' 65 | ); 66 | }); 67 | 68 | test('does not create any profile directories', () => { 69 | // Act 70 | mockCreateCodexStructure(); 71 | 72 | // Assert - Codex profile should not create any directories 73 | // Only the temp directory creation calls should exist 74 | const mkdirCalls = fs.mkdirSync.mock.calls.filter( 75 | (call) => !call[0].includes('task-master-test-') 76 | ); 77 | expect(mkdirCalls).toHaveLength(0); 78 | }); 79 | 80 | test('does not create MCP configuration files', () => { 81 | // Act 82 | mockCreateCodexStructure(); 83 | 84 | // Assert - Codex profile should not create any MCP config files 85 | const writeFileCalls = fs.writeFileSync.mock.calls; 86 | const mcpConfigCalls = writeFileCalls.filter( 87 | (call) => 88 | call[0].toString().includes('mcp.json') || 89 | call[0].toString().includes('mcp_settings.json') 90 | ); 91 | expect(mcpConfigCalls).toHaveLength(0); 92 | }); 93 | 94 | test('only creates the target integration guide file', () => { 95 | // Act 96 | mockCreateCodexStructure(); 97 | 98 | // Assert - Should only create AGENTS.md 99 | const writeFileCalls = fs.writeFileSync.mock.calls; 100 | expect(writeFileCalls).toHaveLength(1); 101 | expect(writeFileCalls[0][0]).toBe(path.join(tempDir, 'AGENTS.md')); 102 | }); 103 | 104 | test('uses the same filename as source (AGENTS.md)', () => { 105 | // Act 106 | mockCreateCodexStructure(); 107 | 108 | // Assert - Codex should keep the same filename unlike Claude which renames it 109 | const writeFileCalls = fs.writeFileSync.mock.calls; 110 | expect(writeFileCalls[0][0]).toContain('AGENTS.md'); 111 | expect(writeFileCalls[0][0]).not.toContain('CLAUDE.md'); 112 | }); 113 | }); 114 | ``` -------------------------------------------------------------------------------- /tests/unit/progress/base-progress-tracker.test.js: -------------------------------------------------------------------------------- ```javascript 1 | import { jest } from '@jest/globals'; 2 | 3 | // Mock cli-progress factory before importing BaseProgressTracker 4 | jest.unstable_mockModule( 5 | '../../../src/progress/cli-progress-factory.js', 6 | () => ({ 7 | newMultiBar: jest.fn(() => ({ 8 | create: jest.fn(() => ({ 9 | update: jest.fn() 10 | })), 11 | stop: jest.fn() 12 | })) 13 | }) 14 | ); 15 | 16 | const { newMultiBar } = await import( 17 | '../../../src/progress/cli-progress-factory.js' 18 | ); 19 | const { BaseProgressTracker } = await import( 20 | '../../../src/progress/base-progress-tracker.js' 21 | ); 22 | 23 | describe('BaseProgressTracker', () => { 24 | let tracker; 25 | let mockMultiBar; 26 | let mockProgressBar; 27 | let mockTimeTokensBar; 28 | 29 | beforeEach(() => { 30 | jest.clearAllMocks(); 31 | jest.useFakeTimers(); 32 | 33 | // Setup mocks 34 | mockProgressBar = { update: jest.fn() }; 35 | mockTimeTokensBar = { update: jest.fn() }; 36 | mockMultiBar = { 37 | create: jest 38 | .fn() 39 | .mockReturnValueOnce(mockTimeTokensBar) 40 | .mockReturnValueOnce(mockProgressBar), 41 | stop: jest.fn() 42 | }; 43 | newMultiBar.mockReturnValue(mockMultiBar); 44 | 45 | tracker = new BaseProgressTracker({ numUnits: 10, unitName: 'task' }); 46 | }); 47 | 48 | afterEach(() => { 49 | jest.useRealTimers(); 50 | }); 51 | 52 | describe('cleanup', () => { 53 | it('should stop and clear timer interval', () => { 54 | tracker.start(); 55 | expect(tracker._timerInterval).toBeTruthy(); 56 | 57 | tracker.cleanup(); 58 | expect(tracker._timerInterval).toBeNull(); 59 | }); 60 | 61 | it('should stop and null multibar reference', () => { 62 | tracker.start(); 63 | expect(tracker.multibar).toBeTruthy(); 64 | 65 | tracker.cleanup(); 66 | expect(mockMultiBar.stop).toHaveBeenCalled(); 67 | expect(tracker.multibar).toBeNull(); 68 | }); 69 | 70 | it('should null progress bar references', () => { 71 | tracker.start(); 72 | expect(tracker.timeTokensBar).toBeTruthy(); 73 | expect(tracker.progressBar).toBeTruthy(); 74 | 75 | tracker.cleanup(); 76 | expect(tracker.timeTokensBar).toBeNull(); 77 | expect(tracker.progressBar).toBeNull(); 78 | }); 79 | 80 | it('should set finished state', () => { 81 | tracker.start(); 82 | expect(tracker.isStarted).toBe(true); 83 | expect(tracker.isFinished).toBe(false); 84 | 85 | tracker.cleanup(); 86 | expect(tracker.isStarted).toBe(false); 87 | expect(tracker.isFinished).toBe(true); 88 | }); 89 | 90 | it('should handle cleanup when multibar.stop throws error', () => { 91 | tracker.start(); 92 | mockMultiBar.stop.mockImplementation(() => { 93 | throw new Error('Stop failed'); 94 | }); 95 | 96 | expect(() => tracker.cleanup()).not.toThrow(); 97 | expect(tracker.multibar).toBeNull(); 98 | }); 99 | 100 | it('should be safe to call multiple times', () => { 101 | tracker.start(); 102 | 103 | tracker.cleanup(); 104 | tracker.cleanup(); 105 | tracker.cleanup(); 106 | 107 | expect(mockMultiBar.stop).toHaveBeenCalledTimes(1); 108 | }); 109 | 110 | it('should be safe to call without starting', () => { 111 | expect(() => tracker.cleanup()).not.toThrow(); 112 | expect(tracker.multibar).toBeNull(); 113 | }); 114 | }); 115 | 116 | describe('stop vs cleanup', () => { 117 | it('stop should call cleanup and null multibar reference', () => { 118 | tracker.start(); 119 | tracker.stop(); 120 | 121 | // stop() now calls cleanup() which nulls the multibar 122 | expect(tracker.multibar).toBeNull(); 123 | expect(tracker.isFinished).toBe(true); 124 | }); 125 | 126 | it('cleanup should null multibar preventing getSummary', () => { 127 | tracker.start(); 128 | tracker.cleanup(); 129 | 130 | expect(tracker.multibar).toBeNull(); 131 | expect(tracker.isFinished).toBe(true); 132 | }); 133 | }); 134 | }); 135 | ``` -------------------------------------------------------------------------------- /.taskmaster/reports/task-complexity-report_test-prd-tag.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "meta": { 3 | "generatedAt": "2025-06-13T06:52:00.611Z", 4 | "tasksAnalyzed": 5, 5 | "totalTasks": 5, 6 | "analysisCount": 5, 7 | "thresholdScore": 5, 8 | "projectName": "Taskmaster", 9 | "usedResearch": true 10 | }, 11 | "complexityAnalysis": [ 12 | { 13 | "taskId": 1, 14 | "taskTitle": "Setup Project Repository and Node.js Environment", 15 | "complexityScore": 4, 16 | "recommendedSubtasks": 6, 17 | "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.", 18 | "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." 19 | }, 20 | { 21 | "taskId": 2, 22 | "taskTitle": "Implement Core Functionality and CLI Interface", 23 | "complexityScore": 7, 24 | "recommendedSubtasks": 7, 25 | "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.", 26 | "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." 27 | }, 28 | { 29 | "taskId": 3, 30 | "taskTitle": "Implement Testing Suite and Validation", 31 | "complexityScore": 6, 32 | "recommendedSubtasks": 6, 33 | "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.", 34 | "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." 35 | }, 36 | { 37 | "taskId": 4, 38 | "taskTitle": "Setup Node.js Project with CLI Interface", 39 | "complexityScore": 5, 40 | "recommendedSubtasks": 7, 41 | "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.", 42 | "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." 43 | }, 44 | { 45 | "taskId": 5, 46 | "taskTitle": "Implement Core Functionality with Testing", 47 | "complexityScore": 8, 48 | "recommendedSubtasks": 8, 49 | "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.", 50 | "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." 51 | } 52 | ] 53 | } 54 | ``` -------------------------------------------------------------------------------- /mcp-server/src/index.js: -------------------------------------------------------------------------------- ```javascript 1 | import { FastMCP } from 'fastmcp'; 2 | import path from 'path'; 3 | import dotenv from 'dotenv'; 4 | import { fileURLToPath } from 'url'; 5 | import fs from 'fs'; 6 | import logger from './logger.js'; 7 | import { registerTaskMasterTools } from './tools/index.js'; 8 | import ProviderRegistry from '../../src/provider-registry/index.js'; 9 | import { MCPProvider } from './providers/mcp-provider.js'; 10 | import packageJson from '../../package.json' with { type: 'json' }; 11 | 12 | // Load environment variables 13 | dotenv.config(); 14 | 15 | // Constants 16 | const __filename = fileURLToPath(import.meta.url); 17 | const __dirname = path.dirname(__filename); 18 | 19 | /** 20 | * Main MCP server class that integrates with Task Master 21 | */ 22 | class TaskMasterMCPServer { 23 | constructor() { 24 | this.options = { 25 | name: 'Task Master MCP Server', 26 | version: packageJson.version 27 | }; 28 | 29 | this.server = new FastMCP(this.options); 30 | this.initialized = false; 31 | 32 | // Bind methods 33 | this.init = this.init.bind(this); 34 | this.start = this.start.bind(this); 35 | this.stop = this.stop.bind(this); 36 | 37 | // Setup logging 38 | this.logger = logger; 39 | } 40 | 41 | /** 42 | * Initialize the MCP server with necessary tools and routes 43 | */ 44 | async init() { 45 | if (this.initialized) return; 46 | 47 | // Pass the manager instance to the tool registration function 48 | registerTaskMasterTools(this.server, this.asyncManager); 49 | 50 | this.initialized = true; 51 | 52 | return this; 53 | } 54 | 55 | /** 56 | * Start the MCP server 57 | */ 58 | async start() { 59 | if (!this.initialized) { 60 | await this.init(); 61 | } 62 | 63 | this.server.on('connect', (event) => { 64 | event.session.server.sendLoggingMessage({ 65 | data: { 66 | context: event.session.context, 67 | message: `MCP Server connected: ${event.session.name}` 68 | }, 69 | level: 'info' 70 | }); 71 | this.registerRemoteProvider(event.session); 72 | }); 73 | 74 | // Start the FastMCP server with increased timeout 75 | await this.server.start({ 76 | transportType: 'stdio', 77 | timeout: 120000 // 2 minutes timeout (in milliseconds) 78 | }); 79 | 80 | return this; 81 | } 82 | 83 | /** 84 | * Register both MCP providers with the provider registry 85 | */ 86 | registerRemoteProvider(session) { 87 | // Check if the server has at least one session 88 | if (session) { 89 | // Make sure session has required capabilities 90 | if (!session.clientCapabilities || !session.clientCapabilities.sampling) { 91 | session.server.sendLoggingMessage({ 92 | data: { 93 | context: session.context, 94 | message: `MCP session missing required sampling capabilities, providers not registered` 95 | }, 96 | level: 'info' 97 | }); 98 | return; 99 | } 100 | 101 | // Register MCP provider with the Provider Registry 102 | 103 | // Register the unified MCP provider 104 | const mcpProvider = new MCPProvider(); 105 | mcpProvider.setSession(session); 106 | 107 | // Register provider with the registry 108 | const providerRegistry = ProviderRegistry.getInstance(); 109 | providerRegistry.registerProvider('mcp', mcpProvider); 110 | 111 | session.server.sendLoggingMessage({ 112 | data: { 113 | context: session.context, 114 | message: `MCP Server connected` 115 | }, 116 | level: 'info' 117 | }); 118 | } else { 119 | session.server.sendLoggingMessage({ 120 | data: { 121 | context: session.context, 122 | message: `No MCP sessions available, providers not registered` 123 | }, 124 | level: 'warn' 125 | }); 126 | } 127 | } 128 | 129 | /** 130 | * Stop the MCP server 131 | */ 132 | async stop() { 133 | if (this.server) { 134 | await this.server.stop(); 135 | } 136 | } 137 | } 138 | 139 | export default TaskMasterMCPServer; 140 | ```