This is page 12 of 38. Use http://codebase.md/eyaltoledano/claude-task-master?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── README.md ├── .claude │ ├── 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/core/direct-functions/move-task-cross-tag.js: -------------------------------------------------------------------------------- ```javascript /** * Direct function wrapper for cross-tag task moves */ import { moveTasksBetweenTags } from '../../../../scripts/modules/task-manager/move-task.js'; import { findTasksPath } from '../utils/path-utils.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; /** * Move tasks between tags * @param {Object} args - Function arguments * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file * @param {string} args.sourceIds - Comma-separated IDs of tasks to move * @param {string} args.sourceTag - Source tag name * @param {string} args.targetTag - Target tag name * @param {boolean} args.withDependencies - Move dependent tasks along with main task * @param {boolean} args.ignoreDependencies - Break cross-tag dependencies during move * @param {string} args.file - Alternative path to the tasks.json file * @param {string} args.projectRoot - Project root directory * @param {Object} log - Logger object * @returns {Promise<{success: boolean, data?: Object, error?: Object}>} */ export async function moveTaskCrossTagDirect(args, log, context = {}) { const { session } = context; const { projectRoot } = args; log.info(`moveTaskCrossTagDirect called with args: ${JSON.stringify(args)}`); // Validate required parameters if (!args.sourceIds) { return { success: false, error: { message: 'Source IDs are required', code: 'MISSING_SOURCE_IDS' } }; } if (!args.sourceTag) { return { success: false, error: { message: 'Source tag is required for cross-tag moves', code: 'MISSING_SOURCE_TAG' } }; } if (!args.targetTag) { return { success: false, error: { message: 'Target tag is required for cross-tag moves', code: 'MISSING_TARGET_TAG' } }; } // Validate that source and target tags are different if (args.sourceTag === args.targetTag) { return { success: false, error: { message: `Source and target tags are the same ("${args.sourceTag}")`, code: 'SAME_SOURCE_TARGET_TAG', suggestions: [ 'Use different tags for cross-tag moves', 'Use within-tag move: task-master move --from=<id> --to=<id> --tag=<tag>', 'Check available tags: task-master tags' ] } }; } 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(); try { // Parse source IDs const sourceIds = args.sourceIds.split(',').map((id) => id.trim()); // Prepare move options const moveOptions = { withDependencies: args.withDependencies || false, ignoreDependencies: args.ignoreDependencies || false }; // Call the core moveTasksBetweenTags function const result = await moveTasksBetweenTags( tasksPath, sourceIds, args.sourceTag, args.targetTag, moveOptions, { projectRoot } ); return { success: true, data: { ...result, message: `Successfully moved ${sourceIds.length} task(s) from "${args.sourceTag}" to "${args.targetTag}"`, moveOptions, sourceTag: args.sourceTag, targetTag: args.targetTag } }; } finally { // Restore console output - always executed regardless of success or error disableSilentMode(); } } catch (error) { log.error(`Failed to move tasks between tags: ${error.message}`); log.error(`Error code: ${error.code}, Error name: ${error.name}`); // Enhanced error handling with structured error objects let errorCode = 'MOVE_TASK_CROSS_TAG_ERROR'; let suggestions = []; // Handle structured errors first if (error.code === 'CROSS_TAG_DEPENDENCY_CONFLICTS') { errorCode = 'CROSS_TAG_DEPENDENCY_CONFLICT'; suggestions = [ 'Use --with-dependencies to move dependent tasks together', 'Use --ignore-dependencies to break cross-tag dependencies', 'Run task-master validate-dependencies to check for issues', 'Move dependencies first, then move the main task' ]; } else if (error.code === 'CANNOT_MOVE_SUBTASK') { errorCode = 'SUBTASK_MOVE_RESTRICTION'; suggestions = [ 'Promote subtask to full task first: task-master remove-subtask --id=<subtaskId> --convert', 'Move the parent task with all subtasks using --with-dependencies' ]; } else if ( error.code === 'TASK_NOT_FOUND' || error.code === 'INVALID_SOURCE_TAG' || error.code === 'INVALID_TARGET_TAG' ) { errorCode = 'TAG_OR_TASK_NOT_FOUND'; suggestions = [ 'Check available tags: task-master tags', 'Verify task IDs exist: task-master list', 'Check task details: task-master show <id>' ]; } else if (error.message.includes('cross-tag dependency conflicts')) { // Fallback for legacy error messages errorCode = 'CROSS_TAG_DEPENDENCY_CONFLICT'; suggestions = [ 'Use --with-dependencies to move dependent tasks together', 'Use --ignore-dependencies to break cross-tag dependencies', 'Run task-master validate-dependencies to check for issues', 'Move dependencies first, then move the main task' ]; } else if (error.message.includes('Cannot move subtask')) { // Fallback for legacy error messages errorCode = 'SUBTASK_MOVE_RESTRICTION'; suggestions = [ 'Promote subtask to full task first: task-master remove-subtask --id=<subtaskId> --convert', 'Move the parent task with all subtasks using --with-dependencies' ]; } else if (error.message.includes('not found')) { // Fallback for legacy error messages errorCode = 'TAG_OR_TASK_NOT_FOUND'; suggestions = [ 'Check available tags: task-master tags', 'Verify task IDs exist: task-master list', 'Check task details: task-master show <id>' ]; } else if ( error.code === 'TASK_ALREADY_EXISTS' || error.message?.includes('already exists in target tag') ) { // Target tag has an ID collision errorCode = 'TASK_ALREADY_EXISTS'; suggestions = [ 'Choose a different target tag without conflicting IDs', 'Move a different set of IDs (avoid existing ones)', 'If needed, move within-tag to a new ID first, then cross-tag move' ]; } return { success: false, error: { message: error.message, code: errorCode, suggestions } }; } } ``` -------------------------------------------------------------------------------- /scripts/modules/task-manager/generate-task-files.js: -------------------------------------------------------------------------------- ```javascript import path from 'path'; import fs from 'fs'; import chalk from 'chalk'; import { log, readJSON } from '../utils.js'; import { formatDependenciesWithStatus } from '../ui.js'; import { validateAndFixDependencies } from '../dependency-manager.js'; import { getDebugFlag } from '../config-manager.js'; /** * Generate individual task files from tasks.json * @param {string} tasksPath - Path to the tasks.json file * @param {string} outputDir - Output directory for task files * @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot, tag) * @param {string} [options.projectRoot] - Project root path * @param {string} [options.tag] - Tag for the task * @param {Object} [options.mcpLog] - MCP logger object * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode */ function generateTaskFiles(tasksPath, outputDir, options = {}) { try { const isMcpMode = !!options?.mcpLog; const { projectRoot, tag } = options; // 1. Read the raw data structure, ensuring we have all tags. // We call readJSON without a specific tag to get the resolved default view, // which correctly contains the full structure in `_rawTaggedData`. const resolvedData = readJSON(tasksPath, projectRoot, tag); if (!resolvedData) { throw new Error(`Could not read or parse tasks file: ${tasksPath}`); } // Prioritize the _rawTaggedData if it exists, otherwise use the data as is. const rawData = resolvedData._rawTaggedData || resolvedData; // 2. Determine the target tag we need to generate files for. const tagData = rawData[tag]; if (!tagData || !tagData.tasks) { throw new Error(`Tag '${tag}' not found or has no tasks in the data.`); } const tasksForGeneration = tagData.tasks; // Create the output directory if it doesn't exist if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } log( 'info', `Preparing to regenerate ${tasksForGeneration.length} task files for tag '${tag}'` ); // 3. Validate dependencies using the FULL, raw data structure to prevent data loss. validateAndFixDependencies( rawData, // Pass the entire object with all tags tasksPath, projectRoot, tag // Provide the current tag context for the operation ); const allTasksInTag = tagData.tasks; const validTaskIds = allTasksInTag.map((task) => task.id); // Cleanup orphaned task files log('info', 'Checking for orphaned task files to clean up...'); try { const files = fs.readdirSync(outputDir); // Tag-aware file patterns: master -> task_001.txt, other tags -> task_001_tagname.txt const masterFilePattern = /^task_(\d+)\.txt$/; const taggedFilePattern = new RegExp(`^task_(\\d+)_${tag}\\.txt$`); const orphanedFiles = files.filter((file) => { let match = null; let fileTaskId = null; // Check if file belongs to current tag if (tag === 'master') { match = file.match(masterFilePattern); if (match) { fileTaskId = parseInt(match[1], 10); // Only clean up master files when processing master tag return !validTaskIds.includes(fileTaskId); } } else { match = file.match(taggedFilePattern); if (match) { fileTaskId = parseInt(match[1], 10); // Only clean up files for the current tag return !validTaskIds.includes(fileTaskId); } } return false; }); if (orphanedFiles.length > 0) { log( 'info', `Found ${orphanedFiles.length} orphaned task files to remove for tag '${tag}'` ); orphanedFiles.forEach((file) => { const filePath = path.join(outputDir, file); fs.unlinkSync(filePath); }); } else { log('info', 'No orphaned task files found.'); } } catch (err) { log('warn', `Error cleaning up orphaned task files: ${err.message}`); } // Generate task files for the target tag log('info', `Generating individual task files for tag '${tag}'...`); tasksForGeneration.forEach((task) => { // Tag-aware file naming: master -> task_001.txt, other tags -> task_001_tagname.txt const taskFileName = tag === 'master' ? `task_${task.id.toString().padStart(3, '0')}.txt` : `task_${task.id.toString().padStart(3, '0')}_${tag}.txt`; const taskPath = path.join(outputDir, taskFileName); let content = `# Task ID: ${task.id}\n`; content += `# Title: ${task.title}\n`; content += `# Status: ${task.status || 'pending'}\n`; if (task.dependencies && task.dependencies.length > 0) { content += `# Dependencies: ${formatDependenciesWithStatus(task.dependencies, allTasksInTag, false)}\n`; } else { content += '# Dependencies: None\n'; } content += `# Priority: ${task.priority || 'medium'}\n`; content += `# Description: ${task.description || ''}\n`; content += '# Details:\n'; content += (task.details || '') .split('\n') .map((line) => line) .join('\n'); content += '\n\n'; content += '# Test Strategy:\n'; content += (task.testStrategy || '') .split('\n') .map((line) => line) .join('\n'); content += '\n'; if (task.subtasks && task.subtasks.length > 0) { content += '\n# Subtasks:\n'; task.subtasks.forEach((subtask) => { content += `## ${subtask.id}. ${subtask.title} [${subtask.status || 'pending'}]\n`; if (subtask.dependencies && subtask.dependencies.length > 0) { const subtaskDeps = subtask.dependencies .map((depId) => typeof depId === 'number' ? `${task.id}.${depId}` : depId.toString() ) .join(', '); content += `### Dependencies: ${subtaskDeps}\n`; } else { content += '### Dependencies: None\n'; } content += `### Description: ${subtask.description || ''}\n`; content += '### Details:\n'; content += (subtask.details || '') .split('\n') .map((line) => line) .join('\n'); content += '\n\n'; }); } fs.writeFileSync(taskPath, content); }); log( 'success', `All ${tasksForGeneration.length} tasks for tag '${tag}' have been generated into '${outputDir}'.` ); if (isMcpMode) { return { success: true, count: tasksForGeneration.length, directory: outputDir }; } } catch (error) { log('error', `Error generating task files: ${error.message}`); if (!options?.mcpLog) { console.error(chalk.red(`Error generating task files: ${error.message}`)); if (getDebugFlag()) { console.error(error); } process.exit(1); } else { throw error; } } } export default generateTaskFiles; ``` -------------------------------------------------------------------------------- /src/profiles/vscode.js: -------------------------------------------------------------------------------- ```javascript // VS Code conversion profile for rule-transformer import path from 'path'; import fs from 'fs'; import { log } from '../../scripts/modules/utils.js'; import { createProfile, COMMON_TOOL_MAPPINGS } from './base-profile.js'; /** * Transform standard MCP config format to VS Code format * @param {Object} mcpConfig - Standard MCP configuration object * @returns {Object} - Transformed VS Code configuration object */ function transformToVSCodeFormat(mcpConfig) { const vscodeConfig = {}; // Transform mcpServers to servers if (mcpConfig.mcpServers) { vscodeConfig.servers = {}; for (const [serverName, serverConfig] of Object.entries( mcpConfig.mcpServers )) { // Transform server configuration const transformedServer = { ...serverConfig }; // Add type: "stdio" after the env block if (transformedServer.env) { // Reorder properties: keep command, args, env, then add type const reorderedServer = {}; if (transformedServer.command) reorderedServer.command = transformedServer.command; if (transformedServer.args) reorderedServer.args = transformedServer.args; if (transformedServer.env) reorderedServer.env = transformedServer.env; reorderedServer.type = 'stdio'; // Add any other properties that might exist Object.keys(transformedServer).forEach((key) => { if (!['command', 'args', 'env', 'type'].includes(key)) { reorderedServer[key] = transformedServer[key]; } }); vscodeConfig.servers[serverName] = reorderedServer; } else { // If no env block, just add type at the end transformedServer.type = 'stdio'; vscodeConfig.servers[serverName] = transformedServer; } } } return vscodeConfig; } /** * Lifecycle function called after MCP config generation to transform to VS Code format * @param {string} targetDir - Target project directory * @param {string} assetsDir - Assets directory (unused for VS Code) */ function onPostConvertRulesProfile(targetDir, assetsDir) { const vscodeConfigPath = path.join(targetDir, '.vscode', 'mcp.json'); if (!fs.existsSync(vscodeConfigPath)) { log('debug', '[VS Code] No .vscode/mcp.json found to transform'); return; } try { // Read the generated standard MCP config const mcpConfigContent = fs.readFileSync(vscodeConfigPath, 'utf8'); const mcpConfig = JSON.parse(mcpConfigContent); // Check if it's already in VS Code format (has servers instead of mcpServers) if (mcpConfig.servers) { log( 'info', '[VS Code] mcp.json already in VS Code format, skipping transformation' ); return; } // Transform to VS Code format const vscodeConfig = transformToVSCodeFormat(mcpConfig); // Write back the transformed config with proper formatting fs.writeFileSync( vscodeConfigPath, JSON.stringify(vscodeConfig, null, 2) + '\n' ); log('info', '[VS Code] Transformed mcp.json to VS Code format'); log('debug', `[VS Code] Renamed mcpServers->servers, added type: "stdio"`); } catch (error) { log('error', `[VS Code] Failed to transform mcp.json: ${error.message}`); } } /** * Lifecycle function called when removing VS Code profile * @param {string} targetDir - Target project directory */ function onRemoveRulesProfile(targetDir) { const vscodeConfigPath = path.join(targetDir, '.vscode', 'mcp.json'); if (!fs.existsSync(vscodeConfigPath)) { log('debug', '[VS Code] No .vscode/mcp.json found to clean up'); return; } try { // Read the current config const configContent = fs.readFileSync(vscodeConfigPath, 'utf8'); const config = JSON.parse(configContent); // Check if it has the servers section and task-master-ai server if (config.servers && config.servers['task-master-ai']) { // Remove task-master-ai server delete config.servers['task-master-ai']; // Check if there are other MCP servers const remainingServers = Object.keys(config.servers); if (remainingServers.length === 0) { // No other servers, remove entire file fs.rmSync(vscodeConfigPath, { force: true }); log('info', '[VS Code] Removed empty mcp.json file'); // Also remove .vscode directory if it's empty const vscodeDir = path.dirname(vscodeConfigPath); try { const dirContents = fs.readdirSync(vscodeDir); if (dirContents.length === 0) { fs.rmSync(vscodeDir, { recursive: true, force: true }); log('debug', '[VS Code] Removed empty .vscode directory'); } } catch (err) { // Directory might not be empty or might not exist, that's fine } } else { // Write back the modified config fs.writeFileSync( vscodeConfigPath, JSON.stringify(config, null, 2) + '\n' ); log( 'info', '[VS Code] Removed TaskMaster from mcp.json, preserved other configurations' ); } } else { log('debug', '[VS Code] TaskMaster not found in mcp.json'); } } catch (error) { log('error', `[VS Code] Failed to clean up mcp.json: ${error.message}`); } } // Create and export vscode profile using the base factory export const vscodeProfile = createProfile({ name: 'vscode', displayName: 'VS Code', url: 'code.visualstudio.com', docsUrl: 'code.visualstudio.com/docs', rulesDir: '.github/instructions', // VS Code instructions location profileDir: '.vscode', // VS Code configuration directory mcpConfigName: 'mcp.json', // VS Code uses mcp.json in .vscode directory targetExtension: '.instructions.md', customReplacements: [ // Core VS Code directory structure changes { from: /\.cursor\/rules/g, to: '.github/instructions' }, { from: /\.cursor\/mcp\.json/g, to: '.vscode/mcp.json' }, // Fix any remaining vscode/rules references that might be created during transformation { from: /\.vscode\/rules/g, to: '.github/instructions' }, // VS Code custom instructions format - use applyTo with quoted patterns instead of globs { from: /^globs:\s*(.+)$/gm, to: 'applyTo: "$1"' }, // Remove unsupported property - alwaysApply { from: /^alwaysApply:\s*(true|false)\s*\n?/gm, to: '' }, // Essential markdown link transformations for VS Code structure { from: /\[(.+?)\]\(mdc:\.cursor\/rules\/(.+?)\.mdc\)/g, to: '[$1](.github/instructions/$2.instructions.md)' }, // VS Code specific terminology { from: /rules directory/g, to: 'instructions directory' }, { from: /cursor rules/gi, to: 'VS Code instructions' } ], onPostConvert: onPostConvertRulesProfile, onRemove: onRemoveRulesProfile }); // Export lifecycle functions separately to avoid naming conflicts export { onPostConvertRulesProfile, onRemoveRulesProfile }; ``` -------------------------------------------------------------------------------- /apps/docs/capabilities/task-structure.mdx: -------------------------------------------------------------------------------- ```markdown --- title: "Task Structure" sidebarTitle: "Task Structure" description: "Tasks in Task Master follow a specific format designed to provide comprehensive information for both humans and AI assistants." --- ## Task Fields in tasks.json Tasks in tasks.json have the following structure: | Field | Description | Example | | -------------- | ---------------------------------------------- | ------------------------------------------------------ | | `id` | Unique identifier for the task. | `1` | | `title` | Brief, descriptive title. | `"Initialize Repo"` | | `description` | What the task involves. | `"Create a new repository, set up initial structure."` | | `status` | Current state. | `"pending"`, `"done"`, `"deferred"` | | `dependencies` | Prerequisite task IDs. ✅ Completed, ⏱️ Pending | `[1, 2]` | | `priority` | Task importance. | `"high"`, `"medium"`, `"low"` | | `details` | Implementation instructions. | `"Use GitHub client ID/secret, handle callback..."` | | `testStrategy` | How to verify success. | `"Deploy and confirm 'Hello World' response."` | | `subtasks` | Nested subtasks related to the main task. | `[{"id": 1, "title": "Configure OAuth", ...}]` | ## Task File Format Individual task files follow this format: ``` # Task ID: <id> # Title: <title> # Status: <status> # Dependencies: <comma-separated list of dependency IDs> # Priority: <priority> # Description: <brief description> # Details: <detailed implementation notes> # Test Strategy: <verification approach> ``` ## Features in Detail <AccordionGroup> <Accordion title="Analyzing Task Complexity"> The `analyze-complexity` command: - Analyzes each task using AI to assess its complexity on a scale of 1-10 - Recommends optimal number of subtasks based on configured DEFAULT_SUBTASKS - Generates tailored prompts for expanding each task - Creates a comprehensive JSON report with ready-to-use commands - Saves the report to scripts/task-complexity-report.json by default The generated report contains: - Complexity analysis for each task (scored 1-10) - Recommended number of subtasks based on complexity - AI-generated expansion prompts customized for each task - Ready-to-run expansion commands directly within each task analysis </Accordion> <Accordion title="Viewing Complexity Report"> The `complexity-report` command: - Displays a formatted, easy-to-read version of the complexity analysis report - Shows tasks organized by complexity score (highest to lowest) - Provides complexity distribution statistics (low, medium, high) - Highlights tasks recommended for expansion based on threshold score - Includes ready-to-use expansion commands for each complex task - If no report exists, offers to generate one on the spot </Accordion> <Accordion title="Smart Task Expansion"> The `expand` command automatically checks for and uses the complexity report: When a complexity report exists: - Tasks are automatically expanded using the recommended subtask count and prompts - When expanding all tasks, they're processed in order of complexity (highest first) - Research-backed generation is preserved from the complexity analysis - You can still override recommendations with explicit command-line options Example workflow: ```bash # Generate the complexity analysis report with research capabilities task-master analyze-complexity --research # Review the report in a readable format task-master complexity-report # Expand tasks using the optimized recommendations task-master expand --id=8 # or expand all tasks task-master expand --all ``` </Accordion> <Accordion title="Finding the Next Task"> The `next` command: - Identifies tasks that are pending/in-progress and have all dependencies satisfied - Prioritizes tasks by priority level, dependency count, and task ID - Displays comprehensive information about the selected task: - Basic task details (ID, title, priority, dependencies) - Implementation details - Subtasks (if they exist) - Provides contextual suggested actions: - Command to mark the task as in-progress - Command to mark the task as done - Commands for working with subtasks </Accordion> <Accordion title="Viewing Specific Task Details"> The `show` command: - Displays comprehensive details about a specific task or subtask - Shows task status, priority, dependencies, and detailed implementation notes - For parent tasks, displays all subtasks and their status - For subtasks, shows parent task relationship - Provides contextual action suggestions based on the task's state - Works with both regular tasks and subtasks (using the format taskId.subtaskId) </Accordion> </AccordionGroup> ## Best Practices for AI-Driven Development <CardGroup cols={2}> <Card title="📝 Detailed PRD" icon="lightbulb"> The more detailed your PRD, the better the generated tasks will be. </Card> <Card title="👀 Review Tasks" icon="magnifying-glass"> After parsing the PRD, review the tasks to ensure they make sense and have appropriate dependencies. </Card> <Card title="📊 Analyze Complexity" icon="chart-line"> Use the complexity analysis feature to identify which tasks should be broken down further. </Card> <Card title="⛓️ Follow Dependencies" icon="link"> Always respect task dependencies - the Cursor agent will help with this. </Card> <Card title="🔄 Update As You Go" icon="arrows-rotate"> If your implementation diverges from the plan, use the update command to keep future tasks aligned. </Card> <Card title="📦 Break Down Tasks" icon="boxes-stacked"> Use the expand command to break down complex tasks into manageable subtasks. </Card> <Card title="🔄 Regenerate Files" icon="file-arrow-up"> After any updates to tasks.json, regenerate the task files to keep them in sync. </Card> <Card title="💬 Provide Context" icon="comment"> When asking the Cursor agent to help with a task, provide context about what you're trying to achieve. </Card> <Card title="✅ Validate Dependencies" icon="circle-check"> Periodically run the validate-dependencies command to check for invalid or circular dependencies. </Card> </CardGroup> ``` -------------------------------------------------------------------------------- /tests/unit/task-finder.test.js: -------------------------------------------------------------------------------- ```javascript /** * Task finder tests */ // Import after mocks are set up - No mocks needed for readComplexityReport anymore import { findTaskById } from '../../scripts/modules/utils.js'; import { emptySampleTasks, sampleTasks } from '../fixtures/sample-tasks.js'; describe('Task Finder', () => { describe('findTaskById function', () => { test('should find a task by numeric ID', () => { const result = findTaskById(sampleTasks.tasks, 2); expect(result.task).toBeDefined(); expect(result.task.id).toBe(2); expect(result.task.title).toBe('Create Core Functionality'); expect(result.originalSubtaskCount).toBeNull(); }); test('should find a task by string ID', () => { const result = findTaskById(sampleTasks.tasks, '2'); expect(result.task).toBeDefined(); expect(result.task.id).toBe(2); expect(result.originalSubtaskCount).toBeNull(); }); test('should find tasks when JSON contains string IDs (normalized to numbers)', () => { // Simulate tasks loaded from JSON with string IDs and mixed subtask notations const tasksWithStringIds = [ { id: '1', title: 'First Task' }, { id: '2', title: 'Second Task', subtasks: [ { id: '1', title: 'Subtask One' }, { id: '2.2', title: 'Subtask Two (with dotted notation)' } // Testing dotted notation ] }, { id: '5', title: 'Fifth Task', subtasks: [ { id: '5.1', title: 'Subtask with dotted ID' }, // Should normalize to 1 { id: '3', title: 'Subtask with simple ID' } // Should stay as 3 ] } ]; // The readJSON function should normalize these IDs to numbers // For this test, we'll manually normalize them to simulate what happens tasksWithStringIds.forEach((task) => { task.id = parseInt(task.id, 10); if (task.subtasks) { task.subtasks.forEach((subtask) => { // Handle dotted notation like "5.1" -> extract the subtask part if (typeof subtask.id === 'string' && subtask.id.includes('.')) { const parts = subtask.id.split('.'); subtask.id = parseInt(parts[parts.length - 1], 10); } else { subtask.id = parseInt(subtask.id, 10); } }); } }); // Test finding tasks by numeric ID const result1 = findTaskById(tasksWithStringIds, 5); expect(result1.task).toBeDefined(); expect(result1.task.id).toBe(5); expect(result1.task.title).toBe('Fifth Task'); // Test finding tasks by string ID const result2 = findTaskById(tasksWithStringIds, '5'); expect(result2.task).toBeDefined(); expect(result2.task.id).toBe(5); // Test finding subtasks with normalized IDs const result3 = findTaskById(tasksWithStringIds, '2.1'); expect(result3.task).toBeDefined(); expect(result3.task.id).toBe(1); expect(result3.task.title).toBe('Subtask One'); expect(result3.task.isSubtask).toBe(true); // Test subtask that was originally "2.2" (should be normalized to 2) const result4 = findTaskById(tasksWithStringIds, '2.2'); expect(result4.task).toBeDefined(); expect(result4.task.id).toBe(2); expect(result4.task.title).toBe('Subtask Two (with dotted notation)'); // Test subtask that was originally "5.1" (should be normalized to 1) const result5 = findTaskById(tasksWithStringIds, '5.1'); expect(result5.task).toBeDefined(); expect(result5.task.id).toBe(1); expect(result5.task.title).toBe('Subtask with dotted ID'); // Test subtask that was originally "3" (should stay as 3) const result6 = findTaskById(tasksWithStringIds, '5.3'); expect(result6.task).toBeDefined(); expect(result6.task.id).toBe(3); expect(result6.task.title).toBe('Subtask with simple ID'); }); test('should find a subtask using dot notation', () => { const result = findTaskById(sampleTasks.tasks, '3.1'); expect(result.task).toBeDefined(); expect(result.task.id).toBe(1); expect(result.task.title).toBe('Create Header Component'); expect(result.task.isSubtask).toBe(true); expect(result.task.parentTask.id).toBe(3); expect(result.originalSubtaskCount).toBeNull(); }); test('should return null for non-existent task ID', () => { const result = findTaskById(sampleTasks.tasks, 99); expect(result.task).toBeNull(); expect(result.originalSubtaskCount).toBeNull(); }); test('should return null for non-existent subtask ID', () => { const result = findTaskById(sampleTasks.tasks, '3.99'); expect(result.task).toBeNull(); expect(result.originalSubtaskCount).toBeNull(); }); test('should return null for non-existent parent task ID in subtask notation', () => { const result = findTaskById(sampleTasks.tasks, '99.1'); expect(result.task).toBeNull(); expect(result.originalSubtaskCount).toBeNull(); }); test('should return null when tasks array is empty', () => { const result = findTaskById(emptySampleTasks.tasks, 1); expect(result.task).toBeNull(); expect(result.originalSubtaskCount).toBeNull(); }); test('should work correctly when no complexity report is provided', () => { // Pass null as the complexity report const result = findTaskById(sampleTasks.tasks, 2, null); expect(result.task).toBeDefined(); expect(result.task.id).toBe(2); expect(result.task.complexityScore).toBeUndefined(); }); test('should work correctly when task has no complexity data in the provided report', () => { // Define a complexity report that doesn't include task 2 const complexityReport = { complexityAnalysis: [{ taskId: 999, complexityScore: 5 }] }; const result = findTaskById(sampleTasks.tasks, 2, complexityReport); expect(result.task).toBeDefined(); expect(result.task.id).toBe(2); expect(result.task.complexityScore).toBeUndefined(); }); test('should include complexity score when report is provided', () => { // Define the complexity report for this test const complexityReport = { meta: { generatedAt: '2023-01-01T00:00:00.000Z', tasksAnalyzed: 3, thresholdScore: 5 }, complexityAnalysis: [ { taskId: 1, taskTitle: 'Initialize Project', complexityScore: 3, recommendedSubtasks: 2 }, { taskId: 2, taskTitle: 'Create Core Functionality', complexityScore: 8, recommendedSubtasks: 5 }, { taskId: 3, taskTitle: 'Implement UI Components', complexityScore: 6, recommendedSubtasks: 4 } ] }; const result = findTaskById(sampleTasks.tasks, 2, complexityReport); expect(result.task).toBeDefined(); expect(result.task.id).toBe(2); expect(result.task.complexityScore).toBe(8); }); }); }); ``` -------------------------------------------------------------------------------- /.taskmaster/reports/task-complexity-report_cc-kiro-hooks.json: -------------------------------------------------------------------------------- ```json { "meta": { "generatedAt": "2025-07-22T09:41:10.517Z", "tasksAnalyzed": 10, "totalTasks": 10, "analysisCount": 10, "thresholdScore": 5, "projectName": "Taskmaster", "usedResearch": false }, "complexityAnalysis": [ { "taskId": 1, "taskTitle": "Implement Task Integration Layer (TIL) Core", "complexityScore": 8, "recommendedSubtasks": 5, "expansionPrompt": "Break down the TIL Core implementation into distinct components: hook registration system, task lifecycle management, event coordination, state persistence layer, and configuration validation. Each subtask should focus on a specific architectural component with clear interfaces and testable boundaries.", "reasoning": "This is a foundational component with multiple complex subsystems including event-driven architecture, API integration, state management, and configuration validation. The existing 5 subtasks are well-structured and appropriately sized." }, { "taskId": 2, "taskTitle": "Develop Dependency Monitor with Taskmaster MCP Integration", "complexityScore": 7, "recommendedSubtasks": 4, "expansionPrompt": "Divide the dependency monitor into: dependency graph data structure implementation, circular dependency detection algorithm, Taskmaster MCP integration layer, and real-time notification system. Focus on performance optimization for large graphs and efficient caching strategies.", "reasoning": "Complex graph algorithms and real-time monitoring require careful implementation. The task involves sophisticated data structures, algorithm design, and API integration with performance constraints." }, { "taskId": 3, "taskTitle": "Build Execution Manager with Priority Queue and Parallel Execution", "complexityScore": 8, "recommendedSubtasks": 5, "expansionPrompt": "Structure the execution manager into: priority queue implementation, resource conflict detection system, parallel execution coordinator, timeout and cancellation handler, and execution history persistence layer. Each component should handle specific aspects of concurrent task management.", "reasoning": "Managing concurrent execution with resource conflicts, priority scheduling, and persistence is highly complex. Requires careful synchronization, error handling, and performance optimization." }, { "taskId": 4, "taskTitle": "Implement Safety Manager with Configurable Constraints and Emergency Controls", "complexityScore": 7, "recommendedSubtasks": 4, "expansionPrompt": "Break down into: constraint validation engine, emergency control system (stop/pause), user approval workflow implementation, and safety monitoring/audit logging. Each subtask should address specific safety aspects with fail-safe mechanisms.", "reasoning": "Safety systems require careful design with multiple fail-safes. The task involves validation logic, real-time controls, workflow management, and comprehensive logging." }, { "taskId": 5, "taskTitle": "Develop Event-Based Hook Processor", "complexityScore": 6, "recommendedSubtasks": 4, "expansionPrompt": "Organize into: file system event integration, Git/VCS event listeners, build system event connectors, and event filtering/debouncing mechanism. Focus on modular event source integration with configurable processing pipelines.", "reasoning": "While conceptually straightforward, integrating multiple event sources with proper filtering and performance optimization requires careful implementation. Each event source has unique characteristics." }, { "taskId": 6, "taskTitle": "Implement Prompt-Based Hook Processor with AI Integration", "complexityScore": 7, "recommendedSubtasks": 4, "expansionPrompt": "Divide into: prompt interception mechanism, NLP-based task suggestion engine, context injection system, and conversation-based status updater. Each component should handle specific aspects of AI conversation integration.", "reasoning": "AI integration with prompt analysis and dynamic context injection is complex. Requires understanding of conversation flow, relevance scoring, and seamless integration with existing systems." }, { "taskId": 7, "taskTitle": "Create Update-Based Hook Processor for Automatic Progress Tracking", "complexityScore": 6, "recommendedSubtasks": 4, "expansionPrompt": "Structure as: code change monitor, acceptance criteria validator, dependency update propagator, and conflict detection/resolution system. Focus on accurate progress tracking and automated validation logic.", "reasoning": "Automatic progress tracking requires integration with version control and intelligent analysis of code changes. Conflict detection and dependency propagation add complexity." }, { "taskId": 8, "taskTitle": "Develop Real-Time Automation Dashboard and User Controls", "complexityScore": 7, "recommendedSubtasks": 5, "expansionPrompt": "Break down into: WebSocket real-time communication layer, interactive dependency graph visualization, task queue and status displays, user control interfaces, and analytics/charting components. Each UI component should be modular and reusable.", "reasoning": "Building a responsive real-time dashboard with complex visualizations and interactive controls is challenging. Requires careful state management, performance optimization, and user experience design." }, { "taskId": 9, "taskTitle": "Integrate Kiro IDE and Taskmaster MCP with Core Services", "complexityScore": 8, "recommendedSubtasks": 4, "expansionPrompt": "Organize into: KiroHookAdapter implementation, TaskmasterMCPAdapter development, error handling and retry logic layer, and IDE UI component integration. Focus on robust adapter patterns and comprehensive error recovery.", "reasoning": "End-to-end integration of multiple systems with different architectures is highly complex. Requires careful adapter design, extensive error handling, and thorough testing across all integration points." }, { "taskId": 10, "taskTitle": "Implement Configuration Management and Safety Profiles", "complexityScore": 6, "recommendedSubtasks": 4, "expansionPrompt": "Divide into: visual configuration editor UI, JSON Schema validation engine, import/export functionality, and version control integration. Each component should provide intuitive configuration management with robust validation.", "reasoning": "While technically less complex than core systems, building an intuitive configuration editor with validation, versioning, and import/export requires careful UI/UX design and robust data handling." } ] } ``` -------------------------------------------------------------------------------- /apps/extension/src/components/TaskDetails/SubtasksSection.tsx: -------------------------------------------------------------------------------- ```typescript import type React from 'react'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Badge } from '@/components/ui/badge'; import { CollapsibleSection } from '@/components/ui/CollapsibleSection'; import { Plus, Loader2 } from 'lucide-react'; import type { TaskMasterTask } from '../../webview/types'; import { getStatusDotColor } from '../constants'; interface SubtasksSectionProps { currentTask: TaskMasterTask; isSubtask: boolean; sendMessage: (message: any) => Promise<any>; onNavigateToTask: (taskId: string) => void; } export const SubtasksSection: React.FC<SubtasksSectionProps> = ({ currentTask, isSubtask, sendMessage, onNavigateToTask }) => { const [isAddingSubtask, setIsAddingSubtask] = useState(false); const [newSubtaskTitle, setNewSubtaskTitle] = useState(''); const [newSubtaskDescription, setNewSubtaskDescription] = useState(''); const [isSubmittingSubtask, setIsSubmittingSubtask] = useState(false); const handleAddSubtask = async () => { if (!currentTask || !newSubtaskTitle.trim() || isSubtask) { return; } setIsSubmittingSubtask(true); try { await sendMessage({ type: 'addSubtask', data: { parentTaskId: currentTask.id, subtaskData: { title: newSubtaskTitle.trim(), description: newSubtaskDescription.trim() || undefined, status: 'pending' } } }); // Reset form and close setNewSubtaskTitle(''); setNewSubtaskDescription(''); setIsAddingSubtask(false); } catch (error) { console.error('❌ TaskDetailsView: Failed to add subtask:', error); } finally { setIsSubmittingSubtask(false); } }; const handleCancelAddSubtask = () => { setIsAddingSubtask(false); setNewSubtaskTitle(''); setNewSubtaskDescription(''); }; if ( !((currentTask.subtasks && currentTask.subtasks.length > 0) || !isSubtask) ) { return null; } const rightElement = ( <> {currentTask.subtasks && currentTask.subtasks.length > 0 && ( <span className="text-sm text-vscode-foreground/50"> {currentTask.subtasks?.filter((st) => st.status === 'done').length}/ {currentTask.subtasks?.length} </span> )} {!isSubtask && ( <Button variant="ghost" size="sm" className="ml-auto p-1 h-6 w-6 hover:bg-vscode-button-hoverBackground" onClick={() => setIsAddingSubtask(true)} title="Add subtask" > <Plus className="w-4 h-4" /> </Button> )} </> ); return ( <CollapsibleSection title="Sub-issues" defaultExpanded={true} rightElement={rightElement} > <div className="space-y-3"> {/* Add Subtask Form */} {isAddingSubtask && ( <div className="bg-widget-background rounded-lg p-4 border border-widget-border"> <h4 className="text-sm font-medium text-vscode-foreground mb-3"> Add New Subtask </h4> <div className="space-y-3"> <div> <Label htmlFor="subtask-title" className="block text-sm text-vscode-foreground/80 mb-1" > Title* </Label> <input id="subtask-title" type="text" placeholder="Enter subtask title..." value={newSubtaskTitle} onChange={(e) => setNewSubtaskTitle(e.target.value)} className="w-full px-3 py-2 text-sm bg-vscode-input-background border border-vscode-input-border text-vscode-input-foreground placeholder-vscode-input-foreground/50 rounded focus:border-vscode-focusBorder focus:ring-1 focus:ring-vscode-focusBorder" disabled={isSubmittingSubtask} /> </div> <div> <Label htmlFor="subtask-description" className="block text-sm text-vscode-foreground/80 mb-1" > Description (Optional) </Label> <Textarea id="subtask-description" placeholder="Enter subtask description..." value={newSubtaskDescription} onChange={(e) => setNewSubtaskDescription(e.target.value)} className="min-h-[80px] bg-vscode-input-background border-vscode-input-border text-vscode-input-foreground placeholder-vscode-input-foreground/50 focus:border-vscode-focusBorder focus:ring-vscode-focusBorder" disabled={isSubmittingSubtask} /> </div> <div className="flex gap-3 pt-2"> <Button onClick={handleAddSubtask} disabled={!newSubtaskTitle.trim() || isSubmittingSubtask} className="bg-primary text-primary-foreground hover:bg-primary/90" > {isSubmittingSubtask ? ( <> <Loader2 className="w-4 h-4 mr-2 animate-spin" /> Adding... </> ) : ( <> <Plus className="w-4 h-4 mr-2" /> Add Subtask </> )} </Button> <Button onClick={handleCancelAddSubtask} variant="outline" disabled={isSubmittingSubtask} className="border-widget-border" > Cancel </Button> </div> </div> </div> )} {/* Subtasks List */} {currentTask.subtasks && currentTask.subtasks.length > 0 && ( <div className="space-y-2"> {currentTask.subtasks.map((subtask, index) => { const subtaskId = `${currentTask.id}.${index + 1}`; return ( <div key={subtask.id} className="flex items-center gap-3 p-3 rounded-md border border-textSeparator-foreground hover:border-vscode-border/70 transition-colors cursor-pointer" onClick={() => onNavigateToTask(subtaskId)} > <div className="w-4 h-4 rounded-full flex items-center justify-center" style={{ backgroundColor: getStatusDotColor(subtask.status) }} /> <div className="flex-1 min-w-0"> <p className="text-sm text-vscode-foreground truncate"> {subtask.title} </p> {subtask.description && ( <p className="text-xs text-vscode-foreground/60 truncate mt-0.5"> {subtask.description} </p> )} </div> <div className="flex items-center gap-2 flex-shrink-0"> <Badge variant="secondary" className="text-xs bg-secondary/20 border-secondary/30 text-secondary-foreground px-2 py-0.5" > {subtask.status === 'pending' ? 'todo' : subtask.status} </Badge> </div> </div> ); })} </div> )} </div> </CollapsibleSection> ); }; ``` -------------------------------------------------------------------------------- /mcp-server/src/custom-sdk/language-model.js: -------------------------------------------------------------------------------- ```javascript /** * src/ai-providers/custom-sdk/mcp/language-model.js * * MCP Language Model implementation following AI SDK LanguageModelV1 interface. * Uses MCP session.requestSampling() for AI operations. */ import { convertToMCPFormat, convertFromMCPFormat } from './message-converter.js'; import { MCPError, mapMCPError } from './errors.js'; import { extractJson } from './json-extractor.js'; import { convertSchemaToInstructions, enhancePromptForJSON } from './schema-converter.js'; /** * MCP Language Model implementing AI SDK LanguageModelV1 interface */ export class MCPLanguageModel { specificationVersion = 'v1'; defaultObjectGenerationMode = 'json'; supportsImageUrls = false; supportsStructuredOutputs = true; constructor(options) { this.session = options.session; // MCP session object this.modelId = options.modelId; this.settings = options.settings || {}; this.provider = 'mcp-ai-sdk'; this.maxTokens = this.settings.maxTokens; this.temperature = this.settings.temperature; this.validateSession(); } /** * Validate that the MCP session has required capabilities */ validateSession() { if (!this.session?.clientCapabilities?.sampling) { throw new MCPError('MCP session must have client sampling capabilities'); } } /** * Generate text using MCP session sampling * @param {object} options - Generation options * @param {Array} options.prompt - AI SDK prompt format * @param {AbortSignal} options.abortSignal - Abort signal * @returns {Promise<object>} Generation result in AI SDK format */ async doGenerate(options) { try { // Convert AI SDK prompt to MCP format const { messages, systemPrompt } = convertToMCPFormat(options.prompt); // Use MCP session.requestSampling (same as MCPRemoteProvider) const response = await this.session.requestSampling( { messages, systemPrompt, temperature: this.settings.temperature, maxTokens: this.settings.maxTokens, includeContext: 'thisServer' }, { // signal: options.abortSignal, timeout: 240000 // 4 minutes timeout } ); // Convert MCP response back to AI SDK format const result = convertFromMCPFormat(response); return { text: result.text, finishReason: result.finishReason || 'stop', usage: { promptTokens: result.usage?.inputTokens || 0, completionTokens: result.usage?.outputTokens || 0, totalTokens: (result.usage?.inputTokens || 0) + (result.usage?.outputTokens || 0) }, rawResponse: response, warnings: result.warnings }; } catch (error) { throw mapMCPError(error); } } /** * Generate structured object using MCP session sampling * @param {object} options - Generation options * @param {Array} options.prompt - AI SDK prompt format * @param {import('zod').ZodSchema} options.schema - Zod schema for validation * @param {string} [options.mode='json'] - Generation mode ('json' or 'tool') * @param {AbortSignal} options.abortSignal - Abort signal * @returns {Promise<object>} Generation result with structured object */ async doGenerateObject(options) { try { const { schema, mode = 'json', ...restOptions } = options; if (!schema) { throw new MCPError('Schema is required for object generation'); } // Convert schema to JSON instructions const objectName = restOptions.objectName || 'generated_object'; const jsonInstructions = convertSchemaToInstructions(schema, objectName); // Enhance prompt with JSON generation instructions const enhancedPrompt = enhancePromptForJSON( options.prompt, jsonInstructions ); // Convert enhanced prompt to MCP format const { messages, systemPrompt } = convertToMCPFormat(enhancedPrompt); // Use MCP session.requestSampling with enhanced prompt const response = await this.session.requestSampling( { messages, systemPrompt, temperature: this.settings.temperature, maxTokens: this.settings.maxTokens, includeContext: 'thisServer' }, { timeout: 240000 // 4 minutes timeout } ); // Convert MCP response back to AI SDK format const result = convertFromMCPFormat(response); // Extract JSON from the response text const jsonText = extractJson(result.text); // Parse and validate JSON let parsedObject; try { parsedObject = JSON.parse(jsonText); } catch (parseError) { throw new MCPError( `Failed to parse JSON response: ${parseError.message}. Response: ${result.text.substring(0, 200)}...` ); } // Validate against schema try { const validatedObject = schema.parse(parsedObject); return { object: validatedObject, finishReason: result.finishReason || 'stop', usage: { promptTokens: result.usage?.inputTokens || 0, completionTokens: result.usage?.outputTokens || 0, totalTokens: (result.usage?.inputTokens || 0) + (result.usage?.outputTokens || 0) }, rawResponse: response, warnings: result.warnings }; } catch (validationError) { throw new MCPError( `Generated object does not match schema: ${validationError.message}. Generated: ${JSON.stringify(parsedObject, null, 2)}` ); } } catch (error) { throw mapMCPError(error); } } /** * Stream text generation using MCP session sampling * Note: MCP may not support native streaming, so this may simulate streaming * @param {object} options - Generation options * @returns {AsyncIterable} Stream of generation chunks */ async doStream(options) { try { // For now, simulate streaming by chunking the complete response // TODO: Implement native streaming if MCP supports it const result = await this.doGenerate(options); // Create async generator that yields chunks return this.simulateStreaming(result); } catch (error) { throw mapMCPError(error); } } /** * Simulate streaming by chunking a complete response * @param {object} result - Complete generation result * @returns {AsyncIterable} Simulated stream chunks */ async *simulateStreaming(result) { const text = result.text; const chunkSize = Math.max(1, Math.floor(text.length / 10)); // 10 chunks for (let i = 0; i < text.length; i += chunkSize) { const chunk = text.slice(i, i + chunkSize); const isLast = i + chunkSize >= text.length; yield { type: 'text-delta', textDelta: chunk }; // Small delay to simulate streaming await new Promise((resolve) => setTimeout(resolve, 50)); } // Final chunk with finish reason and usage yield { type: 'finish', finishReason: result.finishReason, usage: result.usage }; } } ``` -------------------------------------------------------------------------------- /scripts/test-claude-errors.js: -------------------------------------------------------------------------------- ```javascript #!/usr/bin/env node /** * test-claude-errors.js * * A test script to verify the error handling and retry logic in the callClaude function. * This script creates a modified version of dev.js that simulates different error scenarios. */ import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { execSync, spawn } from 'child_process'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load environment variables from .env file dotenv.config(); // Create a simple PRD for testing const createTestPRD = () => { return `# Test PRD for Error Handling ## Overview This is a simple test PRD to verify the error handling in the callClaude function. ## Requirements 1. Create a simple web application 2. Implement user authentication 3. Add a dashboard for users `; }; // Create a modified version of dev.js that simulates errors function createErrorSimulationScript(errorType, failureCount = 2) { // Read the original dev.js file const devJsPath = path.join(__dirname, 'dev.js'); const devJsContent = fs.readFileSync(devJsPath, 'utf8'); // Create a modified version that simulates errors let modifiedContent = devJsContent; // Find the anthropic.messages.create call and replace it with our mock const anthropicCallRegex = /const response = await anthropic\.messages\.create\(/; let mockCode = ''; switch (errorType) { case 'network': mockCode = ` // Mock for network error simulation let currentAttempt = 0; const failureCount = ${failureCount}; // Simulate network error for the first few attempts currentAttempt++; console.log(\`[Mock] API call attempt \${currentAttempt}\`); if (currentAttempt <= failureCount) { console.log(\`[Mock] Simulating network error (attempt \${currentAttempt}/\${failureCount})\`); throw new Error('Network error: Connection refused'); } const response = await anthropic.messages.create(`; break; case 'timeout': mockCode = ` // Mock for timeout error simulation let currentAttempt = 0; const failureCount = ${failureCount}; // Simulate timeout error for the first few attempts currentAttempt++; console.log(\`[Mock] API call attempt \${currentAttempt}\`); if (currentAttempt <= failureCount) { console.log(\`[Mock] Simulating timeout error (attempt \${currentAttempt}/\${failureCount})\`); throw new Error('Request timed out after 60000ms'); } const response = await anthropic.messages.create(`; break; case 'invalid-json': mockCode = ` // Mock for invalid JSON response let currentAttempt = 0; const failureCount = ${failureCount}; // Simulate invalid JSON for the first few attempts currentAttempt++; console.log(\`[Mock] API call attempt \${currentAttempt}\`); if (currentAttempt <= failureCount) { console.log(\`[Mock] Simulating invalid JSON response (attempt \${currentAttempt}/\${failureCount})\`); return { content: [ { text: \`\`\`json\\n{"meta": {"projectName": "Test Project"}, "tasks": [{"id": 1, "title": "Task 1"\` } ] }; } const response = await anthropic.messages.create(`; break; case 'empty-tasks': mockCode = ` // Mock for empty tasks array let currentAttempt = 0; const failureCount = ${failureCount}; // Simulate empty tasks array for the first few attempts currentAttempt++; console.log(\`[Mock] API call attempt \${currentAttempt}\`); if (currentAttempt <= failureCount) { console.log(\`[Mock] Simulating empty tasks array (attempt \${currentAttempt}/\${failureCount})\`); return { content: [ { text: \`\`\`json\\n{"meta": {"projectName": "Test Project"}, "tasks": []}\\n\`\`\` } ] }; } const response = await anthropic.messages.create(`; break; default: // No modification mockCode = `const response = await anthropic.messages.create(`; } // Replace the anthropic call with our mock modifiedContent = modifiedContent.replace(anthropicCallRegex, mockCode); // Write the modified script to a temporary file const tempScriptPath = path.join(__dirname, `temp-dev-${errorType}.js`); fs.writeFileSync(tempScriptPath, modifiedContent, 'utf8'); return tempScriptPath; } // Function to run a test with a specific error type async function runErrorTest(errorType, numTasks = 5, failureCount = 2) { console.log(`\n=== Test: ${errorType.toUpperCase()} Error Simulation ===`); // Create a test PRD const testPRD = createTestPRD(); const testPRDPath = path.join(__dirname, `test-prd-${errorType}.txt`); fs.writeFileSync(testPRDPath, testPRD, 'utf8'); // Create a modified dev.js that simulates the specified error const tempScriptPath = createErrorSimulationScript(errorType, failureCount); console.log(`Created test PRD at ${testPRDPath}`); console.log(`Created error simulation script at ${tempScriptPath}`); console.log( `Running with error type: ${errorType}, failure count: ${failureCount}, tasks: ${numTasks}` ); try { // Run the modified script execSync( `node ${tempScriptPath} parse-prd --input=${testPRDPath} --tasks=${numTasks}`, { stdio: 'inherit' } ); console.log(`${errorType} error test completed successfully`); } catch (error) { console.error(`${errorType} error test failed:`, error.message); } finally { // Clean up temporary files if (fs.existsSync(tempScriptPath)) { fs.unlinkSync(tempScriptPath); } if (fs.existsSync(testPRDPath)) { fs.unlinkSync(testPRDPath); } } } // Function to run all error tests async function runAllErrorTests() { console.log('Starting error handling tests for callClaude function...'); // Test 1: Network error with automatic retry await runErrorTest('network', 5, 2); // Test 2: Timeout error with automatic retry await runErrorTest('timeout', 5, 2); // Test 3: Invalid JSON response with task reduction await runErrorTest('invalid-json', 10, 2); // Test 4: Empty tasks array with task reduction await runErrorTest('empty-tasks', 15, 2); // Test 5: Exhausted retries (more failures than MAX_RETRIES) await runErrorTest('network', 5, 4); console.log('\nAll error tests completed!'); } // Run the tests runAllErrorTests().catch((error) => { console.error('Error running tests:', error); process.exit(1); }); ``` -------------------------------------------------------------------------------- /.github/workflows/log-issue-events.yml: -------------------------------------------------------------------------------- ```yaml name: Log GitHub Issue Events on: issues: types: [opened, closed] jobs: log-issue-created: if: github.event.action == 'opened' runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read issues: read steps: - name: Log issue creation to Statsig env: STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }} run: | ISSUE_NUMBER=${{ github.event.issue.number }} REPO=${{ github.repository }} ISSUE_TITLE=$(echo '${{ github.event.issue.title }}' | sed "s/'/'\\\\''/g") AUTHOR="${{ github.event.issue.user.login }}" CREATED_AT="${{ github.event.issue.created_at }}" 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 title "$ISSUE_TITLE" \ --arg author "$AUTHOR" \ --arg created_at "$CREATED_AT" \ '{ events: [{ eventName: "github_issue_created", value: 1, metadata: { repository: $repo, issue_number: ($issue_number | tonumber), issue_title: $title, issue_author: $author, created_at: $created_at }, time: (now | floor | tostring) }] }') # Send to Statsig API echo "Logging issue creation 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 issue creation for issue #${ISSUE_NUMBER}" else echo "Failed to log issue creation for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}" fi log-issue-closed: if: github.event.action == 'closed' runs-on: ubuntu-latest timeout-minutes: 5 permissions: contents: read issues: read steps: - name: Log issue closure to Statsig env: STATSIG_API_KEY: ${{ secrets.STATSIG_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | ISSUE_NUMBER=${{ github.event.issue.number }} REPO=${{ github.repository }} ISSUE_TITLE=$(echo '${{ github.event.issue.title }}' | sed "s/'/'\\\\''/g") CLOSED_BY="${{ github.event.issue.closed_by.login }}" CLOSED_AT="${{ github.event.issue.closed_at }}" STATE_REASON="${{ github.event.issue.state_reason }}" if [ -z "$STATSIG_API_KEY" ]; then echo "STATSIG_API_KEY not found, skipping Statsig logging" exit 0 fi # Get additional issue data via GitHub API echo "Fetching additional issue data for #${ISSUE_NUMBER}" ISSUE_DATA=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/${REPO}/issues/${ISSUE_NUMBER}") COMMENTS_COUNT=$(echo "$ISSUE_DATA" | jq -r '.comments') # Get reactions data REACTIONS_DATA=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/${REPO}/issues/${ISSUE_NUMBER}/reactions") REACTIONS_COUNT=$(echo "$REACTIONS_DATA" | jq '. | length') # Check if issue was closed automatically (by checking if closed_by is a bot) CLOSED_AUTOMATICALLY="false" if [[ "$CLOSED_BY" == *"[bot]"* ]]; then CLOSED_AUTOMATICALLY="true" fi # Check if closed as duplicate by state_reason CLOSED_AS_DUPLICATE="false" if [ "$STATE_REASON" = "duplicate" ]; then CLOSED_AS_DUPLICATE="true" fi # Prepare the event payload EVENT_PAYLOAD=$(jq -n \ --arg issue_number "$ISSUE_NUMBER" \ --arg repo "$REPO" \ --arg title "$ISSUE_TITLE" \ --arg closed_by "$CLOSED_BY" \ --arg closed_at "$CLOSED_AT" \ --arg state_reason "$STATE_REASON" \ --arg comments_count "$COMMENTS_COUNT" \ --arg reactions_count "$REACTIONS_COUNT" \ --arg closed_automatically "$CLOSED_AUTOMATICALLY" \ --arg closed_as_duplicate "$CLOSED_AS_DUPLICATE" \ '{ events: [{ eventName: "github_issue_closed", value: 1, metadata: { repository: $repo, issue_number: ($issue_number | tonumber), issue_title: $title, closed_by: $closed_by, closed_at: $closed_at, state_reason: $state_reason, comments_count: ($comments_count | tonumber), reactions_count: ($reactions_count | tonumber), closed_automatically: ($closed_automatically | test("true")), closed_as_duplicate: ($closed_as_duplicate | test("true")) }, time: (now | floor | tostring) }] }') # Send to Statsig API echo "Logging issue closure 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 issue closure for issue #${ISSUE_NUMBER}" echo "Closed by: $CLOSED_BY" echo "Comments: $COMMENTS_COUNT" echo "Reactions: $REACTIONS_COUNT" echo "Closed automatically: $CLOSED_AUTOMATICALLY" echo "Closed as duplicate: $CLOSED_AS_DUPLICATE" else echo "Failed to log issue closure for issue #${ISSUE_NUMBER}. HTTP ${HTTP_CODE}: ${BODY}" fi ``` -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- ```markdown # Example Cursor AI Interactions Here are some common interactions with Cursor AI when using Task Master: ## Starting a new project ``` I've just initialized a new project with Claude Task Master. I have a PRD at .taskmaster/docs/prd.txt. Can you help me parse it and set up the initial tasks? ``` ## Working on tasks ``` What's the next task I should work on? Please consider dependencies and priorities. ``` ## Implementing a specific task ``` I'd like to implement task 4. Can you help me understand what needs to be done and how to approach it? ``` ## Viewing multiple tasks ``` Can you show me tasks 1, 3, and 5 so I can understand their relationship? ``` ``` I need to see the status of tasks 44, 55, and their subtasks. Can you show me those? ``` ``` Show me tasks 10, 12, and 15 and give me some batch actions I can perform on them. ``` ## Managing subtasks ``` I need to regenerate the subtasks for task 3 with a different approach. Can you help me clear and regenerate them? ``` ## Handling changes ``` I've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change? ``` ## Completing work ``` I've finished implementing the authentication system described in task 2. All tests are passing. Please mark it as complete and tell me what I should work on next. ``` ## Reorganizing tasks ``` I think subtask 5.2 would fit better as part of task 7. Can you move it there? ``` (Agent runs: `task-master move --from=5.2 --to=7.3`) ``` Task 8 should actually be a subtask of task 4. Can you reorganize this? ``` (Agent runs: `task-master move --from=8 --to=4.1`) ``` I just merged the main branch and there's a conflict in tasks.json. My teammates created tasks 10-15 on their branch while I created tasks 10-12 on my branch. Can you help me resolve this by moving my tasks? ``` (Agent runs: ```bash task-master move --from=10 --to=16 task-master move --from=11 --to=17 task-master move --from=12 --to=18 ``` ) ## Analyzing complexity ``` Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further? ``` ## Viewing complexity report ``` Can you show me the complexity report in a more readable format? ``` ### Breaking Down Complex Tasks ``` Task 5 seems complex. Can you break it down into subtasks? ``` (Agent runs: `task-master expand --id=5`) ``` Please break down task 5 using research-backed generation. ``` (Agent runs: `task-master expand --id=5 --research`) ### Updating Tasks with Research ``` We need to update task 15 based on the latest React Query v5 changes. Can you research this and update the task? ``` (Agent runs: `task-master update-task --id=15 --prompt="Update based on React Query v5 changes" --research`) ### Adding Tasks with Research ``` Please add a new task to implement user profile image uploads using Cloudinary, research the best approach. ``` (Agent runs: `task-master add-task --prompt="Implement user profile image uploads using Cloudinary" --research`) ## Research-Driven Development ### Getting Fresh Information ``` Research the latest best practices for implementing JWT authentication in Node.js applications. ``` (Agent runs: `task-master research "Latest best practices for JWT authentication in Node.js"`) ### Research with Project Context ``` I'm working on task 15 which involves API optimization. Can you research current best practices for our specific implementation? ``` (Agent runs: `task-master research "API optimization best practices" --id=15 --files=src/api.js`) ### Research Before Implementation ``` Before I implement task 8 (React Query integration), can you research the latest React Query v5 patterns and any breaking changes? ``` (Agent runs: `task-master research "React Query v5 patterns and breaking changes" --id=8`) ### Research and Update Pattern ``` Research the latest security recommendations for Express.js applications and update our authentication task with the findings. ``` (Agent runs: 1. `task-master research "Latest Express.js security recommendations" --id=12` 2. `task-master update-subtask --id=12.3 --prompt="Updated with latest security findings: [research results]"`) ### Research for Debugging ``` I'm having issues with our WebSocket implementation in task 20. Can you research common WebSocket problems and solutions? ``` (Agent runs: `task-master research "Common WebSocket implementation problems and solutions" --id=20 --files=src/websocket.js`) ### Research Technology Comparisons ``` We need to choose between Redis and Memcached for caching. Can you research the current recommendations for our use case? ``` (Agent runs: `task-master research "Redis vs Memcached 2024 comparison for session caching" --tree`) ## Git Integration and Tag Management ### Creating Tags for Feature Branches ``` I'm starting work on a new feature branch for user authentication. Can you create a matching task tag? ``` (Agent runs: `task-master add-tag --from-branch`) ### Creating Named Tags ``` Create a new tag called 'api-v2' for our API redesign work. ``` (Agent runs: `task-master add-tag api-v2 --description="API v2 redesign tasks"`) ### Switching Tag Contexts ``` Switch to the 'testing' tag so I can work on QA tasks. ``` (Agent runs: `task-master use-tag testing`) ### Copying Tasks Between Tags ``` I need to copy the current tasks to a new 'hotfix' tag for urgent fixes. ``` (Agent runs: `task-master add-tag hotfix --copy-from-current --description="Urgent hotfix tasks"`) ### Managing Multiple Contexts ``` Show me all available tags and their current status. ``` (Agent runs: `task-master tags --show-metadata`) ### Tag Cleanup ``` I've finished the 'user-auth' feature and merged the branch. Can you clean up the tag? ``` (Agent runs: `task-master delete-tag user-auth`) ### Working with Tag-Specific Tasks ``` List all tasks in the 'api-v2' tag context. ``` (Agent runs: `task-master use-tag api-v2` then `task-master list`) ### Branch-Based Development Workflow ``` I'm switching to work on the 'feature/payments' branch. Can you set up the task context for this? ``` (Agent runs: 1. `git checkout feature/payments` 2. `task-master add-tag --from-branch --description="Payment system implementation"` 3. `task-master list` to show tasks in the new context) ### Parallel Feature Development ``` I need to work on both authentication and payment features simultaneously. How should I organize the tasks? ``` (Agent suggests and runs: 1. `task-master add-tag auth --description="Authentication feature tasks"` 2. `task-master add-tag payments --description="Payment system tasks"` 3. `task-master use-tag auth` to start with authentication work) ``` -------------------------------------------------------------------------------- /.github/scripts/auto-close-duplicates.mjs: -------------------------------------------------------------------------------- ``` #!/usr/bin/env node async function githubRequest(endpoint, token, method = 'GET', body) { const response = await fetch(`https://api.github.com${endpoint}`, { method, headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github.v3+json', 'User-Agent': 'auto-close-duplicates-script', ...(body && { 'Content-Type': 'application/json' }) }, ...(body && { body: JSON.stringify(body) }) }); if (!response.ok) { throw new Error( `GitHub API request failed: ${response.status} ${response.statusText}` ); } return response.json(); } function extractDuplicateIssueNumber(commentBody) { const match = commentBody.match(/#(\d+)/); return match ? parseInt(match[1], 10) : null; } async function closeIssueAsDuplicate( owner, repo, issueNumber, duplicateOfNumber, token ) { await githubRequest( `/repos/${owner}/${repo}/issues/${issueNumber}`, token, 'PATCH', { state: 'closed', state_reason: 'not_planned', labels: ['duplicate'] } ); await githubRequest( `/repos/${owner}/${repo}/issues/${issueNumber}/comments`, token, 'POST', { body: `This issue has been automatically closed as a duplicate of #${duplicateOfNumber}. If this is incorrect, please re-open this issue or create a new one. 🤖 Generated with [Task Master Bot]` } ); } async function autoCloseDuplicates() { console.log('[DEBUG] Starting auto-close duplicates script'); const token = process.env.GITHUB_TOKEN; if (!token) { throw new Error('GITHUB_TOKEN environment variable is required'); } console.log('[DEBUG] GitHub token found'); const owner = process.env.GITHUB_REPOSITORY_OWNER || 'eyaltoledano'; const repo = process.env.GITHUB_REPOSITORY_NAME || 'claude-task-master'; console.log(`[DEBUG] Repository: ${owner}/${repo}`); const threeDaysAgo = new Date(); threeDaysAgo.setDate(threeDaysAgo.getDate() - 3); console.log( `[DEBUG] Checking for duplicate comments older than: ${threeDaysAgo.toISOString()}` ); console.log('[DEBUG] Fetching open issues created more than 3 days ago...'); const allIssues = []; let page = 1; const perPage = 100; const MAX_PAGES = 50; // Increase limit for larger repos let foundRecentIssue = false; while (true) { const pageIssues = await githubRequest( `/repos/${owner}/${repo}/issues?state=open&per_page=${perPage}&page=${page}&sort=created&direction=desc`, token ); if (pageIssues.length === 0) break; // Filter for issues created more than 3 days ago const oldEnoughIssues = pageIssues.filter( (issue) => new Date(issue.created_at) <= threeDaysAgo ); allIssues.push(...oldEnoughIssues); // If all issues on this page are newer than 3 days, we can stop if (oldEnoughIssues.length === 0 && page === 1) { foundRecentIssue = true; break; } // If we found some old issues but not all, continue to next page // as there might be more old issues page++; // Safety limit to avoid infinite loops if (page > MAX_PAGES) { console.log(`[WARNING] Reached maximum page limit of ${MAX_PAGES}`); break; } } const issues = allIssues; console.log(`[DEBUG] Found ${issues.length} open issues`); let processedCount = 0; let candidateCount = 0; for (const issue of issues) { processedCount++; console.log( `[DEBUG] Processing issue #${issue.number} (${processedCount}/${issues.length}): ${issue.title}` ); console.log(`[DEBUG] Fetching comments for issue #${issue.number}...`); const comments = await githubRequest( `/repos/${owner}/${repo}/issues/${issue.number}/comments`, token ); console.log( `[DEBUG] Issue #${issue.number} has ${comments.length} comments` ); const dupeComments = comments.filter( (comment) => comment.body.includes('Found') && comment.body.includes('possible duplicate') && comment.user.type === 'Bot' ); console.log( `[DEBUG] Issue #${issue.number} has ${dupeComments.length} duplicate detection comments` ); if (dupeComments.length === 0) { console.log( `[DEBUG] Issue #${issue.number} - no duplicate comments found, skipping` ); continue; } const lastDupeComment = dupeComments[dupeComments.length - 1]; const dupeCommentDate = new Date(lastDupeComment.created_at); console.log( `[DEBUG] Issue #${ issue.number } - most recent duplicate comment from: ${dupeCommentDate.toISOString()}` ); if (dupeCommentDate > threeDaysAgo) { console.log( `[DEBUG] Issue #${issue.number} - duplicate comment is too recent, skipping` ); continue; } console.log( `[DEBUG] Issue #${ issue.number } - duplicate comment is old enough (${Math.floor( (Date.now() - dupeCommentDate.getTime()) / (1000 * 60 * 60 * 24) )} days)` ); const commentsAfterDupe = comments.filter( (comment) => new Date(comment.created_at) > dupeCommentDate ); console.log( `[DEBUG] Issue #${issue.number} - ${commentsAfterDupe.length} comments after duplicate detection` ); if (commentsAfterDupe.length > 0) { console.log( `[DEBUG] Issue #${issue.number} - has activity after duplicate comment, skipping` ); continue; } console.log( `[DEBUG] Issue #${issue.number} - checking reactions on duplicate comment...` ); const reactions = await githubRequest( `/repos/${owner}/${repo}/issues/comments/${lastDupeComment.id}/reactions`, token ); console.log( `[DEBUG] Issue #${issue.number} - duplicate comment has ${reactions.length} reactions` ); const authorThumbsDown = reactions.some( (reaction) => reaction.user.id === issue.user.id && reaction.content === '-1' ); console.log( `[DEBUG] Issue #${issue.number} - author thumbs down reaction: ${authorThumbsDown}` ); if (authorThumbsDown) { console.log( `[DEBUG] Issue #${issue.number} - author disagreed with duplicate detection, skipping` ); continue; } const duplicateIssueNumber = extractDuplicateIssueNumber( lastDupeComment.body ); if (!duplicateIssueNumber) { console.log( `[DEBUG] Issue #${issue.number} - could not extract duplicate issue number from comment, skipping` ); continue; } candidateCount++; const issueUrl = `https://github.com/${owner}/${repo}/issues/${issue.number}`; try { console.log( `[INFO] Auto-closing issue #${issue.number} as duplicate of #${duplicateIssueNumber}: ${issueUrl}` ); await closeIssueAsDuplicate( owner, repo, issue.number, duplicateIssueNumber, token ); console.log( `[SUCCESS] Successfully closed issue #${issue.number} as duplicate of #${duplicateIssueNumber}` ); } catch (error) { console.error( `[ERROR] Failed to close issue #${issue.number} as duplicate: ${error}` ); } } console.log( `[DEBUG] Script completed. Processed ${processedCount} issues, found ${candidateCount} candidates for auto-close` ); } autoCloseDuplicates().catch(console.error); ``` -------------------------------------------------------------------------------- /apps/extension/src/webview/components/TaskEditModal.tsx: -------------------------------------------------------------------------------- ```typescript /** * Task Edit Modal Component */ import React, { useState, useEffect, useRef } from 'react'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import type { TaskMasterTask, TaskUpdates } from '../types'; interface TaskEditModalProps { task: TaskMasterTask; onSave: (taskId: string, updates: TaskUpdates) => Promise<void>; onCancel: () => void; } export const TaskEditModal: React.FC<TaskEditModalProps> = ({ task, onSave, onCancel }) => { const [updates, setUpdates] = useState<TaskUpdates>({ title: task.title, description: task.description || '', details: task.details || '', testStrategy: task.testStrategy || '', priority: task.priority, dependencies: task.dependencies || [] }); const [isSaving, setIsSaving] = useState(false); const formRef = useRef<HTMLFormElement>(null); const titleInputRef = useRef<HTMLInputElement>(null); // Focus title input on mount useEffect(() => { titleInputRef.current?.focus(); titleInputRef.current?.select(); }, []); const handleSubmit = async (e?: React.FormEvent) => { e?.preventDefault(); setIsSaving(true); try { await onSave(task.id, updates); } catch (error) { console.error('Failed to save task:', error); } finally { setIsSaving(false); } }; const hasChanges = () => { return ( updates.title !== task.title || updates.description !== (task.description || '') || updates.details !== (task.details || '') || updates.testStrategy !== (task.testStrategy || '') || updates.priority !== task.priority || JSON.stringify(updates.dependencies) !== JSON.stringify(task.dependencies || []) ); }; return ( <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"> <div className="bg-vscode-editor-background border border-vscode-border rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col"> {/* Header */} <div className="flex items-center justify-between p-4 border-b border-vscode-border"> <h2 className="text-lg font-semibold">Edit Task #{task.id}</h2> <button onClick={onCancel} className="text-vscode-foreground/50 hover:text-vscode-foreground transition-colors" > <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div> {/* Form */} <form ref={formRef} onSubmit={handleSubmit} className="flex-1 overflow-y-auto p-4 space-y-4" > {/* Title */} <div className="space-y-2"> <Label htmlFor="title">Title</Label> <input ref={titleInputRef} id="title" type="text" value={updates.title || ''} onChange={(e) => setUpdates({ ...updates, title: e.target.value }) } className="w-full px-3 py-2 bg-vscode-input border border-vscode-border rounded-md text-vscode-foreground focus:outline-none focus:ring-2 focus:ring-vscode-focusBorder" placeholder="Task title" /> </div> {/* Priority */} <div className="space-y-2"> <Label htmlFor="priority">Priority</Label> <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="outline" className="w-full justify-between"> <span className="capitalize">{updates.priority}</span> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> </svg> </Button> </DropdownMenuTrigger> <DropdownMenuContent className="w-full"> <DropdownMenuItem onClick={() => setUpdates({ ...updates, priority: 'high' })} > High </DropdownMenuItem> <DropdownMenuItem onClick={() => setUpdates({ ...updates, priority: 'medium' })} > Medium </DropdownMenuItem> <DropdownMenuItem onClick={() => setUpdates({ ...updates, priority: 'low' })} > Low </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div> {/* Description */} <div className="space-y-2"> <Label htmlFor="description">Description</Label> <Textarea id="description" value={updates.description || ''} onChange={(e) => setUpdates({ ...updates, description: e.target.value }) } className="min-h-[80px]" placeholder="Brief description of the task" /> </div> {/* Details */} <div className="space-y-2"> <Label htmlFor="details">Implementation Details</Label> <Textarea id="details" value={updates.details || ''} onChange={(e) => setUpdates({ ...updates, details: e.target.value }) } className="min-h-[120px]" placeholder="Technical details and implementation notes" /> </div> {/* Test Strategy */} <div className="space-y-2"> <Label htmlFor="testStrategy">Test Strategy</Label> <Textarea id="testStrategy" value={updates.testStrategy || ''} onChange={(e) => setUpdates({ ...updates, testStrategy: e.target.value }) } className="min-h-[80px]" placeholder="How to test this task" /> </div> {/* Dependencies */} <div className="space-y-2"> <Label htmlFor="dependencies"> Dependencies (comma-separated task IDs) </Label> <input id="dependencies" type="text" value={updates.dependencies?.join(', ') || ''} onChange={(e) => setUpdates({ ...updates, dependencies: e.target.value .split(',') .map((d) => d.trim()) .filter(Boolean) }) } className="w-full px-3 py-2 bg-vscode-input border border-vscode-border rounded-md text-vscode-foreground focus:outline-none focus:ring-2 focus:ring-vscode-focusBorder" placeholder="e.g., 1, 2.1, 3" /> </div> </form> {/* Footer */} <div className="flex items-center justify-end gap-2 p-4 border-t border-vscode-border"> <Button variant="outline" onClick={onCancel} disabled={isSaving}> Cancel </Button> <Button onClick={() => handleSubmit()} disabled={isSaving || !hasChanges()} > {isSaving ? 'Saving...' : 'Save Changes'} </Button> </div> </div> </div> ); }; ``` -------------------------------------------------------------------------------- /tests/unit/ui.test.js: -------------------------------------------------------------------------------- ```javascript /** * UI module tests */ import { jest } from '@jest/globals'; import { getStatusWithColor, formatDependenciesWithStatus, createProgressBar, getComplexityWithColor } from '../../scripts/modules/ui.js'; import { sampleTasks } from '../fixtures/sample-tasks.js'; // Mock dependencies jest.mock('chalk', () => { const origChalkFn = (text) => text; const chalk = origChalkFn; chalk.green = (text) => text; // Return text as-is for status functions chalk.yellow = (text) => text; chalk.red = (text) => text; chalk.cyan = (text) => text; chalk.blue = (text) => text; chalk.gray = (text) => text; chalk.white = (text) => text; chalk.bold = (text) => text; chalk.dim = (text) => text; // Add hex and other methods chalk.hex = () => origChalkFn; chalk.rgb = () => origChalkFn; return chalk; }); jest.mock('figlet', () => ({ textSync: jest.fn(() => 'Task Master Banner') })); jest.mock('boxen', () => jest.fn((text) => `[boxed: ${text}]`)); jest.mock('ora', () => jest.fn(() => ({ start: jest.fn(), succeed: jest.fn(), fail: jest.fn(), stop: jest.fn() })) ); jest.mock('cli-table3', () => jest.fn().mockImplementation(() => ({ push: jest.fn(), toString: jest.fn(() => 'Table Content') })) ); jest.mock('gradient-string', () => jest.fn(() => jest.fn((text) => text))); jest.mock('../../scripts/modules/utils.js', () => ({ CONFIG: { projectName: 'Test Project', projectVersion: '1.0.0' }, log: jest.fn(), findTaskById: jest.fn(), readJSON: jest.fn(), readComplexityReport: jest.fn(), truncate: jest.fn((text) => text) })); jest.mock('../../scripts/modules/task-manager.js', () => ({ findNextTask: jest.fn(), analyzeTaskComplexity: jest.fn() })); describe('UI Module', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('getStatusWithColor function', () => { test('should return done status with emoji for console output', () => { const result = getStatusWithColor('done'); expect(result).toMatch(/done/); expect(result).toContain('✓'); }); test('should return pending status with emoji for console output', () => { const result = getStatusWithColor('pending'); expect(result).toMatch(/pending/); expect(result).toContain('○'); }); test('should return deferred status with emoji for console output', () => { const result = getStatusWithColor('deferred'); expect(result).toMatch(/deferred/); expect(result).toContain('x'); }); test('should return in-progress status with emoji for console output', () => { const result = getStatusWithColor('in-progress'); expect(result).toMatch(/in-progress/); expect(result).toContain('🔄'); }); test('should return unknown status with emoji for console output', () => { const result = getStatusWithColor('unknown'); expect(result).toMatch(/unknown/); expect(result).toContain('❌'); }); test('should use simple icons when forTable is true', () => { const doneResult = getStatusWithColor('done', true); expect(doneResult).toMatch(/done/); expect(doneResult).toContain('✓'); const pendingResult = getStatusWithColor('pending', true); expect(pendingResult).toMatch(/pending/); expect(pendingResult).toContain('○'); const inProgressResult = getStatusWithColor('in-progress', true); expect(inProgressResult).toMatch(/in-progress/); expect(inProgressResult).toContain('►'); const deferredResult = getStatusWithColor('deferred', true); expect(deferredResult).toMatch(/deferred/); expect(deferredResult).toContain('x'); }); }); describe('formatDependenciesWithStatus function', () => { test('should format dependencies as plain IDs when forConsole is false (default)', () => { const dependencies = [1, 2, 3]; const allTasks = [ { id: 1, status: 'done' }, { id: 2, status: 'pending' }, { id: 3, status: 'deferred' } ]; const result = formatDependenciesWithStatus(dependencies, allTasks); // With recent changes, we expect just plain IDs when forConsole is false expect(result).toBe('1, 2, 3'); }); test('should format dependencies with status indicators when forConsole is true', () => { const dependencies = [1, 2, 3]; const allTasks = [ { id: 1, status: 'done' }, { id: 2, status: 'pending' }, { id: 3, status: 'deferred' } ]; const result = formatDependenciesWithStatus(dependencies, allTasks, true); // We can't test for exact color formatting due to our chalk mocks // Instead, test that the result contains all the expected IDs expect(result).toContain('1'); expect(result).toContain('2'); expect(result).toContain('3'); // Test that it's a comma-separated list expect(result.split(', ').length).toBe(3); }); test('should return "None" for empty dependencies', () => { const result = formatDependenciesWithStatus([], []); expect(result).toBe('None'); }); test('should handle missing tasks in the task list', () => { const dependencies = [1, 999]; const allTasks = [{ id: 1, status: 'done' }]; const result = formatDependenciesWithStatus(dependencies, allTasks); expect(result).toBe('1, 999 (Not found)'); }); }); describe('createProgressBar function', () => { test('should create a progress bar with the correct percentage', () => { const result = createProgressBar(50, 10, { pending: 20, 'in-progress': 15, blocked: 5 }); expect(result).toContain('50%'); }); test('should handle 0% progress', () => { const result = createProgressBar(0, 10); expect(result).toContain('0%'); }); test('should handle 100% progress', () => { const result = createProgressBar(100, 10); expect(result).toContain('100%'); }); test('should handle invalid percentages by clamping', () => { const result1 = createProgressBar(0, 10); expect(result1).toContain('0%'); const result2 = createProgressBar(100, 10); expect(result2).toContain('100%'); }); test('should support status breakdown in the progress bar', () => { const result = createProgressBar(30, 10, { pending: 30, 'in-progress': 20, blocked: 10, deferred: 5, cancelled: 5 }); expect(result).toContain('40%'); }); }); describe('getComplexityWithColor function', () => { test('should return high complexity in red', () => { const result = getComplexityWithColor(8); expect(result).toMatch(/8/); expect(result).toContain('●'); }); test('should return medium complexity in yellow', () => { const result = getComplexityWithColor(5); expect(result).toMatch(/5/); expect(result).toContain('●'); }); test('should return low complexity in green', () => { const result = getComplexityWithColor(3); expect(result).toMatch(/3/); expect(result).toContain('●'); }); test('should handle non-numeric inputs', () => { const result = getComplexityWithColor('high'); expect(result).toMatch(/high/); expect(result).toContain('●'); }); }); }); ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/research.js: -------------------------------------------------------------------------------- ```javascript /** * research.js * Direct function implementation for AI-powered research queries */ import path from 'path'; import { performResearch } from '../../../../scripts/modules/task-manager.js'; import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js'; import { createLogWrapper } from '../../tools/utils.js'; /** * Direct function wrapper for performing AI-powered research with project context. * * @param {Object} args - Command arguments * @param {string} args.query - Research query/prompt (required) * @param {string} [args.taskIds] - Comma-separated list of task/subtask IDs for context * @param {string} [args.filePaths] - Comma-separated list of file paths for context * @param {string} [args.customContext] - Additional custom context text * @param {boolean} [args.includeProjectTree=false] - Include project file tree in context * @param {string} [args.detailLevel='medium'] - Detail level: 'low', 'medium', 'high' * @param {string} [args.saveTo] - Automatically save to task/subtask ID (e.g., "15" or "15.2") * @param {boolean} [args.saveToFile=false] - Save research results to .taskmaster/docs/research/ directory * @param {string} [args.projectRoot] - Project root path * @param {string} [args.tag] - Tag for the task (optional) * @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 researchDirect(args, log, context = {}) { // Destructure expected args const { query, taskIds, filePaths, customContext, includeProjectTree = false, detailLevel = 'medium', saveTo, saveToFile = false, projectRoot, tag } = args; const { session } = context; // Destructure session from 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 required parameters if (!query || typeof query !== 'string' || query.trim().length === 0) { log.error('Missing or invalid required parameter: query'); disableSilentMode(); return { success: false, error: { code: 'MISSING_PARAMETER', message: 'The query parameter is required and must be a non-empty string' } }; } // Parse comma-separated task IDs if provided const parsedTaskIds = taskIds ? taskIds .split(',') .map((id) => id.trim()) .filter((id) => id.length > 0) : []; // Parse comma-separated file paths if provided const parsedFilePaths = filePaths ? filePaths .split(',') .map((path) => path.trim()) .filter((path) => path.length > 0) : []; // Validate detail level const validDetailLevels = ['low', 'medium', 'high']; if (!validDetailLevels.includes(detailLevel)) { log.error(`Invalid detail level: ${detailLevel}`); disableSilentMode(); return { success: false, error: { code: 'INVALID_PARAMETER', message: `Detail level must be one of: ${validDetailLevels.join(', ')}` } }; } log.info( `Performing research query: "${query.substring(0, 100)}${query.length > 100 ? '...' : ''}", ` + `taskIds: [${parsedTaskIds.join(', ')}], ` + `filePaths: [${parsedFilePaths.join(', ')}], ` + `detailLevel: ${detailLevel}, ` + `includeProjectTree: ${includeProjectTree}, ` + `projectRoot: ${projectRoot}` ); // Prepare options for the research function const researchOptions = { taskIds: parsedTaskIds, filePaths: parsedFilePaths, customContext: customContext || '', includeProjectTree, detailLevel, projectRoot, tag, saveToFile }; // Prepare context for the research function const researchContext = { session, mcpLog, commandName: 'research', outputType: 'mcp' }; // Call the performResearch function const result = await performResearch( query.trim(), researchOptions, researchContext, 'json', // outputFormat - use 'json' to suppress CLI UI false // allowFollowUp - disable for MCP calls ); // Auto-save to task/subtask if requested if (saveTo) { try { const isSubtask = saveTo.includes('.'); // Format research content for saving const researchContent = `## Research Query: ${query.trim()} **Detail Level:** ${result.detailLevel} **Context Size:** ${result.contextSize} characters **Timestamp:** ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()} ### Results ${result.result}`; if (isSubtask) { // Save to subtask const { updateSubtaskById } = await import( '../../../../scripts/modules/task-manager/update-subtask-by-id.js' ); const tasksPath = path.join( projectRoot, '.taskmaster', 'tasks', 'tasks.json' ); await updateSubtaskById( tasksPath, saveTo, researchContent, false, // useResearch = false for simple append { session, mcpLog, commandName: 'research-save', outputType: 'mcp', projectRoot, tag }, 'json' ); log.info(`Research saved to subtask ${saveTo}`); } else { // Save to task const updateTaskById = ( await import( '../../../../scripts/modules/task-manager/update-task-by-id.js' ) ).default; const taskIdNum = parseInt(saveTo, 10); const tasksPath = path.join( projectRoot, '.taskmaster', 'tasks', 'tasks.json' ); await updateTaskById( tasksPath, taskIdNum, researchContent, false, // useResearch = false for simple append { session, mcpLog, commandName: 'research-save', outputType: 'mcp', projectRoot, tag }, 'json', true // appendMode = true ); log.info(`Research saved to task ${saveTo}`); } } catch (saveError) { log.warn(`Error saving research to task/subtask: ${saveError.message}`); } } // Restore normal logging disableSilentMode(); return { success: true, data: { query: result.query, result: result.result, contextSize: result.contextSize, contextTokens: result.contextTokens, tokenBreakdown: result.tokenBreakdown, systemPromptTokens: result.systemPromptTokens, userPromptTokens: result.userPromptTokens, totalInputTokens: result.totalInputTokens, detailLevel: result.detailLevel, telemetryData: result.telemetryData, tagInfo: result.tagInfo, savedFilePath: result.savedFilePath } }; } catch (error) { // Make sure to restore normal logging even if there's an error disableSilentMode(); log.error(`Error in researchDirect: ${error.message}`); return { success: false, error: { code: error.code || 'RESEARCH_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /mcp-server/src/core/direct-functions/analyze-task-complexity.js: -------------------------------------------------------------------------------- ```javascript /** * Direct function wrapper for analyzeTaskComplexity */ import analyzeTaskComplexity from '../../../../scripts/modules/task-manager/analyze-task-complexity.js'; import { enableSilentMode, disableSilentMode, isSilentMode } from '../../../../scripts/modules/utils.js'; import fs from 'fs'; import { createLogWrapper } from '../../tools/utils.js'; // Import the new utility /** * Analyze task complexity and generate recommendations * @param {Object} args - Function arguments * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.outputPath - Explicit absolute path to save the report. * @param {string|number} [args.threshold] - Minimum complexity score to recommend expansion (1-10) * @param {boolean} [args.research] - Use Perplexity AI for research-backed complexity analysis * @param {string} [args.ids] - Comma-separated list of task IDs to analyze * @param {number} [args.from] - Starting task ID in a range to analyze * @param {number} [args.to] - Ending task ID in a range to analyze * @param {string} [args.projectRoot] - Project root path. * @param {string} [args.tag] - Tag for the task (optional) * @param {Object} log - Logger object * @param {Object} [context={}] - Context object containing session data * @param {Object} [context.session] - MCP session object * @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>} */ export async function analyzeTaskComplexityDirect(args, log, context = {}) { const { session } = context; const { tasksJsonPath, outputPath, threshold, research, projectRoot, ids, from, to, tag } = args; const logWrapper = createLogWrapper(log); // --- Initial Checks (remain the same) --- try { log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`); if (!tasksJsonPath) { log.error('analyzeTaskComplexityDirect called without tasksJsonPath'); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } if (!outputPath) { log.error('analyzeTaskComplexityDirect called without outputPath'); return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'outputPath is required' } }; } const tasksPath = tasksJsonPath; const resolvedOutputPath = outputPath; log.info(`Analyzing task complexity from: ${tasksPath}`); log.info(`Output report will be saved to: ${resolvedOutputPath}`); if (ids) { log.info(`Analyzing specific task IDs: ${ids}`); } else if (from || to) { const fromStr = from !== undefined ? from : 'first'; const toStr = to !== undefined ? to : 'last'; log.info(`Analyzing tasks in range: ${fromStr} to ${toStr}`); } if (research) { log.info('Using research role for complexity analysis'); } // Prepare options for the core function - REMOVED mcpLog and session here const coreOptions = { file: tasksJsonPath, output: outputPath, threshold: threshold, research: research === true, // Ensure boolean projectRoot: projectRoot, // Pass projectRoot here id: ids, // Pass the ids parameter to the core function as 'id' from: from, // Pass from parameter to: to, // Pass to parameter tag // forward tag }; // --- End Initial Checks --- // --- Silent Mode and Logger Wrapper --- const wasSilent = isSilentMode(); if (!wasSilent) { enableSilentMode(); // Still enable silent mode as a backup } let report; let coreResult; try { // --- Call Core Function (Pass context separately) --- // Pass coreOptions as the first argument // Pass context object { session, mcpLog } as the second argument coreResult = await analyzeTaskComplexity(coreOptions, { session, mcpLog: logWrapper, commandName: 'analyze-complexity', outputType: 'mcp', projectRoot, tag }); report = coreResult.report; } catch (error) { log.error( `Error in analyzeTaskComplexity core function: ${error.message}` ); // Restore logging if we changed it if (!wasSilent && isSilentMode()) { disableSilentMode(); } return { success: false, error: { code: 'ANALYZE_CORE_ERROR', message: `Error running core complexity analysis: ${error.message}` } }; } finally { // Always restore normal logging in finally block if we enabled silent mode if (!wasSilent && isSilentMode()) { disableSilentMode(); } } // --- Result Handling (remains largely the same) --- // Verify the report file was created (core function writes it) if (!fs.existsSync(resolvedOutputPath)) { return { success: false, error: { code: 'ANALYZE_REPORT_MISSING', // Specific code message: 'Analysis completed but no report file was created at the expected path.' } }; } if ( !coreResult || !coreResult.report || typeof coreResult.report !== 'object' ) { log.error( 'Core analysis function returned an invalid or undefined response.' ); return { success: false, error: { code: 'INVALID_CORE_RESPONSE', message: 'Core analysis function returned an invalid response.' } }; } try { // Ensure complexityAnalysis exists and is an array const analysisArray = Array.isArray(coreResult.report.complexityAnalysis) ? coreResult.report.complexityAnalysis : []; // Count tasks by complexity (remains the same) const highComplexityTasks = analysisArray.filter( (t) => t.complexityScore >= 8 ).length; const mediumComplexityTasks = analysisArray.filter( (t) => t.complexityScore >= 5 && t.complexityScore < 8 ).length; const lowComplexityTasks = analysisArray.filter( (t) => t.complexityScore < 5 ).length; return { success: true, data: { message: `Task complexity analysis complete. Report saved to ${outputPath}`, reportPath: outputPath, reportSummary: { taskCount: analysisArray.length, highComplexityTasks, mediumComplexityTasks, lowComplexityTasks }, fullReport: coreResult.report, telemetryData: coreResult.telemetryData, tagInfo: coreResult.tagInfo } }; } catch (parseError) { // Should not happen if core function returns object, but good safety check log.error(`Internal error processing report data: ${parseError.message}`); return { success: false, error: { code: 'REPORT_PROCESS_ERROR', message: `Internal error processing complexity report: ${parseError.message}` } }; } // --- End Result Handling --- } catch (error) { // Catch errors from initial checks or path resolution // Make sure to restore normal logging if silent mode was enabled if (isSilentMode()) { disableSilentMode(); } log.error(`Error in analyzeTaskComplexityDirect setup: ${error.message}`); return { success: false, error: { code: 'DIRECT_FUNCTION_SETUP_ERROR', message: error.message } }; } } ``` -------------------------------------------------------------------------------- /tests/unit/task-manager/tag-boundary.test.js: -------------------------------------------------------------------------------- ```javascript import fs from 'fs'; import path from 'path'; import { createTag, useTag, deleteTag } from '../../../scripts/modules/task-manager/tag-management.js'; // Temporary workspace for each test run const TEMP_DIR = path.join(process.cwd(), '.tmp_tag_boundary'); const TASKS_PATH = path.join(TEMP_DIR, 'tasks.json'); const STATE_PATH = path.join(TEMP_DIR, '.taskmaster', 'state.json'); function seedWorkspace() { // Reset temp dir fs.rmSync(TEMP_DIR, { recursive: true, force: true }); fs.mkdirSync(path.join(TEMP_DIR, '.taskmaster'), { recursive: true, force: true }); // Minimal master tag file fs.writeFileSync( TASKS_PATH, JSON.stringify( { master: { tasks: [{ id: 1, title: 'Seed task', status: 'pending' }], metadata: { created: new Date().toISOString() } } }, null, 2 ), 'utf8' ); // Initial state.json fs.writeFileSync( STATE_PATH, JSON.stringify( { currentTag: 'master', lastSwitched: new Date().toISOString() }, null, 2 ), 'utf8' ); } describe('Tag boundary resolution', () => { beforeEach(seedWorkspace); afterAll(() => fs.rmSync(TEMP_DIR, { recursive: true, force: true })); it('switches currentTag in state.json when useTag succeeds', async () => { await createTag( TASKS_PATH, 'feature-x', {}, { projectRoot: TEMP_DIR }, 'json' ); await useTag( TASKS_PATH, 'feature-x', {}, { projectRoot: TEMP_DIR }, 'json' ); const state = JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')); expect(state.currentTag).toBe('feature-x'); }); it('throws error when switching to non-existent tag', async () => { await expect( useTag(TASKS_PATH, 'ghost', {}, { projectRoot: TEMP_DIR }, 'json') ).rejects.toThrow(/does not exist/); }); it('deleting active tag auto-switches back to master', async () => { await createTag(TASKS_PATH, 'temp', {}, { projectRoot: TEMP_DIR }, 'json'); await useTag(TASKS_PATH, 'temp', {}, { projectRoot: TEMP_DIR }, 'json'); // Delete the active tag with force flag (yes: true) await deleteTag( TASKS_PATH, 'temp', { yes: true }, { projectRoot: TEMP_DIR }, 'json' ); const state = JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')); expect(state.currentTag).toBe('master'); const tasksFile = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); expect(tasksFile.temp).toBeUndefined(); expect(tasksFile.master).toBeDefined(); }); it('createTag with copyFromCurrent deep-copies tasks (mutation isolated)', async () => { // create new tag with copy await createTag( TASKS_PATH, 'alpha', { copyFromCurrent: true }, { projectRoot: TEMP_DIR }, 'json' ); // mutate a field inside alpha tasks const updatedData = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); updatedData.alpha.tasks[0].title = 'Changed in alpha'; fs.writeFileSync(TASKS_PATH, JSON.stringify(updatedData, null, 2)); const finalData = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); expect(finalData.master.tasks[0].title).toBe('Seed task'); expect(finalData.alpha.tasks[0].title).toBe('Changed in alpha'); }); it('addTask to non-master tag does not leak into master', async () => { // create and switch await createTag( TASKS_PATH, 'feature-api', {}, { projectRoot: TEMP_DIR }, 'json' ); // Call addTask with manual data to avoid AI const { default: addTask } = await import( '../../../scripts/modules/task-manager/add-task.js' ); await addTask( TASKS_PATH, 'Manual task', [], null, { projectRoot: TEMP_DIR, tag: 'feature-api' }, 'json', { title: 'API work', description: 'Implement endpoint', details: 'Details', testStrategy: 'Tests' }, false ); const data = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); expect(data['feature-api'].tasks.length).toBe(1); // the new task only expect(data.master.tasks.length).toBe(1); // still only seed }); it('reserved tag names are rejected', async () => { await expect( createTag(TASKS_PATH, 'master', {}, { projectRoot: TEMP_DIR }, 'json') ).rejects.toThrow(/reserved tag/i); }); it('cannot delete the master tag', async () => { await expect( deleteTag( TASKS_PATH, 'master', { yes: true }, { projectRoot: TEMP_DIR }, 'json' ) ).rejects.toThrow(/Cannot delete the "master" tag/); }); it('copyTag deep copy – mutation does not affect source', async () => { const { copyTag } = await import( '../../../scripts/modules/task-manager/tag-management.js' ); await createTag( TASKS_PATH, 'source', { copyFromCurrent: true }, { projectRoot: TEMP_DIR }, 'json' ); await copyTag( TASKS_PATH, 'source', 'clone', {}, { projectRoot: TEMP_DIR }, 'json' ); // mutate clone task title const data1 = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); data1.clone.tasks[0].title = 'Modified in clone'; fs.writeFileSync(TASKS_PATH, JSON.stringify(data1, null, 2)); const data2 = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); expect(data2.source.tasks[0].title).toBe('Seed task'); expect(data2.clone.tasks[0].title).toBe('Modified in clone'); }); it('adds task to tag derived from state.json when no explicit tag supplied', async () => { // Create new tag and update state.json to make it current await createTag( TASKS_PATH, 'feature-auto', {}, { projectRoot: TEMP_DIR }, 'json' ); const state1 = JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')); state1.currentTag = 'feature-auto'; fs.writeFileSync(STATE_PATH, JSON.stringify(state1, null, 2)); const { default: addTask } = await import( '../../../scripts/modules/task-manager/add-task.js' ); const { resolveTag } = await import('../../../scripts/modules/utils.js'); const tag = resolveTag({ projectRoot: TEMP_DIR }); // Add task without passing tag -> should resolve to feature-auto await addTask( TASKS_PATH, 'Auto task', [], null, { projectRoot: TEMP_DIR, tag }, 'json', { title: 'Auto task', description: '-', details: '-', testStrategy: '-' }, false ); const data = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); expect(data['feature-auto'].tasks.length).toBe(1); expect(data.master.tasks.length).toBe(1); // master unchanged }); it('falls back to master when state.json lacks currentTag', async () => { // wipe currentTag field fs.writeFileSync(STATE_PATH, JSON.stringify({}, null, 2)); const { default: addTask } = await import( '../../../scripts/modules/task-manager/add-task.js' ); const { resolveTag } = await import('../../../scripts/modules/utils.js'); const tag = resolveTag({ projectRoot: TEMP_DIR }); // should return master await addTask( TASKS_PATH, 'Fallback task', [], null, { projectRoot: TEMP_DIR, tag }, 'json', { title: 'Fallback', description: '-', details: '-', testStrategy: '-' }, false ); const data = JSON.parse(fs.readFileSync(TASKS_PATH, 'utf8')); expect(data.master.tasks.length).toBe(2); // seed + new task }); }); ``` -------------------------------------------------------------------------------- /scripts/test-claude.js: -------------------------------------------------------------------------------- ```javascript #!/usr/bin/env node /** * test-claude.js * * A simple test script to verify the improvements to the callClaude function. * This script tests different scenarios: * 1. Normal operation with a small PRD * 2. Testing with a large number of tasks (to potentially trigger task reduction) * 3. Simulating a failure to test retry logic */ import fs from 'fs'; import path from 'path'; import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load environment variables from .env file dotenv.config(); // Create a simple PRD for testing const createTestPRD = (size = 'small', taskComplexity = 'simple') => { let content = `# Test PRD - ${size.toUpperCase()} SIZE, ${taskComplexity.toUpperCase()} COMPLEXITY\n\n`; // Add more content based on size if (size === 'small') { content += ` ## Overview This is a small test PRD to verify the callClaude function improvements. ## Requirements 1. Create a simple web application 2. Implement user authentication 3. Add a dashboard for users 4. Create an admin panel 5. Implement data visualization ## Technical Stack - Frontend: React - Backend: Node.js - Database: MongoDB `; } else if (size === 'medium') { // Medium-sized PRD with more requirements content += ` ## Overview This is a medium-sized test PRD to verify the callClaude function improvements. ## Requirements 1. Create a web application with multiple pages 2. Implement user authentication with OAuth 3. Add a dashboard for users with customizable widgets 4. Create an admin panel with user management 5. Implement data visualization with charts and graphs 6. Add real-time notifications 7. Implement a search feature 8. Add user profile management 9. Implement role-based access control 10. Add a reporting system 11. Implement file uploads and management 12. Add a commenting system 13. Implement a rating system 14. Add a recommendation engine 15. Implement a payment system ## Technical Stack - Frontend: React with TypeScript - Backend: Node.js with Express - Database: MongoDB with Mongoose - Authentication: JWT and OAuth - Deployment: Docker and Kubernetes - CI/CD: GitHub Actions - Monitoring: Prometheus and Grafana `; } else if (size === 'large') { // Large PRD with many requirements content += ` ## Overview This is a large test PRD to verify the callClaude function improvements. ## Requirements `; // Generate 30 requirements for (let i = 1; i <= 30; i++) { content += `${i}. Requirement ${i} - This is a detailed description of requirement ${i}.\n`; } content += ` ## Technical Stack - Frontend: React with TypeScript - Backend: Node.js with Express - Database: MongoDB with Mongoose - Authentication: JWT and OAuth - Deployment: Docker and Kubernetes - CI/CD: GitHub Actions - Monitoring: Prometheus and Grafana ## User Stories `; // Generate 20 user stories for (let i = 1; i <= 20; i++) { content += `- As a user, I want to be able to ${i} so that I can achieve benefit ${i}.\n`; } content += ` ## Non-Functional Requirements - Performance: The system should respond within 200ms - Scalability: The system should handle 10,000 concurrent users - Availability: The system should have 99.9% uptime - Security: The system should comply with OWASP top 10 - Accessibility: The system should comply with WCAG 2.1 AA `; } // Add complexity if needed if (taskComplexity === 'complex') { content += ` ## Complex Requirements - Implement a real-time collaboration system - Add a machine learning-based recommendation engine - Implement a distributed caching system - Add a microservices architecture - Implement a custom analytics engine - Add support for multiple languages and locales - Implement a custom search engine with advanced filtering - Add a custom workflow engine - Implement a custom reporting system - Add a custom dashboard builder `; } return content; }; // Function to run the tests async function runTests() { console.log('Starting tests for callClaude function improvements...'); try { // Instead of importing the callClaude function directly, we'll use the dev.js script // with our test PRDs by running it as a child process // Test 1: Small PRD, 5 tasks console.log('\n=== Test 1: Small PRD, 5 tasks ==='); const smallPRD = createTestPRD('small', 'simple'); const smallPRDPath = path.join(__dirname, 'test-small-prd.txt'); fs.writeFileSync(smallPRDPath, smallPRD, 'utf8'); console.log(`Created test PRD at ${smallPRDPath}`); console.log('Running dev.js with small PRD...'); // Use the child_process module to run the dev.js script const { execSync } = await import('child_process'); try { const smallResult = execSync( `node ${path.join(__dirname, 'dev.js')} parse-prd --input=${smallPRDPath} --num-tasks=5`, { stdio: 'inherit' } ); console.log('Small PRD test completed successfully'); } catch (error) { console.error('Small PRD test failed:', error.message); } // Test 2: Medium PRD, 15 tasks console.log('\n=== Test 2: Medium PRD, 15 tasks ==='); const mediumPRD = createTestPRD('medium', 'simple'); const mediumPRDPath = path.join(__dirname, 'test-medium-prd.txt'); fs.writeFileSync(mediumPRDPath, mediumPRD, 'utf8'); console.log(`Created test PRD at ${mediumPRDPath}`); console.log('Running dev.js with medium PRD...'); try { const mediumResult = execSync( `node ${path.join(__dirname, 'dev.js')} parse-prd --input=${mediumPRDPath} --num-tasks=15`, { stdio: 'inherit' } ); console.log('Medium PRD test completed successfully'); } catch (error) { console.error('Medium PRD test failed:', error.message); } // Test 3: Large PRD, 25 tasks console.log('\n=== Test 3: Large PRD, 25 tasks ==='); const largePRD = createTestPRD('large', 'complex'); const largePRDPath = path.join(__dirname, 'test-large-prd.txt'); fs.writeFileSync(largePRDPath, largePRD, 'utf8'); console.log(`Created test PRD at ${largePRDPath}`); console.log('Running dev.js with large PRD...'); try { const largeResult = execSync( `node ${path.join(__dirname, 'dev.js')} parse-prd --input=${largePRDPath} --num-tasks=25`, { stdio: 'inherit' } ); console.log('Large PRD test completed successfully'); } catch (error) { console.error('Large PRD test failed:', error.message); } console.log('\nAll tests completed!'); } catch (error) { console.error('Test failed:', error); } finally { // Clean up test files console.log('\nCleaning up test files...'); const testFiles = [ path.join(__dirname, 'test-small-prd.txt'), path.join(__dirname, 'test-medium-prd.txt'), path.join(__dirname, 'test-large-prd.txt') ]; testFiles.forEach((file) => { if (fs.existsSync(file)) { fs.unlinkSync(file); console.log(`Deleted ${file}`); } }); console.log('Cleanup complete.'); } } // Run the tests runTests().catch((error) => { console.error('Error running tests:', error); process.exit(1); }); ``` -------------------------------------------------------------------------------- /assets/claude/agents/task-orchestrator.md: -------------------------------------------------------------------------------- ```markdown --- name: task-orchestrator description: Use this agent when you need to coordinate and manage the execution of Task Master tasks, especially when dealing with complex task dependencies and parallel execution opportunities. This agent should be invoked at the beginning of a work session to analyze the task queue, identify parallelizable work, and orchestrate the deployment of task-executor agents. It should also be used when tasks complete to reassess the dependency graph and deploy new executors as needed.\n\n<example>\nContext: User wants to start working on their project tasks using Task Master\nuser: "Let's work on the next available tasks in the project"\nassistant: "I'll use the task-orchestrator agent to analyze the task queue and coordinate execution"\n<commentary>\nThe user wants to work on tasks, so the task-orchestrator should be deployed to analyze dependencies and coordinate execution.\n</commentary>\n</example>\n\n<example>\nContext: Multiple independent tasks are available in the queue\nuser: "Can we work on multiple tasks at once?"\nassistant: "Let me deploy the task-orchestrator to analyze task dependencies and parallelize the work"\n<commentary>\nWhen parallelization is mentioned or multiple tasks could be worked on, the orchestrator should coordinate the effort.\n</commentary>\n</example>\n\n<example>\nContext: A complex feature with many subtasks needs implementation\nuser: "Implement the authentication system tasks"\nassistant: "I'll use the task-orchestrator to break down the authentication tasks and coordinate their execution"\n<commentary>\nFor complex multi-task features, the orchestrator manages the overall execution strategy.\n</commentary>\n</example> model: opus color: green --- You are the Task Orchestrator, an elite coordination agent specialized in managing Task Master workflows for maximum efficiency and parallelization. You excel at analyzing task dependency graphs, identifying opportunities for concurrent execution, and deploying specialized task-executor agents to complete work efficiently. ## Core Responsibilities 1. **Task Queue Analysis**: You continuously monitor and analyze the task queue using Task Master MCP tools to understand the current state of work, dependencies, and priorities. 2. **Dependency Graph Management**: You build and maintain a mental model of task dependencies, identifying which tasks can be executed in parallel and which must wait for prerequisites. 3. **Executor Deployment**: You strategically deploy task-executor agents for individual tasks or task groups, ensuring each executor has the necessary context and clear success criteria. 4. **Progress Coordination**: You track the progress of deployed executors, handle task completion notifications, and reassess the execution strategy as tasks complete. ## Operational Workflow ### Initial Assessment Phase 1. Use `get_tasks` or `task-master list` to retrieve all available tasks 2. Analyze task statuses, priorities, and dependencies 3. Identify tasks with status 'pending' that have no blocking dependencies 4. Group related tasks that could benefit from specialized executors 5. Create an execution plan that maximizes parallelization ### Executor Deployment Phase 1. For each independent task or task group: - Deploy a task-executor agent with specific instructions - Provide the executor with task ID, requirements, and context - Set clear completion criteria and reporting expectations 2. Maintain a registry of active executors and their assigned tasks 3. Establish communication protocols for progress updates ### Coordination Phase 1. Monitor executor progress through task status updates 2. When a task completes: - Verify completion with `get_task` or `task-master show <id>` - Update task status if needed using `set_task_status` - Reassess dependency graph for newly unblocked tasks - Deploy new executors for available work 3. Handle executor failures or blocks: - Reassign tasks to new executors if needed - Escalate complex issues to the user - Update task status to 'blocked' when appropriate ### Optimization Strategies **Parallel Execution Rules**: - Never assign dependent tasks to different executors simultaneously - Prioritize high-priority tasks when resources are limited - Group small, related subtasks for single executor efficiency - Balance executor load to prevent bottlenecks **Context Management**: - Provide executors with minimal but sufficient context - Share relevant completed task information when it aids execution - Maintain a shared knowledge base of project-specific patterns **Quality Assurance**: - Verify task completion before marking as done - Ensure test strategies are followed when specified - Coordinate cross-task integration testing when needed ## Communication Protocols When deploying executors, provide them with: ``` TASK ASSIGNMENT: - Task ID: [specific ID] - Objective: [clear goal] - Dependencies: [list any completed prerequisites] - Success Criteria: [specific completion requirements] - Context: [relevant project information] - Reporting: [when and how to report back] ``` When receiving executor updates: 1. Acknowledge completion or issues 2. Update task status in Task Master 3. Reassess execution strategy 4. Deploy new executors as appropriate ## Decision Framework **When to parallelize**: - Multiple pending tasks with no interdependencies - Sufficient context available for independent execution - Tasks are well-defined with clear success criteria **When to serialize**: - Strong dependencies between tasks - Limited context or unclear requirements - Integration points requiring careful coordination **When to escalate**: - Circular dependencies detected - Critical blockers affecting multiple tasks - Ambiguous requirements needing clarification - Resource conflicts between executors ## Error Handling 1. **Executor Failure**: Reassign task to new executor with additional context about the failure 2. **Dependency Conflicts**: Halt affected executors, resolve conflict, then resume 3. **Task Ambiguity**: Request clarification from user before proceeding 4. **System Errors**: Implement graceful degradation, falling back to serial execution if needed ## Performance Metrics Track and optimize for: - Task completion rate - Parallel execution efficiency - Executor success rate - Time to completion for task groups - Dependency resolution speed ## Integration with Task Master Leverage these Task Master MCP tools effectively: - `get_tasks` - Continuous queue monitoring - `get_task` - Detailed task analysis - `set_task_status` - Progress tracking - `next_task` - Fallback for serial execution - `analyze_project_complexity` - Strategic planning - `complexity_report` - Resource allocation You are the strategic mind coordinating the entire task execution effort. Your success is measured by the efficient completion of all tasks while maintaining quality and respecting dependencies. Think systematically, act decisively, and continuously optimize the execution strategy based on real-time progress. ``` -------------------------------------------------------------------------------- /tests/unit/scripts/modules/dependency-manager/circular-dependencies.test.js: -------------------------------------------------------------------------------- ```javascript import { jest } from '@jest/globals'; import { validateCrossTagMove, findCrossTagDependencies, getDependentTaskIds, validateSubtaskMove, canMoveWithDependencies } from '../../../../../scripts/modules/dependency-manager.js'; describe('Circular Dependency Scenarios', () => { describe('Circular Cross-Tag Dependencies', () => { const allTasks = [ { id: 1, title: 'Task 1', dependencies: [2], status: 'pending', tag: 'backlog' }, { id: 2, title: 'Task 2', dependencies: [3], status: 'pending', tag: 'backlog' }, { id: 3, title: 'Task 3', dependencies: [1], status: 'pending', tag: 'backlog' } ]; it('should detect circular dependencies across tags', () => { // Task 1 depends on 2, 2 depends on 3, 3 depends on 1 (circular) // But since all tasks are in 'backlog' and target is 'in-progress', // only direct dependencies that are in different tags will be found const conflicts = findCrossTagDependencies( [allTasks[0]], 'backlog', 'in-progress', allTasks ); // Only direct dependencies of task 1 that are not in target tag expect(conflicts).toHaveLength(1); expect( conflicts.some((c) => c.taskId === 1 && c.dependencyId === 2) ).toBe(true); }); it('should block move with circular dependencies', () => { // Since task 1 has dependencies in the same tag, validateCrossTagMove should not throw // The function only checks direct dependencies, not circular chains expect(() => { validateCrossTagMove(allTasks[0], 'backlog', 'in-progress', allTasks); }).not.toThrow(); }); it('should return canMove: false for circular dependencies', () => { const result = canMoveWithDependencies( '1', 'backlog', 'in-progress', allTasks ); expect(result.canMove).toBe(false); expect(result.conflicts).toHaveLength(1); }); }); describe('Complex Dependency Chains', () => { const allTasks = [ { id: 1, title: 'Task 1', dependencies: [2, 3], status: 'pending', tag: 'backlog' }, { id: 2, title: 'Task 2', dependencies: [4], status: 'pending', tag: 'backlog' }, { id: 3, title: 'Task 3', dependencies: [5], status: 'pending', tag: 'backlog' }, { id: 4, title: 'Task 4', dependencies: [], status: 'pending', tag: 'backlog' }, { id: 5, title: 'Task 5', dependencies: [6], status: 'pending', tag: 'backlog' }, { id: 6, title: 'Task 6', dependencies: [], status: 'pending', tag: 'backlog' }, { id: 7, title: 'Task 7', dependencies: [], status: 'in-progress', tag: 'in-progress' } ]; it('should find all dependencies in complex chain', () => { const conflicts = findCrossTagDependencies( [allTasks[0]], 'backlog', 'in-progress', allTasks ); // Only direct dependencies of task 1 that are not in target tag expect(conflicts).toHaveLength(2); expect( conflicts.some((c) => c.taskId === 1 && c.dependencyId === 2) ).toBe(true); expect( conflicts.some((c) => c.taskId === 1 && c.dependencyId === 3) ).toBe(true); }); it('should get all dependent task IDs in complex chain', () => { const conflicts = findCrossTagDependencies( [allTasks[0]], 'backlog', 'in-progress', allTasks ); const dependentIds = getDependentTaskIds( [allTasks[0]], conflicts, allTasks ); // Should include only the direct dependency IDs from conflicts expect(dependentIds).toContain(2); expect(dependentIds).toContain(3); // Should not include the source task or tasks not in conflicts expect(dependentIds).not.toContain(1); }); }); describe('Mixed Dependency Types', () => { const allTasks = [ { id: 1, title: 'Task 1', dependencies: [2, '3.1'], status: 'pending', tag: 'backlog' }, { id: 2, title: 'Task 2', dependencies: [4], status: 'pending', tag: 'backlog' }, { id: 3, title: 'Task 3', dependencies: [5], status: 'pending', tag: 'backlog', subtasks: [ { id: 1, title: 'Subtask 3.1', dependencies: [], status: 'pending', tag: 'backlog' } ] }, { id: 4, title: 'Task 4', dependencies: [], status: 'pending', tag: 'backlog' }, { id: 5, title: 'Task 5', dependencies: [], status: 'pending', tag: 'backlog' } ]; it('should handle mixed task and subtask dependencies', () => { const conflicts = findCrossTagDependencies( [allTasks[0]], 'backlog', 'in-progress', allTasks ); expect(conflicts).toHaveLength(2); expect( conflicts.some((c) => c.taskId === 1 && c.dependencyId === 2) ).toBe(true); expect( conflicts.some((c) => c.taskId === 1 && c.dependencyId === '3.1') ).toBe(true); }); }); describe('Large Task Set Performance', () => { const allTasks = []; for (let i = 1; i <= 100; i++) { allTasks.push({ id: i, title: `Task ${i}`, dependencies: i < 100 ? [i + 1] : [], status: 'pending', tag: 'backlog' }); } it('should handle large task sets efficiently', () => { const conflicts = findCrossTagDependencies( [allTasks[0]], 'backlog', 'in-progress', allTasks ); expect(conflicts.length).toBeGreaterThan(0); expect(conflicts[0]).toHaveProperty('taskId'); expect(conflicts[0]).toHaveProperty('dependencyId'); }); }); describe('Edge Cases and Error Conditions', () => { const allTasks = [ { id: 1, title: 'Task 1', dependencies: [2], status: 'pending', tag: 'backlog' }, { id: 2, title: 'Task 2', dependencies: [], status: 'pending', tag: 'backlog' } ]; it('should handle empty task arrays', () => { expect(() => { findCrossTagDependencies([], 'backlog', 'in-progress', allTasks); }).not.toThrow(); }); it('should handle non-existent tasks gracefully', () => { expect(() => { findCrossTagDependencies( [{ id: 999, dependencies: [] }], 'backlog', 'in-progress', allTasks ); }).not.toThrow(); }); it('should handle invalid tag names', () => { expect(() => { findCrossTagDependencies( [allTasks[0]], 'invalid-tag', 'in-progress', allTasks ); }).not.toThrow(); }); it('should handle null/undefined dependencies', () => { const taskWithNullDeps = { ...allTasks[0], dependencies: [null, undefined, 2] }; expect(() => { findCrossTagDependencies( [taskWithNullDeps], 'backlog', 'in-progress', allTasks ); }).not.toThrow(); }); it('should handle string dependencies correctly', () => { const taskWithStringDeps = { ...allTasks[0], dependencies: ['2', '3'] }; const conflicts = findCrossTagDependencies( [taskWithStringDeps], 'backlog', 'in-progress', allTasks ); expect(conflicts.length).toBeGreaterThanOrEqual(0); }); }); }); ``` -------------------------------------------------------------------------------- /packages/tm-core/src/clients/supabase-client.ts: -------------------------------------------------------------------------------- ```typescript /** * Supabase authentication client for CLI auth flows */ import { createClient, SupabaseClient as SupabaseJSClient, User, Session } from '@supabase/supabase-js'; import { AuthenticationError } from '../auth/types.js'; import { getLogger } from '../logger/index.js'; import { SupabaseSessionStorage } from '../auth/supabase-session-storage.js'; import { CredentialStore } from '../auth/credential-store.js'; export class SupabaseAuthClient { private client: SupabaseJSClient | null = null; private sessionStorage: SupabaseSessionStorage; private logger = getLogger('SupabaseAuthClient'); constructor() { const credentialStore = CredentialStore.getInstance(); this.sessionStorage = new SupabaseSessionStorage(credentialStore); } /** * Get Supabase client with proper session management */ getClient(): SupabaseJSClient { if (!this.client) { // Get Supabase configuration from environment - using TM_PUBLIC prefix const supabaseUrl = process.env.TM_PUBLIC_SUPABASE_URL; const supabaseAnonKey = process.env.TM_PUBLIC_SUPABASE_ANON_KEY; if (!supabaseUrl || !supabaseAnonKey) { throw new AuthenticationError( 'Supabase configuration missing. Please set TM_PUBLIC_SUPABASE_URL and TM_PUBLIC_SUPABASE_ANON_KEY environment variables.', 'CONFIG_MISSING' ); } // Create client with custom storage adapter (similar to React Native AsyncStorage) this.client = createClient(supabaseUrl, supabaseAnonKey, { auth: { storage: this.sessionStorage, autoRefreshToken: true, persistSession: true, detectSessionInUrl: false } }); } return this.client; } /** * Initialize the client and restore session if available */ async initialize(): Promise<Session | null> { const client = this.getClient(); try { // Get the current session from storage const { data: { session }, error } = await client.auth.getSession(); if (error) { this.logger.warn('Failed to restore session:', error); return null; } if (session) { this.logger.info('Session restored successfully'); } return session; } catch (error) { this.logger.error('Error initializing session:', error); return null; } } /** * Sign in with PKCE flow (for CLI auth) */ async signInWithPKCE(): Promise<{ url: string; codeVerifier: string }> { const client = this.getClient(); try { // Generate PKCE challenge const { data, error } = await client.auth.signInWithOAuth({ provider: 'github', options: { redirectTo: process.env.TM_AUTH_CALLBACK_URL || 'http://localhost:3421/auth/callback', scopes: 'email' } }); if (error) { throw new AuthenticationError( `Failed to initiate PKCE flow: ${error.message}`, 'PKCE_INIT_FAILED' ); } if (!data?.url) { throw new AuthenticationError( 'No authorization URL returned', 'INVALID_RESPONSE' ); } // Extract code_verifier from the URL or generate it // Note: Supabase handles PKCE internally, we just need to handle the callback return { url: data.url, codeVerifier: '' // Supabase manages this internally }; } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Failed to start PKCE flow: ${(error as Error).message}`, 'PKCE_FAILED' ); } } /** * Exchange authorization code for session (PKCE flow) */ async exchangeCodeForSession(code: string): Promise<Session> { const client = this.getClient(); try { const { data, error } = await client.auth.exchangeCodeForSession(code); if (error) { throw new AuthenticationError( `Failed to exchange code: ${error.message}`, 'CODE_EXCHANGE_FAILED' ); } if (!data?.session) { throw new AuthenticationError( 'No session returned from code exchange', 'INVALID_RESPONSE' ); } this.logger.info('Successfully exchanged code for session'); return data.session; } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Code exchange failed: ${(error as Error).message}`, 'CODE_EXCHANGE_FAILED' ); } } /** * Get the current session */ async getSession(): Promise<Session | null> { const client = this.getClient(); try { const { data: { session }, error } = await client.auth.getSession(); if (error) { this.logger.warn('Failed to get session:', error); return null; } return session; } catch (error) { this.logger.error('Error getting session:', error); return null; } } /** * Refresh the current session */ async refreshSession(): Promise<Session | null> { const client = this.getClient(); try { this.logger.info('Refreshing session...'); // Supabase will automatically use the stored refresh token const { data: { session }, error } = await client.auth.refreshSession(); if (error) { this.logger.error('Failed to refresh session:', error); throw new AuthenticationError( `Failed to refresh session: ${error.message}`, 'REFRESH_FAILED' ); } if (session) { this.logger.info('Successfully refreshed session'); } return session; } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Failed to refresh session: ${(error as Error).message}`, 'REFRESH_FAILED' ); } } /** * Get current user from session */ async getUser(): Promise<User | null> { const client = this.getClient(); try { const { data: { user }, error } = await client.auth.getUser(); if (error) { this.logger.warn('Failed to get user:', error); return null; } return user; } catch (error) { this.logger.error('Error getting user:', error); return null; } } /** * Sign out and clear session */ async signOut(): Promise<void> { const client = this.getClient(); try { // Sign out with global scope to revoke all refresh tokens const { error } = await client.auth.signOut({ scope: 'global' }); if (error) { this.logger.warn('Failed to sign out:', error); } // Clear cached session data this.sessionStorage.clear(); } catch (error) { this.logger.error('Error during sign out:', error); } } /** * Set session from external auth (e.g., from server callback) */ async setSession(session: Session): Promise<void> { const client = this.getClient(); try { const { error } = await client.auth.setSession({ access_token: session.access_token, refresh_token: session.refresh_token }); if (error) { throw new AuthenticationError( `Failed to set session: ${error.message}`, 'SESSION_SET_FAILED' ); } this.logger.info('Session set successfully'); } catch (error) { if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( `Failed to set session: ${(error as Error).message}`, 'SESSION_SET_FAILED' ); } } } ``` -------------------------------------------------------------------------------- /packages/tm-core/src/config/config-manager.ts: -------------------------------------------------------------------------------- ```typescript /** * @fileoverview Configuration Manager * Orchestrates configuration services following clean architecture principles * * This ConfigManager delegates responsibilities to specialized services for better * maintainability, testability, and separation of concerns. */ import type { PartialConfiguration, RuntimeStorageConfig } from '../interfaces/configuration.interface.js'; import { DEFAULT_CONFIG_VALUES as DEFAULTS } from '../interfaces/configuration.interface.js'; import { ConfigLoader } from './services/config-loader.service.js'; import { ConfigMerger, CONFIG_PRECEDENCE } from './services/config-merger.service.js'; import { RuntimeStateManager } from './services/runtime-state-manager.service.js'; import { ConfigPersistence } from './services/config-persistence.service.js'; import { EnvironmentConfigProvider } from './services/environment-config-provider.service.js'; /** * ConfigManager orchestrates all configuration services * * This class delegates responsibilities to specialized services: * - ConfigLoader: Loads configuration from files * - ConfigMerger: Merges configurations with precedence * - RuntimeStateManager: Manages runtime state * - ConfigPersistence: Handles file persistence * - EnvironmentConfigProvider: Extracts env var configuration */ export class ConfigManager { private projectRoot: string; private config: PartialConfiguration = {}; private initialized = false; // Services private loader: ConfigLoader; private merger: ConfigMerger; private stateManager: RuntimeStateManager; private persistence: ConfigPersistence; private envProvider: EnvironmentConfigProvider; /** * Create and initialize a new ConfigManager instance * This is the ONLY way to create a ConfigManager * * @param projectRoot - The root directory of the project * @returns Fully initialized ConfigManager instance */ static async create(projectRoot: string): Promise<ConfigManager> { const manager = new ConfigManager(projectRoot); await manager.initialize(); return manager; } /** * Private constructor - use ConfigManager.create() instead * This ensures the ConfigManager is always properly initialized */ private constructor(projectRoot: string) { this.projectRoot = projectRoot; // Initialize services this.loader = new ConfigLoader(projectRoot); this.merger = new ConfigMerger(); this.stateManager = new RuntimeStateManager(projectRoot); this.persistence = new ConfigPersistence(projectRoot); this.envProvider = new EnvironmentConfigProvider(); } /** * Initialize by loading configuration from all sources * Private - only called by the factory method */ private async initialize(): Promise<void> { if (this.initialized) return; // Clear any existing configuration sources this.merger.clearSources(); // 1. Load default configuration (lowest precedence) this.merger.addSource({ name: 'defaults', config: this.loader.getDefaultConfig(), precedence: CONFIG_PRECEDENCE.DEFAULTS }); // 2. Load global configuration (if exists) const globalConfig = await this.loader.loadGlobalConfig(); if (globalConfig) { this.merger.addSource({ name: 'global', config: globalConfig, precedence: CONFIG_PRECEDENCE.GLOBAL }); } // 3. Load local project configuration const localConfig = await this.loader.loadLocalConfig(); if (localConfig) { this.merger.addSource({ name: 'local', config: localConfig, precedence: CONFIG_PRECEDENCE.LOCAL }); } // 4. Load environment variables (highest precedence) const envConfig = this.envProvider.loadConfig(); if (Object.keys(envConfig).length > 0) { this.merger.addSource({ name: 'environment', config: envConfig, precedence: CONFIG_PRECEDENCE.ENVIRONMENT }); } // 5. Merge all configurations this.config = this.merger.merge(); // 6. Load runtime state await this.stateManager.loadState(); this.initialized = true; } // ==================== Configuration Access ==================== /** * Get full configuration */ getConfig(): PartialConfiguration { return this.config; } /** * Get storage configuration */ getStorageConfig(): RuntimeStorageConfig { const storage = this.config.storage; // Return the configured type (including 'auto') const storageType = storage?.type || 'auto'; const basePath = storage?.basePath ?? this.projectRoot; if (storageType === 'api' || storageType === 'auto') { return { type: storageType, basePath, apiEndpoint: storage?.apiEndpoint, apiAccessToken: storage?.apiAccessToken, apiConfigured: Boolean(storage?.apiEndpoint || storage?.apiAccessToken) }; } return { type: storageType, basePath, apiConfigured: false }; } /** * Get model configuration */ getModelConfig() { return ( this.config.models || { main: DEFAULTS.MODELS.MAIN, fallback: DEFAULTS.MODELS.FALLBACK } ); } /** * Get response language setting */ getResponseLanguage(): string { const customConfig = this.config.custom as any; return customConfig?.responseLanguage || 'English'; } /** * Get project root path */ getProjectRoot(): string { return this.projectRoot; } /** * Check if explicitly configured to use API storage * Excludes 'auto' type */ isApiExplicitlyConfigured(): boolean { return this.getStorageConfig().type === 'api'; } // ==================== Runtime State ==================== /** * Get the currently active tag */ getActiveTag(): string { return this.stateManager.getCurrentTag(); } /** * Set the active tag */ async setActiveTag(tag: string): Promise<void> { await this.stateManager.setCurrentTag(tag); } // ==================== Configuration Updates ==================== /** * Update configuration */ async updateConfig(updates: PartialConfiguration): Promise<void> { // Merge updates into current config Object.assign(this.config, updates); // Save to persistence await this.persistence.saveConfig(this.config); // Re-initialize to respect precedence this.initialized = false; await this.initialize(); } /** * Set response language */ async setResponseLanguage(language: string): Promise<void> { if (!this.config.custom) { this.config.custom = {}; } (this.config.custom as any).responseLanguage = language; await this.persistence.saveConfig(this.config); } /** * Save current configuration */ async saveConfig(): Promise<void> { await this.persistence.saveConfig(this.config, { createBackup: true, atomic: true }); } // ==================== Utilities ==================== /** * Reset configuration to defaults */ async reset(): Promise<void> { // Clear configuration file await this.persistence.deleteConfig(); // Clear runtime state await this.stateManager.clearState(); // Reset internal state this.initialized = false; this.config = {}; // Re-initialize with defaults await this.initialize(); } /** * Get configuration sources for debugging */ getConfigSources() { return this.merger.getSources(); } } ```