This is page 5 of 38. Use http://codebase.md/eyaltoledano/claude-task-master?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── README.md ├── .claude │ ├── agents │ │ ├── task-checker.md │ │ ├── task-executor.md │ │ └── task-orchestrator.md │ ├── commands │ │ ├── dedupe.md │ │ └── tm │ │ ├── add-dependency │ │ │ └── add-dependency.md │ │ ├── add-subtask │ │ │ ├── add-subtask.md │ │ │ └── convert-task-to-subtask.md │ │ ├── add-task │ │ │ └── add-task.md │ │ ├── analyze-complexity │ │ │ └── analyze-complexity.md │ │ ├── complexity-report │ │ │ └── complexity-report.md │ │ ├── expand │ │ │ ├── expand-all-tasks.md │ │ │ └── expand-task.md │ │ ├── fix-dependencies │ │ │ └── fix-dependencies.md │ │ ├── generate │ │ │ └── generate-tasks.md │ │ ├── help.md │ │ ├── init │ │ │ ├── init-project-quick.md │ │ │ └── init-project.md │ │ ├── learn.md │ │ ├── list │ │ │ ├── list-tasks-by-status.md │ │ │ ├── list-tasks-with-subtasks.md │ │ │ └── list-tasks.md │ │ ├── models │ │ │ ├── setup-models.md │ │ │ └── view-models.md │ │ ├── next │ │ │ └── next-task.md │ │ ├── parse-prd │ │ │ ├── parse-prd-with-research.md │ │ │ └── parse-prd.md │ │ ├── remove-dependency │ │ │ └── remove-dependency.md │ │ ├── remove-subtask │ │ │ └── remove-subtask.md │ │ ├── remove-subtasks │ │ │ ├── remove-all-subtasks.md │ │ │ └── remove-subtasks.md │ │ ├── remove-task │ │ │ └── remove-task.md │ │ ├── set-status │ │ │ ├── to-cancelled.md │ │ │ ├── to-deferred.md │ │ │ ├── to-done.md │ │ │ ├── to-in-progress.md │ │ │ ├── to-pending.md │ │ │ └── to-review.md │ │ ├── setup │ │ │ ├── install-taskmaster.md │ │ │ └── quick-install-taskmaster.md │ │ ├── show │ │ │ └── show-task.md │ │ ├── status │ │ │ └── project-status.md │ │ ├── sync-readme │ │ │ └── sync-readme.md │ │ ├── tm-main.md │ │ ├── update │ │ │ ├── update-single-task.md │ │ │ ├── update-task.md │ │ │ └── update-tasks-from-id.md │ │ ├── utils │ │ │ └── analyze-project.md │ │ ├── validate-dependencies │ │ │ └── validate-dependencies.md │ │ └── workflows │ │ ├── auto-implement-tasks.md │ │ ├── command-pipeline.md │ │ └── smart-workflow.md │ └── TM_COMMANDS_GUIDE.md ├── .coderabbit.yaml ├── .cursor │ ├── mcp.json │ └── rules │ ├── ai_providers.mdc │ ├── ai_services.mdc │ ├── architecture.mdc │ ├── changeset.mdc │ ├── commands.mdc │ ├── context_gathering.mdc │ ├── cursor_rules.mdc │ ├── dependencies.mdc │ ├── dev_workflow.mdc │ ├── git_workflow.mdc │ ├── glossary.mdc │ ├── mcp.mdc │ ├── new_features.mdc │ ├── self_improve.mdc │ ├── tags.mdc │ ├── taskmaster.mdc │ ├── tasks.mdc │ ├── telemetry.mdc │ ├── test_workflow.mdc │ ├── tests.mdc │ ├── ui.mdc │ └── utilities.mdc ├── .cursorignore ├── .env.example ├── .github │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ ├── enhancements---feature-requests.md │ │ └── feedback.md │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bugfix.md │ │ ├── config.yml │ │ ├── feature.md │ │ └── integration.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── scripts │ │ ├── auto-close-duplicates.mjs │ │ ├── backfill-duplicate-comments.mjs │ │ ├── check-pre-release-mode.mjs │ │ ├── parse-metrics.mjs │ │ ├── release.mjs │ │ ├── tag-extension.mjs │ │ └── utils.mjs │ └── workflows │ ├── auto-close-duplicates.yml │ ├── backfill-duplicate-comments.yml │ ├── ci.yml │ ├── claude-dedupe-issues.yml │ ├── claude-docs-trigger.yml │ ├── claude-docs-updater.yml │ ├── claude-issue-triage.yml │ ├── claude.yml │ ├── extension-ci.yml │ ├── extension-release.yml │ ├── log-issue-events.yml │ ├── pre-release.yml │ ├── release-check.yml │ ├── release.yml │ ├── update-models-md.yml │ └── weekly-metrics-discord.yml ├── .gitignore ├── .kiro │ ├── hooks │ │ ├── tm-code-change-task-tracker.kiro.hook │ │ ├── tm-complexity-analyzer.kiro.hook │ │ ├── tm-daily-standup-assistant.kiro.hook │ │ ├── tm-git-commit-task-linker.kiro.hook │ │ ├── tm-pr-readiness-checker.kiro.hook │ │ ├── tm-task-dependency-auto-progression.kiro.hook │ │ └── tm-test-success-task-completer.kiro.hook │ ├── settings │ │ └── mcp.json │ └── steering │ ├── dev_workflow.md │ ├── kiro_rules.md │ ├── self_improve.md │ ├── taskmaster_hooks_workflow.md │ └── taskmaster.md ├── .manypkg.json ├── .mcp.json ├── .npmignore ├── .nvmrc ├── .taskmaster │ ├── CLAUDE.md │ ├── config.json │ ├── docs │ │ ├── MIGRATION-ROADMAP.md │ │ ├── prd-tm-start.txt │ │ ├── prd.txt │ │ ├── README.md │ │ ├── research │ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md │ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md │ │ │ ├── 2025-06-14_test-save-functionality.md │ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md │ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md │ │ ├── task-template-importing-prd.txt │ │ ├── test-prd.txt │ │ └── tm-core-phase-1.txt │ ├── reports │ │ ├── task-complexity-report_cc-kiro-hooks.json │ │ ├── task-complexity-report_test-prd-tag.json │ │ ├── task-complexity-report_tm-core-phase-1.json │ │ ├── task-complexity-report.json │ │ └── tm-core-complexity.json │ ├── state.json │ ├── tasks │ │ ├── task_001_tm-start.txt │ │ ├── task_002_tm-start.txt │ │ ├── task_003_tm-start.txt │ │ ├── task_004_tm-start.txt │ │ ├── task_007_tm-start.txt │ │ └── tasks.json │ └── templates │ └── example_prd.txt ├── .vscode │ ├── extensions.json │ └── settings.json ├── apps │ ├── cli │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── commands │ │ │ │ ├── auth.command.ts │ │ │ │ ├── context.command.ts │ │ │ │ ├── list.command.ts │ │ │ │ ├── set-status.command.ts │ │ │ │ ├── show.command.ts │ │ │ │ └── start.command.ts │ │ │ ├── index.ts │ │ │ ├── ui │ │ │ │ ├── components │ │ │ │ │ ├── dashboard.component.ts │ │ │ │ │ ├── header.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── next-task.component.ts │ │ │ │ │ ├── suggested-steps.component.ts │ │ │ │ │ └── task-detail.component.ts │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ ├── auto-update.ts │ │ │ └── ui.ts │ │ └── tsconfig.json │ ├── docs │ │ ├── archive │ │ │ ├── ai-client-utils-example.mdx │ │ │ ├── ai-development-workflow.mdx │ │ │ ├── command-reference.mdx │ │ │ ├── configuration.mdx │ │ │ ├── cursor-setup.mdx │ │ │ ├── examples.mdx │ │ │ └── Installation.mdx │ │ ├── best-practices │ │ │ ├── advanced-tasks.mdx │ │ │ ├── configuration-advanced.mdx │ │ │ └── index.mdx │ │ ├── capabilities │ │ │ ├── cli-root-commands.mdx │ │ │ ├── index.mdx │ │ │ ├── mcp.mdx │ │ │ └── task-structure.mdx │ │ ├── CHANGELOG.md │ │ ├── docs.json │ │ ├── favicon.svg │ │ ├── getting-started │ │ │ ├── contribute.mdx │ │ │ ├── faq.mdx │ │ │ └── quick-start │ │ │ ├── configuration-quick.mdx │ │ │ ├── execute-quick.mdx │ │ │ ├── installation.mdx │ │ │ ├── moving-forward.mdx │ │ │ ├── prd-quick.mdx │ │ │ ├── quick-start.mdx │ │ │ ├── requirements.mdx │ │ │ ├── rules-quick.mdx │ │ │ └── tasks-quick.mdx │ │ ├── introduction.mdx │ │ ├── licensing.md │ │ ├── logo │ │ │ ├── dark.svg │ │ │ ├── light.svg │ │ │ └── task-master-logo.png │ │ ├── package.json │ │ ├── README.md │ │ ├── style.css │ │ ├── vercel.json │ │ └── whats-new.mdx │ └── extension │ ├── .vscodeignore │ ├── assets │ │ ├── banner.png │ │ ├── icon-dark.svg │ │ ├── icon-light.svg │ │ ├── icon.png │ │ ├── screenshots │ │ │ ├── kanban-board.png │ │ │ └── task-details.png │ │ └── sidebar-icon.svg │ ├── CHANGELOG.md │ ├── components.json │ ├── docs │ │ ├── extension-CI-setup.md │ │ └── extension-development-guide.md │ ├── esbuild.js │ ├── LICENSE │ ├── package.json │ ├── package.mjs │ ├── package.publish.json │ ├── README.md │ ├── src │ │ ├── components │ │ │ ├── ConfigView.tsx │ │ │ ├── constants.ts │ │ │ ├── TaskDetails │ │ │ │ ├── AIActionsSection.tsx │ │ │ │ ├── DetailsSection.tsx │ │ │ │ ├── PriorityBadge.tsx │ │ │ │ ├── SubtasksSection.tsx │ │ │ │ ├── TaskMetadataSidebar.tsx │ │ │ │ └── useTaskDetails.ts │ │ │ ├── TaskDetailsView.tsx │ │ │ ├── TaskMasterLogo.tsx │ │ │ └── ui │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── CollapsibleSection.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── label.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── separator.tsx │ │ │ ├── shadcn-io │ │ │ │ └── kanban │ │ │ │ └── index.tsx │ │ │ └── textarea.tsx │ │ ├── extension.ts │ │ ├── index.ts │ │ ├── lib │ │ │ └── utils.ts │ │ ├── services │ │ │ ├── config-service.ts │ │ │ ├── error-handler.ts │ │ │ ├── notification-preferences.ts │ │ │ ├── polling-service.ts │ │ │ ├── polling-strategies.ts │ │ │ ├── sidebar-webview-manager.ts │ │ │ ├── task-repository.ts │ │ │ ├── terminal-manager.ts │ │ │ └── webview-manager.ts │ │ ├── test │ │ │ └── extension.test.ts │ │ ├── utils │ │ │ ├── configManager.ts │ │ │ ├── connectionManager.ts │ │ │ ├── errorHandler.ts │ │ │ ├── event-emitter.ts │ │ │ ├── logger.ts │ │ │ ├── mcpClient.ts │ │ │ ├── notificationPreferences.ts │ │ │ └── task-master-api │ │ │ ├── cache │ │ │ │ └── cache-manager.ts │ │ │ ├── index.ts │ │ │ ├── mcp-client.ts │ │ │ ├── transformers │ │ │ │ └── task-transformer.ts │ │ │ └── types │ │ │ └── index.ts │ │ └── webview │ │ ├── App.tsx │ │ ├── components │ │ │ ├── AppContent.tsx │ │ │ ├── EmptyState.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── PollingStatus.tsx │ │ │ ├── PriorityBadge.tsx │ │ │ ├── SidebarView.tsx │ │ │ ├── TagDropdown.tsx │ │ │ ├── TaskCard.tsx │ │ │ ├── TaskEditModal.tsx │ │ │ ├── TaskMasterKanban.tsx │ │ │ ├── ToastContainer.tsx │ │ │ └── ToastNotification.tsx │ │ ├── constants │ │ │ └── index.ts │ │ ├── contexts │ │ │ └── VSCodeContext.tsx │ │ ├── hooks │ │ │ ├── useTaskQueries.ts │ │ │ ├── useVSCodeMessages.ts │ │ │ └── useWebviewHeight.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── providers │ │ │ └── QueryProvider.tsx │ │ ├── reducers │ │ │ └── appReducer.ts │ │ ├── sidebar.tsx │ │ ├── types │ │ │ └── index.ts │ │ └── utils │ │ ├── logger.ts │ │ └── toast.ts │ └── tsconfig.json ├── assets │ ├── .windsurfrules │ ├── AGENTS.md │ ├── claude │ │ ├── agents │ │ │ ├── task-checker.md │ │ │ ├── task-executor.md │ │ │ └── task-orchestrator.md │ │ ├── commands │ │ │ └── tm │ │ │ ├── add-dependency │ │ │ │ └── add-dependency.md │ │ │ ├── add-subtask │ │ │ │ ├── add-subtask.md │ │ │ │ └── convert-task-to-subtask.md │ │ │ ├── add-task │ │ │ │ └── add-task.md │ │ │ ├── analyze-complexity │ │ │ │ └── analyze-complexity.md │ │ │ ├── clear-subtasks │ │ │ │ ├── clear-all-subtasks.md │ │ │ │ └── clear-subtasks.md │ │ │ ├── complexity-report │ │ │ │ └── complexity-report.md │ │ │ ├── expand │ │ │ │ ├── expand-all-tasks.md │ │ │ │ └── expand-task.md │ │ │ ├── fix-dependencies │ │ │ │ └── fix-dependencies.md │ │ │ ├── generate │ │ │ │ └── generate-tasks.md │ │ │ ├── help.md │ │ │ ├── init │ │ │ │ ├── init-project-quick.md │ │ │ │ └── init-project.md │ │ │ ├── learn.md │ │ │ ├── list │ │ │ │ ├── list-tasks-by-status.md │ │ │ │ ├── list-tasks-with-subtasks.md │ │ │ │ └── list-tasks.md │ │ │ ├── models │ │ │ │ ├── setup-models.md │ │ │ │ └── view-models.md │ │ │ ├── next │ │ │ │ └── next-task.md │ │ │ ├── parse-prd │ │ │ │ ├── parse-prd-with-research.md │ │ │ │ └── parse-prd.md │ │ │ ├── remove-dependency │ │ │ │ └── remove-dependency.md │ │ │ ├── remove-subtask │ │ │ │ └── remove-subtask.md │ │ │ ├── remove-subtasks │ │ │ │ ├── remove-all-subtasks.md │ │ │ │ └── remove-subtasks.md │ │ │ ├── remove-task │ │ │ │ └── remove-task.md │ │ │ ├── set-status │ │ │ │ ├── to-cancelled.md │ │ │ │ ├── to-deferred.md │ │ │ │ ├── to-done.md │ │ │ │ ├── to-in-progress.md │ │ │ │ ├── to-pending.md │ │ │ │ └── to-review.md │ │ │ ├── setup │ │ │ │ ├── install-taskmaster.md │ │ │ │ └── quick-install-taskmaster.md │ │ │ ├── show │ │ │ │ └── show-task.md │ │ │ ├── status │ │ │ │ └── project-status.md │ │ │ ├── sync-readme │ │ │ │ └── sync-readme.md │ │ │ ├── tm-main.md │ │ │ ├── update │ │ │ │ ├── update-single-task.md │ │ │ │ ├── update-task.md │ │ │ │ └── update-tasks-from-id.md │ │ │ ├── utils │ │ │ │ └── analyze-project.md │ │ │ ├── validate-dependencies │ │ │ │ └── validate-dependencies.md │ │ │ └── workflows │ │ │ ├── auto-implement-tasks.md │ │ │ ├── command-pipeline.md │ │ │ └── smart-workflow.md │ │ └── TM_COMMANDS_GUIDE.md │ ├── config.json │ ├── env.example │ ├── example_prd.txt │ ├── gitignore │ ├── kiro-hooks │ │ ├── tm-code-change-task-tracker.kiro.hook │ │ ├── tm-complexity-analyzer.kiro.hook │ │ ├── tm-daily-standup-assistant.kiro.hook │ │ ├── tm-git-commit-task-linker.kiro.hook │ │ ├── tm-pr-readiness-checker.kiro.hook │ │ ├── tm-task-dependency-auto-progression.kiro.hook │ │ └── tm-test-success-task-completer.kiro.hook │ ├── roocode │ │ ├── .roo │ │ │ ├── rules-architect │ │ │ │ └── architect-rules │ │ │ ├── rules-ask │ │ │ │ └── ask-rules │ │ │ ├── rules-code │ │ │ │ └── code-rules │ │ │ ├── rules-debug │ │ │ │ └── debug-rules │ │ │ ├── rules-orchestrator │ │ │ │ └── orchestrator-rules │ │ │ └── rules-test │ │ │ └── test-rules │ │ └── .roomodes │ ├── rules │ │ ├── cursor_rules.mdc │ │ ├── dev_workflow.mdc │ │ ├── self_improve.mdc │ │ ├── taskmaster_hooks_workflow.mdc │ │ └── taskmaster.mdc │ └── scripts_README.md ├── bin │ └── task-master.js ├── biome.json ├── CHANGELOG.md ├── CLAUDE.md ├── context │ ├── chats │ │ ├── add-task-dependencies-1.md │ │ └── max-min-tokens.txt.md │ ├── fastmcp-core.txt │ ├── fastmcp-docs.txt │ ├── MCP_INTEGRATION.md │ ├── mcp-js-sdk-docs.txt │ ├── mcp-protocol-repo.txt │ ├── mcp-protocol-schema-03262025.json │ └── mcp-protocol-spec.txt ├── CONTRIBUTING.md ├── docs │ ├── CLI-COMMANDER-PATTERN.md │ ├── command-reference.md │ ├── configuration.md │ ├── contributor-docs │ │ └── testing-roo-integration.md │ ├── cross-tag-task-movement.md │ ├── examples │ │ └── claude-code-usage.md │ ├── examples.md │ ├── licensing.md │ ├── mcp-provider-guide.md │ ├── mcp-provider.md │ ├── migration-guide.md │ ├── models.md │ ├── providers │ │ └── gemini-cli.md │ ├── README.md │ ├── scripts │ │ └── models-json-to-markdown.js │ ├── task-structure.md │ └── tutorial.md ├── images │ └── logo.png ├── index.js ├── jest.config.js ├── jest.resolver.cjs ├── LICENSE ├── llms-install.md ├── mcp-server │ ├── server.js │ └── src │ ├── core │ │ ├── __tests__ │ │ │ └── context-manager.test.js │ │ ├── context-manager.js │ │ ├── direct-functions │ │ │ ├── add-dependency.js │ │ │ ├── add-subtask.js │ │ │ ├── add-tag.js │ │ │ ├── add-task.js │ │ │ ├── analyze-task-complexity.js │ │ │ ├── cache-stats.js │ │ │ ├── clear-subtasks.js │ │ │ ├── complexity-report.js │ │ │ ├── copy-tag.js │ │ │ ├── create-tag-from-branch.js │ │ │ ├── delete-tag.js │ │ │ ├── expand-all-tasks.js │ │ │ ├── expand-task.js │ │ │ ├── fix-dependencies.js │ │ │ ├── generate-task-files.js │ │ │ ├── initialize-project.js │ │ │ ├── list-tags.js │ │ │ ├── list-tasks.js │ │ │ ├── models.js │ │ │ ├── move-task-cross-tag.js │ │ │ ├── move-task.js │ │ │ ├── next-task.js │ │ │ ├── parse-prd.js │ │ │ ├── remove-dependency.js │ │ │ ├── remove-subtask.js │ │ │ ├── remove-task.js │ │ │ ├── rename-tag.js │ │ │ ├── research.js │ │ │ ├── response-language.js │ │ │ ├── rules.js │ │ │ ├── scope-down.js │ │ │ ├── scope-up.js │ │ │ ├── set-task-status.js │ │ │ ├── show-task.js │ │ │ ├── update-subtask-by-id.js │ │ │ ├── update-task-by-id.js │ │ │ ├── update-tasks.js │ │ │ ├── use-tag.js │ │ │ └── validate-dependencies.js │ │ ├── task-master-core.js │ │ └── utils │ │ ├── env-utils.js │ │ └── path-utils.js │ ├── custom-sdk │ │ ├── errors.js │ │ ├── index.js │ │ ├── json-extractor.js │ │ ├── language-model.js │ │ ├── message-converter.js │ │ └── schema-converter.js │ ├── index.js │ ├── logger.js │ ├── providers │ │ └── mcp-provider.js │ └── tools │ ├── add-dependency.js │ ├── add-subtask.js │ ├── add-tag.js │ ├── add-task.js │ ├── analyze.js │ ├── clear-subtasks.js │ ├── complexity-report.js │ ├── copy-tag.js │ ├── delete-tag.js │ ├── expand-all.js │ ├── expand-task.js │ ├── fix-dependencies.js │ ├── generate.js │ ├── get-operation-status.js │ ├── get-task.js │ ├── get-tasks.js │ ├── index.js │ ├── initialize-project.js │ ├── list-tags.js │ ├── models.js │ ├── move-task.js │ ├── next-task.js │ ├── parse-prd.js │ ├── remove-dependency.js │ ├── remove-subtask.js │ ├── remove-task.js │ ├── rename-tag.js │ ├── research.js │ ├── response-language.js │ ├── rules.js │ ├── scope-down.js │ ├── scope-up.js │ ├── set-task-status.js │ ├── update-subtask.js │ ├── update-task.js │ ├── update.js │ ├── use-tag.js │ ├── utils.js │ └── validate-dependencies.js ├── mcp-test.js ├── output.json ├── package-lock.json ├── package.json ├── packages │ ├── build-config │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ └── tsdown.base.ts │ │ └── tsconfig.json │ └── tm-core │ ├── .gitignore │ ├── CHANGELOG.md │ ├── docs │ │ └── listTasks-architecture.md │ ├── package.json │ ├── POC-STATUS.md │ ├── README.md │ ├── src │ │ ├── auth │ │ │ ├── auth-manager.test.ts │ │ │ ├── auth-manager.ts │ │ │ ├── config.ts │ │ │ ├── credential-store.test.ts │ │ │ ├── credential-store.ts │ │ │ ├── index.ts │ │ │ ├── oauth-service.ts │ │ │ ├── supabase-session-storage.ts │ │ │ └── types.ts │ │ ├── clients │ │ │ ├── index.ts │ │ │ └── supabase-client.ts │ │ ├── config │ │ │ ├── config-manager.spec.ts │ │ │ ├── config-manager.ts │ │ │ ├── index.ts │ │ │ └── services │ │ │ ├── config-loader.service.spec.ts │ │ │ ├── config-loader.service.ts │ │ │ ├── config-merger.service.spec.ts │ │ │ ├── config-merger.service.ts │ │ │ ├── config-persistence.service.spec.ts │ │ │ ├── config-persistence.service.ts │ │ │ ├── environment-config-provider.service.spec.ts │ │ │ ├── environment-config-provider.service.ts │ │ │ ├── index.ts │ │ │ ├── runtime-state-manager.service.spec.ts │ │ │ └── runtime-state-manager.service.ts │ │ ├── constants │ │ │ └── index.ts │ │ ├── entities │ │ │ └── task.entity.ts │ │ ├── errors │ │ │ ├── index.ts │ │ │ └── task-master-error.ts │ │ ├── executors │ │ │ ├── base-executor.ts │ │ │ ├── claude-executor.ts │ │ │ ├── executor-factory.ts │ │ │ ├── executor-service.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── interfaces │ │ │ ├── ai-provider.interface.ts │ │ │ ├── configuration.interface.ts │ │ │ ├── index.ts │ │ │ └── storage.interface.ts │ │ ├── logger │ │ │ ├── factory.ts │ │ │ ├── index.ts │ │ │ └── logger.ts │ │ ├── mappers │ │ │ └── TaskMapper.ts │ │ ├── parser │ │ │ └── index.ts │ │ ├── providers │ │ │ ├── ai │ │ │ │ ├── base-provider.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── repositories │ │ │ ├── supabase-task-repository.ts │ │ │ └── task-repository.interface.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── organization.service.ts │ │ │ ├── task-execution-service.ts │ │ │ └── task-service.ts │ │ ├── storage │ │ │ ├── api-storage.ts │ │ │ ├── file-storage │ │ │ │ ├── file-operations.ts │ │ │ │ ├── file-storage.ts │ │ │ │ ├── format-handler.ts │ │ │ │ ├── index.ts │ │ │ │ └── path-resolver.ts │ │ │ ├── index.ts │ │ │ └── storage-factory.ts │ │ ├── subpath-exports.test.ts │ │ ├── task-master-core.ts │ │ ├── types │ │ │ ├── database.types.ts │ │ │ ├── index.ts │ │ │ └── legacy.ts │ │ └── utils │ │ ├── id-generator.ts │ │ └── index.ts │ ├── tests │ │ ├── integration │ │ │ └── list-tasks.test.ts │ │ ├── mocks │ │ │ └── mock-provider.ts │ │ ├── setup.ts │ │ └── unit │ │ ├── base-provider.test.ts │ │ ├── executor.test.ts │ │ └── smoke.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── README-task-master.md ├── README.md ├── scripts │ ├── dev.js │ ├── init.js │ ├── modules │ │ ├── ai-services-unified.js │ │ ├── commands.js │ │ ├── config-manager.js │ │ ├── dependency-manager.js │ │ ├── index.js │ │ ├── prompt-manager.js │ │ ├── supported-models.json │ │ ├── sync-readme.js │ │ ├── task-manager │ │ │ ├── add-subtask.js │ │ │ ├── add-task.js │ │ │ ├── analyze-task-complexity.js │ │ │ ├── clear-subtasks.js │ │ │ ├── expand-all-tasks.js │ │ │ ├── expand-task.js │ │ │ ├── find-next-task.js │ │ │ ├── generate-task-files.js │ │ │ ├── is-task-dependent.js │ │ │ ├── list-tasks.js │ │ │ ├── migrate.js │ │ │ ├── models.js │ │ │ ├── move-task.js │ │ │ ├── parse-prd │ │ │ │ ├── index.js │ │ │ │ ├── parse-prd-config.js │ │ │ │ ├── parse-prd-helpers.js │ │ │ │ ├── parse-prd-non-streaming.js │ │ │ │ ├── parse-prd-streaming.js │ │ │ │ └── parse-prd.js │ │ │ ├── remove-subtask.js │ │ │ ├── remove-task.js │ │ │ ├── research.js │ │ │ ├── response-language.js │ │ │ ├── scope-adjustment.js │ │ │ ├── set-task-status.js │ │ │ ├── tag-management.js │ │ │ ├── task-exists.js │ │ │ ├── update-single-task-status.js │ │ │ ├── update-subtask-by-id.js │ │ │ ├── update-task-by-id.js │ │ │ └── update-tasks.js │ │ ├── task-manager.js │ │ ├── ui.js │ │ ├── update-config-tokens.js │ │ ├── utils │ │ │ ├── contextGatherer.js │ │ │ ├── fuzzyTaskSearch.js │ │ │ └── git-utils.js │ │ └── utils.js │ ├── task-complexity-report.json │ ├── test-claude-errors.js │ └── test-claude.js ├── src │ ├── ai-providers │ │ ├── anthropic.js │ │ ├── azure.js │ │ ├── base-provider.js │ │ ├── bedrock.js │ │ ├── claude-code.js │ │ ├── custom-sdk │ │ │ ├── claude-code │ │ │ │ ├── errors.js │ │ │ │ ├── index.js │ │ │ │ ├── json-extractor.js │ │ │ │ ├── language-model.js │ │ │ │ ├── message-converter.js │ │ │ │ └── types.js │ │ │ └── grok-cli │ │ │ ├── errors.js │ │ │ ├── index.js │ │ │ ├── json-extractor.js │ │ │ ├── language-model.js │ │ │ ├── message-converter.js │ │ │ └── types.js │ │ ├── gemini-cli.js │ │ ├── google-vertex.js │ │ ├── google.js │ │ ├── grok-cli.js │ │ ├── groq.js │ │ ├── index.js │ │ ├── ollama.js │ │ ├── openai.js │ │ ├── openrouter.js │ │ ├── perplexity.js │ │ └── xai.js │ ├── constants │ │ ├── commands.js │ │ ├── paths.js │ │ ├── profiles.js │ │ ├── providers.js │ │ ├── rules-actions.js │ │ ├── task-priority.js │ │ └── task-status.js │ ├── profiles │ │ ├── amp.js │ │ ├── base-profile.js │ │ ├── claude.js │ │ ├── cline.js │ │ ├── codex.js │ │ ├── cursor.js │ │ ├── gemini.js │ │ ├── index.js │ │ ├── kilo.js │ │ ├── kiro.js │ │ ├── opencode.js │ │ ├── roo.js │ │ ├── trae.js │ │ ├── vscode.js │ │ ├── windsurf.js │ │ └── zed.js │ ├── progress │ │ ├── base-progress-tracker.js │ │ ├── cli-progress-factory.js │ │ ├── parse-prd-tracker.js │ │ ├── progress-tracker-builder.js │ │ └── tracker-ui.js │ ├── prompts │ │ ├── add-task.json │ │ ├── analyze-complexity.json │ │ ├── expand-task.json │ │ ├── parse-prd.json │ │ ├── README.md │ │ ├── research.json │ │ ├── schemas │ │ │ ├── parameter.schema.json │ │ │ ├── prompt-template.schema.json │ │ │ ├── README.md │ │ │ └── variant.schema.json │ │ ├── update-subtask.json │ │ ├── update-task.json │ │ └── update-tasks.json │ ├── provider-registry │ │ └── index.js │ ├── task-master.js │ ├── ui │ │ ├── confirm.js │ │ ├── indicators.js │ │ └── parse-prd.js │ └── utils │ ├── asset-resolver.js │ ├── create-mcp-config.js │ ├── format.js │ ├── getVersion.js │ ├── logger-utils.js │ ├── manage-gitignore.js │ ├── path-utils.js │ ├── profiles.js │ ├── rule-transformer.js │ ├── stream-parser.js │ └── timeout-manager.js ├── test-clean-tags.js ├── test-config-manager.js ├── test-prd.txt ├── test-tag-functions.js ├── test-version-check-full.js ├── test-version-check.js ├── tests │ ├── e2e │ │ ├── e2e_helpers.sh │ │ ├── parse_llm_output.cjs │ │ ├── run_e2e.sh │ │ ├── run_fallback_verification.sh │ │ └── test_llm_analysis.sh │ ├── fixture │ │ └── test-tasks.json │ ├── fixtures │ │ ├── .taskmasterconfig │ │ ├── sample-claude-response.js │ │ ├── sample-prd.txt │ │ └── sample-tasks.js │ ├── integration │ │ ├── claude-code-optional.test.js │ │ ├── cli │ │ │ ├── commands.test.js │ │ │ ├── complex-cross-tag-scenarios.test.js │ │ │ └── move-cross-tag.test.js │ │ ├── manage-gitignore.test.js │ │ ├── mcp-server │ │ │ └── direct-functions.test.js │ │ ├── move-task-cross-tag.integration.test.js │ │ ├── move-task-simple.integration.test.js │ │ └── profiles │ │ ├── amp-init-functionality.test.js │ │ ├── claude-init-functionality.test.js │ │ ├── cline-init-functionality.test.js │ │ ├── codex-init-functionality.test.js │ │ ├── cursor-init-functionality.test.js │ │ ├── gemini-init-functionality.test.js │ │ ├── opencode-init-functionality.test.js │ │ ├── roo-files-inclusion.test.js │ │ ├── roo-init-functionality.test.js │ │ ├── rules-files-inclusion.test.js │ │ ├── trae-init-functionality.test.js │ │ ├── vscode-init-functionality.test.js │ │ └── windsurf-init-functionality.test.js │ ├── manual │ │ ├── progress │ │ │ ├── parse-prd-analysis.js │ │ │ ├── test-parse-prd.js │ │ │ └── TESTING_GUIDE.md │ │ └── prompts │ │ ├── prompt-test.js │ │ └── README.md │ ├── README.md │ ├── setup.js │ └── unit │ ├── ai-providers │ │ ├── claude-code.test.js │ │ ├── custom-sdk │ │ │ └── claude-code │ │ │ └── language-model.test.js │ │ ├── gemini-cli.test.js │ │ ├── mcp-components.test.js │ │ └── openai.test.js │ ├── ai-services-unified.test.js │ ├── commands.test.js │ ├── config-manager.test.js │ ├── config-manager.test.mjs │ ├── dependency-manager.test.js │ ├── init.test.js │ ├── initialize-project.test.js │ ├── kebab-case-validation.test.js │ ├── manage-gitignore.test.js │ ├── mcp │ │ └── tools │ │ ├── __mocks__ │ │ │ └── move-task.js │ │ ├── add-task.test.js │ │ ├── analyze-complexity.test.js │ │ ├── expand-all.test.js │ │ ├── get-tasks.test.js │ │ ├── initialize-project.test.js │ │ ├── move-task-cross-tag-options.test.js │ │ ├── move-task-cross-tag.test.js │ │ └── remove-task.test.js │ ├── mcp-providers │ │ ├── mcp-components.test.js │ │ └── mcp-provider.test.js │ ├── parse-prd.test.js │ ├── profiles │ │ ├── amp-integration.test.js │ │ ├── claude-integration.test.js │ │ ├── cline-integration.test.js │ │ ├── codex-integration.test.js │ │ ├── cursor-integration.test.js │ │ ├── gemini-integration.test.js │ │ ├── kilo-integration.test.js │ │ ├── kiro-integration.test.js │ │ ├── mcp-config-validation.test.js │ │ ├── opencode-integration.test.js │ │ ├── profile-safety-check.test.js │ │ ├── roo-integration.test.js │ │ ├── rule-transformer-cline.test.js │ │ ├── rule-transformer-cursor.test.js │ │ ├── rule-transformer-gemini.test.js │ │ ├── rule-transformer-kilo.test.js │ │ ├── rule-transformer-kiro.test.js │ │ ├── rule-transformer-opencode.test.js │ │ ├── rule-transformer-roo.test.js │ │ ├── rule-transformer-trae.test.js │ │ ├── rule-transformer-vscode.test.js │ │ ├── rule-transformer-windsurf.test.js │ │ ├── rule-transformer-zed.test.js │ │ ├── rule-transformer.test.js │ │ ├── selective-profile-removal.test.js │ │ ├── subdirectory-support.test.js │ │ ├── trae-integration.test.js │ │ ├── vscode-integration.test.js │ │ ├── windsurf-integration.test.js │ │ └── zed-integration.test.js │ ├── progress │ │ └── base-progress-tracker.test.js │ ├── prompt-manager.test.js │ ├── prompts │ │ └── expand-task-prompt.test.js │ ├── providers │ │ └── provider-registry.test.js │ ├── scripts │ │ └── modules │ │ ├── commands │ │ │ ├── move-cross-tag.test.js │ │ │ └── README.md │ │ ├── dependency-manager │ │ │ ├── circular-dependencies.test.js │ │ │ ├── cross-tag-dependencies.test.js │ │ │ └── fix-dependencies-command.test.js │ │ ├── task-manager │ │ │ ├── add-subtask.test.js │ │ │ ├── add-task.test.js │ │ │ ├── analyze-task-complexity.test.js │ │ │ ├── clear-subtasks.test.js │ │ │ ├── complexity-report-tag-isolation.test.js │ │ │ ├── expand-all-tasks.test.js │ │ │ ├── expand-task.test.js │ │ │ ├── find-next-task.test.js │ │ │ ├── generate-task-files.test.js │ │ │ ├── list-tasks.test.js │ │ │ ├── move-task-cross-tag.test.js │ │ │ ├── move-task.test.js │ │ │ ├── parse-prd.test.js │ │ │ ├── remove-subtask.test.js │ │ │ ├── remove-task.test.js │ │ │ ├── research.test.js │ │ │ ├── scope-adjustment.test.js │ │ │ ├── set-task-status.test.js │ │ │ ├── setup.js │ │ │ ├── update-single-task-status.test.js │ │ │ ├── update-subtask-by-id.test.js │ │ │ ├── update-task-by-id.test.js │ │ │ └── update-tasks.test.js │ │ ├── ui │ │ │ └── cross-tag-error-display.test.js │ │ └── utils-tag-aware-paths.test.js │ ├── task-finder.test.js │ ├── task-manager │ │ ├── clear-subtasks.test.js │ │ ├── move-task.test.js │ │ ├── tag-boundary.test.js │ │ └── tag-management.test.js │ ├── task-master.test.js │ ├── ui │ │ └── indicators.test.js │ ├── ui.test.js │ ├── utils-strip-ansi.test.js │ └── utils.test.js ├── tsconfig.json ├── tsdown.config.ts └── turbo.json ``` # Files -------------------------------------------------------------------------------- /mcp-server/src/tools/initialize-project.js: -------------------------------------------------------------------------------- ```javascript import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { initializeProjectDirect } from '../core/task-master-core.js'; import { RULE_PROFILES } from '../../../src/constants/profiles.js'; export function registerInitializeProjectTool(server) { server.addTool({ name: 'initialize_project', description: 'Initializes a new Task Master project structure by calling the core initialization logic. Creates necessary folders and configuration files for Task Master in the current directory.', parameters: z.object({ skipInstall: z .boolean() .optional() .default(false) .describe( 'Skip installing dependencies automatically. Never do this unless you are sure the project is already installed.' ), addAliases: z .boolean() .optional() .default(true) .describe('Add shell aliases (tm, taskmaster) to shell config file.'), initGit: z .boolean() .optional() .default(true) .describe('Initialize Git repository in project root.'), storeTasksInGit: z .boolean() .optional() .default(true) .describe('Store tasks in Git (tasks.json and tasks/ directory).'), yes: z .boolean() .optional() .default(true) .describe( 'Skip prompts and use default values. Always set to true for MCP tools.' ), projectRoot: z .string() .describe( 'The root directory for the project. ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY. IF NOT SET, THE TOOL WILL NOT WORK.' ), rules: z .array(z.enum(RULE_PROFILES)) .optional() .describe( `List of rule profiles to include at initialization. If omitted, defaults to Cursor profile only. Available options: ${RULE_PROFILES.join(', ')}` ) }), execute: withNormalizedProjectRoot(async (args, context) => { const { log } = context; const session = context.session; try { log.info( `Executing initialize_project tool with args: ${JSON.stringify(args)}` ); const result = await initializeProjectDirect(args, log, { session }); return handleApiResult( result, log, 'Initialization failed', undefined, args.projectRoot ); } catch (error) { const errorMessage = `Project initialization tool failed: ${error.message || 'Unknown error'}`; log.error(errorMessage, error); return createErrorResponse(errorMessage, { details: error.stack }); } }) }); } ``` -------------------------------------------------------------------------------- /.claude/commands/tm/update/update-single-task.md: -------------------------------------------------------------------------------- ```markdown Update a single specific task with new information. Arguments: $ARGUMENTS Parse task ID and update details. ## Single Task Update Precisely update one task with AI assistance to maintain consistency. ## Argument Parsing Natural language updates: - "5: add caching requirement" - "update 5 to include error handling" - "task 5 needs rate limiting" - "5 change priority to high" ## Execution ```bash task-master update-task --id=<id> --prompt="<context>" ``` ## Update Types ### 1. **Content Updates** - Enhance description - Add requirements - Clarify details - Update acceptance criteria ### 2. **Metadata Updates** - Change priority - Adjust time estimates - Update complexity - Modify dependencies ### 3. **Strategic Updates** - Revise approach - Change test strategy - Update implementation notes - Adjust subtask needs ## AI-Powered Updates The AI: 1. **Understands Context** - Reads current task state - Identifies update intent - Maintains consistency - Preserves important info 2. **Applies Changes** - Updates relevant fields - Keeps style consistent - Adds without removing - Enhances clarity 3. **Validates Results** - Checks coherence - Verifies completeness - Maintains relationships - Suggests related updates ## Example Updates ``` /project:tm/update/single 5: add rate limiting → Updating Task #5: "Implement API endpoints" Current: Basic CRUD endpoints Adding: Rate limiting requirements Updated sections: ✓ Description: Added rate limiting mention ✓ Details: Added specific limits (100/min) ✓ Test Strategy: Added rate limit tests ✓ Complexity: Increased from 5 to 6 ✓ Time Estimate: Increased by 2 hours Suggestion: Also update task #6 (API Gateway) for consistency? ``` ## Smart Features 1. **Incremental Updates** - Adds without overwriting - Preserves work history - Tracks what changed - Shows diff view 2. **Consistency Checks** - Related task alignment - Subtask compatibility - Dependency validity - Timeline impact 3. **Update History** - Timestamp changes - Track who/what updated - Reason for update - Previous versions ## Field-Specific Updates Quick syntax for specific fields: - "5 priority:high" → Update priority only - "5 add-time:4h" → Add to time estimate - "5 status:review" → Change status - "5 depends:3,4" → Add dependencies ## Post-Update - Show updated task - Highlight changes - Check related tasks - Update suggestions - Timeline adjustments ``` -------------------------------------------------------------------------------- /assets/claude/commands/tm/update/update-single-task.md: -------------------------------------------------------------------------------- ```markdown Update a single specific task with new information. Arguments: $ARGUMENTS Parse task ID and update details. ## Single Task Update Precisely update one task with AI assistance to maintain consistency. ## Argument Parsing Natural language updates: - "5: add caching requirement" - "update 5 to include error handling" - "task 5 needs rate limiting" - "5 change priority to high" ## Execution ```bash task-master update-task --id=<id> --prompt="<context>" ``` ## Update Types ### 1. **Content Updates** - Enhance description - Add requirements - Clarify details - Update acceptance criteria ### 2. **Metadata Updates** - Change priority - Adjust time estimates - Update complexity - Modify dependencies ### 3. **Strategic Updates** - Revise approach - Change test strategy - Update implementation notes - Adjust subtask needs ## AI-Powered Updates The AI: 1. **Understands Context** - Reads current task state - Identifies update intent - Maintains consistency - Preserves important info 2. **Applies Changes** - Updates relevant fields - Keeps style consistent - Adds without removing - Enhances clarity 3. **Validates Results** - Checks coherence - Verifies completeness - Maintains relationships - Suggests related updates ## Example Updates ``` /project:tm/update/single 5: add rate limiting → Updating Task #5: "Implement API endpoints" Current: Basic CRUD endpoints Adding: Rate limiting requirements Updated sections: ✓ Description: Added rate limiting mention ✓ Details: Added specific limits (100/min) ✓ Test Strategy: Added rate limit tests ✓ Complexity: Increased from 5 to 6 ✓ Time Estimate: Increased by 2 hours Suggestion: Also update task #6 (API Gateway) for consistency? ``` ## Smart Features 1. **Incremental Updates** - Adds without overwriting - Preserves work history - Tracks what changed - Shows diff view 2. **Consistency Checks** - Related task alignment - Subtask compatibility - Dependency validity - Timeline impact 3. **Update History** - Timestamp changes - Track who/what updated - Reason for update - Previous versions ## Field-Specific Updates Quick syntax for specific fields: - "5 priority:high" → Update priority only - "5 add-time:4h" → Add to time estimate - "5 status:review" → Change status - "5 depends:3,4" → Add dependencies ## Post-Update - Show updated task - Highlight changes - Check related tasks - Update suggestions - Timeline adjustments ``` -------------------------------------------------------------------------------- /tests/manual/progress/TESTING_GUIDE.md: -------------------------------------------------------------------------------- ```markdown # Task Master Progress Testing Guide Quick reference for testing streaming/non-streaming functionality with token tracking. ## 🎯 Test Modes 1. **MCP Streaming** - Has `reportProgress` + `mcpLog`, shows emoji indicators (🔴🟠🟢) 2. **CLI Streaming** - No `reportProgress`, shows terminal progress bars 3. **Non-Streaming** - No progress reporting, single response ## 🚀 Quick Commands ```bash # Test Scripts (accept: mcp-streaming, cli-streaming, non-streaming, both, all) node test-parse-prd.js [mode] node test-analyze-complexity.js [mode] node test-expand.js [mode] [num_subtasks] node test-expand-all.js [mode] [num_subtasks] node parse-prd-analysis.js [accuracy|complexity|all] # CLI Commands node scripts/dev.js parse-prd test.txt # Local dev (streaming) node scripts/dev.js analyze-complexity --research node scripts/dev.js expand --id=1 --force node scripts/dev.js expand --all --force task-master [command] # Global CLI (non-streaming) ``` ## ✅ Success Indicators ### Indicators - **Priority**: 🔴🔴🔴 (high), 🟠🟠⚪ (medium), 🟢⚪⚪ (low) - **Complexity**: ●●● (7-10), ●●○ (4-6), ●○○ (1-3) ### Token Format `Tokens (I/O): 2,150/1,847 ($0.0423)` (~4 chars per token) ### Progress Bars ``` Single: Generating subtasks... |████████░░| 80% (4/5) Dual: Expanding 3 tasks | Task 2/3 |████████░░| 66% Generating 5 subtasks... |██████░░░░| 60% ``` ### Fractional Progress `(completedTasks + currentSubtask/totalSubtasks) / totalTasks` Example: 33% → 46% → 60% → 66% → 80% → 93% → 100% ## 🐛 Quick Fixes | Issue | Fix | |-------|-----| | No streaming | Check `reportProgress` is passed | | NaN% progress | Filter duplicate `subtask_progress` events | | Missing tokens | Check `.env` has API keys | | Broken bars | Terminal width > 80 | | projectRoot.split | Use `projectRoot` not `session` | ```bash # Debug TASKMASTER_DEBUG=true node test-expand.js npm run lint ``` ## 📊 Benchmarks - Single task: 10-20s (5 subtasks) - Expand all: 30-45s (3 tasks) - Streaming: ~10-20% faster - Updates: Every 2-5s ## 🔄 Test Workflow ```bash # Quick check node test-parse-prd.js both && npm test # Full suite (before release) for test in parse-prd analyze-complexity expand expand-all; do node test-$test.js all done node parse-prd-analysis.js all npm test ``` ## 🎯 MCP Tool Example ```javascript { "tool": "parse_prd", "args": { "input": "prd.txt", "numTasks": "8", "force": true, "projectRoot": "/path/to/project" } } ``` -------------------------------------------------------------------------------- /src/ai-providers/grok-cli.js: -------------------------------------------------------------------------------- ```javascript /** * grok-cli.js * AI provider implementation for Grok models using Grok CLI. */ import { createGrokCli } from './custom-sdk/grok-cli/index.js'; import { BaseAIProvider } from './base-provider.js'; import { getGrokCliSettingsForCommand } from '../../scripts/modules/config-manager.js'; export class GrokCliProvider extends BaseAIProvider { constructor() { super(); this.name = 'Grok CLI'; } /** * Returns the environment variable name required for this provider's API key. * @returns {string} The environment variable name for the Grok API key */ getRequiredApiKeyName() { return 'GROK_CLI_API_KEY'; } /** * Override to indicate that API key is optional since Grok CLI can be configured separately * @returns {boolean} False since Grok CLI can use its own config */ isRequiredApiKey() { return false; // Grok CLI can use its own config file } /** * Override validateAuth to be more flexible with API key validation * @param {object} params - Parameters to validate */ validateAuth(params) { // Grok CLI can work with: // 1. API key passed in params // 2. Environment variable GROK_CLI_API_KEY // 3. Grok CLI's own config file (~/.grok/user-settings.json) // So we don't enforce API key requirement here // Suppress unused parameter warning void params; } /** * Creates and returns a Grok CLI client instance. * @param {object} params - Parameters for client initialization * @param {string} [params.apiKey] - Grok CLI API key (optional if configured in CLI) * @param {string} [params.baseURL] - Optional custom API endpoint * @param {string} [params.workingDirectory] - Working directory for CLI commands * @param {number} [params.timeout] - Timeout for CLI commands in milliseconds * @param {string} [params.commandName] - Name of the command invoking the service * @returns {Function} Grok CLI client function * @throws {Error} If initialization fails */ getClient(params) { try { const { apiKey, baseURL, workingDirectory, timeout, commandName } = params; // Get Grok CLI settings from config const grokCliSettings = getGrokCliSettingsForCommand(commandName); return createGrokCli({ defaultSettings: { apiKey, baseURL, workingDirectory: workingDirectory || grokCliSettings.workingDirectory, timeout: timeout || grokCliSettings.timeout, defaultModel: grokCliSettings.defaultModel } }); } catch (error) { this.handleError('client initialization', error); } } } ``` -------------------------------------------------------------------------------- /tests/unit/profiles/zed-integration.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import fs from 'fs'; import path from 'path'; import os from 'os'; // Mock external modules jest.mock('child_process', () => ({ execSync: jest.fn() })); // Mock console methods jest.mock('console', () => ({ log: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), clear: jest.fn() })); describe('Zed Integration', () => { let tempDir; beforeEach(() => { jest.clearAllMocks(); // Create a temporary directory for testing tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-')); // Spy on fs methods jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => { if (filePath.toString().includes('settings.json')) { return JSON.stringify({ context_servers: {} }, null, 2); } return '{}'; }); jest.spyOn(fs, 'existsSync').mockImplementation(() => false); jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {}); }); afterEach(() => { // Clean up the temporary directory try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (err) { console.error(`Error cleaning up: ${err.message}`); } }); // Test function that simulates the createProjectStructure behavior for Zed files function mockCreateZedStructure() { // Create main .zed directory fs.mkdirSync(path.join(tempDir, '.zed'), { recursive: true }); // Create MCP config file (settings.json) fs.writeFileSync( path.join(tempDir, '.zed', 'settings.json'), JSON.stringify({ context_servers: {} }, null, 2) ); // Create AGENTS.md in project root fs.writeFileSync( path.join(tempDir, 'AGENTS.md'), '# Task Master Instructions\n\nThis is the Task Master agents file.' ); } test('creates all required .zed directories', () => { // Act mockCreateZedStructure(); // Assert expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.zed'), { recursive: true }); }); test('creates Zed settings.json with context_servers format', () => { // Act mockCreateZedStructure(); // Assert expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, '.zed', 'settings.json'), JSON.stringify({ context_servers: {} }, null, 2) ); }); test('creates AGENTS.md in project root', () => { // Act mockCreateZedStructure(); // Assert expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, 'AGENTS.md'), '# Task Master Instructions\n\nThis is the Task Master agents file.' ); }); }); ``` -------------------------------------------------------------------------------- /apps/extension/src/services/sidebar-webview-manager.ts: -------------------------------------------------------------------------------- ```typescript import * as vscode from 'vscode'; import type { TaskMasterApi } from '../utils/task-master-api'; export class SidebarWebviewManager implements vscode.WebviewViewProvider { private webviewView?: vscode.WebviewView; private api?: TaskMasterApi; constructor(private readonly extensionUri: vscode.Uri) {} setApi(api: TaskMasterApi): void { this.api = api; // Update connection status if webview exists if (this.webviewView) { this.updateConnectionStatus(); } } resolveWebviewView( webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken ): void { this.webviewView = webviewView; webviewView.webview.options = { enableScripts: true, localResourceRoots: [ vscode.Uri.joinPath(this.extensionUri, 'dist'), vscode.Uri.joinPath(this.extensionUri, 'assets') ] }; webviewView.webview.html = this.getHtmlContent(webviewView.webview); // Handle messages from the webview webviewView.webview.onDidReceiveMessage((message) => { if (message.command === 'openBoard') { vscode.commands.executeCommand('tm.showKanbanBoard'); } }); // Update connection status on load this.updateConnectionStatus(); } updateConnectionStatus(): void { if (!this.webviewView || !this.api) return; const status = this.api.getConnectionStatus(); this.webviewView.webview.postMessage({ type: 'connectionStatus', data: status }); } private getHtmlContent(webview: vscode.Webview): string { const scriptUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, 'dist', 'sidebar.js') ); const styleUri = webview.asWebviewUri( vscode.Uri.joinPath(this.extensionUri, 'dist', 'index.css') ); const nonce = this.getNonce(); return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}'; style-src ${webview.cspSource} 'unsafe-inline';"> <link href="${styleUri}" rel="stylesheet"> <title>TaskMaster</title> </head> <body> <div id="root"></div> <script nonce="${nonce}" src="${scriptUri}"></script> </body> </html>`; } private getNonce(): string { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < 32; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; } } ``` -------------------------------------------------------------------------------- /.claude/commands/tm/setup/install-taskmaster.md: -------------------------------------------------------------------------------- ```markdown Check if Task Master is installed and install it if needed. This command helps you get Task Master set up globally on your system. ## Detection and Installation Process 1. **Check Current Installation** ```bash # Check if task-master command exists which task-master || echo "Task Master not found" # Check npm global packages npm list -g task-master-ai ``` 2. **System Requirements Check** ```bash # Verify Node.js is installed node --version # Verify npm is installed npm --version # Check Node version (need 16+) ``` 3. **Install Task Master Globally** If not installed, run: ```bash npm install -g task-master-ai ``` 4. **Verify Installation** ```bash # Check version task-master --version # Verify command is available which task-master ``` 5. **Initial Setup** ```bash # Initialize in current directory task-master init ``` 6. **Configure AI Provider** Ensure you have at least one AI provider API key set: ```bash # Check current configuration task-master models --status # If no API keys found, guide setup echo "You'll need at least one API key:" echo "- ANTHROPIC_API_KEY for Claude" echo "- OPENAI_API_KEY for GPT models" echo "- PERPLEXITY_API_KEY for research" echo "" echo "Set them in your shell profile or .env file" ``` 7. **Quick Test** ```bash # Create a test PRD echo "Build a simple hello world API" > test-prd.txt # Try parsing it task-master parse-prd test-prd.txt -n 3 ``` ## Troubleshooting If installation fails: **Permission Errors:** ```bash # Try with sudo (macOS/Linux) sudo npm install -g task-master-ai # Or fix npm permissions npm config set prefix ~/.npm-global export PATH=~/.npm-global/bin:$PATH ``` **Network Issues:** ```bash # Use different registry npm install -g task-master-ai --registry https://registry.npmjs.org/ ``` **Node Version Issues:** ```bash # Install Node 18+ via nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash nvm install 18 nvm use 18 ``` ## Success Confirmation Once installed, you should see: ``` ✅ Task Master v0.16.2 (or higher) installed ✅ Command 'task-master' available globally ✅ AI provider configured ✅ Ready to use slash commands! Try: /project:task-master:init your-prd.md ``` ## Next Steps After installation: 1. Run `/project:utils:check-health` to verify setup 2. Configure AI providers with `/project:task-master:models` 3. Start using Task Master commands! ``` -------------------------------------------------------------------------------- /assets/claude/commands/tm/setup/install-taskmaster.md: -------------------------------------------------------------------------------- ```markdown Check if Task Master is installed and install it if needed. This command helps you get Task Master set up globally on your system. ## Detection and Installation Process 1. **Check Current Installation** ```bash # Check if task-master command exists which task-master || echo "Task Master not found" # Check npm global packages npm list -g task-master-ai ``` 2. **System Requirements Check** ```bash # Verify Node.js is installed node --version # Verify npm is installed npm --version # Check Node version (need 16+) ``` 3. **Install Task Master Globally** If not installed, run: ```bash npm install -g task-master-ai ``` 4. **Verify Installation** ```bash # Check version task-master --version # Verify command is available which task-master ``` 5. **Initial Setup** ```bash # Initialize in current directory task-master init ``` 6. **Configure AI Provider** Ensure you have at least one AI provider API key set: ```bash # Check current configuration task-master models --status # If no API keys found, guide setup echo "You'll need at least one API key:" echo "- ANTHROPIC_API_KEY for Claude" echo "- OPENAI_API_KEY for GPT models" echo "- PERPLEXITY_API_KEY for research" echo "" echo "Set them in your shell profile or .env file" ``` 7. **Quick Test** ```bash # Create a test PRD echo "Build a simple hello world API" > test-prd.txt # Try parsing it task-master parse-prd test-prd.txt -n 3 ``` ## Troubleshooting If installation fails: **Permission Errors:** ```bash # Try with sudo (macOS/Linux) sudo npm install -g task-master-ai # Or fix npm permissions npm config set prefix ~/.npm-global export PATH=~/.npm-global/bin:$PATH ``` **Network Issues:** ```bash # Use different registry npm install -g task-master-ai --registry https://registry.npmjs.org/ ``` **Node Version Issues:** ```bash # Install Node 18+ via nvm curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash nvm install 18 nvm use 18 ``` ## Success Confirmation Once installed, you should see: ``` ✅ Task Master v0.16.2 (or higher) installed ✅ Command 'task-master' available globally ✅ AI provider configured ✅ Ready to use slash commands! Try: /project:task-master:init your-prd.md ``` ## Next Steps After installation: 1. Run `/project:utils:check-health` to verify setup 2. Configure AI providers with `/project:task-master:models` 3. Start using Task Master commands! ``` -------------------------------------------------------------------------------- /apps/extension/src/utils/task-master-api/mcp-client.ts: -------------------------------------------------------------------------------- ```typescript /** * MCP Client Wrapper * Handles MCP tool calls with retry logic */ import type { ExtensionLogger } from '../logger'; import type { MCPClientManager } from '../mcpClient'; export class MCPClient { constructor( private mcpClient: MCPClientManager, private logger: ExtensionLogger, private config: { timeout: number; retryAttempts: number } ) {} /** * Call MCP tool with retry logic */ async callTool( toolName: string, args: Record<string, unknown> ): Promise<any> { let lastError: Error | null = null; for (let attempt = 1; attempt <= this.config.retryAttempts; attempt++) { try { const rawResponse = await this.mcpClient.callTool(toolName, args); this.logger.debug( `Raw MCP response for ${toolName}:`, JSON.stringify(rawResponse, null, 2) ); // Parse MCP response format if ( rawResponse && rawResponse.content && Array.isArray(rawResponse.content) && rawResponse.content[0] ) { const contentItem = rawResponse.content[0]; if (contentItem.type === 'text' && contentItem.text) { try { const parsedData = JSON.parse(contentItem.text); this.logger.debug(`Parsed MCP data for ${toolName}:`, parsedData); return parsedData; } catch (parseError) { this.logger.error( `Failed to parse MCP response text for ${toolName}:`, parseError ); this.logger.error(`Raw text was:`, contentItem.text); return rawResponse; // Fall back to original response } } } // If not in expected format, return as-is this.logger.warn( `Unexpected MCP response format for ${toolName}, returning raw response` ); return rawResponse; } catch (error) { lastError = error instanceof Error ? error : new Error('Unknown error'); this.logger.warn( `Attempt ${attempt}/${this.config.retryAttempts} failed for ${toolName}:`, lastError.message ); if (attempt < this.config.retryAttempts) { // Exponential backoff const delay = Math.min(1000 * 2 ** (attempt - 1), 5000); await new Promise((resolve) => setTimeout(resolve, delay)); } } } throw ( lastError || new Error( `Failed to call ${toolName} after ${this.config.retryAttempts} attempts` ) ); } /** * Get connection status */ getStatus(): { isRunning: boolean; error?: string } { return this.mcpClient.getStatus(); } /** * Test connection */ async testConnection(): Promise<boolean> { return this.mcpClient.testConnection(); } } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/add-tag.js: -------------------------------------------------------------------------------- ```javascript /** * tools/add-tag.js * Tool to create a new tag */ import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { addTagDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; /** * Register the addTag tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerAddTagTool(server) { server.addTool({ name: 'add_tag', description: 'Create a new tag for organizing tasks in different contexts', parameters: z.object({ name: z.string().describe('Name of the new tag to create'), copyFromCurrent: z .boolean() .optional() .describe( 'Whether to copy tasks from the current tag (default: false)' ), copyFromTag: z .string() .optional() .describe('Specific tag to copy tasks from'), fromBranch: z .boolean() .optional() .describe( 'Create tag name from current git branch (ignores name parameter)' ), description: z .string() .optional() .describe('Optional description for the tag'), file: z .string() .optional() .describe('Path to the tasks file (default: tasks/tasks.json)'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Starting add-tag with args: ${JSON.stringify(args)}`); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // Call the direct function const result = await addTagDirect( { tasksJsonPath: tasksJsonPath, name: args.name, copyFromCurrent: args.copyFromCurrent, copyFromTag: args.copyFromTag, fromBranch: args.fromBranch, description: args.description, projectRoot: args.projectRoot }, log, { session } ); return handleApiResult( result, log, 'Error creating tag', undefined, args.projectRoot ); } catch (error) { log.error(`Error in add-tag tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/remove-dependency.js: -------------------------------------------------------------------------------- ```javascript /** * tools/remove-dependency.js * Tool for removing a dependency from a task */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { removeDependencyDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the removeDependency tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerRemoveDependencyTool(server) { server.addTool({ name: 'remove_dependency', description: 'Remove a dependency from a task', parameters: z.object({ id: z.string().describe('Task ID to remove dependency from'), dependsOn: z.string().describe('Task ID to remove as a dependency'), file: z .string() .optional() .describe( 'Absolute path to the tasks file (default: tasks/tasks.json)' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); log.info( `Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}` ); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const result = await removeDependencyDirect( { tasksJsonPath: tasksJsonPath, id: args.id, dependsOn: args.dependsOn, projectRoot: args.projectRoot, tag: resolvedTag }, log ); if (result.success) { log.info(`Successfully removed dependency: ${result.data.message}`); } else { log.error(`Failed to remove dependency: ${result.error.message}`); } return handleApiResult( result, log, 'Error removing dependency', undefined, args.projectRoot ); } catch (error) { log.error(`Error in removeDependency tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /.claude/commands/tm/help.md: -------------------------------------------------------------------------------- ```markdown Show help for Task Master commands. Arguments: $ARGUMENTS Display help for Task Master commands. If arguments provided, show specific command help. ## Task Master Command Help ### Quick Navigation Type `/project:tm/` and use tab completion to explore all commands. ### Command Categories #### 🚀 Setup & Installation - `/project:tm/setup/install` - Comprehensive installation guide - `/project:tm/setup/quick-install` - One-line global install #### 📋 Project Setup - `/project:tm/init` - Initialize new project - `/project:tm/init/quick` - Quick setup with auto-confirm - `/project:tm/models` - View AI configuration - `/project:tm/models/setup` - Configure AI providers #### 🎯 Task Generation - `/project:tm/parse-prd` - Generate tasks from PRD - `/project:tm/parse-prd/with-research` - Enhanced parsing - `/project:tm/generate` - Create task files #### 📝 Task Management - `/project:tm/list` - List tasks (natural language filters) - `/project:tm/show <id>` - Display task details - `/project:tm/add-task` - Create new task - `/project:tm/update` - Update tasks naturally - `/project:tm/next` - Get next task recommendation #### 🔄 Status Management - `/project:tm/set-status/to-pending <id>` - `/project:tm/set-status/to-in-progress <id>` - `/project:tm/set-status/to-done <id>` - `/project:tm/set-status/to-review <id>` - `/project:tm/set-status/to-deferred <id>` - `/project:tm/set-status/to-cancelled <id>` #### 🔍 Analysis & Breakdown - `/project:tm/analyze-complexity` - Analyze task complexity - `/project:tm/expand <id>` - Break down complex task - `/project:tm/expand/all` - Expand all eligible tasks #### 🔗 Dependencies - `/project:tm/add-dependency` - Add task dependency - `/project:tm/remove-dependency` - Remove dependency - `/project:tm/validate-dependencies` - Check for issues #### 🤖 Workflows - `/project:tm/workflows/smart-flow` - Intelligent workflows - `/project:tm/workflows/pipeline` - Command chaining - `/project:tm/workflows/auto-implement` - Auto-implementation #### 📊 Utilities - `/project:tm/utils/analyze` - Project analysis - `/project:tm/status` - Project dashboard - `/project:tm/learn` - Interactive learning ### Natural Language Examples ``` /project:tm/list pending high priority /project:tm/update mark all API tasks as done /project:tm/add-task create login system with OAuth /project:tm/show current ``` ### Getting Started 1. Install: `/project:tm/setup/quick-install` 2. Initialize: `/project:tm/init/quick` 3. Learn: `/project:tm/learn start` 4. Work: `/project:tm/workflows/smart-flow` For detailed command info: `/project:tm/help <command-name>` ``` -------------------------------------------------------------------------------- /assets/claude/commands/tm/help.md: -------------------------------------------------------------------------------- ```markdown Show help for Task Master commands. Arguments: $ARGUMENTS Display help for Task Master commands. If arguments provided, show specific command help. ## Task Master Command Help ### Quick Navigation Type `/project:tm/` and use tab completion to explore all commands. ### Command Categories #### 🚀 Setup & Installation - `/project:tm/setup/install` - Comprehensive installation guide - `/project:tm/setup/quick-install` - One-line global install #### 📋 Project Setup - `/project:tm/init` - Initialize new project - `/project:tm/init/quick` - Quick setup with auto-confirm - `/project:tm/models` - View AI configuration - `/project:tm/models/setup` - Configure AI providers #### 🎯 Task Generation - `/project:tm/parse-prd` - Generate tasks from PRD - `/project:tm/parse-prd/with-research` - Enhanced parsing - `/project:tm/generate` - Create task files #### 📝 Task Management - `/project:tm/list` - List tasks (natural language filters) - `/project:tm/show <id>` - Display task details - `/project:tm/add-task` - Create new task - `/project:tm/update` - Update tasks naturally - `/project:tm/next` - Get next task recommendation #### 🔄 Status Management - `/project:tm/set-status/to-pending <id>` - `/project:tm/set-status/to-in-progress <id>` - `/project:tm/set-status/to-done <id>` - `/project:tm/set-status/to-review <id>` - `/project:tm/set-status/to-deferred <id>` - `/project:tm/set-status/to-cancelled <id>` #### 🔍 Analysis & Breakdown - `/project:tm/analyze-complexity` - Analyze task complexity - `/project:tm/expand <id>` - Break down complex task - `/project:tm/expand/all` - Expand all eligible tasks #### 🔗 Dependencies - `/project:tm/add-dependency` - Add task dependency - `/project:tm/remove-dependency` - Remove dependency - `/project:tm/validate-dependencies` - Check for issues #### 🤖 Workflows - `/project:tm/workflows/smart-flow` - Intelligent workflows - `/project:tm/workflows/pipeline` - Command chaining - `/project:tm/workflows/auto-implement` - Auto-implementation #### 📊 Utilities - `/project:tm/utils/analyze` - Project analysis - `/project:tm/status` - Project dashboard - `/project:tm/learn` - Interactive learning ### Natural Language Examples ``` /project:tm/list pending high priority /project:tm/update mark all API tasks as done /project:tm/add-task create login system with OAuth /project:tm/show current ``` ### Getting Started 1. Install: `/project:tm/setup/quick-install` 2. Initialize: `/project:tm/init/quick` 3. Learn: `/project:tm/learn start` 4. Work: `/project:tm/workflows/smart-flow` For detailed command info: `/project:tm/help <command-name>` ``` -------------------------------------------------------------------------------- /tests/integration/profiles/claude-init-functionality.test.js: -------------------------------------------------------------------------------- ```javascript import fs from 'fs'; import path from 'path'; import { claudeProfile } from '../../../src/profiles/claude.js'; describe('Claude Profile Initialization Functionality', () => { let claudeProfileContent; beforeAll(() => { const claudeJsPath = path.join( process.cwd(), 'src', 'profiles', 'claude.js' ); claudeProfileContent = fs.readFileSync(claudeJsPath, 'utf8'); }); test('claude.js has correct asset-only profile configuration', () => { // Check for explicit, non-default values in the source file expect(claudeProfileContent).toContain("name: 'claude'"); expect(claudeProfileContent).toContain("displayName: 'Claude Code'"); expect(claudeProfileContent).toContain("profileDir: '.'"); // non-default expect(claudeProfileContent).toContain("rulesDir: '.'"); // non-default expect(claudeProfileContent).toContain("mcpConfigName: '.mcp.json'"); // non-default expect(claudeProfileContent).toContain('includeDefaultRules: false'); // non-default expect(claudeProfileContent).toContain( "'AGENTS.md': '.taskmaster/CLAUDE.md'" ); // Check the final computed properties on the profile object expect(claudeProfile.profileName).toBe('claude'); expect(claudeProfile.displayName).toBe('Claude Code'); expect(claudeProfile.profileDir).toBe('.'); expect(claudeProfile.rulesDir).toBe('.'); expect(claudeProfile.mcpConfig).toBe(true); // default from base profile expect(claudeProfile.mcpConfigName).toBe('.mcp.json'); // explicitly set expect(claudeProfile.mcpConfigPath).toBe('.mcp.json'); // computed expect(claudeProfile.includeDefaultRules).toBe(false); expect(claudeProfile.fileMap['AGENTS.md']).toBe('.taskmaster/CLAUDE.md'); }); test('claude.js has lifecycle functions for file management', () => { expect(claudeProfileContent).toContain('function onAddRulesProfile'); expect(claudeProfileContent).toContain('function onRemoveRulesProfile'); expect(claudeProfileContent).toContain( 'function onPostConvertRulesProfile' ); }); test('claude.js handles .claude directory and .taskmaster/CLAUDE.md import in lifecycle functions', () => { expect(claudeProfileContent).toContain('.claude'); expect(claudeProfileContent).toContain('copyRecursiveSync'); expect(claudeProfileContent).toContain('.taskmaster/CLAUDE.md'); expect(claudeProfileContent).toContain('@./.taskmaster/CLAUDE.md'); }); test('claude.js has proper error handling in lifecycle functions', () => { expect(claudeProfileContent).toContain('try {'); expect(claudeProfileContent).toContain('} catch (err) {'); expect(claudeProfileContent).toContain("log('error'"); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/generate.js: -------------------------------------------------------------------------------- ```javascript /** * tools/generate.js * Tool to generate individual task files from tasks.json */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { generateTaskFilesDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; import path from 'path'; /** * Register the generate tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerGenerateTool(server) { server.addTool({ name: 'generate', description: 'Generates individual task files in tasks/ directory based on tasks.json', parameters: z.object({ file: z.string().optional().describe('Absolute path to the tasks file'), output: z .string() .optional() .describe('Output directory (default: same directory as tasks file)'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Generating task files with args: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const outputDir = args.output ? path.resolve(args.projectRoot, args.output) : path.dirname(tasksJsonPath); const result = await generateTaskFilesDirect( { tasksJsonPath: tasksJsonPath, outputDir: outputDir, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); if (result.success) { log.info(`Successfully generated task files: ${result.data.message}`); } else { log.error( `Failed to generate task files: ${result.error?.message || 'Unknown error'}` ); } return handleApiResult( result, log, 'Error generating task files', undefined, args.projectRoot ); } catch (error) { log.error(`Error in generate tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /.claude/commands/tm/learn.md: -------------------------------------------------------------------------------- ```markdown Learn about Task Master capabilities through interactive exploration. Arguments: $ARGUMENTS ## Interactive Task Master Learning Based on your input, I'll help you discover capabilities: ### 1. **What are you trying to do?** If $ARGUMENTS contains: - "start" / "begin" → Show project initialization workflows - "manage" / "organize" → Show task management commands - "automate" / "auto" → Show automation workflows - "analyze" / "report" → Show analysis tools - "fix" / "problem" → Show troubleshooting commands - "fast" / "quick" → Show efficiency shortcuts ### 2. **Intelligent Suggestions** Based on your project state: **No tasks yet?** ``` You'll want to start with: 1. /project:task-master:init <prd-file> → Creates tasks from requirements 2. /project:task-master:parse-prd <file> → Alternative task generation Try: /project:task-master:init demo-prd.md ``` **Have tasks?** Let me analyze what you might need... - Many pending tasks? → Learn sprint planning - Complex tasks? → Learn task expansion - Daily work? → Learn workflow automation ### 3. **Command Discovery** **By Category:** - 📋 Task Management: list, show, add, update, complete - 🔄 Workflows: auto-implement, sprint-plan, daily-standup - 🛠️ Utilities: check-health, complexity-report, sync-memory - 🔍 Analysis: validate-deps, show dependencies **By Scenario:** - "I want to see what to work on" → `/project:task-master:next` - "I need to break this down" → `/project:task-master:expand <id>` - "Show me everything" → `/project:task-master:status` - "Just do it for me" → `/project:workflows:auto-implement` ### 4. **Power User Patterns** **Command Chaining:** ``` /project:task-master:next /project:task-master:start <id> /project:workflows:auto-implement ``` **Smart Filters:** ``` /project:task-master:list pending high /project:task-master:list blocked /project:task-master:list 1-5 tree ``` **Automation:** ``` /project:workflows:pipeline init → expand-all → sprint-plan ``` ### 5. **Learning Path** Based on your experience level: **Beginner Path:** 1. init → Create project 2. status → Understand state 3. next → Find work 4. complete → Finish task **Intermediate Path:** 1. expand → Break down complex tasks 2. sprint-plan → Organize work 3. complexity-report → Understand difficulty 4. validate-deps → Ensure consistency **Advanced Path:** 1. pipeline → Chain operations 2. smart-flow → Context-aware automation 3. Custom commands → Extend the system ### 6. **Try This Now** Based on what you asked about, try: [Specific command suggestion based on $ARGUMENTS] Want to learn more about a specific command? Type: /project:help <command-name> ``` -------------------------------------------------------------------------------- /assets/claude/commands/tm/learn.md: -------------------------------------------------------------------------------- ```markdown Learn about Task Master capabilities through interactive exploration. Arguments: $ARGUMENTS ## Interactive Task Master Learning Based on your input, I'll help you discover capabilities: ### 1. **What are you trying to do?** If $ARGUMENTS contains: - "start" / "begin" → Show project initialization workflows - "manage" / "organize" → Show task management commands - "automate" / "auto" → Show automation workflows - "analyze" / "report" → Show analysis tools - "fix" / "problem" → Show troubleshooting commands - "fast" / "quick" → Show efficiency shortcuts ### 2. **Intelligent Suggestions** Based on your project state: **No tasks yet?** ``` You'll want to start with: 1. /project:task-master:init <prd-file> → Creates tasks from requirements 2. /project:task-master:parse-prd <file> → Alternative task generation Try: /project:task-master:init demo-prd.md ``` **Have tasks?** Let me analyze what you might need... - Many pending tasks? → Learn sprint planning - Complex tasks? → Learn task expansion - Daily work? → Learn workflow automation ### 3. **Command Discovery** **By Category:** - 📋 Task Management: list, show, add, update, complete - 🔄 Workflows: auto-implement, sprint-plan, daily-standup - 🛠️ Utilities: check-health, complexity-report, sync-memory - 🔍 Analysis: validate-deps, show dependencies **By Scenario:** - "I want to see what to work on" → `/project:task-master:next` - "I need to break this down" → `/project:task-master:expand <id>` - "Show me everything" → `/project:task-master:status` - "Just do it for me" → `/project:workflows:auto-implement` ### 4. **Power User Patterns** **Command Chaining:** ``` /project:task-master:next /project:task-master:start <id> /project:workflows:auto-implement ``` **Smart Filters:** ``` /project:task-master:list pending high /project:task-master:list blocked /project:task-master:list 1-5 tree ``` **Automation:** ``` /project:workflows:pipeline init → expand-all → sprint-plan ``` ### 5. **Learning Path** Based on your experience level: **Beginner Path:** 1. init → Create project 2. status → Understand state 3. next → Find work 4. complete → Finish task **Intermediate Path:** 1. expand → Break down complex tasks 2. sprint-plan → Organize work 3. complexity-report → Understand difficulty 4. validate-deps → Ensure consistency **Advanced Path:** 1. pipeline → Chain operations 2. smart-flow → Context-aware automation 3. Custom commands → Extend the system ### 6. **Try This Now** Based on what you asked about, try: [Specific command suggestion based on $ARGUMENTS] Want to learn more about a specific command? Type: /project:help <command-name> ``` -------------------------------------------------------------------------------- /test-version-check-full.js: -------------------------------------------------------------------------------- ```javascript import { checkForUpdate, displayUpgradeNotification, compareVersions } from './scripts/modules/commands.js'; import fs from 'fs'; import path from 'path'; // Force our current version for testing process.env.FORCE_VERSION = '0.9.30'; // Create a mock package.json in memory for testing const mockPackageJson = { name: 'task-master-ai', version: '0.9.30' }; // Modified version of checkForUpdate that doesn't use HTTP for testing async function testCheckForUpdate(simulatedLatestVersion) { // Get current version - use our forced version const currentVersion = process.env.FORCE_VERSION || '0.9.30'; console.log(`Using simulated current version: ${currentVersion}`); console.log(`Using simulated latest version: ${simulatedLatestVersion}`); // Compare versions const needsUpdate = compareVersions(currentVersion, simulatedLatestVersion) < 0; return { currentVersion, latestVersion: simulatedLatestVersion, needsUpdate }; } // Test with current version older than latest (should show update notice) async function runTest() { console.log('=== Testing version check scenarios ===\n'); // Scenario 1: Update available console.log( '\n--- Scenario 1: Update available (Current: 0.9.30, Latest: 1.0.0) ---' ); const updateInfo1 = await testCheckForUpdate('1.0.0'); console.log('Update check results:'); console.log(`- Current version: ${updateInfo1.currentVersion}`); console.log(`- Latest version: ${updateInfo1.latestVersion}`); console.log(`- Update needed: ${updateInfo1.needsUpdate}`); if (updateInfo1.needsUpdate) { console.log('\nDisplaying upgrade notification:'); displayUpgradeNotification( updateInfo1.currentVersion, updateInfo1.latestVersion ); } // Scenario 2: No update needed (versions equal) console.log( '\n--- Scenario 2: No update needed (Current: 0.9.30, Latest: 0.9.30) ---' ); const updateInfo2 = await testCheckForUpdate('0.9.30'); console.log('Update check results:'); console.log(`- Current version: ${updateInfo2.currentVersion}`); console.log(`- Latest version: ${updateInfo2.latestVersion}`); console.log(`- Update needed: ${updateInfo2.needsUpdate}`); // Scenario 3: Development version (current newer than latest) console.log( '\n--- Scenario 3: Development version (Current: 0.9.30, Latest: 0.9.0) ---' ); const updateInfo3 = await testCheckForUpdate('0.9.0'); console.log('Update check results:'); console.log(`- Current version: ${updateInfo3.currentVersion}`); console.log(`- Latest version: ${updateInfo3.latestVersion}`); console.log(`- Update needed: ${updateInfo3.needsUpdate}`); console.log('\n=== Test complete ==='); } // Run all tests runTest(); ``` -------------------------------------------------------------------------------- /apps/docs/getting-started/quick-start/prd-quick.mdx: -------------------------------------------------------------------------------- ```markdown --- title: PRD Creation and Parsing sidebarTitle: "PRD Creation and Parsing" --- # Writing a PRD A PRD (Product Requirements Document) is the starting point of every task flow in Task Master. It defines what you're building and why. A clear PRD dramatically improves the quality of your tasks, your model outputs, and your final product — so it’s worth taking the time to get it right. <Tip> You don’t need to define your whole app up front. You can write a focused PRD just for the next feature or module you’re working on. </Tip> <Tip> You can start with an empty project or you can start with a feature PRD on an existing project. </Tip> <Tip> You can add and parse multiple PRDs per project using the --append flag </Tip> ## What Makes a Good PRD? - Clear objective — what’s the outcome or feature? - Context — what’s already in place or assumed? - Constraints — what limits or requirements need to be respected? - Reasoning — why are you building it this way? The more context you give the model, the better the breakdown and results. --- ## Writing a PRD for Task Master <Note>An example PRD can be found in .taskmaster/templates/example_prd.txt</Note> You can co-write your PRD with an LLM model using the following workflow: 1. **Chat about requirements** — explain what you want to build. 2. **Show an example PRD** — share the example PRD so the model understands the expected format. The example uses formatting that work well with Task Master's code. Following the example will yield better results. 3. **Iterate and refine** — work with the model to shape the draft into a clear and well-structured PRD. This approach works great in Cursor, or anywhere you use a chat-based LLM. --- ## Where to Save Your PRD Place your PRD file in the `.taskmaster/docs` folder in your project. - You can have **multiple PRDs** per project. - Name your PRDs clearly so they’re easy to reference later. - Examples: `dashboard_redesign.txt`, `user_onboarding.txt` --- # Parse your PRD into Tasks This is where the Task Master magic begins. In Cursor's AI chat, instruct the agent to generate tasks from your PRD: ``` Please use the task-master parse-prd command to generate tasks from my PRD. The PRD is located at .taskmaster/docs/<prd-name>.txt. ``` The agent will execute the following command which you can alternatively paste into the CLI: ```bash task-master parse-prd .taskmaster/docs/<prd-name>.txt ``` This will: - Parse your PRD document - Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies Now that you have written and parsed a PRD, you are ready to start setting up your tasks. ``` -------------------------------------------------------------------------------- /apps/extension/src/components/TaskMasterLogo.tsx: -------------------------------------------------------------------------------- ```typescript import React from 'react'; interface TaskMasterLogoProps { className?: string; } export const TaskMasterLogo: React.FC<TaskMasterLogoProps> = ({ className = '' }) => { return ( <svg className={className} viewBox="0 0 224 291" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M101.635 286.568L71.4839 256.414C65.6092 250.539 65.6092 241.03 71.4839 235.155L142.52 164.11C144.474 162.156 147.643 162.156 149.61 164.11L176.216 190.719C178.17 192.673 181.339 192.673 183.305 190.719L189.719 184.305C191.673 182.35 191.673 179.181 189.719 177.214L163.113 150.605C161.159 148.651 161.159 145.481 163.113 143.514L191.26 115.365C193.214 113.41 193.214 110.241 191.26 108.274L182.316 99.3291C180.362 97.3748 177.193 97.3748 175.226 99.3291L55.8638 218.706C49.989 224.581 40.4816 224.581 34.6068 218.706L4.4061 188.501C-1.4687 182.626 -1.4687 173.117 4.4061 167.242L23.8342 147.811C25.7883 145.857 25.7883 142.688 23.8342 140.721L4.78187 121.666C-1.09293 115.791 -1.09293 106.282 4.78187 100.406L34.7195 70.4527C40.5943 64.5772 50.1017 64.5772 55.9765 70.4527L75.555 90.0335C77.5091 91.9879 80.6782 91.9879 82.6448 90.0335L124.144 48.5292C126.098 46.5749 126.098 43.4054 124.144 41.4385L115.463 32.7568C113.509 30.8025 110.34 30.8025 108.374 32.7568L99.8683 41.2632C97.9143 43.2175 94.7451 43.2175 92.7785 41.2632L82.1438 30.6271C80.1897 28.6728 80.1897 25.5033 82.1438 23.5364L101.271 4.40662C107.146 -1.46887 116.653 -1.46887 122.528 4.40662L152.478 34.3604C158.353 40.2359 158.353 49.7444 152.478 55.6199L82.6323 125.474C80.6782 127.429 77.5091 127.429 75.5425 125.474L48.8741 98.8029C46.9201 96.8486 43.7509 96.8486 41.7843 98.8029L33.1036 107.485C31.1496 109.439 31.1496 112.608 33.1036 114.575L59.2458 140.721C61.1999 142.675 61.1999 145.844 59.2458 147.811L32.7404 174.32C30.7863 176.274 30.7863 179.444 32.7404 181.411L41.6841 190.355C43.6382 192.31 46.8073 192.31 48.7739 190.355L168.136 70.9789C174.011 65.1034 183.518 65.1034 189.393 70.9789L219.594 101.183C225.469 107.059 225.469 116.567 219.594 122.443L198.537 143.502C196.583 145.456 196.583 148.626 198.537 150.592L218.053 170.111C223.928 175.986 223.928 185.495 218.053 191.37L190.37 219.056C184.495 224.932 174.988 224.932 169.113 219.056L149.597 199.538C147.643 197.584 144.474 197.584 142.508 199.538L99.8057 242.245C97.8516 244.2 97.8516 247.369 99.8057 249.336L108.699 258.231C110.653 260.185 113.823 260.185 115.789 258.231L122.954 251.065C124.908 249.11 128.077 249.11 130.044 251.065L140.679 261.701C142.633 263.655 142.633 266.825 140.679 268.791L122.879 286.593C117.004 292.469 107.497 292.469 101.622 286.593L101.635 286.568Z" fill="currentColor" /> </svg> ); }; ``` -------------------------------------------------------------------------------- /tests/integration/profiles/cline-init-functionality.test.js: -------------------------------------------------------------------------------- ```javascript import fs from 'fs'; import path from 'path'; import { clineProfile } from '../../../src/profiles/cline.js'; describe('Cline Profile Initialization Functionality', () => { let clineProfileContent; beforeAll(() => { const clineJsPath = path.join(process.cwd(), 'src', 'profiles', 'cline.js'); clineProfileContent = fs.readFileSync(clineJsPath, 'utf8'); }); test('cline.js uses factory pattern with correct configuration', () => { // Check for explicit, non-default values in the source file expect(clineProfileContent).toContain("name: 'cline'"); expect(clineProfileContent).toContain("displayName: 'Cline'"); expect(clineProfileContent).toContain("profileDir: '.clinerules'"); // non-default expect(clineProfileContent).toContain("rulesDir: '.clinerules'"); // non-default expect(clineProfileContent).toContain('mcpConfig: false'); // non-default // Check the final computed properties on the profile object expect(clineProfile.profileName).toBe('cline'); expect(clineProfile.displayName).toBe('Cline'); expect(clineProfile.profileDir).toBe('.clinerules'); expect(clineProfile.rulesDir).toBe('.clinerules'); expect(clineProfile.mcpConfig).toBe(false); expect(clineProfile.mcpConfigName).toBe(null); }); test('cline.js configures .mdc to .md extension mapping', () => { // Check that the profile object has the correct file mapping behavior (cline converts to .md) expect(clineProfile.fileMap['rules/cursor_rules.mdc']).toBe( 'cline_rules.md' ); }); test('cline.js uses standard tool mappings', () => { // Check that the profile uses default tool mappings (equivalent to COMMON_TOOL_MAPPINGS.STANDARD) // This verifies the architectural pattern: no custom toolMappings = standard tool names expect(clineProfileContent).not.toContain('toolMappings:'); expect(clineProfileContent).not.toContain('apply_diff'); expect(clineProfileContent).not.toContain('search_files'); // Verify the result: default mappings means tools keep their original names expect(clineProfile.conversionConfig.toolNames.edit_file).toBe('edit_file'); expect(clineProfile.conversionConfig.toolNames.search).toBe('search'); }); test('cline.js has custom file mapping for cursor_rules.mdc', () => { // Check actual behavior - cline gets default rule files expect(Object.keys(clineProfile.fileMap)).toContain( 'rules/cursor_rules.mdc' ); expect(clineProfile.fileMap['rules/cursor_rules.mdc']).toBe( 'cline_rules.md' ); }); test('cline.js uses createProfile factory function', () => { expect(clineProfileContent).toContain('createProfile'); expect(clineProfileContent).toContain('export const clineProfile'); }); }); ``` -------------------------------------------------------------------------------- /packages/tm-core/src/executors/executor-service.ts: -------------------------------------------------------------------------------- ```typescript /** * Service for managing task execution */ import type { Task } from '../types/index.js'; import type { ITaskExecutor, ExecutorOptions, ExecutionResult, ExecutorType } from './types.js'; import { ExecutorFactory } from './executor-factory.js'; import { getLogger } from '../logger/index.js'; export interface ExecutorServiceOptions { projectRoot: string; defaultExecutor?: ExecutorType; executorConfig?: Record<string, any>; } export class ExecutorService { private logger = getLogger('ExecutorService'); private projectRoot: string; private defaultExecutor?: ExecutorType; private executorConfig: Record<string, any>; private currentExecutor?: ITaskExecutor; constructor(options: ExecutorServiceOptions) { this.projectRoot = options.projectRoot; this.defaultExecutor = options.defaultExecutor; this.executorConfig = options.executorConfig || {}; } /** * Execute a task */ async executeTask( task: Task, executorType?: ExecutorType ): Promise<ExecutionResult> { try { // Determine executor type const type = executorType || this.defaultExecutor || (await ExecutorFactory.getDefaultExecutor(this.projectRoot)); if (!type) { return { success: false, taskId: task.id, executorType: 'claude', error: 'No executor available. Please install Claude CLI or specify an executor type.', startTime: new Date().toISOString() }; } // Create executor const executorOptions: ExecutorOptions = { type, projectRoot: this.projectRoot, config: this.executorConfig }; this.currentExecutor = ExecutorFactory.create(executorOptions); // Check if executor is available const isAvailable = await this.currentExecutor.isAvailable(); if (!isAvailable) { return { success: false, taskId: task.id, executorType: type, error: `Executor ${type} is not available or not configured properly`, startTime: new Date().toISOString() }; } // Execute the task this.logger.info(`Starting task ${task.id} with ${type} executor`); const result = await this.currentExecutor.execute(task); return result; } catch (error: any) { this.logger.error(`Failed to execute task ${task.id}:`, error); return { success: false, taskId: task.id, executorType: executorType || 'claude', error: error.message || 'Unknown error occurred', startTime: new Date().toISOString() }; } } /** * Stop the current task execution */ async stopCurrentTask(): Promise<void> { if (this.currentExecutor && this.currentExecutor.stop) { await this.currentExecutor.stop(); this.currentExecutor = undefined; } } } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/remove-task.js: -------------------------------------------------------------------------------- ```javascript /** * tools/remove-task.js * Tool to remove a task by ID */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { removeTaskDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the remove-task tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerRemoveTaskTool(server) { server.addTool({ name: 'remove_task', description: 'Remove a task or subtask permanently from the tasks list', parameters: z.object({ id: z .string() .describe( "ID of the task or subtask to remove (e.g., '5' or '5.2'). Can be comma-separated to update multiple tasks/subtasks at once." ), file: z.string().optional().describe('Absolute path to the tasks file'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), confirm: z .boolean() .optional() .describe('Whether to skip confirmation prompt (default: false)'), tag: z .string() .optional() .describe( 'Specify which tag context to operate on. Defaults to the current active tag.' ) }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Removing task(s) with ID(s): ${args.id}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } log.info(`Using tasks file path: ${tasksJsonPath}`); const result = await removeTaskDirect( { tasksJsonPath: tasksJsonPath, id: args.id, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); if (result.success) { log.info(`Successfully removed task: ${args.id}`); } else { log.error(`Failed to remove task: ${result.error.message}`); } return handleApiResult( result, log, 'Error removing task', undefined, args.projectRoot ); } catch (error) { log.error(`Error in remove-task tool: ${error.message}`); return createErrorResponse(`Failed to remove task: ${error.message}`); } }) }); } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/scope-up.js: -------------------------------------------------------------------------------- ```javascript /** * tools/scope-up.js * Tool to scope up task complexity */ import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { scopeUpDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the scopeUp tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerScopeUpTool(server) { server.addTool({ name: 'scope_up_task', description: 'Increase the complexity of one or more tasks using AI', parameters: z.object({ id: z .string() .describe( 'Comma-separated list of task IDs to scope up (e.g., "1,3,5")' ), strength: z .string() .optional() .describe( 'Strength level: light, regular, or heavy (default: regular)' ), prompt: z .string() .optional() .describe('Custom prompt for specific scoping adjustments'), file: z .string() .optional() .describe('Path to the tasks file (default: tasks/tasks.json)'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on'), research: z .boolean() .optional() .describe('Whether to use research capabilities for scoping') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Starting scope-up with args: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // Call the direct function const result = await scopeUpDirect( { tasksJsonPath: tasksJsonPath, id: args.id, strength: args.strength, prompt: args.prompt, research: args.research, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); return handleApiResult( result, log, 'Error scoping up task', undefined, args.projectRoot ); } catch (error) { log.error(`Error in scope-up tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /mcp-server/src/custom-sdk/message-converter.js: -------------------------------------------------------------------------------- ```javascript /** * src/ai-providers/custom-sdk/mcp/message-converter.js * * Message conversion utilities for converting between AI SDK prompt format * and MCP sampling format. */ /** * Convert AI SDK prompt format to MCP sampling format * @param {Array} prompt - AI SDK prompt array * @returns {object} MCP format with messages and systemPrompt */ export function convertToMCPFormat(prompt) { const messages = []; let systemPrompt = ''; for (const message of prompt) { if (message.role === 'system') { // Extract system prompt systemPrompt = extractTextContent(message.content); } else if (message.role === 'user' || message.role === 'assistant') { // Convert user/assistant messages messages.push({ role: message.role, content: { type: 'text', text: extractTextContent(message.content) } }); } } return { messages, systemPrompt }; } /** * Convert MCP response format to AI SDK format * @param {object} response - MCP sampling response * @returns {object} AI SDK compatible result */ export function convertFromMCPFormat(response) { // Handle different possible response formats let text = ''; let usage = null; let finishReason = 'stop'; let warnings = []; if (typeof response === 'string') { text = response; } else if (response.content) { text = extractTextContent(response.content); usage = response.usage; finishReason = response.finishReason || 'stop'; } else if (response.text) { text = response.text; usage = response.usage; finishReason = response.finishReason || 'stop'; } else { // Fallback: try to extract text from response text = JSON.stringify(response); warnings.push('Unexpected MCP response format, used JSON fallback'); } return { text, usage, finishReason, warnings }; } /** * Extract text content from various content formats * @param {string|Array|object} content - Content in various formats * @returns {string} Extracted text */ function extractTextContent(content) { if (typeof content === 'string') { return content; } if (Array.isArray(content)) { // Handle array of content parts return content .map((part) => { if (typeof part === 'string') { return part; } if (part.type === 'text' && part.text) { return part.text; } if (part.text) { return part.text; } // Skip non-text content (images, etc.) return ''; }) .filter((text) => text.length > 0) .join(' '); } if (content && typeof content === 'object') { if (content.type === 'text' && content.text) { return content.text; } if (content.text) { return content.text; } } // Fallback return String(content || ''); } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/use-tag.js: -------------------------------------------------------------------------------- ```javascript /** * use-tag.js * Direct function implementation for switching to a tag */ import { useTag } from '../../../../scripts/modules/task-manager/tag-management.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; import { createLogWrapper } from '../../tools/utils.js'; /** * Direct function wrapper for switching to a tag with error handling. * * @param {Object} args - Command arguments * @param {string} args.name - Name of the tag to switch to * @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool) * @param {string} [args.projectRoot] - Project root path * @param {Object} log - Logger object * @param {Object} context - Additional context (session) * @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } } */ export async function useTagDirect(args, log, context = {}) { // Destructure expected args const { tasksJsonPath, name, projectRoot } = args; const { session } = context; // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); // Create logger wrapper using the utility const mcpLog = createLogWrapper(log); try { // Check if tasksJsonPath was provided if (!tasksJsonPath) { log.error('useTagDirect called without tasksJsonPath'); disableSilentMode(); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } // Check required parameters if (!name || typeof name !== 'string') { log.error('Missing required parameter: name'); disableSilentMode(); return { success: false, error: { code: 'MISSING_PARAMETER', message: 'Tag name is required and must be a string' } }; } log.info(`Switching to tag: ${name}`); // Call the useTag function const result = await useTag( tasksJsonPath, name, {}, // options (empty for now) { session, mcpLog, projectRoot }, 'json' // outputFormat - use 'json' to suppress CLI UI ); // Restore normal logging disableSilentMode(); return { success: true, data: { tagName: result.currentTag, switched: result.switched, previousTag: result.previousTag, taskCount: result.taskCount, message: `Successfully switched to tag "${result.currentTag}"` } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in useTagDirect: ${error.message}`); return { success: false, error: { code: error.code || 'USE_TAG_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/add-dependency.js: -------------------------------------------------------------------------------- ```javascript /** * tools/add-dependency.js * Tool for adding a dependency to a task */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { addDependencyDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the addDependency tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerAddDependencyTool(server) { server.addTool({ name: 'add_dependency', description: 'Add a dependency relationship between two tasks', parameters: z.object({ id: z.string().describe('ID of task that will depend on another task'), dependsOn: z .string() .describe('ID of task that will become a dependency'), file: z .string() .optional() .describe( 'Absolute path to the tasks file (default: tasks/tasks.json)' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info( `Adding dependency for task ${args.id} to depend on ${args.dependsOn}` ); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // Call the direct function with the resolved path const result = await addDependencyDirect( { // Pass the explicitly resolved path tasksJsonPath: tasksJsonPath, // Pass other relevant args id: args.id, dependsOn: args.dependsOn, projectRoot: args.projectRoot, tag: resolvedTag }, log // Remove context object ); // Log result if (result.success) { log.info(`Successfully added dependency: ${result.data.message}`); } else { log.error(`Failed to add dependency: ${result.error.message}`); } // Use handleApiResult to format the response return handleApiResult( result, log, 'Error adding dependency', undefined, args.projectRoot ); } catch (error) { log.error(`Error in addDependency tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/clear-subtasks.js: -------------------------------------------------------------------------------- ```javascript /** * tools/clear-subtasks.js * Tool for clearing subtasks from parent tasks */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { clearSubtasksDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the clearSubtasks tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerClearSubtasksTool(server) { server.addTool({ name: 'clear_subtasks', description: 'Clear subtasks from specified tasks', parameters: z .object({ id: z .string() .optional() .describe('Task IDs (comma-separated) to clear subtasks from'), all: z.boolean().optional().describe('Clear subtasks from all tasks'), file: z .string() .optional() .describe( 'Absolute path to the tasks file (default: tasks/tasks.json)' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }) .refine((data) => data.id || data.all, { message: "Either 'id' or 'all' parameter must be provided", path: ['id', 'all'] }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const result = await clearSubtasksDirect( { tasksJsonPath: tasksJsonPath, id: args.id, all: args.all, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); if (result.success) { log.info(`Subtasks cleared successfully: ${result.data.message}`); } else { log.error(`Failed to clear subtasks: ${result.error.message}`); } return handleApiResult( result, log, 'Error clearing subtasks', undefined, args.projectRoot ); } catch (error) { log.error(`Error in clearSubtasks tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /tests/unit/scripts/modules/utils-tag-aware-paths.test.js: -------------------------------------------------------------------------------- ```javascript /** * Test for getTagAwareFilePath utility function * Tests the fix for Issue #850 */ import { getTagAwareFilePath } from '../../../../scripts/modules/utils.js'; import path from 'path'; describe('getTagAwareFilePath utility function', () => { const projectRoot = '/test/project'; const basePath = '.taskmaster/reports/task-complexity-report.json'; it('should return base path for master tag', () => { const result = getTagAwareFilePath(basePath, 'master', projectRoot); const expected = path.join(projectRoot, basePath); expect(result).toBe(expected); }); it('should return base path for null tag', () => { const result = getTagAwareFilePath(basePath, null, projectRoot); const expected = path.join(projectRoot, basePath); expect(result).toBe(expected); }); it('should return base path for undefined tag', () => { const result = getTagAwareFilePath(basePath, undefined, projectRoot); const expected = path.join(projectRoot, basePath); expect(result).toBe(expected); }); it('should return tag-specific path for non-master tag', () => { const tag = 'feature-branch'; const result = getTagAwareFilePath(basePath, tag, projectRoot); const expected = path.join( projectRoot, '.taskmaster/reports/task-complexity-report_feature-branch.json' ); expect(result).toBe(expected); }); it('should handle different file extensions', () => { const csvBasePath = '.taskmaster/reports/export.csv'; const tag = 'dev-branch'; const result = getTagAwareFilePath(csvBasePath, tag, projectRoot); const expected = path.join( projectRoot, '.taskmaster/reports/export_dev-branch.csv' ); expect(result).toBe(expected); }); it('should handle paths without extensions', () => { const noExtPath = '.taskmaster/reports/summary'; const tag = 'test-tag'; const result = getTagAwareFilePath(noExtPath, tag, projectRoot); // Since there's no extension, it should append the tag const expected = path.join( projectRoot, '.taskmaster/reports/summary_test-tag' ); expect(result).toBe(expected); }); it('should use default project root when not provided', () => { const tag = 'feature-tag'; const result = getTagAwareFilePath(basePath, tag); const expected = path.join( '.', '.taskmaster/reports/task-complexity-report_feature-tag.json' ); expect(result).toBe(expected); }); it('should handle complex tag names with special characters', () => { const tag = 'feature-user-auth-v2'; const result = getTagAwareFilePath(basePath, tag, projectRoot); const expected = path.join( projectRoot, '.taskmaster/reports/task-complexity-report_feature-user-auth-v2.json' ); expect(result).toBe(expected); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/scope-down.js: -------------------------------------------------------------------------------- ```javascript /** * tools/scope-down.js * Tool to scope down task complexity */ import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { scopeDownDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the scopeDown tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerScopeDownTool(server) { server.addTool({ name: 'scope_down_task', description: 'Decrease the complexity of one or more tasks using AI', parameters: z.object({ id: z .string() .describe( 'Comma-separated list of task IDs to scope down (e.g., "1,3,5")' ), strength: z .string() .optional() .describe( 'Strength level: light, regular, or heavy (default: regular)' ), prompt: z .string() .optional() .describe('Custom prompt for specific scoping adjustments'), file: z .string() .optional() .describe('Path to the tasks file (default: tasks/tasks.json)'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on'), research: z .boolean() .optional() .describe('Whether to use research capabilities for scoping') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Starting scope-down with args: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // Call the direct function const result = await scopeDownDirect( { tasksJsonPath: tasksJsonPath, id: args.id, strength: args.strength, prompt: args.prompt, research: args.research, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); return handleApiResult( result, log, 'Error scoping down task', undefined, args.projectRoot ); } catch (error) { log.error(`Error in scope-down tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/next-task.js: -------------------------------------------------------------------------------- ```javascript /** * tools/next-task.js * Tool to find the next task to work on based on dependencies and status */ import { z } from 'zod'; import { createErrorResponse, handleApiResult, withNormalizedProjectRoot } from './utils.js'; import { nextTaskDirect } from '../core/task-master-core.js'; import { resolveTasksPath, resolveComplexityReportPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the nextTask tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerNextTaskTool(server) { server.addTool({ name: 'next_task', description: 'Find the next task to work on based on dependencies and status', parameters: z.object({ file: z.string().optional().describe('Absolute path to the tasks file'), complexityReport: z .string() .optional() .describe( 'Path to the complexity report file (relative to project root or absolute)' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Finding next task with args: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Resolve the path to tasks.json using new path utilities let tasksJsonPath; try { tasksJsonPath = resolveTasksPath(args, session); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } // Resolve the path to complexity report (optional) let complexityReportPath; try { complexityReportPath = resolveComplexityReportPath( { ...args, tag: resolvedTag }, session ); } catch (error) { log.error(`Error finding complexity report: ${error.message}`); // This is optional, so we don't fail the operation complexityReportPath = null; } const result = await nextTaskDirect( { tasksJsonPath: tasksJsonPath, reportPath: complexityReportPath, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); log.info(`Next task result: ${result.success ? 'found' : 'none'}`); return handleApiResult( result, log, 'Error finding next task', undefined, args.projectRoot ); } catch (error) { log.error(`Error finding next task: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /src/utils/asset-resolver.js: -------------------------------------------------------------------------------- ```javascript /** * Asset Resolver Module * Handles resolving paths to asset files in the package * * The public/assets folder is copied to dist/assets during build via tsup's publicDir, * so we can reliably find it relative to the bundled files. */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); /** * Get the assets directory path * When bundled, assets are in dist/assets * When in development, assets are in public/assets * @returns {string} Path to the assets directory */ export function getAssetsDir() { // Check multiple possible locations const possiblePaths = [ // When running from dist (bundled) - assets are in dist/assets path.join(__dirname, 'assets'), path.join(__dirname, '..', 'assets'), // When running from source in development - now in public/assets path.join(__dirname, '..', '..', 'public', 'assets'), // When installed as npm package - assets at package root path.join(process.cwd(), 'assets'), // For npx usage - check node_modules path.join( process.cwd(), 'node_modules', 'task-master-ai', 'dist', 'assets' ), path.join(process.cwd(), 'node_modules', 'task-master-ai', 'assets') ]; // Find the first existing assets directory for (const assetPath of possiblePaths) { if (fs.existsSync(assetPath)) { // Verify it's actually the assets directory by checking for known files const testFile = path.join(assetPath, 'rules', 'taskmaster.mdc'); if (fs.existsSync(testFile)) { return assetPath; } } } // If no assets directory found, throw an error throw new Error( 'Assets directory not found. This is likely a packaging issue.' ); } /** * Get path to a specific asset file * @param {string} relativePath - Path relative to assets directory * @returns {string} Full path to the asset file */ export function getAssetPath(relativePath) { const assetsDir = getAssetsDir(); return path.join(assetsDir, relativePath); } /** * Check if an asset file exists * @param {string} relativePath - Path relative to assets directory * @returns {boolean} True if the asset exists */ export function assetExists(relativePath) { try { const assetPath = getAssetPath(relativePath); return fs.existsSync(assetPath); } catch (error) { return false; } } /** * Read an asset file * @param {string} relativePath - Path relative to assets directory * @param {string} encoding - File encoding (default: 'utf8') * @returns {string|Buffer} File contents */ export function readAsset(relativePath, encoding = 'utf8') { const assetPath = getAssetPath(relativePath); return fs.readFileSync(assetPath, encoding); } ``` -------------------------------------------------------------------------------- /tests/unit/scripts/modules/task-manager/move-task.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; // --- Mocks --- // Only mock the specific functions that move-task actually uses jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({ readJSON: jest.fn(), writeJSON: jest.fn(), log: jest.fn(), setTasksForTag: jest.fn(), traverseDependencies: jest.fn(() => []) })); jest.unstable_mockModule( '../../../../../scripts/modules/task-manager/generate-task-files.js', () => ({ default: jest.fn().mockResolvedValue() }) ); jest.unstable_mockModule( '../../../../../scripts/modules/task-manager/is-task-dependent.js', () => ({ default: jest.fn(() => false) }) ); jest.unstable_mockModule( '../../../../../scripts/modules/dependency-manager.js', () => ({ findCrossTagDependencies: jest.fn(() => []), getDependentTaskIds: jest.fn(() => []), validateSubtaskMove: jest.fn() }) ); const { readJSON, writeJSON, log } = await import( '../../../../../scripts/modules/utils.js' ); const generateTaskFiles = ( await import( '../../../../../scripts/modules/task-manager/generate-task-files.js' ) ).default; const { default: moveTask } = await import( '../../../../../scripts/modules/task-manager/move-task.js' ); const sampleTagged = () => ({ master: { tasks: [ { id: 1, title: 'A' }, { id: 2, title: 'B', subtasks: [{ id: 1, title: 'B.1' }] } ], metadata: {} }, feature: { tasks: [{ id: 10, title: 'X' }], metadata: {} } }); const clone = () => JSON.parse(JSON.stringify(sampleTagged())); describe('moveTask (unit)', () => { beforeEach(() => { jest.clearAllMocks(); readJSON.mockImplementation((path, projectRoot, tag) => { const data = clone(); return { ...data[tag], tag, _rawTaggedData: data }; }); writeJSON.mockResolvedValue(); log.mockImplementation(() => {}); }); test('moves task to new ID in same tag', async () => { await moveTask('tasks.json', '1', '3', false, { tag: 'master' }); expect(writeJSON).toHaveBeenCalled(); const written = writeJSON.mock.calls[0][1]; const ids = written.master.tasks.map((t) => t.id); expect(ids).toEqual(expect.arrayContaining([2, 3])); expect(ids).not.toContain(1); }); test('throws when counts of source and dest mismatch', async () => { await expect( moveTask('tasks.json', '1,2', '3', {}, { tag: 'master' }) ).rejects.toThrow(/Number of source IDs/); }); test('batch move calls generateTaskFiles once when flag true', async () => { await moveTask('tasks.json', '1,2', '3,4', true, { tag: 'master' }); expect(generateTaskFiles).toHaveBeenCalledTimes(1); }); test('error when tag invalid', async () => { await expect( moveTask('tasks.json', '1', '2', false, { tag: 'ghost' }) ).rejects.toThrow(/tag "ghost" not found/); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/move-task.js: -------------------------------------------------------------------------------- ```javascript /** * Direct function wrapper for moveTask */ import { moveTask } from '../../../../scripts/modules/task-manager.js'; import { findTasksPath } from '../utils/path-utils.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; /** * Move a task or subtask to a new position * @param {Object} args - Function arguments * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file * @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2' or '5,6,7') * @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3' or '7,8,9') * @param {string} args.file - Alternative path to the tasks.json file * @param {string} args.projectRoot - Project root directory * @param {string} args.tag - Tag for the task (optional) * @param {boolean} args.generateFiles - Whether to regenerate task files after moving (default: true) * @param {Object} log - Logger object * @returns {Promise<{success: boolean, data?: Object, error?: Object}>} */ export async function moveTaskDirect(args, log, context = {}) { const { session } = context; const { projectRoot, tag } = args; // Validate required parameters if (!args.sourceId) { return { success: false, error: { message: 'Source ID is required', code: 'MISSING_SOURCE_ID' } }; } if (!args.destinationId) { return { success: false, error: { message: 'Destination ID is required', code: 'MISSING_DESTINATION_ID' } }; } try { // Find tasks.json path if not provided let tasksPath = args.tasksJsonPath || args.file; if (!tasksPath) { if (!args.projectRoot) { return { success: false, error: { message: 'Project root is required if tasksJsonPath is not provided', code: 'MISSING_PROJECT_ROOT' } }; } tasksPath = findTasksPath(args, log); } // Enable silent mode to prevent console output during MCP operation enableSilentMode(); // Call the core moveTask function with file generation control const generateFiles = args.generateFiles !== false; // Default to true const result = await moveTask( tasksPath, args.sourceId, args.destinationId, generateFiles, { projectRoot, tag } ); // Restore console output disableSilentMode(); return { success: true, data: { ...result, message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}` } }; } catch (error) { // Restore console output in case of error disableSilentMode(); log.error(`Failed to move task: ${error.message}`); return { success: false, error: { message: error.message, code: 'MOVE_TASK_ERROR' } }; } } ``` -------------------------------------------------------------------------------- /.github/workflows/claude-dedupe-issues.yml: -------------------------------------------------------------------------------- ```yaml name: Claude Issue Dedupe # description: Automatically dedupe GitHub issues using Claude Code on: issues: types: [opened] workflow_dispatch: inputs: issue_number: description: "Issue number to process for duplicate detection" required: true type: string jobs: claude-dedupe-issues: runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read issues: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Run Claude Code slash command uses: anthropics/claude-code-base-action@beta with: prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}" anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} claude_env: | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Log duplicate comment event to Statsig if: always() env: STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }} run: | ISSUE_NUMBER=${{ github.event.issue.number || inputs.issue_number }} REPO=${{ github.repository }} if [ -z "$STATSIG_API_KEY" ]; then echo "STATSIG_API_KEY not found, skipping Statsig logging" exit 0 fi # Prepare the event payload EVENT_PAYLOAD=$(jq -n \ --arg issue_number "$ISSUE_NUMBER" \ --arg repo "$REPO" \ --arg triggered_by "${{ github.event_name }}" \ '{ events: [{ eventName: "github_duplicate_comment_added", value: 1, metadata: { repository: $repo, issue_number: ($issue_number | tonumber), triggered_by: $triggered_by, workflow_run_id: "${{ github.run_id }}" }, time: (now | floor | tostring) }] }') # Send to Statsig API echo "Logging duplicate comment event to Statsig for issue #${ISSUE_NUMBER}" RESPONSE=$(curl -s -w "\n%{http_code}" -X POST https://events.statsigapi.net/v1/log_event \ -H "Content-Type: application/json" \ -H "STATSIG-API-KEY: ${STATSIG_API_KEY}" \ -d "$EVENT_PAYLOAD") HTTP_CODE=$(echo "$RESPONSE" | tail -n1) BODY=$(echo "$RESPONSE" | head -n-1) if [ "$HTTP_CODE" -eq 200 ] || [ "$HTTP_CODE" -eq 202 ]; then echo "Successfully logged duplicate comment event for issue #${ISSUE_NUMBER}" else echo "Failed to log duplicate comment event for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}" fi ``` -------------------------------------------------------------------------------- /tests/unit/ai-providers/mcp-components.test.js: -------------------------------------------------------------------------------- ```javascript /** * tests/unit/ai-providers/mcp-components.test.js * Unit tests for MCP AI SDK custom components */ import { jest } from '@jest/globals'; describe('MCP Custom SDK Components', () => { describe('Message Converter', () => { let messageConverter; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/message-converter.js' ); messageConverter = module; }); describe('convertToMCPFormat', () => { it('should convert AI SDK messages to MCP format', () => { const input = [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Hello!' } ]; const result = messageConverter.convertToMCPFormat(input); expect(result).toBeDefined(); expect(result.messages).toBeDefined(); expect(Array.isArray(result.messages)).toBe(true); expect(result.systemPrompt).toBe('You are a helpful assistant.'); expect(result.messages).toHaveLength(1); expect(result.messages[0].role).toBe('user'); expect(result.messages[0].content.text).toBe('Hello!'); }); }); describe('convertFromMCPFormat', () => { it('should convert MCP response to AI SDK format', () => { const input = { content: 'Hello! How can I help you?', usage: { inputTokens: 10, outputTokens: 8 } }; const result = messageConverter.convertFromMCPFormat(input); expect(result).toBeDefined(); expect(result.text).toBe('Hello! How can I help you?'); expect(result.usage).toEqual({ inputTokens: 10, outputTokens: 8 }); expect(result.finishReason).toBe('stop'); expect(result.warnings).toBeDefined(); }); }); }); describe('Language Model', () => { let languageModel; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/language-model.js' ); languageModel = module; }); it('should export MCPLanguageModel class', () => { expect(languageModel.MCPLanguageModel).toBeDefined(); expect(typeof languageModel.MCPLanguageModel).toBe('function'); }); }); describe('Error Handling', () => { let errors; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/errors.js' ); errors = module; }); it('should export error classes', () => { expect(errors.MCPError).toBeDefined(); expect(typeof errors.MCPError).toBe('function'); }); }); describe('Index Module', () => { let index; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/index.js' ); index = module; }); it('should export createMCP function', () => { expect(index.createMCP).toBeDefined(); expect(typeof index.createMCP).toBe('function'); }); }); }); ``` -------------------------------------------------------------------------------- /tests/unit/mcp-providers/mcp-components.test.js: -------------------------------------------------------------------------------- ```javascript /** * tests/unit/mcp-providers/mcp-components.test.js * Unit tests for MCP AI SDK custom components */ import { jest } from '@jest/globals'; describe('MCP Custom SDK Components', () => { describe('Message Converter', () => { let messageConverter; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/message-converter.js' ); messageConverter = module; }); describe('convertToMCPFormat', () => { it('should convert AI SDK messages to MCP format', () => { const input = [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Hello!' } ]; const result = messageConverter.convertToMCPFormat(input); expect(result).toBeDefined(); expect(result.messages).toBeDefined(); expect(Array.isArray(result.messages)).toBe(true); expect(result.systemPrompt).toBe('You are a helpful assistant.'); expect(result.messages).toHaveLength(1); expect(result.messages[0].role).toBe('user'); expect(result.messages[0].content.text).toBe('Hello!'); }); }); describe('convertFromMCPFormat', () => { it('should convert MCP response to AI SDK format', () => { const input = { content: 'Hello! How can I help you?', usage: { inputTokens: 10, outputTokens: 8 } }; const result = messageConverter.convertFromMCPFormat(input); expect(result).toBeDefined(); expect(result.text).toBe('Hello! How can I help you?'); expect(result.usage).toEqual({ inputTokens: 10, outputTokens: 8 }); expect(result.finishReason).toBe('stop'); expect(result.warnings).toBeDefined(); }); }); }); describe('Language Model', () => { let languageModel; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/language-model.js' ); languageModel = module; }); it('should export MCPLanguageModel class', () => { expect(languageModel.MCPLanguageModel).toBeDefined(); expect(typeof languageModel.MCPLanguageModel).toBe('function'); }); }); describe('Error Handling', () => { let errors; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/errors.js' ); errors = module; }); it('should export error classes', () => { expect(errors.MCPError).toBeDefined(); expect(typeof errors.MCPError).toBe('function'); }); }); describe('Index Module', () => { let index; beforeAll(async () => { const module = await import( '../../../mcp-server/src/custom-sdk/index.js' ); index = module; }); it('should export createMCP function', () => { expect(index.createMCP).toBeDefined(); expect(typeof index.createMCP).toBe('function'); }); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/remove-subtask.js: -------------------------------------------------------------------------------- ```javascript /** * tools/remove-subtask.js * Tool for removing subtasks from parent tasks */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { removeSubtaskDirect } from '../core/task-master-core.js'; import { findTasksPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the removeSubtask tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerRemoveSubtaskTool(server) { server.addTool({ name: 'remove_subtask', description: 'Remove a subtask from its parent task', parameters: z.object({ id: z .string() .describe( "Subtask ID to remove in format 'parentId.subtaskId' (required)" ), convert: z .boolean() .optional() .describe( 'Convert the subtask to a standalone task instead of deleting it' ), file: z .string() .optional() .describe( 'Absolute path to the tasks file (default: tasks/tasks.json)' ), skipGenerate: z .boolean() .optional() .describe('Skip regenerating task files'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); log.info(`Removing subtask with args: ${JSON.stringify(args)}`); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const result = await removeSubtaskDirect( { tasksJsonPath: tasksJsonPath, id: args.id, convert: args.convert, skipGenerate: args.skipGenerate, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); if (result.success) { log.info(`Subtask removed successfully: ${result.data.message}`); } else { log.error(`Failed to remove subtask: ${result.error.message}`); } return handleApiResult( result, log, 'Error removing subtask', undefined, args.projectRoot ); } catch (error) { log.error(`Error in removeSubtask tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/models.js: -------------------------------------------------------------------------------- ```javascript /** * models.js * MCP tool for managing AI model configurations */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { modelsDirect } from '../core/task-master-core.js'; /** * Register the models tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerModelsTool(server) { server.addTool({ name: 'models', description: 'Get information about available AI models or set model configurations. Run without arguments to get the current model configuration and API key status for the selected model providers.', parameters: z.object({ setMain: z .string() .optional() .describe( 'Set the primary model for task generation/updates. Model provider API key is required in the MCP config ENV.' ), setResearch: z .string() .optional() .describe( 'Set the model for research-backed operations. Model provider API key is required in the MCP config ENV.' ), setFallback: z .string() .optional() .describe( 'Set the model to use if the primary fails. Model provider API key is required in the MCP config ENV.' ), listAvailableModels: z .boolean() .optional() .describe( 'List all available models not currently in use. Input/output costs values are in dollars (3 is $3.00).' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), openrouter: z .boolean() .optional() .describe('Indicates the set model ID is a custom OpenRouter model.'), ollama: z .boolean() .optional() .describe('Indicates the set model ID is a custom Ollama model.'), bedrock: z .boolean() .optional() .describe('Indicates the set model ID is a custom AWS Bedrock model.'), azure: z .boolean() .optional() .describe('Indicates the set model ID is a custom Azure OpenAI model.'), vertex: z .boolean() .optional() .describe( 'Indicates the set model ID is a custom Google Vertex AI model.' ) }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Starting models tool with args: ${JSON.stringify(args)}`); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) const result = await modelsDirect( { ...args, projectRoot: args.projectRoot }, log, { session } ); return handleApiResult( result, log, 'Error managing models', undefined, args.projectRoot ); } catch (error) { log.error(`Error in models tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /packages/tm-core/src/config/services/config-merger.service.ts: -------------------------------------------------------------------------------- ```typescript /** * @fileoverview Configuration Merger Service * Responsible for merging configurations from multiple sources with precedence */ import type { PartialConfiguration } from '../../interfaces/configuration.interface.js'; /** * Configuration source with precedence */ export interface ConfigSource { /** Source name for debugging */ name: string; /** Configuration data from this source */ config: PartialConfiguration; /** Precedence level (higher = more important) */ precedence: number; } /** * Configuration precedence levels (higher number = higher priority) */ export const CONFIG_PRECEDENCE = { DEFAULTS: 0, GLOBAL: 1, // Reserved for future implementation LOCAL: 2, ENVIRONMENT: 3 } as const; /** * ConfigMerger handles merging configurations with precedence rules * Single responsibility: Configuration merging logic */ export class ConfigMerger { private configSources: ConfigSource[] = []; /** * Add a configuration source */ addSource(source: ConfigSource): void { this.configSources.push(source); } /** * Clear all configuration sources */ clearSources(): void { this.configSources = []; } /** * Merge all configuration sources based on precedence */ merge(): PartialConfiguration { // Sort sources by precedence (lowest first) const sortedSources = [...this.configSources].sort( (a, b) => a.precedence - b.precedence ); // Merge from lowest to highest precedence let merged: PartialConfiguration = {}; for (const source of sortedSources) { merged = this.deepMerge(merged, source.config); } return merged; } /** * Deep merge two configuration objects * Higher precedence values override lower ones */ private deepMerge(target: any, source: any): any { if (!source) return target; if (!target) return source; const result = { ...target }; for (const key in source) { if (source[key] === null || source[key] === undefined) { continue; } if (typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMerge(result[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } /** * Get configuration sources for debugging */ getSources(): ConfigSource[] { return [...this.configSources].sort((a, b) => b.precedence - a.precedence); } /** * Check if a source exists */ hasSource(name: string): boolean { return this.configSources.some((source) => source.name === name); } /** * Remove a source by name */ removeSource(name: string): boolean { const initialLength = this.configSources.length; this.configSources = this.configSources.filter( (source) => source.name !== name ); return this.configSources.length < initialLength; } } ``` -------------------------------------------------------------------------------- /.claude/commands/tm/analyze-complexity/analyze-complexity.md: -------------------------------------------------------------------------------- ```markdown Analyze task complexity and generate expansion recommendations. Arguments: $ARGUMENTS Perform deep analysis of task complexity across the project. ## Complexity Analysis Uses AI to analyze tasks and recommend which ones need breakdown. ## Execution Options ```bash task-master analyze-complexity [--research] [--threshold=5] ``` ## Analysis Parameters - `--research` → Use research AI for deeper analysis - `--threshold=5` → Only flag tasks above complexity 5 - Default: Analyze all pending tasks ## Analysis Process ### 1. **Task Evaluation** For each task, AI evaluates: - Technical complexity - Time requirements - Dependency complexity - Risk factors - Knowledge requirements ### 2. **Complexity Scoring** Assigns score 1-10 based on: - Implementation difficulty - Integration challenges - Testing requirements - Unknown factors - Technical debt risk ### 3. **Recommendations** For complex tasks: - Suggest expansion approach - Recommend subtask breakdown - Identify risk areas - Propose mitigation strategies ## Smart Analysis Features 1. **Pattern Recognition** - Similar task comparisons - Historical complexity accuracy - Team velocity consideration - Technology stack factors 2. **Contextual Factors** - Team expertise - Available resources - Timeline constraints - Business criticality 3. **Risk Assessment** - Technical risks - Timeline risks - Dependency risks - Knowledge gaps ## Output Format ``` Task Complexity Analysis Report ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ High Complexity Tasks (>7): 📍 #5 "Implement real-time sync" - Score: 9/10 Factors: WebSocket complexity, state management, conflict resolution Recommendation: Expand into 5-7 subtasks Risks: Performance, data consistency 📍 #12 "Migrate database schema" - Score: 8/10 Factors: Data migration, zero downtime, rollback strategy Recommendation: Expand into 4-5 subtasks Risks: Data loss, downtime Medium Complexity Tasks (5-7): 📍 #23 "Add export functionality" - Score: 6/10 Consider expansion if timeline tight Low Complexity Tasks (<5): ✅ 15 tasks - No expansion needed Summary: - Expand immediately: 2 tasks - Consider expanding: 5 tasks - Keep as-is: 15 tasks ``` ## Actionable Output For each high-complexity task: 1. Complexity score with reasoning 2. Specific expansion suggestions 3. Risk mitigation approaches 4. Recommended subtask structure ## Integration Results are: - Saved to `.taskmaster/reports/complexity-analysis.md` - Used by expand command - Inform sprint planning - Guide resource allocation ## Next Steps After analysis: ``` /project:tm/expand 5 # Expand specific task /project:tm/expand/all # Expand all recommended /project:tm/complexity-report # View detailed report ``` ``` -------------------------------------------------------------------------------- /assets/claude/commands/tm/analyze-complexity/analyze-complexity.md: -------------------------------------------------------------------------------- ```markdown Analyze task complexity and generate expansion recommendations. Arguments: $ARGUMENTS Perform deep analysis of task complexity across the project. ## Complexity Analysis Uses AI to analyze tasks and recommend which ones need breakdown. ## Execution Options ```bash task-master analyze-complexity [--research] [--threshold=5] ``` ## Analysis Parameters - `--research` → Use research AI for deeper analysis - `--threshold=5` → Only flag tasks above complexity 5 - Default: Analyze all pending tasks ## Analysis Process ### 1. **Task Evaluation** For each task, AI evaluates: - Technical complexity - Time requirements - Dependency complexity - Risk factors - Knowledge requirements ### 2. **Complexity Scoring** Assigns score 1-10 based on: - Implementation difficulty - Integration challenges - Testing requirements - Unknown factors - Technical debt risk ### 3. **Recommendations** For complex tasks: - Suggest expansion approach - Recommend subtask breakdown - Identify risk areas - Propose mitigation strategies ## Smart Analysis Features 1. **Pattern Recognition** - Similar task comparisons - Historical complexity accuracy - Team velocity consideration - Technology stack factors 2. **Contextual Factors** - Team expertise - Available resources - Timeline constraints - Business criticality 3. **Risk Assessment** - Technical risks - Timeline risks - Dependency risks - Knowledge gaps ## Output Format ``` Task Complexity Analysis Report ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ High Complexity Tasks (>7): 📍 #5 "Implement real-time sync" - Score: 9/10 Factors: WebSocket complexity, state management, conflict resolution Recommendation: Expand into 5-7 subtasks Risks: Performance, data consistency 📍 #12 "Migrate database schema" - Score: 8/10 Factors: Data migration, zero downtime, rollback strategy Recommendation: Expand into 4-5 subtasks Risks: Data loss, downtime Medium Complexity Tasks (5-7): 📍 #23 "Add export functionality" - Score: 6/10 Consider expansion if timeline tight Low Complexity Tasks (<5): ✅ 15 tasks - No expansion needed Summary: - Expand immediately: 2 tasks - Consider expanding: 5 tasks - Keep as-is: 15 tasks ``` ## Actionable Output For each high-complexity task: 1. Complexity score with reasoning 2. Specific expansion suggestions 3. Risk mitigation approaches 4. Recommended subtask structure ## Integration Results are: - Saved to `.taskmaster/reports/complexity-analysis.md` - Used by expand command - Inform sprint planning - Guide resource allocation ## Next Steps After analysis: ``` /project:tm/expand 5 # Expand specific task /project:tm/expand/all # Expand all recommended /project:tm/complexity-report # View detailed report ``` ``` -------------------------------------------------------------------------------- /tests/unit/profiles/cline-integration.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import fs from 'fs'; import path from 'path'; import os from 'os'; // Mock external modules jest.mock('child_process', () => ({ execSync: jest.fn() })); // Mock console methods jest.mock('console', () => ({ log: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), clear: jest.fn() })); describe('Cline Integration', () => { let tempDir; beforeEach(() => { jest.clearAllMocks(); // Create a temporary directory for testing tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-')); // Spy on fs methods jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => { if (filePath.toString().includes('.clinerules')) { return 'Existing cline rules content'; } return '{}'; }); jest.spyOn(fs, 'existsSync').mockImplementation(() => false); jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {}); }); afterEach(() => { // Clean up the temporary directory try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (err) { console.error(`Error cleaning up: ${err.message}`); } }); // Test function that simulates the createProjectStructure behavior for Cline files function mockCreateClineStructure() { // Create main .clinerules directory fs.mkdirSync(path.join(tempDir, '.clinerules'), { recursive: true }); // Create rule files const ruleFiles = [ 'dev_workflow.md', 'taskmaster.md', 'architecture.md', 'commands.md', 'dependencies.md' ]; for (const ruleFile of ruleFiles) { fs.writeFileSync( path.join(tempDir, '.clinerules', ruleFile), `Content for ${ruleFile}` ); } } test('creates all required .clinerules directories', () => { // Act mockCreateClineStructure(); // Assert expect(fs.mkdirSync).toHaveBeenCalledWith( path.join(tempDir, '.clinerules'), { recursive: true } ); }); test('creates rule files for Cline', () => { // Act mockCreateClineStructure(); // Assert - check rule files are created expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, '.clinerules', 'dev_workflow.md'), expect.any(String) ); expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, '.clinerules', 'taskmaster.md'), expect.any(String) ); expect(fs.writeFileSync).toHaveBeenCalledWith( path.join(tempDir, '.clinerules', 'architecture.md'), expect.any(String) ); }); test('does not create MCP configuration files', () => { // Act mockCreateClineStructure(); // Assert - Cline doesn't use MCP configuration expect(fs.writeFileSync).not.toHaveBeenCalledWith( path.join(tempDir, '.clinerules', 'mcp.json'), expect.any(String) ); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/core/__tests__/context-manager.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import { ContextManager } from '../context-manager.js'; describe('ContextManager', () => { let contextManager; beforeEach(() => { contextManager = new ContextManager({ maxCacheSize: 10, ttl: 1000, // 1 second for testing maxContextSize: 1000 }); }); describe('getContext', () => { it('should create a new context when not in cache', async () => { const context = await contextManager.getContext('test-id', { test: true }); expect(context.id).toBe('test-id'); expect(context.metadata.test).toBe(true); expect(contextManager.stats.misses).toBe(1); expect(contextManager.stats.hits).toBe(0); }); it('should return cached context when available', async () => { // First call creates the context await contextManager.getContext('test-id', { test: true }); // Second call should hit cache const context = await contextManager.getContext('test-id', { test: true }); expect(context.id).toBe('test-id'); expect(context.metadata.test).toBe(true); expect(contextManager.stats.hits).toBe(1); expect(contextManager.stats.misses).toBe(1); }); it('should respect TTL settings', async () => { // Create context await contextManager.getContext('test-id', { test: true }); // Wait for TTL to expire await new Promise((resolve) => setTimeout(resolve, 1100)); // Should create new context await contextManager.getContext('test-id', { test: true }); expect(contextManager.stats.misses).toBe(2); expect(contextManager.stats.hits).toBe(0); }); }); describe('updateContext', () => { it('should update existing context metadata', async () => { await contextManager.getContext('test-id', { initial: true }); const updated = await contextManager.updateContext('test-id', { updated: true }); expect(updated.metadata.initial).toBe(true); expect(updated.metadata.updated).toBe(true); }); }); describe('invalidateContext', () => { it('should remove context from cache', async () => { await contextManager.getContext('test-id', { test: true }); contextManager.invalidateContext('test-id', { test: true }); // Should be a cache miss await contextManager.getContext('test-id', { test: true }); expect(contextManager.stats.invalidations).toBe(1); expect(contextManager.stats.misses).toBe(2); }); }); describe('getStats', () => { it('should return current cache statistics', async () => { await contextManager.getContext('test-id', { test: true }); const stats = contextManager.getStats(); expect(stats.hits).toBe(0); expect(stats.misses).toBe(1); expect(stats.invalidations).toBe(0); expect(stats.size).toBe(1); expect(stats.maxSize).toBe(10); expect(stats.ttl).toBe(1000); }); }); }); ``` -------------------------------------------------------------------------------- /tests/unit/profiles/rule-transformer-gemini.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import { getRulesProfile } from '../../../src/utils/rule-transformer.js'; import { geminiProfile } from '../../../src/profiles/gemini.js'; describe('Rule Transformer - Gemini Profile', () => { test('should have correct profile configuration', () => { const geminiProfile = getRulesProfile('gemini'); expect(geminiProfile).toBeDefined(); expect(geminiProfile.profileName).toBe('gemini'); expect(geminiProfile.displayName).toBe('Gemini'); expect(geminiProfile.profileDir).toBe('.gemini'); expect(geminiProfile.rulesDir).toBe('.'); expect(geminiProfile.mcpConfig).toBe(true); expect(geminiProfile.mcpConfigName).toBe('settings.json'); expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json'); expect(geminiProfile.includeDefaultRules).toBe(false); expect(geminiProfile.fileMap).toEqual({ 'AGENTS.md': 'GEMINI.md' }); }); test('should have minimal profile implementation', () => { // Verify that gemini.js is minimal (no lifecycle functions) expect(geminiProfile.onAddRulesProfile).toBeUndefined(); expect(geminiProfile.onRemoveRulesProfile).toBeUndefined(); expect(geminiProfile.onPostConvertRulesProfile).toBeUndefined(); }); test('should use settings.json instead of mcp.json', () => { const geminiProfile = getRulesProfile('gemini'); expect(geminiProfile.mcpConfigName).toBe('settings.json'); expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json'); }); test('should not include default rules', () => { const geminiProfile = getRulesProfile('gemini'); expect(geminiProfile.includeDefaultRules).toBe(false); }); test('should have correct file mapping', () => { const geminiProfile = getRulesProfile('gemini'); expect(geminiProfile.fileMap).toEqual({ 'AGENTS.md': 'GEMINI.md' }); }); test('should place GEMINI.md in root directory', () => { const geminiProfile = getRulesProfile('gemini'); // rulesDir determines where fileMap files go expect(geminiProfile.rulesDir).toBe('.'); // This means AGENTS.md -> GEMINI.md will be placed in the root }); test('should place settings.json in .gemini directory', () => { const geminiProfile = getRulesProfile('gemini'); // profileDir + mcpConfigName determines MCP config location expect(geminiProfile.profileDir).toBe('.gemini'); expect(geminiProfile.mcpConfigName).toBe('settings.json'); expect(geminiProfile.mcpConfigPath).toBe('.gemini/settings.json'); }); test('should have proper conversion config', () => { const geminiProfile = getRulesProfile('gemini'); // Gemini should have the standard conversion config expect(geminiProfile.conversionConfig).toBeDefined(); expect(geminiProfile.globalReplacements).toBeDefined(); expect(Array.isArray(geminiProfile.globalReplacements)).toBe(true); }); }); ``` -------------------------------------------------------------------------------- /apps/extension/src/utils/logger.ts: -------------------------------------------------------------------------------- ```typescript import * as vscode from 'vscode'; /** * Logger interface for dependency injection */ export interface ILogger { log(message: string, ...args: any[]): void; error(message: string, ...args: any[]): void; warn(message: string, ...args: any[]): void; debug(message: string, ...args: any[]): void; show(): void; dispose(): void; } /** * Logger that outputs to VS Code's output channel instead of console * This prevents interference with MCP stdio communication */ export class ExtensionLogger implements ILogger { private static instance: ExtensionLogger; private outputChannel: vscode.OutputChannel; private debugMode: boolean; private constructor() { this.outputChannel = vscode.window.createOutputChannel('TaskMaster'); const config = vscode.workspace.getConfiguration('taskmaster'); this.debugMode = config.get<boolean>('debug.enableLogging', true); } static getInstance(): ExtensionLogger { if (!ExtensionLogger.instance) { ExtensionLogger.instance = new ExtensionLogger(); } return ExtensionLogger.instance; } log(message: string, ...args: any[]): void { if (!this.debugMode) { return; } const timestamp = new Date().toISOString(); const formattedMessage = this.formatMessage(message, args); this.outputChannel.appendLine(`[${timestamp}] ${formattedMessage}`); } error(message: string, ...args: any[]): void { const timestamp = new Date().toISOString(); const formattedMessage = this.formatMessage(message, args); this.outputChannel.appendLine(`[${timestamp}] ERROR: ${formattedMessage}`); } warn(message: string, ...args: any[]): void { if (!this.debugMode) { return; } const timestamp = new Date().toISOString(); const formattedMessage = this.formatMessage(message, args); this.outputChannel.appendLine(`[${timestamp}] WARN: ${formattedMessage}`); } debug(message: string, ...args: any[]): void { if (!this.debugMode) { return; } const timestamp = new Date().toISOString(); const formattedMessage = this.formatMessage(message, args); this.outputChannel.appendLine(`[${timestamp}] DEBUG: ${formattedMessage}`); } private formatMessage(message: string, args: any[]): string { if (args.length === 0) { return message; } // Convert objects to JSON for better readability const formattedArgs = args.map((arg) => { if (typeof arg === 'object' && arg !== null) { try { return JSON.stringify(arg, null, 2); } catch { return String(arg); } } return String(arg); }); return `${message} ${formattedArgs.join(' ')}`; } show(): void { this.outputChannel.show(); } dispose(): void { this.outputChannel.dispose(); } setDebugMode(enabled: boolean): void { this.debugMode = enabled; } } // Export a singleton instance for convenience export const logger = ExtensionLogger.getInstance(); ``` -------------------------------------------------------------------------------- /src/progress/progress-tracker-builder.js: -------------------------------------------------------------------------------- ```javascript /** * Configuration for progress tracker features */ class TrackerConfig { constructor() { this.features = new Set(); this.spinnerFrames = null; this.unitName = 'unit'; this.totalUnits = 100; } addFeature(feature) { this.features.add(feature); } hasFeature(feature) { return this.features.has(feature); } getOptions() { return { numUnits: this.totalUnits, unitName: this.unitName, spinnerFrames: this.spinnerFrames, features: Array.from(this.features) }; } } /** * Builder for creating configured progress trackers */ export class ProgressTrackerBuilder { constructor() { this.config = new TrackerConfig(); } withPercent() { this.config.addFeature('percent'); return this; } withTokens() { this.config.addFeature('tokens'); return this; } withTasks() { this.config.addFeature('tasks'); return this; } withSpinner(messages) { if (!messages || !Array.isArray(messages)) { throw new Error('Spinner messages must be an array'); } this.config.spinnerFrames = messages; return this; } withUnits(total, unitName = 'unit') { this.config.totalUnits = total; this.config.unitName = unitName; return this; } build() { return new ProgressTracker(this.config); } } /** * Base progress tracker with configurable features */ class ProgressTracker { constructor(config) { this.config = config; this.isActive = false; this.current = 0; this.spinnerIndex = 0; this.startTime = null; } start() { this.isActive = true; this.startTime = Date.now(); this.current = 0; if (this.config.spinnerFrames) { this._startSpinner(); } } update(data = {}) { if (!this.isActive) return; if (data.current !== undefined) { this.current = data.current; } const progress = this._buildProgressData(data); return progress; } finish() { this.isActive = false; if (this.spinnerInterval) { clearInterval(this.spinnerInterval); this.spinnerInterval = null; } return this._buildSummary(); } _startSpinner() { this.spinnerInterval = setInterval(() => { this.spinnerIndex = (this.spinnerIndex + 1) % this.config.spinnerFrames.length; }, 100); } _buildProgressData(data) { const progress = { ...data }; if (this.config.hasFeature('percent')) { progress.percentage = Math.round( (this.current / this.config.totalUnits) * 100 ); } if (this.config.hasFeature('tasks')) { progress.tasks = `${this.current}/${this.config.totalUnits}`; } if (this.config.spinnerFrames) { progress.spinner = this.config.spinnerFrames[this.spinnerIndex]; } return progress; } _buildSummary() { const elapsed = Date.now() - this.startTime; return { total: this.config.totalUnits, completed: this.current, elapsedMs: elapsed, features: Array.from(this.config.features) }; } } ``` -------------------------------------------------------------------------------- /src/profiles/kiro.js: -------------------------------------------------------------------------------- ```javascript // Kiro profile for rule-transformer import { createProfile } from './base-profile.js'; import fs from 'fs'; import path from 'path'; import { log } from '../../scripts/modules/utils.js'; // Create and export kiro profile using the base factory export const kiroProfile = createProfile({ name: 'kiro', displayName: 'Kiro', url: 'kiro.dev', docsUrl: 'kiro.dev/docs', profileDir: '.kiro', rulesDir: '.kiro/steering', // Kiro rules location (full path) mcpConfig: true, mcpConfigName: 'settings/mcp.json', // Create directly in settings subdirectory includeDefaultRules: true, // Include default rules to get all the standard files targetExtension: '.md', fileMap: { // Override specific mappings - the base profile will create: // 'rules/cursor_rules.mdc': 'kiro_rules.md' // 'rules/dev_workflow.mdc': 'dev_workflow.md' // 'rules/self_improve.mdc': 'self_improve.md' // 'rules/taskmaster.mdc': 'taskmaster.md' // We can add additional custom mappings here if needed 'rules/taskmaster_hooks_workflow.mdc': 'taskmaster_hooks_workflow.md' }, customReplacements: [ // Core Kiro directory structure changes { from: /\.cursor\/rules/g, to: '.kiro/steering' }, { from: /\.cursor\/mcp\.json/g, to: '.kiro/settings/mcp.json' }, // Fix any remaining kiro/rules references that might be created during transformation { from: /\.kiro\/rules/g, to: '.kiro/steering' }, // Essential markdown link transformations for Kiro structure { from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g, to: '[$1](.kiro/steering/$2.md)' }, // Kiro specific terminology { from: /rules directory/g, to: 'steering directory' }, { from: /cursor rules/gi, to: 'Kiro steering files' }, // Transform frontmatter to Kiro format // This regex matches the entire frontmatter block and replaces it { from: /^---\n(?:description:\s*[^\n]*\n)?(?:globs:\s*[^\n]*\n)?(?:alwaysApply:\s*true\n)?---/m, to: '---\ninclusion: always\n---' } ], // Add lifecycle hook to copy Kiro hooks onPostConvert: (projectRoot, assetsDir) => { const hooksSourceDir = path.join(assetsDir, 'kiro-hooks'); const hooksTargetDir = path.join(projectRoot, '.kiro', 'hooks'); // Create hooks directory if it doesn't exist if (!fs.existsSync(hooksTargetDir)) { fs.mkdirSync(hooksTargetDir, { recursive: true }); } // Copy all .kiro.hook files if (fs.existsSync(hooksSourceDir)) { const hookFiles = fs .readdirSync(hooksSourceDir) .filter((f) => f.endsWith('.kiro.hook')); hookFiles.forEach((file) => { const sourcePath = path.join(hooksSourceDir, file); const targetPath = path.join(hooksTargetDir, file); fs.copyFileSync(sourcePath, targetPath); }); if (hookFiles.length > 0) { log( 'info', `[Kiro] Installed ${hookFiles.length} Taskmaster hooks in .kiro/hooks/` ); } } } }); ``` -------------------------------------------------------------------------------- /apps/extension/src/services/notification-preferences.ts: -------------------------------------------------------------------------------- ```typescript /** * Notification Preferences Service * Manages user preferences for notifications */ import * as vscode from 'vscode'; import { ErrorCategory, ErrorSeverity } from './error-handler'; export enum NotificationLevel { ALL = 'all', ERRORS_ONLY = 'errors_only', CRITICAL_ONLY = 'critical_only', NONE = 'none' } interface NotificationRule { category: ErrorCategory; minSeverity: ErrorSeverity; enabled: boolean; } export class NotificationPreferences { private defaultRules: NotificationRule[] = [ { category: ErrorCategory.MCP_CONNECTION, minSeverity: ErrorSeverity.HIGH, enabled: true }, { category: ErrorCategory.CONFIGURATION, minSeverity: ErrorSeverity.MEDIUM, enabled: true }, { category: ErrorCategory.TASK_LOADING, minSeverity: ErrorSeverity.HIGH, enabled: true }, { category: ErrorCategory.NETWORK, minSeverity: ErrorSeverity.HIGH, enabled: true }, { category: ErrorCategory.INTERNAL, minSeverity: ErrorSeverity.CRITICAL, enabled: true } ]; /** * Check if a notification should be shown */ shouldShowNotification( category: ErrorCategory, severity: ErrorSeverity ): boolean { // Get user's notification level preference const level = this.getNotificationLevel(); if (level === NotificationLevel.NONE) { return false; } if ( level === NotificationLevel.CRITICAL_ONLY && severity !== ErrorSeverity.CRITICAL ) { return false; } if ( level === NotificationLevel.ERRORS_ONLY && severity !== ErrorSeverity.CRITICAL && severity !== ErrorSeverity.HIGH ) { return false; } // Check category-specific rules const rule = this.defaultRules.find((r) => r.category === category); if (!rule || !rule.enabled) { return false; } // Check if severity meets minimum threshold return this.compareSeverity(severity, rule.minSeverity) >= 0; } /** * Get user's notification level preference */ private getNotificationLevel(): NotificationLevel { const config = vscode.workspace.getConfiguration('taskmaster'); return config.get<NotificationLevel>( 'notifications.level', NotificationLevel.ERRORS_ONLY ); } /** * Compare severity levels */ private compareSeverity(a: ErrorSeverity, b: ErrorSeverity): number { const severityOrder = { [ErrorSeverity.LOW]: 0, [ErrorSeverity.MEDIUM]: 1, [ErrorSeverity.HIGH]: 2, [ErrorSeverity.CRITICAL]: 3 }; return severityOrder[a] - severityOrder[b]; } /** * Get toast notification duration based on severity */ getToastDuration(severity: ErrorSeverity): number { switch (severity) { case ErrorSeverity.CRITICAL: return 10000; // 10 seconds case ErrorSeverity.HIGH: return 7000; // 7 seconds case ErrorSeverity.MEDIUM: return 5000; // 5 seconds case ErrorSeverity.LOW: return 3000; // 3 seconds } } } ``` -------------------------------------------------------------------------------- /mcp-server/src/tools/expand-task.js: -------------------------------------------------------------------------------- ```javascript /** * tools/expand-task.js * Tool to expand a task into subtasks */ import { z } from 'zod'; import { handleApiResult, createErrorResponse, withNormalizedProjectRoot } from './utils.js'; import { expandTaskDirect } from '../core/task-master-core.js'; import { findTasksPath, findComplexityReportPath } from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** * Register the expand-task tool with the MCP server * @param {Object} server - FastMCP server instance */ export function registerExpandTaskTool(server) { server.addTool({ name: 'expand_task', description: 'Expand a task into subtasks for detailed implementation', parameters: z.object({ id: z.string().describe('ID of task to expand'), num: z.string().optional().describe('Number of subtasks to generate'), research: z .boolean() .optional() .default(false) .describe('Use research role for generation'), prompt: z .string() .optional() .describe('Additional context for subtask generation'), file: z .string() .optional() .describe( 'Path to the tasks file relative to project root (e.g., tasks/tasks.json)' ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), force: z .boolean() .optional() .default(false) .describe('Force expansion even if subtasks exist'), tag: z.string().optional().describe('Tag context to operate on') }), execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { log.info(`Starting expand-task with args: ${JSON.stringify(args)}`); const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const complexityReportPath = findComplexityReportPath( { ...args, tag: resolvedTag }, log ); const result = await expandTaskDirect( { tasksJsonPath: tasksJsonPath, id: args.id, num: args.num, research: args.research, prompt: args.prompt, force: args.force, complexityReportPath, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); return handleApiResult( result, log, 'Error expanding task', undefined, args.projectRoot ); } catch (error) { log.error(`Error in expand-task tool: ${error.message}`); return createErrorResponse(error.message); } }) }); } ``` -------------------------------------------------------------------------------- /apps/extension/src/utils/task-master-api/types/index.ts: -------------------------------------------------------------------------------- ```typescript /** * TaskMaster API Types * All type definitions for the TaskMaster API */ // MCP Response Types export interface MCPTaskResponse { data?: { tasks?: Array<{ id: number | string; title: string; description: string; status: string; priority: string; details?: string; testStrategy?: string; dependencies?: Array<number | string>; complexityScore?: number; subtasks?: Array<{ id: number; title: string; description?: string; status: string; details?: string; dependencies?: Array<number | string>; }>; }>; tag?: { currentTag: string; availableTags: string[]; }; }; version?: { version: string; name: string; }; error?: string; } // Internal Task Interface export interface TaskMasterTask { id: string; title: string; description: string; status: | 'pending' | 'in-progress' | 'review' | 'done' | 'deferred' | 'cancelled'; priority: 'high' | 'medium' | 'low'; details?: string; testStrategy?: string; dependencies?: string[]; complexityScore?: number; subtasks?: Array<{ id: number; title: string; description?: string; status: string; details?: string; testStrategy?: string; dependencies?: Array<number | string>; }>; } // API Response Wrapper export interface TaskMasterApiResponse<T = any> { success: boolean; data?: T; error?: string; requestDuration?: number; } // API Configuration export interface TaskMasterApiConfig { timeout: number; retryAttempts: number; cacheDuration: number; projectRoot?: string; cache?: CacheConfig; } export interface CacheConfig { maxSize: number; enableBackgroundRefresh: boolean; refreshInterval: number; enableAnalytics: boolean; enablePrefetch: boolean; compressionEnabled: boolean; persistToDisk: boolean; } // Cache Types export interface CacheEntry { data: any; timestamp: number; accessCount: number; lastAccessed: number; size: number; ttl?: number; tags: string[]; } export interface CacheAnalytics { hits: number; misses: number; evictions: number; refreshes: number; totalSize: number; averageAccessTime: number; hitRate: number; } // Method Options export interface GetTasksOptions { status?: string; withSubtasks?: boolean; tag?: string; projectRoot?: string; } export interface UpdateTaskStatusOptions { projectRoot?: string; } export interface UpdateTaskOptions { projectRoot?: string; append?: boolean; research?: boolean; } export interface UpdateSubtaskOptions { projectRoot?: string; research?: boolean; } export interface AddSubtaskOptions { projectRoot?: string; } export interface TaskUpdate { title?: string; description?: string; details?: string; priority?: 'high' | 'medium' | 'low'; testStrategy?: string; dependencies?: string[]; } export interface SubtaskData { title: string; description?: string; dependencies?: string[]; status?: string; } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/remove-dependency.js: -------------------------------------------------------------------------------- ```javascript /** * Direct function wrapper for removeDependency */ import { removeDependency } from '../../../../scripts/modules/dependency-manager.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; /** * Remove a dependency from a task * @param {Object} args - Function arguments * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string|number} args.id - Task ID to remove dependency from * @param {string|number} args.dependsOn - Task ID to remove as a dependency * @param {string} args.projectRoot - Project root path (for MCP/env fallback) * @param {string} args.tag - Tag for the task (optional) * @param {Object} log - Logger object * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} */ export async function removeDependencyDirect(args, log) { // Destructure expected args const { tasksJsonPath, id, dependsOn, projectRoot, tag } = args; try { log.info(`Removing dependency with args: ${JSON.stringify(args)}`); // Check if tasksJsonPath was provided if (!tasksJsonPath) { log.error('removeDependencyDirect called without tasksJsonPath'); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } // Validate required parameters if (!id) { return { success: false, error: { code: 'INPUT_VALIDATION_ERROR', message: 'Task ID (id) is required' } }; } if (!dependsOn) { return { success: false, error: { code: 'INPUT_VALIDATION_ERROR', message: 'Dependency ID (dependsOn) is required' } }; } // Use provided path const tasksPath = tasksJsonPath; // Format IDs for the core function const taskId = id && id.includes && id.includes('.') ? id : parseInt(id, 10); const dependencyId = dependsOn && dependsOn.includes && dependsOn.includes('.') ? dependsOn : parseInt(dependsOn, 10); log.info( `Removing dependency: task ${taskId} no longer depends on ${dependencyId}` ); // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); // Call the core function using the provided tasksPath await removeDependency(tasksPath, taskId, dependencyId, { projectRoot, tag }); // Restore normal logging disableSilentMode(); return { success: true, data: { message: `Successfully removed dependency: Task ${taskId} no longer depends on ${dependencyId}`, taskId: taskId, dependencyId: dependencyId } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in removeDependencyDirect: ${error.message}`); return { success: false, error: { code: 'CORE_FUNCTION_ERROR', message: error.message } }; } } ```