This is page 67 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── commands
│ │ └── dedupe.md
│ └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│ └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│ ├── mcp.json
│ └── rules
│ ├── ai_providers.mdc
│ ├── ai_services.mdc
│ ├── architecture.mdc
│ ├── changeset.mdc
│ ├── commands.mdc
│ ├── context_gathering.mdc
│ ├── cursor_rules.mdc
│ ├── dependencies.mdc
│ ├── dev_workflow.mdc
│ ├── git_workflow.mdc
│ ├── glossary.mdc
│ ├── mcp.mdc
│ ├── new_features.mdc
│ ├── self_improve.mdc
│ ├── tags.mdc
│ ├── taskmaster.mdc
│ ├── tasks.mdc
│ ├── telemetry.mdc
│ ├── test_workflow.mdc
│ ├── tests.mdc
│ ├── ui.mdc
│ └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── enhancements---feature-requests.md
│ │ └── feedback.md
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bugfix.md
│ │ ├── config.yml
│ │ ├── feature.md
│ │ └── integration.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts
│ │ ├── auto-close-duplicates.mjs
│ │ ├── backfill-duplicate-comments.mjs
│ │ ├── check-pre-release-mode.mjs
│ │ ├── parse-metrics.mjs
│ │ ├── release.mjs
│ │ ├── tag-extension.mjs
│ │ ├── utils.mjs
│ │ └── validate-changesets.mjs
│ └── workflows
│ ├── auto-close-duplicates.yml
│ ├── backfill-duplicate-comments.yml
│ ├── ci.yml
│ ├── claude-dedupe-issues.yml
│ ├── claude-docs-trigger.yml
│ ├── claude-docs-updater.yml
│ ├── claude-issue-triage.yml
│ ├── claude.yml
│ ├── extension-ci.yml
│ ├── extension-release.yml
│ ├── log-issue-events.yml
│ ├── pre-release.yml
│ ├── release-check.yml
│ ├── release.yml
│ ├── update-models-md.yml
│ └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│ ├── hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── settings
│ │ └── mcp.json
│ └── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ ├── taskmaster_hooks_workflow.md
│ └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│ ├── CLAUDE.md
│ ├── config.json
│ ├── docs
│ │ ├── autonomous-tdd-git-workflow.md
│ │ ├── MIGRATION-ROADMAP.md
│ │ ├── prd-tm-start.txt
│ │ ├── prd.txt
│ │ ├── README.md
│ │ ├── research
│ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│ │ │ ├── 2025-06-14_test-save-functionality.md
│ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│ │ ├── task-template-importing-prd.txt
│ │ ├── tdd-workflow-phase-0-spike.md
│ │ ├── tdd-workflow-phase-1-core-rails.md
│ │ ├── tdd-workflow-phase-1-orchestrator.md
│ │ ├── tdd-workflow-phase-2-pr-resumability.md
│ │ ├── tdd-workflow-phase-3-extensibility-guardrails.md
│ │ ├── test-prd.txt
│ │ └── tm-core-phase-1.txt
│ ├── reports
│ │ ├── task-complexity-report_autonomous-tdd-git-workflow.json
│ │ ├── task-complexity-report_cc-kiro-hooks.json
│ │ ├── task-complexity-report_tdd-phase-1-core-rails.json
│ │ ├── task-complexity-report_tdd-workflow-phase-0.json
│ │ ├── task-complexity-report_test-prd-tag.json
│ │ ├── task-complexity-report_tm-core-phase-1.json
│ │ ├── task-complexity-report.json
│ │ └── tm-core-complexity.json
│ ├── state.json
│ ├── tasks
│ │ ├── task_001_tm-start.txt
│ │ ├── task_002_tm-start.txt
│ │ ├── task_003_tm-start.txt
│ │ ├── task_004_tm-start.txt
│ │ ├── task_007_tm-start.txt
│ │ └── tasks.json
│ └── templates
│ ├── example_prd_rpg.md
│ └── example_prd.md
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── apps
│ ├── cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── command-registry.ts
│ │ │ ├── commands
│ │ │ │ ├── auth.command.ts
│ │ │ │ ├── autopilot
│ │ │ │ │ ├── abort.command.ts
│ │ │ │ │ ├── commit.command.ts
│ │ │ │ │ ├── complete.command.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next.command.ts
│ │ │ │ │ ├── resume.command.ts
│ │ │ │ │ ├── shared.ts
│ │ │ │ │ ├── start.command.ts
│ │ │ │ │ └── status.command.ts
│ │ │ │ ├── briefs.command.ts
│ │ │ │ ├── context.command.ts
│ │ │ │ ├── export.command.ts
│ │ │ │ ├── list.command.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── custom-providers.ts
│ │ │ │ │ ├── fetchers.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompts.ts
│ │ │ │ │ ├── setup.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── next.command.ts
│ │ │ │ ├── set-status.command.ts
│ │ │ │ ├── show.command.ts
│ │ │ │ ├── start.command.ts
│ │ │ │ └── tags.command.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── model-management.ts
│ │ │ ├── types
│ │ │ │ └── tag-management.d.ts
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── cardBox.component.ts
│ │ │ │ │ ├── dashboard.component.ts
│ │ │ │ │ ├── header.component.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next-task.component.ts
│ │ │ │ │ ├── suggested-steps.component.ts
│ │ │ │ │ └── task-detail.component.ts
│ │ │ │ ├── display
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ └── tables.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── complexity-formatters.ts
│ │ │ │ │ ├── dependency-formatters.ts
│ │ │ │ │ ├── priority-formatters.ts
│ │ │ │ │ ├── status-formatters.spec.ts
│ │ │ │ │ └── status-formatters.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ ├── helpers.spec.ts
│ │ │ │ └── helpers.ts
│ │ │ └── utils
│ │ │ ├── auth-helpers.ts
│ │ │ ├── auto-update.ts
│ │ │ ├── brief-selection.ts
│ │ │ ├── display-helpers.ts
│ │ │ ├── error-handler.ts
│ │ │ ├── index.ts
│ │ │ ├── project-root.ts
│ │ │ ├── task-status.ts
│ │ │ ├── ui.spec.ts
│ │ │ └── ui.ts
│ │ ├── tests
│ │ │ ├── integration
│ │ │ │ └── commands
│ │ │ │ └── autopilot
│ │ │ │ └── workflow.test.ts
│ │ │ └── unit
│ │ │ ├── commands
│ │ │ │ ├── autopilot
│ │ │ │ │ └── shared.test.ts
│ │ │ │ ├── list.command.spec.ts
│ │ │ │ └── show.command.spec.ts
│ │ │ └── ui
│ │ │ └── dashboard.component.spec.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── docs
│ │ ├── archive
│ │ │ ├── ai-client-utils-example.mdx
│ │ │ ├── ai-development-workflow.mdx
│ │ │ ├── command-reference.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── cursor-setup.mdx
│ │ │ ├── examples.mdx
│ │ │ └── Installation.mdx
│ │ ├── best-practices
│ │ │ ├── advanced-tasks.mdx
│ │ │ ├── configuration-advanced.mdx
│ │ │ └── index.mdx
│ │ ├── capabilities
│ │ │ ├── cli-root-commands.mdx
│ │ │ ├── index.mdx
│ │ │ ├── mcp.mdx
│ │ │ ├── rpg-method.mdx
│ │ │ └── task-structure.mdx
│ │ ├── CHANGELOG.md
│ │ ├── command-reference.mdx
│ │ ├── configuration.mdx
│ │ ├── docs.json
│ │ ├── favicon.svg
│ │ ├── getting-started
│ │ │ ├── api-keys.mdx
│ │ │ ├── contribute.mdx
│ │ │ ├── faq.mdx
│ │ │ └── quick-start
│ │ │ ├── configuration-quick.mdx
│ │ │ ├── execute-quick.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── moving-forward.mdx
│ │ │ ├── prd-quick.mdx
│ │ │ ├── quick-start.mdx
│ │ │ ├── requirements.mdx
│ │ │ ├── rules-quick.mdx
│ │ │ └── tasks-quick.mdx
│ │ ├── introduction.mdx
│ │ ├── licensing.md
│ │ ├── logo
│ │ │ ├── dark.svg
│ │ │ ├── light.svg
│ │ │ └── task-master-logo.png
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── style.css
│ │ ├── tdd-workflow
│ │ │ ├── ai-agent-integration.mdx
│ │ │ └── quickstart.mdx
│ │ ├── vercel.json
│ │ └── whats-new.mdx
│ ├── extension
│ │ ├── .vscodeignore
│ │ ├── assets
│ │ │ ├── banner.png
│ │ │ ├── icon-dark.svg
│ │ │ ├── icon-light.svg
│ │ │ ├── icon.png
│ │ │ ├── screenshots
│ │ │ │ ├── kanban-board.png
│ │ │ │ └── task-details.png
│ │ │ └── sidebar-icon.svg
│ │ ├── CHANGELOG.md
│ │ ├── components.json
│ │ ├── docs
│ │ │ ├── extension-CI-setup.md
│ │ │ └── extension-development-guide.md
│ │ ├── esbuild.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── package.mjs
│ │ ├── package.publish.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── ConfigView.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── TaskDetails
│ │ │ │ │ ├── AIActionsSection.tsx
│ │ │ │ │ ├── DetailsSection.tsx
│ │ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ │ ├── SubtasksSection.tsx
│ │ │ │ │ ├── TaskMetadataSidebar.tsx
│ │ │ │ │ └── useTaskDetails.ts
│ │ │ │ ├── TaskDetailsView.tsx
│ │ │ │ ├── TaskMasterLogo.tsx
│ │ │ │ └── ui
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── shadcn-io
│ │ │ │ │ └── kanban
│ │ │ │ │ └── index.tsx
│ │ │ │ └── textarea.tsx
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── services
│ │ │ │ ├── config-service.ts
│ │ │ │ ├── error-handler.ts
│ │ │ │ ├── notification-preferences.ts
│ │ │ │ ├── polling-service.ts
│ │ │ │ ├── polling-strategies.ts
│ │ │ │ ├── sidebar-webview-manager.ts
│ │ │ │ ├── task-repository.ts
│ │ │ │ ├── terminal-manager.ts
│ │ │ │ └── webview-manager.ts
│ │ │ ├── test
│ │ │ │ └── extension.test.ts
│ │ │ ├── utils
│ │ │ │ ├── configManager.ts
│ │ │ │ ├── connectionManager.ts
│ │ │ │ ├── errorHandler.ts
│ │ │ │ ├── event-emitter.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── mcpClient.ts
│ │ │ │ ├── notificationPreferences.ts
│ │ │ │ └── task-master-api
│ │ │ │ ├── cache
│ │ │ │ │ └── cache-manager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mcp-client.ts
│ │ │ │ ├── transformers
│ │ │ │ │ └── task-transformer.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ └── webview
│ │ │ ├── App.tsx
│ │ │ ├── components
│ │ │ │ ├── AppContent.tsx
│ │ │ │ ├── EmptyState.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── PollingStatus.tsx
│ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ ├── TagDropdown.tsx
│ │ │ │ ├── TaskCard.tsx
│ │ │ │ ├── TaskEditModal.tsx
│ │ │ │ ├── TaskMasterKanban.tsx
│ │ │ │ ├── ToastContainer.tsx
│ │ │ │ └── ToastNotification.tsx
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── VSCodeContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useTaskQueries.ts
│ │ │ │ ├── useVSCodeMessages.ts
│ │ │ │ └── useWebviewHeight.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── providers
│ │ │ │ └── QueryProvider.tsx
│ │ │ ├── reducers
│ │ │ │ └── appReducer.ts
│ │ │ ├── sidebar.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ ├── logger.ts
│ │ │ └── toast.ts
│ │ └── tsconfig.json
│ └── mcp
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tools
│ │ ├── autopilot
│ │ │ ├── abort.tool.ts
│ │ │ ├── commit.tool.ts
│ │ │ ├── complete.tool.ts
│ │ │ ├── finalize.tool.ts
│ │ │ ├── index.ts
│ │ │ ├── next.tool.ts
│ │ │ ├── resume.tool.ts
│ │ │ ├── start.tool.ts
│ │ │ └── status.tool.ts
│ │ ├── README-ZOD-V3.md
│ │ └── tasks
│ │ ├── get-task.tool.ts
│ │ ├── get-tasks.tool.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── assets
│ ├── .windsurfrules
│ ├── AGENTS.md
│ ├── claude
│ │ └── TM_COMMANDS_GUIDE.md
│ ├── config.json
│ ├── env.example
│ ├── example_prd_rpg.txt
│ ├── example_prd.txt
│ ├── GEMINI.md
│ ├── gitignore
│ ├── kiro-hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── roocode
│ │ ├── .roo
│ │ │ ├── rules-architect
│ │ │ │ └── architect-rules
│ │ │ ├── rules-ask
│ │ │ │ └── ask-rules
│ │ │ ├── rules-code
│ │ │ │ └── code-rules
│ │ │ ├── rules-debug
│ │ │ │ └── debug-rules
│ │ │ ├── rules-orchestrator
│ │ │ │ └── orchestrator-rules
│ │ │ └── rules-test
│ │ │ └── test-rules
│ │ └── .roomodes
│ ├── rules
│ │ ├── cursor_rules.mdc
│ │ ├── dev_workflow.mdc
│ │ ├── self_improve.mdc
│ │ ├── taskmaster_hooks_workflow.mdc
│ │ └── taskmaster.mdc
│ └── scripts_README.md
├── bin
│ └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│ ├── chats
│ │ ├── add-task-dependencies-1.md
│ │ └── max-min-tokens.txt.md
│ ├── fastmcp-core.txt
│ ├── fastmcp-docs.txt
│ ├── MCP_INTEGRATION.md
│ ├── mcp-js-sdk-docs.txt
│ ├── mcp-protocol-repo.txt
│ ├── mcp-protocol-schema-03262025.json
│ └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│ ├── claude-code-integration.md
│ ├── CLI-COMMANDER-PATTERN.md
│ ├── command-reference.md
│ ├── configuration.md
│ ├── contributor-docs
│ │ ├── testing-roo-integration.md
│ │ └── worktree-setup.md
│ ├── cross-tag-task-movement.md
│ ├── examples
│ │ ├── claude-code-usage.md
│ │ └── codex-cli-usage.md
│ ├── examples.md
│ ├── licensing.md
│ ├── mcp-provider-guide.md
│ ├── mcp-provider.md
│ ├── migration-guide.md
│ ├── models.md
│ ├── providers
│ │ ├── codex-cli.md
│ │ └── gemini-cli.md
│ ├── README.md
│ ├── scripts
│ │ └── models-json-to-markdown.js
│ ├── task-structure.md
│ └── tutorial.md
├── images
│ ├── hamster-hiring.png
│ └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│ ├── server.js
│ └── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── context-manager.test.js
│ │ ├── context-manager.js
│ │ ├── direct-functions
│ │ │ ├── add-dependency.js
│ │ │ ├── add-subtask.js
│ │ │ ├── add-tag.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── cache-stats.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── complexity-report.js
│ │ │ ├── copy-tag.js
│ │ │ ├── create-tag-from-branch.js
│ │ │ ├── delete-tag.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── fix-dependencies.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── initialize-project.js
│ │ │ ├── list-tags.js
│ │ │ ├── models.js
│ │ │ ├── move-task-cross-tag.js
│ │ │ ├── move-task.js
│ │ │ ├── next-task.js
│ │ │ ├── parse-prd.js
│ │ │ ├── remove-dependency.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── rename-tag.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── rules.js
│ │ │ ├── scope-down.js
│ │ │ ├── scope-up.js
│ │ │ ├── set-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ ├── update-tasks.js
│ │ │ ├── use-tag.js
│ │ │ └── validate-dependencies.js
│ │ ├── task-master-core.js
│ │ └── utils
│ │ ├── env-utils.js
│ │ └── path-utils.js
│ ├── custom-sdk
│ │ ├── errors.js
│ │ ├── index.js
│ │ ├── json-extractor.js
│ │ ├── language-model.js
│ │ ├── message-converter.js
│ │ └── schema-converter.js
│ ├── index.js
│ ├── logger.js
│ ├── providers
│ │ └── mcp-provider.js
│ └── tools
│ ├── add-dependency.js
│ ├── add-subtask.js
│ ├── add-tag.js
│ ├── add-task.js
│ ├── analyze.js
│ ├── clear-subtasks.js
│ ├── complexity-report.js
│ ├── copy-tag.js
│ ├── delete-tag.js
│ ├── expand-all.js
│ ├── expand-task.js
│ ├── fix-dependencies.js
│ ├── generate.js
│ ├── get-operation-status.js
│ ├── index.js
│ ├── initialize-project.js
│ ├── list-tags.js
│ ├── models.js
│ ├── move-task.js
│ ├── next-task.js
│ ├── parse-prd.js
│ ├── README-ZOD-V3.md
│ ├── remove-dependency.js
│ ├── remove-subtask.js
│ ├── remove-task.js
│ ├── rename-tag.js
│ ├── research.js
│ ├── response-language.js
│ ├── rules.js
│ ├── scope-down.js
│ ├── scope-up.js
│ ├── set-task-status.js
│ ├── tool-registry.js
│ ├── update-subtask.js
│ ├── update-task.js
│ ├── update.js
│ ├── use-tag.js
│ ├── utils.js
│ └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│ ├── ai-sdk-provider-grok-cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── errors.test.ts
│ │ │ ├── errors.ts
│ │ │ ├── grok-cli-language-model.ts
│ │ │ ├── grok-cli-provider.test.ts
│ │ │ ├── grok-cli-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── json-extractor.test.ts
│ │ │ ├── json-extractor.ts
│ │ │ ├── message-converter.test.ts
│ │ │ ├── message-converter.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── build-config
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ └── tsdown.base.ts
│ │ └── tsconfig.json
│ ├── claude-code-plugin
│ │ ├── .claude-plugin
│ │ │ └── plugin.json
│ │ ├── .gitignore
│ │ ├── agents
│ │ │ ├── task-checker.md
│ │ │ ├── task-executor.md
│ │ │ └── task-orchestrator.md
│ │ ├── CHANGELOG.md
│ │ ├── commands
│ │ │ ├── add-dependency.md
│ │ │ ├── add-subtask.md
│ │ │ ├── add-task.md
│ │ │ ├── analyze-complexity.md
│ │ │ ├── analyze-project.md
│ │ │ ├── auto-implement-tasks.md
│ │ │ ├── command-pipeline.md
│ │ │ ├── complexity-report.md
│ │ │ ├── convert-task-to-subtask.md
│ │ │ ├── expand-all-tasks.md
│ │ │ ├── expand-task.md
│ │ │ ├── fix-dependencies.md
│ │ │ ├── generate-tasks.md
│ │ │ ├── help.md
│ │ │ ├── init-project-quick.md
│ │ │ ├── init-project.md
│ │ │ ├── install-taskmaster.md
│ │ │ ├── learn.md
│ │ │ ├── list-tasks-by-status.md
│ │ │ ├── list-tasks-with-subtasks.md
│ │ │ ├── list-tasks.md
│ │ │ ├── next-task.md
│ │ │ ├── parse-prd-with-research.md
│ │ │ ├── parse-prd.md
│ │ │ ├── project-status.md
│ │ │ ├── quick-install-taskmaster.md
│ │ │ ├── remove-all-subtasks.md
│ │ │ ├── remove-dependency.md
│ │ │ ├── remove-subtask.md
│ │ │ ├── remove-subtasks.md
│ │ │ ├── remove-task.md
│ │ │ ├── setup-models.md
│ │ │ ├── show-task.md
│ │ │ ├── smart-workflow.md
│ │ │ ├── sync-readme.md
│ │ │ ├── tm-main.md
│ │ │ ├── to-cancelled.md
│ │ │ ├── to-deferred.md
│ │ │ ├── to-done.md
│ │ │ ├── to-in-progress.md
│ │ │ ├── to-pending.md
│ │ │ ├── to-review.md
│ │ │ ├── update-single-task.md
│ │ │ ├── update-task.md
│ │ │ ├── update-tasks-from-id.md
│ │ │ ├── validate-dependencies.md
│ │ │ └── view-models.md
│ │ ├── mcp.json
│ │ └── package.json
│ ├── tm-bridge
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── add-tag-bridge.ts
│ │ │ ├── bridge-types.ts
│ │ │ ├── bridge-utils.ts
│ │ │ ├── expand-bridge.ts
│ │ │ ├── index.ts
│ │ │ ├── tags-bridge.ts
│ │ │ ├── update-bridge.ts
│ │ │ └── use-tag-bridge.ts
│ │ └── tsconfig.json
│ └── tm-core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docs
│ │ └── listTasks-architecture.md
│ ├── package.json
│ ├── POC-STATUS.md
│ ├── README.md
│ ├── src
│ │ ├── common
│ │ │ ├── constants
│ │ │ │ ├── index.ts
│ │ │ │ ├── paths.ts
│ │ │ │ └── providers.ts
│ │ │ ├── errors
│ │ │ │ ├── index.ts
│ │ │ │ └── task-master-error.ts
│ │ │ ├── interfaces
│ │ │ │ ├── configuration.interface.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── storage.interface.ts
│ │ │ ├── logger
│ │ │ │ ├── factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.spec.ts
│ │ │ │ └── logger.ts
│ │ │ ├── mappers
│ │ │ │ ├── TaskMapper.test.ts
│ │ │ │ └── TaskMapper.ts
│ │ │ ├── types
│ │ │ │ ├── database.types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── legacy.ts
│ │ │ │ └── repository-types.ts
│ │ │ └── utils
│ │ │ ├── git-utils.ts
│ │ │ ├── id-generator.ts
│ │ │ ├── index.ts
│ │ │ ├── path-helpers.ts
│ │ │ ├── path-normalizer.spec.ts
│ │ │ ├── path-normalizer.ts
│ │ │ ├── project-root-finder.spec.ts
│ │ │ ├── project-root-finder.ts
│ │ │ ├── run-id-generator.spec.ts
│ │ │ └── run-id-generator.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── ai
│ │ │ │ ├── index.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── ai-provider.interface.ts
│ │ │ │ └── providers
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth
│ │ │ │ ├── auth-domain.spec.ts
│ │ │ │ ├── auth-domain.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── auth-manager.spec.ts
│ │ │ │ │ └── auth-manager.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── context-store.ts
│ │ │ │ │ ├── oauth-service.ts
│ │ │ │ │ ├── organization.service.ts
│ │ │ │ │ ├── supabase-session-storage.spec.ts
│ │ │ │ │ └── supabase-session-storage.ts
│ │ │ │ └── types.ts
│ │ │ ├── briefs
│ │ │ │ ├── briefs-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── brief-service.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils
│ │ │ │ └── url-parser.ts
│ │ │ ├── commands
│ │ │ │ └── index.ts
│ │ │ ├── config
│ │ │ │ ├── config-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── config-manager.spec.ts
│ │ │ │ │ └── config-manager.ts
│ │ │ │ └── services
│ │ │ │ ├── config-loader.service.spec.ts
│ │ │ │ ├── config-loader.service.ts
│ │ │ │ ├── config-merger.service.spec.ts
│ │ │ │ ├── config-merger.service.ts
│ │ │ │ ├── config-persistence.service.spec.ts
│ │ │ │ ├── config-persistence.service.ts
│ │ │ │ ├── environment-config-provider.service.spec.ts
│ │ │ │ ├── environment-config-provider.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── runtime-state-manager.service.spec.ts
│ │ │ │ └── runtime-state-manager.service.ts
│ │ │ ├── dependencies
│ │ │ │ └── index.ts
│ │ │ ├── execution
│ │ │ │ ├── executors
│ │ │ │ │ ├── base-executor.ts
│ │ │ │ │ ├── claude-executor.ts
│ │ │ │ │ └── executor-factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── executor-service.ts
│ │ │ │ └── types.ts
│ │ │ ├── git
│ │ │ │ ├── adapters
│ │ │ │ │ ├── git-adapter.test.ts
│ │ │ │ │ └── git-adapter.ts
│ │ │ │ ├── git-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── branch-name-generator.spec.ts
│ │ │ │ ├── branch-name-generator.ts
│ │ │ │ ├── commit-message-generator.test.ts
│ │ │ │ ├── commit-message-generator.ts
│ │ │ │ ├── scope-detector.test.ts
│ │ │ │ ├── scope-detector.ts
│ │ │ │ ├── template-engine.test.ts
│ │ │ │ └── template-engine.ts
│ │ │ ├── integration
│ │ │ │ ├── clients
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── supabase-client.ts
│ │ │ │ ├── integration-domain.ts
│ │ │ │ └── services
│ │ │ │ ├── export.service.ts
│ │ │ │ ├── task-expansion.service.ts
│ │ │ │ └── task-retrieval.service.ts
│ │ │ ├── reports
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ └── complexity-report-manager.ts
│ │ │ │ └── types.ts
│ │ │ ├── storage
│ │ │ │ ├── adapters
│ │ │ │ │ ├── activity-logger.ts
│ │ │ │ │ ├── api-storage.ts
│ │ │ │ │ └── file-storage
│ │ │ │ │ ├── file-operations.ts
│ │ │ │ │ ├── file-storage.ts
│ │ │ │ │ ├── format-handler.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── path-resolver.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── storage-factory.ts
│ │ │ │ └── utils
│ │ │ │ └── api-client.ts
│ │ │ ├── tasks
│ │ │ │ ├── entities
│ │ │ │ │ └── task.entity.ts
│ │ │ │ ├── parser
│ │ │ │ │ └── index.ts
│ │ │ │ ├── repositories
│ │ │ │ │ ├── supabase
│ │ │ │ │ │ ├── dependency-fetcher.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── supabase-repository.ts
│ │ │ │ │ └── task-repository.interface.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── preflight-checker.service.ts
│ │ │ │ │ ├── tag.service.ts
│ │ │ │ │ ├── task-execution-service.ts
│ │ │ │ │ ├── task-loader.service.ts
│ │ │ │ │ └── task-service.ts
│ │ │ │ └── tasks-domain.ts
│ │ │ ├── ui
│ │ │ │ └── index.ts
│ │ │ └── workflow
│ │ │ ├── managers
│ │ │ │ ├── workflow-state-manager.spec.ts
│ │ │ │ └── workflow-state-manager.ts
│ │ │ ├── orchestrators
│ │ │ │ ├── workflow-orchestrator.test.ts
│ │ │ │ └── workflow-orchestrator.ts
│ │ │ ├── services
│ │ │ │ ├── test-result-validator.test.ts
│ │ │ │ ├── test-result-validator.ts
│ │ │ │ ├── test-result-validator.types.ts
│ │ │ │ ├── workflow-activity-logger.ts
│ │ │ │ └── workflow.service.ts
│ │ │ ├── types.ts
│ │ │ └── workflow-domain.ts
│ │ ├── subpath-exports.test.ts
│ │ ├── tm-core.ts
│ │ └── utils
│ │ └── time.utils.ts
│ ├── tests
│ │ ├── auth
│ │ │ └── auth-refresh.test.ts
│ │ ├── integration
│ │ │ ├── auth-token-refresh.test.ts
│ │ │ ├── list-tasks.test.ts
│ │ │ └── storage
│ │ │ └── activity-logger.test.ts
│ │ ├── mocks
│ │ │ └── mock-provider.ts
│ │ ├── setup.ts
│ │ └── unit
│ │ ├── base-provider.test.ts
│ │ ├── executor.test.ts
│ │ └── smoke.test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│ ├── create-worktree.sh
│ ├── dev.js
│ ├── init.js
│ ├── list-worktrees.sh
│ ├── modules
│ │ ├── ai-services-unified.js
│ │ ├── bridge-utils.js
│ │ ├── commands.js
│ │ ├── config-manager.js
│ │ ├── dependency-manager.js
│ │ ├── index.js
│ │ ├── prompt-manager.js
│ │ ├── supported-models.json
│ │ ├── sync-readme.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── find-next-task.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── is-task-dependent.js
│ │ │ ├── list-tasks.js
│ │ │ ├── migrate.js
│ │ │ ├── models.js
│ │ │ ├── move-task.js
│ │ │ ├── parse-prd
│ │ │ │ ├── index.js
│ │ │ │ ├── parse-prd-config.js
│ │ │ │ ├── parse-prd-helpers.js
│ │ │ │ ├── parse-prd-non-streaming.js
│ │ │ │ ├── parse-prd-streaming.js
│ │ │ │ └── parse-prd.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── scope-adjustment.js
│ │ │ ├── set-task-status.js
│ │ │ ├── tag-management.js
│ │ │ ├── task-exists.js
│ │ │ ├── update-single-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ └── update-tasks.js
│ │ ├── task-manager.js
│ │ ├── ui.js
│ │ ├── update-config-tokens.js
│ │ ├── utils
│ │ │ ├── contextGatherer.js
│ │ │ ├── fuzzyTaskSearch.js
│ │ │ └── git-utils.js
│ │ └── utils.js
│ ├── task-complexity-report.json
│ ├── test-claude-errors.js
│ └── test-claude.js
├── sonar-project.properties
├── src
│ ├── ai-providers
│ │ ├── anthropic.js
│ │ ├── azure.js
│ │ ├── base-provider.js
│ │ ├── bedrock.js
│ │ ├── claude-code.js
│ │ ├── codex-cli.js
│ │ ├── gemini-cli.js
│ │ ├── google-vertex.js
│ │ ├── google.js
│ │ ├── grok-cli.js
│ │ ├── groq.js
│ │ ├── index.js
│ │ ├── lmstudio.js
│ │ ├── ollama.js
│ │ ├── openai-compatible.js
│ │ ├── openai.js
│ │ ├── openrouter.js
│ │ ├── perplexity.js
│ │ ├── xai.js
│ │ ├── zai-coding.js
│ │ └── zai.js
│ ├── constants
│ │ ├── commands.js
│ │ ├── paths.js
│ │ ├── profiles.js
│ │ ├── rules-actions.js
│ │ ├── task-priority.js
│ │ └── task-status.js
│ ├── profiles
│ │ ├── amp.js
│ │ ├── base-profile.js
│ │ ├── claude.js
│ │ ├── cline.js
│ │ ├── codex.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── index.js
│ │ ├── kilo.js
│ │ ├── kiro.js
│ │ ├── opencode.js
│ │ ├── roo.js
│ │ ├── trae.js
│ │ ├── vscode.js
│ │ ├── windsurf.js
│ │ └── zed.js
│ ├── progress
│ │ ├── base-progress-tracker.js
│ │ ├── cli-progress-factory.js
│ │ ├── parse-prd-tracker.js
│ │ ├── progress-tracker-builder.js
│ │ └── tracker-ui.js
│ ├── prompts
│ │ ├── add-task.json
│ │ ├── analyze-complexity.json
│ │ ├── expand-task.json
│ │ ├── parse-prd.json
│ │ ├── README.md
│ │ ├── research.json
│ │ ├── schemas
│ │ │ ├── parameter.schema.json
│ │ │ ├── prompt-template.schema.json
│ │ │ ├── README.md
│ │ │ └── variant.schema.json
│ │ ├── update-subtask.json
│ │ ├── update-task.json
│ │ └── update-tasks.json
│ ├── provider-registry
│ │ └── index.js
│ ├── schemas
│ │ ├── add-task.js
│ │ ├── analyze-complexity.js
│ │ ├── base-schemas.js
│ │ ├── expand-task.js
│ │ ├── parse-prd.js
│ │ ├── registry.js
│ │ ├── update-subtask.js
│ │ ├── update-task.js
│ │ └── update-tasks.js
│ ├── task-master.js
│ ├── ui
│ │ ├── confirm.js
│ │ ├── indicators.js
│ │ └── parse-prd.js
│ └── utils
│ ├── asset-resolver.js
│ ├── create-mcp-config.js
│ ├── format.js
│ ├── getVersion.js
│ ├── logger-utils.js
│ ├── manage-gitignore.js
│ ├── path-utils.js
│ ├── profiles.js
│ ├── rule-transformer.js
│ ├── stream-parser.js
│ └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│ ├── e2e
│ │ ├── e2e_helpers.sh
│ │ ├── parse_llm_output.cjs
│ │ ├── run_e2e.sh
│ │ ├── run_fallback_verification.sh
│ │ └── test_llm_analysis.sh
│ ├── fixtures
│ │ ├── .taskmasterconfig
│ │ ├── sample-claude-response.js
│ │ ├── sample-prd.txt
│ │ └── sample-tasks.js
│ ├── helpers
│ │ └── tool-counts.js
│ ├── integration
│ │ ├── claude-code-error-handling.test.js
│ │ ├── claude-code-optional.test.js
│ │ ├── cli
│ │ │ ├── commands.test.js
│ │ │ ├── complex-cross-tag-scenarios.test.js
│ │ │ └── move-cross-tag.test.js
│ │ ├── manage-gitignore.test.js
│ │ ├── mcp-server
│ │ │ └── direct-functions.test.js
│ │ ├── move-task-cross-tag.integration.test.js
│ │ ├── move-task-simple.integration.test.js
│ │ ├── profiles
│ │ │ ├── amp-init-functionality.test.js
│ │ │ ├── claude-init-functionality.test.js
│ │ │ ├── cline-init-functionality.test.js
│ │ │ ├── codex-init-functionality.test.js
│ │ │ ├── cursor-init-functionality.test.js
│ │ │ ├── gemini-init-functionality.test.js
│ │ │ ├── opencode-init-functionality.test.js
│ │ │ ├── roo-files-inclusion.test.js
│ │ │ ├── roo-init-functionality.test.js
│ │ │ ├── rules-files-inclusion.test.js
│ │ │ ├── trae-init-functionality.test.js
│ │ │ ├── vscode-init-functionality.test.js
│ │ │ └── windsurf-init-functionality.test.js
│ │ └── providers
│ │ └── temperature-support.test.js
│ ├── manual
│ │ ├── progress
│ │ │ ├── parse-prd-analysis.js
│ │ │ ├── test-parse-prd.js
│ │ │ └── TESTING_GUIDE.md
│ │ └── prompts
│ │ ├── prompt-test.js
│ │ └── README.md
│ ├── README.md
│ ├── setup.js
│ └── unit
│ ├── ai-providers
│ │ ├── base-provider.test.js
│ │ ├── claude-code.test.js
│ │ ├── codex-cli.test.js
│ │ ├── gemini-cli.test.js
│ │ ├── lmstudio.test.js
│ │ ├── mcp-components.test.js
│ │ ├── openai-compatible.test.js
│ │ ├── openai.test.js
│ │ ├── provider-registry.test.js
│ │ ├── zai-coding.test.js
│ │ ├── zai-provider.test.js
│ │ ├── zai-schema-introspection.test.js
│ │ └── zai.test.js
│ ├── ai-services-unified.test.js
│ ├── commands.test.js
│ ├── config-manager.test.js
│ ├── config-manager.test.mjs
│ ├── dependency-manager.test.js
│ ├── init.test.js
│ ├── initialize-project.test.js
│ ├── kebab-case-validation.test.js
│ ├── manage-gitignore.test.js
│ ├── mcp
│ │ └── tools
│ │ ├── __mocks__
│ │ │ └── move-task.js
│ │ ├── add-task.test.js
│ │ ├── analyze-complexity.test.js
│ │ ├── expand-all.test.js
│ │ ├── get-tasks.test.js
│ │ ├── initialize-project.test.js
│ │ ├── move-task-cross-tag-options.test.js
│ │ ├── move-task-cross-tag.test.js
│ │ ├── remove-task.test.js
│ │ └── tool-registration.test.js
│ ├── mcp-providers
│ │ ├── mcp-components.test.js
│ │ └── mcp-provider.test.js
│ ├── parse-prd.test.js
│ ├── profiles
│ │ ├── amp-integration.test.js
│ │ ├── claude-integration.test.js
│ │ ├── cline-integration.test.js
│ │ ├── codex-integration.test.js
│ │ ├── cursor-integration.test.js
│ │ ├── gemini-integration.test.js
│ │ ├── kilo-integration.test.js
│ │ ├── kiro-integration.test.js
│ │ ├── mcp-config-validation.test.js
│ │ ├── opencode-integration.test.js
│ │ ├── profile-safety-check.test.js
│ │ ├── roo-integration.test.js
│ │ ├── rule-transformer-cline.test.js
│ │ ├── rule-transformer-cursor.test.js
│ │ ├── rule-transformer-gemini.test.js
│ │ ├── rule-transformer-kilo.test.js
│ │ ├── rule-transformer-kiro.test.js
│ │ ├── rule-transformer-opencode.test.js
│ │ ├── rule-transformer-roo.test.js
│ │ ├── rule-transformer-trae.test.js
│ │ ├── rule-transformer-vscode.test.js
│ │ ├── rule-transformer-windsurf.test.js
│ │ ├── rule-transformer-zed.test.js
│ │ ├── rule-transformer.test.js
│ │ ├── selective-profile-removal.test.js
│ │ ├── subdirectory-support.test.js
│ │ ├── trae-integration.test.js
│ │ ├── vscode-integration.test.js
│ │ ├── windsurf-integration.test.js
│ │ └── zed-integration.test.js
│ ├── progress
│ │ └── base-progress-tracker.test.js
│ ├── prompt-manager.test.js
│ ├── prompts
│ │ ├── expand-task-prompt.test.js
│ │ └── prompt-migration.test.js
│ ├── scripts
│ │ └── modules
│ │ ├── commands
│ │ │ ├── move-cross-tag.test.js
│ │ │ └── README.md
│ │ ├── dependency-manager
│ │ │ ├── circular-dependencies.test.js
│ │ │ ├── cross-tag-dependencies.test.js
│ │ │ └── fix-dependencies-command.test.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.test.js
│ │ │ ├── add-task.test.js
│ │ │ ├── analyze-task-complexity.test.js
│ │ │ ├── clear-subtasks.test.js
│ │ │ ├── complexity-report-tag-isolation.test.js
│ │ │ ├── expand-all-tasks.test.js
│ │ │ ├── expand-task.test.js
│ │ │ ├── find-next-task.test.js
│ │ │ ├── generate-task-files.test.js
│ │ │ ├── list-tasks.test.js
│ │ │ ├── models-baseurl.test.js
│ │ │ ├── move-task-cross-tag.test.js
│ │ │ ├── move-task.test.js
│ │ │ ├── parse-prd-schema.test.js
│ │ │ ├── parse-prd.test.js
│ │ │ ├── remove-subtask.test.js
│ │ │ ├── remove-task.test.js
│ │ │ ├── research.test.js
│ │ │ ├── scope-adjustment.test.js
│ │ │ ├── set-task-status.test.js
│ │ │ ├── setup.js
│ │ │ ├── update-single-task-status.test.js
│ │ │ ├── update-subtask-by-id.test.js
│ │ │ ├── update-task-by-id.test.js
│ │ │ └── update-tasks.test.js
│ │ ├── ui
│ │ │ └── cross-tag-error-display.test.js
│ │ └── utils-tag-aware-paths.test.js
│ ├── task-finder.test.js
│ ├── task-manager
│ │ ├── clear-subtasks.test.js
│ │ ├── move-task.test.js
│ │ ├── tag-boundary.test.js
│ │ └── tag-management.test.js
│ ├── task-master.test.js
│ ├── ui
│ │ └── indicators.test.js
│ ├── ui.test.js
│ ├── utils-strip-ansi.test.js
│ └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```
# Files
--------------------------------------------------------------------------------
/scripts/modules/commands.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * commands.js
3 | * Command-line interface for the Task Master CLI
4 | */
5 |
6 | import { Command } from 'commander';
7 | import path from 'path';
8 | import chalk from 'chalk';
9 | import boxen from 'boxen';
10 | import fs from 'fs';
11 | import inquirer from 'inquirer';
12 |
13 | import { log, readJSON } from './utils.js';
14 | // Import command registry and utilities from @tm/cli
15 | import {
16 | registerAllCommands,
17 | checkForUpdate,
18 | performAutoUpdate,
19 | displayUpgradeNotification,
20 | restartWithNewVersion,
21 | displayError,
22 | runInteractiveSetup
23 | } from '@tm/cli';
24 |
25 | import {
26 | parsePRD,
27 | updateTasks,
28 | generateTaskFiles,
29 | expandTask,
30 | expandAllTasks,
31 | clearSubtasks,
32 | addTask,
33 | addSubtask,
34 | removeSubtask,
35 | analyzeTaskComplexity,
36 | updateTaskById,
37 | updateSubtaskById,
38 | removeTask,
39 | findTaskById,
40 | taskExists,
41 | moveTask,
42 | migrateProject,
43 | setResponseLanguage,
44 | scopeUpTask,
45 | scopeDownTask,
46 | validateStrength
47 | } from './task-manager.js';
48 |
49 | import { moveTasksBetweenTags } from './task-manager/move-task.js';
50 |
51 | import {
52 | createTag,
53 | deleteTag,
54 | tags,
55 | useTag,
56 | renameTag,
57 | copyTag
58 | } from './task-manager/tag-management.js';
59 |
60 | import {
61 | addDependency,
62 | removeDependency,
63 | validateDependenciesCommand,
64 | fixDependenciesCommand
65 | } from './dependency-manager.js';
66 |
67 | import {
68 | isApiKeySet,
69 | getDebugFlag,
70 | ConfigurationError,
71 | isConfigFilePresent,
72 | getDefaultNumTasks
73 | } from './config-manager.js';
74 |
75 | import { CUSTOM_PROVIDERS } from '@tm/core';
76 |
77 | import {
78 | COMPLEXITY_REPORT_FILE,
79 | TASKMASTER_TASKS_FILE,
80 | TASKMASTER_DOCS_DIR
81 | } from '../../src/constants/paths.js';
82 |
83 | import { initTaskMaster } from '../../src/task-master.js';
84 |
85 | import {
86 | displayBanner,
87 | displayHelp,
88 | displayComplexityReport,
89 | getStatusWithColor,
90 | confirmTaskOverwrite,
91 | startLoadingIndicator,
92 | stopLoadingIndicator,
93 | displayModelConfiguration,
94 | displayAvailableModels,
95 | displayApiKeyStatus,
96 | displayTaggedTasksFYI,
97 | displayCurrentTagIndicator,
98 | displayCrossTagDependencyError,
99 | displaySubtaskMoveError,
100 | displayInvalidTagCombinationError,
101 | displayDependencyValidationHints
102 | } from './ui.js';
103 | import {
104 | confirmProfilesRemove,
105 | confirmRemoveAllRemainingProfiles
106 | } from '../../src/ui/confirm.js';
107 | import {
108 | wouldRemovalLeaveNoProfiles,
109 | getInstalledProfiles
110 | } from '../../src/utils/profiles.js';
111 |
112 | import { initializeProject } from '../init.js';
113 | import {
114 | getModelConfiguration,
115 | getAvailableModelsList,
116 | setModel,
117 | getApiKeyStatusReport
118 | } from './task-manager/models.js';
119 | import {
120 | isValidRulesAction,
121 | RULES_ACTIONS,
122 | RULES_SETUP_ACTION
123 | } from '../../src/constants/rules-actions.js';
124 | import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
125 | import { syncTasksToReadme } from './sync-readme.js';
126 | import { RULE_PROFILES } from '../../src/constants/profiles.js';
127 | import {
128 | convertAllRulesToProfileRules,
129 | removeProfileRules,
130 | isValidProfile,
131 | getRulesProfile
132 | } from '../../src/utils/rule-transformer.js';
133 | import {
134 | runInteractiveProfilesSetup,
135 | generateProfileSummary,
136 | categorizeProfileResults,
137 | generateProfileRemovalSummary,
138 | categorizeRemovalResults
139 | } from '../../src/utils/profiles.js';
140 |
141 | /**
142 | * Configure and register CLI commands
143 | * @param {Object} program - Commander program instance
144 | */
145 | function registerCommands(programInstance) {
146 | // Add global error handler for unknown options
147 | programInstance.on('option:unknown', function (unknownOption) {
148 | const commandName = this._name || 'unknown';
149 | console.error(chalk.red(`Error: Unknown option '${unknownOption}'`));
150 | console.error(
151 | chalk.yellow(
152 | `Run 'task-master ${commandName} --help' to see available options`
153 | )
154 | );
155 | process.exit(1);
156 | });
157 |
158 | // parse-prd command
159 | programInstance
160 | .command('parse-prd')
161 | .description('Parse a PRD file and generate tasks')
162 | .argument('[file]', 'Path to the PRD file')
163 | .option(
164 | '-i, --input <file>',
165 | 'Path to the PRD file (alternative to positional argument)'
166 | )
167 | .option('-o, --output <file>', 'Output file path')
168 | .option(
169 | '-n, --num-tasks <number>',
170 | 'Number of tasks to generate',
171 | getDefaultNumTasks()
172 | )
173 | .option('-f, --force', 'Skip confirmation when overwriting existing tasks')
174 | .option(
175 | '--append',
176 | 'Append new tasks to existing tasks.json instead of overwriting'
177 | )
178 | .option(
179 | '-r, --research',
180 | 'Use Perplexity AI for research-backed task generation, providing more comprehensive and accurate task breakdown'
181 | )
182 | .option('--tag <tag>', 'Specify tag context for task operations')
183 | .action(async (file, options) => {
184 | // Initialize TaskMaster
185 | let taskMaster;
186 | try {
187 | const initOptions = {
188 | prdPath: file || options.input || true,
189 | tag: options.tag
190 | };
191 | // Only include tasksPath if output is explicitly specified
192 | if (options.output) {
193 | initOptions.tasksPath = options.output;
194 | }
195 | taskMaster = initTaskMaster(initOptions);
196 | } catch (error) {
197 | console.log(
198 | boxen(
199 | `${chalk.white.bold('Parse PRD Help')}\n\n${chalk.cyan('Usage:')}\n task-master parse-prd <prd-file.txt> [options]\n\n${chalk.cyan('Options:')}\n -i, --input <file> Path to the PRD file (alternative to positional argument)\n -o, --output <file> Output file path (default: .taskmaster/tasks/tasks.json)\n -n, --num-tasks <number> Number of tasks to generate (default: 10)\n -f, --force Skip confirmation when overwriting existing tasks\n --append Append new tasks to existing tasks.json instead of overwriting\n -r, --research Use Perplexity AI for research-backed task generation\n\n${chalk.cyan('Example:')}\n task-master parse-prd requirements.txt --num-tasks 15\n task-master parse-prd --input=requirements.txt\n task-master parse-prd --force\n task-master parse-prd requirements_v2.txt --append\n task-master parse-prd requirements.txt --research\n\n${chalk.yellow('Note: This command will:')}\n 1. Look for a PRD file at ${TASKMASTER_DOCS_DIR}/PRD.md by default\n 2. Use the file specified by --input or positional argument if provided\n 3. Generate tasks from the PRD and either:\n - Overwrite any existing tasks.json file (default)\n - Append to existing tasks.json if --append is used`,
200 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
201 | )
202 | );
203 | console.error(chalk.red(`\nError: ${error.message}`));
204 | process.exit(1);
205 | }
206 |
207 | const numTasks = parseInt(options.numTasks, 10);
208 | const force = options.force || false;
209 | const append = options.append || false;
210 | const research = options.research || false;
211 | let useForce = force;
212 | const useAppend = append;
213 |
214 | // Resolve tag using standard pattern
215 | const tag = taskMaster.getCurrentTag();
216 |
217 | // Show current tag context
218 | displayCurrentTagIndicator(tag);
219 |
220 | // Helper function to check if there are existing tasks in the target tag and confirm overwrite
221 | async function confirmOverwriteIfNeeded() {
222 | // Check if there are existing tasks in the target tag
223 | let hasExistingTasksInTag = false;
224 | const tasksPath = taskMaster.getTasksPath();
225 | if (fs.existsSync(tasksPath)) {
226 | try {
227 | // Read the entire file to check if the tag exists
228 | const existingFileContent = fs.readFileSync(tasksPath, 'utf8');
229 | const allData = JSON.parse(existingFileContent);
230 |
231 | // Check if the target tag exists and has tasks
232 | if (
233 | allData[tag] &&
234 | Array.isArray(allData[tag].tasks) &&
235 | allData[tag].tasks.length > 0
236 | ) {
237 | hasExistingTasksInTag = true;
238 | }
239 | } catch (error) {
240 | // If we can't read the file or parse it, assume no existing tasks in this tag
241 | hasExistingTasksInTag = false;
242 | }
243 | }
244 |
245 | // Only show confirmation if there are existing tasks in the target tag
246 | if (hasExistingTasksInTag && !useForce && !useAppend) {
247 | const overwrite = await confirmTaskOverwrite(tasksPath);
248 | if (!overwrite) {
249 | log('info', 'Operation cancelled.');
250 | return false;
251 | }
252 | // If user confirms 'y', we should set useForce = true for the parsePRD call
253 | // Only overwrite if not appending
254 | useForce = true;
255 | }
256 | return true;
257 | }
258 |
259 | try {
260 | if (!(await confirmOverwriteIfNeeded())) return;
261 |
262 | console.log(chalk.blue(`Parsing PRD file: ${taskMaster.getPrdPath()}`));
263 | console.log(chalk.blue(`Generating ${numTasks} tasks...`));
264 | if (append) {
265 | console.log(chalk.blue('Appending to existing tasks...'));
266 | }
267 | if (research) {
268 | console.log(
269 | chalk.blue(
270 | 'Using Perplexity AI for research-backed task generation'
271 | )
272 | );
273 | }
274 |
275 | // Handle case where getTasksPath() returns null
276 | const outputPath =
277 | taskMaster.getTasksPath() ||
278 | path.join(taskMaster.getProjectRoot(), TASKMASTER_TASKS_FILE);
279 | await parsePRD(taskMaster.getPrdPath(), outputPath, numTasks, {
280 | append: useAppend,
281 | force: useForce,
282 | research: research,
283 | projectRoot: taskMaster.getProjectRoot(),
284 | tag: tag
285 | });
286 | } catch (error) {
287 | console.error(chalk.red(`Error parsing PRD: ${error.message}`));
288 | process.exit(1);
289 | }
290 | });
291 |
292 | // update command
293 | programInstance
294 | .command('update')
295 | .description(
296 | 'Update multiple tasks with ID >= "from" based on new information or implementation changes'
297 | )
298 | .option(
299 | '-f, --file <file>',
300 | 'Path to the tasks file',
301 | TASKMASTER_TASKS_FILE
302 | )
303 | .option(
304 | '--from <id>',
305 | 'Task ID to start updating from (tasks with ID >= this value will be updated)',
306 | '1'
307 | )
308 | .option(
309 | '-p, --prompt <text>',
310 | 'Prompt explaining the changes or new context (required)'
311 | )
312 | .option(
313 | '-r, --research',
314 | 'Use Perplexity AI for research-backed task updates'
315 | )
316 | .option('--tag <tag>', 'Specify tag context for task operations')
317 | .action(async (options) => {
318 | // Initialize TaskMaster
319 | const taskMaster = initTaskMaster({
320 | tasksPath: options.file || true,
321 | tag: options.tag
322 | });
323 |
324 | const fromId = parseInt(options.from, 10); // Validation happens here
325 | const prompt = options.prompt;
326 | const useResearch = options.research || false;
327 |
328 | const tasksPath = taskMaster.getTasksPath();
329 |
330 | // Resolve tag using standard pattern
331 | const tag = taskMaster.getCurrentTag();
332 |
333 | // Show current tag context
334 | displayCurrentTagIndicator(tag);
335 |
336 | // Check if there's an 'id' option which is a common mistake (instead of 'from')
337 | if (
338 | process.argv.includes('--id') ||
339 | process.argv.some((arg) => arg.startsWith('--id='))
340 | ) {
341 | console.error(
342 | chalk.red('Error: The update command uses --from=<id>, not --id=<id>')
343 | );
344 | console.log(chalk.yellow('\nTo update multiple tasks:'));
345 | console.log(
346 | ` task-master update --from=${fromId} --prompt="Your prompt here"`
347 | );
348 | console.log(
349 | chalk.yellow(
350 | '\nTo update a single specific task, use the update-task command instead:'
351 | )
352 | );
353 | console.log(
354 | ` task-master update-task --id=<id> --prompt="Your prompt here"`
355 | );
356 | process.exit(1);
357 | }
358 |
359 | if (!prompt) {
360 | console.error(
361 | chalk.red(
362 | 'Error: --prompt parameter is required. Please provide information about the changes.'
363 | )
364 | );
365 | process.exit(1);
366 | }
367 |
368 | console.log(
369 | chalk.blue(
370 | `Updating tasks from ID >= ${fromId} with prompt: "${prompt}"`
371 | )
372 | );
373 | console.log(chalk.blue(`Tasks file: ${tasksPath}`));
374 |
375 | if (useResearch) {
376 | console.log(
377 | chalk.blue('Using Perplexity AI for research-backed task updates')
378 | );
379 | }
380 |
381 | // Call core updateTasks, passing context for CLI
382 | await updateTasks(
383 | taskMaster.getTasksPath(),
384 | fromId,
385 | prompt,
386 | useResearch,
387 | { projectRoot: taskMaster.getProjectRoot(), tag } // Pass context with projectRoot and tag
388 | );
389 | });
390 |
391 | // update-task command
392 | programInstance
393 | .command('update-task')
394 | .description(
395 | 'Update a single specific task by ID with new information (use --id parameter)'
396 | )
397 | .option(
398 | '-f, --file <file>',
399 | 'Path to the tasks file',
400 | TASKMASTER_TASKS_FILE
401 | )
402 | .option('-i, --id <id>', 'Task ID to update (required)')
403 | .option(
404 | '-p, --prompt <text>',
405 | 'Prompt explaining the changes or new context (required)'
406 | )
407 | .option(
408 | '-r, --research',
409 | 'Use Perplexity AI for research-backed task updates'
410 | )
411 | .option(
412 | '--append',
413 | 'Append timestamped information to task details instead of full update'
414 | )
415 | .option('--tag <tag>', 'Specify tag context for task operations')
416 | .action(async (options) => {
417 | try {
418 | // Initialize TaskMaster
419 | const taskMaster = initTaskMaster({
420 | tasksPath: options.file || true,
421 | tag: options.tag
422 | });
423 | const tasksPath = taskMaster.getTasksPath();
424 |
425 | // Resolve tag using standard pattern
426 | const tag = taskMaster.getCurrentTag();
427 |
428 | // Show current tag context
429 | displayCurrentTagIndicator(tag);
430 |
431 | // Validate required parameters
432 | if (!options.id) {
433 | console.error(chalk.red('Error: --id parameter is required'));
434 | console.log(
435 | chalk.yellow(
436 | 'Usage example: task-master update-task --id=23 --prompt="Update with new information"'
437 | )
438 | );
439 | process.exit(1);
440 | }
441 |
442 | // Parse the task ID and validate it's a number or a string like ham-123 or tas-456
443 | // Accept valid task IDs:
444 | // - positive integers (e.g. 1,2,3)
445 | // - strings like ham-123, ham-1, tas-456, etc
446 | // Disallow decimals and invalid formats
447 | const validId =
448 | /^\d+$/.test(options.id) || // plain positive integer
449 | /^[a-z]+-\d+$/i.test(options.id); // label-number format (e.g., ham-123)
450 |
451 | if (!validId) {
452 | console.error(
453 | chalk.red(
454 | `Error: Invalid task ID: ${options.id}. Task ID must be a positive integer or in the form "ham-123".`
455 | )
456 | );
457 | console.log(
458 | chalk.yellow(
459 | 'Usage example: task-master update-task --id=23 --prompt="Update with new information"'
460 | )
461 | );
462 | process.exit(1);
463 | }
464 |
465 | const taskId = options.id;
466 |
467 | if (!options.prompt) {
468 | console.error(
469 | chalk.red(
470 | 'Error: --prompt parameter is required. Please provide information about the changes.'
471 | )
472 | );
473 | console.log(
474 | chalk.yellow(
475 | 'Usage example: task-master update-task --id=23 --prompt="Update with new information"'
476 | )
477 | );
478 | process.exit(1);
479 | }
480 |
481 | const prompt = options.prompt;
482 | const useResearch = options.research || false;
483 |
484 | // Validate tasks file exists
485 | if (!fs.existsSync(tasksPath)) {
486 | console.error(
487 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
488 | );
489 | if (tasksPath === TASKMASTER_TASKS_FILE) {
490 | console.log(
491 | chalk.yellow(
492 | 'Hint: Run task-master init or task-master parse-prd to create tasks.json first'
493 | )
494 | );
495 | } else {
496 | console.log(
497 | chalk.yellow(
498 | `Hint: Check if the file path is correct: ${tasksPath}`
499 | )
500 | );
501 | }
502 | process.exit(1);
503 | }
504 |
505 | console.log(
506 | chalk.blue(`Updating task ${taskId} with prompt: "${prompt}"`)
507 | );
508 | console.log(chalk.blue(`Tasks file: ${tasksPath}`));
509 |
510 | if (useResearch) {
511 | // Verify Perplexity API key exists if using research
512 | if (!isApiKeySet('perplexity')) {
513 | console.log(
514 | chalk.yellow(
515 | 'Warning: PERPLEXITY_API_KEY environment variable is missing. Research-backed updates will not be available.'
516 | )
517 | );
518 | console.log(
519 | chalk.yellow('Falling back to Claude AI for task update.')
520 | );
521 | } else {
522 | console.log(
523 | chalk.blue('Using Perplexity AI for research-backed task update')
524 | );
525 | }
526 | }
527 |
528 | const result = await updateTaskById(
529 | taskMaster.getTasksPath(),
530 | taskId,
531 | prompt,
532 | useResearch,
533 | { projectRoot: taskMaster.getProjectRoot(), tag },
534 | 'text',
535 | options.append || false
536 | );
537 |
538 | // If the task wasn't updated (e.g., if it was already marked as done)
539 | if (!result) {
540 | console.log(
541 | chalk.yellow(
542 | '\nTask update was not completed. Review the messages above for details.'
543 | )
544 | );
545 | }
546 | } catch (error) {
547 | console.error(chalk.red(`Error: ${error.message}`));
548 |
549 | // Provide more helpful error messages for common issues
550 | if (
551 | error.message.includes('task') &&
552 | error.message.includes('not found')
553 | ) {
554 | console.log(chalk.yellow('\nTo fix this issue:'));
555 | console.log(
556 | ' 1. Run task-master list to see all available task IDs'
557 | );
558 | console.log(' 2. Use a valid task ID with the --id parameter');
559 | } else if (error.message.includes('API key')) {
560 | console.log(
561 | chalk.yellow(
562 | '\nThis error is related to API keys. Check your environment variables.'
563 | )
564 | );
565 | }
566 |
567 | // Use getDebugFlag getter instead of CONFIG.debug
568 | if (getDebugFlag()) {
569 | console.error(error);
570 | }
571 |
572 | process.exit(1);
573 | }
574 | });
575 |
576 | // update-subtask command
577 | programInstance
578 | .command('update-subtask')
579 | .description(
580 | 'Update a subtask by appending additional timestamped information'
581 | )
582 | .option(
583 | '-f, --file <file>',
584 | 'Path to the tasks file',
585 | TASKMASTER_TASKS_FILE
586 | )
587 | .option(
588 | '-i, --id <id>',
589 | 'Subtask ID to update in format "parentId.subtaskId" (required)'
590 | )
591 | .option(
592 | '-p, --prompt <text>',
593 | 'Prompt explaining what information to add (required)'
594 | )
595 | .option('-r, --research', 'Use Perplexity AI for research-backed updates')
596 | .option('--tag <tag>', 'Specify tag context for task operations')
597 | .action(async (options) => {
598 | try {
599 | // Initialize TaskMaster
600 | const taskMaster = initTaskMaster({
601 | tasksPath: options.file || true,
602 | tag: options.tag
603 | });
604 | const tasksPath = taskMaster.getTasksPath();
605 |
606 | // Resolve tag using standard pattern
607 | const tag = taskMaster.getCurrentTag();
608 |
609 | // Show current tag context
610 | displayCurrentTagIndicator(tag);
611 |
612 | // Validate required parameters
613 | if (!options.id) {
614 | console.error(chalk.red('Error: --id parameter is required'));
615 | console.log(
616 | chalk.yellow(
617 | 'Usage example: task-master update-subtask --id=5.2 --prompt="Add more details about the API endpoint"'
618 | )
619 | );
620 | process.exit(1);
621 | }
622 |
623 | // Validate subtask ID format (should contain a dot)
624 | const subtaskId = options.id;
625 | if (!subtaskId.includes('.')) {
626 | console.error(
627 | chalk.red(
628 | `Error: Invalid subtask ID format: ${subtaskId}. Subtask ID must be in format "parentId.subtaskId"`
629 | )
630 | );
631 | console.log(
632 | chalk.yellow(
633 | 'Usage example: task-master update-subtask --id=5.2 --prompt="Add more details about the API endpoint"'
634 | )
635 | );
636 | process.exit(1);
637 | }
638 |
639 | if (!options.prompt) {
640 | console.error(
641 | chalk.red(
642 | 'Error: --prompt parameter is required. Please provide information to add to the subtask.'
643 | )
644 | );
645 | console.log(
646 | chalk.yellow(
647 | 'Usage example: task-master update-subtask --id=5.2 --prompt="Add more details about the API endpoint"'
648 | )
649 | );
650 | process.exit(1);
651 | }
652 |
653 | const prompt = options.prompt;
654 | const useResearch = options.research || false;
655 |
656 | // Validate tasks file exists
657 | if (!fs.existsSync(tasksPath)) {
658 | console.error(
659 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
660 | );
661 | if (tasksPath === TASKMASTER_TASKS_FILE) {
662 | console.log(
663 | chalk.yellow(
664 | 'Hint: Run task-master init or task-master parse-prd to create tasks.json first'
665 | )
666 | );
667 | } else {
668 | console.log(
669 | chalk.yellow(
670 | `Hint: Check if the file path is correct: ${tasksPath}`
671 | )
672 | );
673 | }
674 | process.exit(1);
675 | }
676 |
677 | console.log(
678 | chalk.blue(`Updating subtask ${subtaskId} with prompt: "${prompt}"`)
679 | );
680 | console.log(chalk.blue(`Tasks file: ${tasksPath}`));
681 |
682 | if (useResearch) {
683 | // Verify Perplexity API key exists if using research
684 | if (!isApiKeySet('perplexity')) {
685 | console.log(
686 | chalk.yellow(
687 | 'Warning: PERPLEXITY_API_KEY environment variable is missing. Research-backed updates will not be available.'
688 | )
689 | );
690 | console.log(
691 | chalk.yellow('Falling back to Claude AI for subtask update.')
692 | );
693 | } else {
694 | console.log(
695 | chalk.blue(
696 | 'Using Perplexity AI for research-backed subtask update'
697 | )
698 | );
699 | }
700 | }
701 |
702 | const result = await updateSubtaskById(
703 | taskMaster.getTasksPath(),
704 | subtaskId,
705 | prompt,
706 | useResearch,
707 | { projectRoot: taskMaster.getProjectRoot(), tag }
708 | );
709 |
710 | if (!result) {
711 | console.log(
712 | chalk.yellow(
713 | '\nSubtask update was not completed. Review the messages above for details.'
714 | )
715 | );
716 | }
717 | } catch (error) {
718 | console.error(chalk.red(`Error: ${error.message}`));
719 |
720 | // Provide more helpful error messages for common issues
721 | if (
722 | error.message.includes('subtask') &&
723 | error.message.includes('not found')
724 | ) {
725 | console.log(chalk.yellow('\nTo fix this issue:'));
726 | console.log(
727 | ' 1. Run task-master list --with-subtasks to see all available subtask IDs'
728 | );
729 | console.log(
730 | ' 2. Use a valid subtask ID with the --id parameter in format "parentId.subtaskId"'
731 | );
732 | } else if (error.message.includes('API key')) {
733 | console.log(
734 | chalk.yellow(
735 | '\nThis error is related to API keys. Check your environment variables.'
736 | )
737 | );
738 | }
739 |
740 | // Use getDebugFlag getter instead of CONFIG.debug
741 | if (getDebugFlag()) {
742 | console.error(error);
743 | }
744 |
745 | process.exit(1);
746 | }
747 | });
748 |
749 | // scope-up command
750 | programInstance
751 | .command('scope-up')
752 | .description('Increase task complexity with AI assistance')
753 | .option(
754 | '-f, --file <file>',
755 | 'Path to the tasks file',
756 | TASKMASTER_TASKS_FILE
757 | )
758 | .option(
759 | '-i, --id <ids>',
760 | 'Comma-separated task/subtask IDs to scope up (required)'
761 | )
762 | .option(
763 | '-s, --strength <level>',
764 | 'Complexity increase strength: light, regular, heavy',
765 | 'regular'
766 | )
767 | .option(
768 | '-p, --prompt <text>',
769 | 'Custom instructions for targeted scope adjustments'
770 | )
771 | .option('-r, --research', 'Use research AI for more informed adjustments')
772 | .option('--tag <tag>', 'Specify tag context for task operations')
773 | .action(async (options) => {
774 | try {
775 | // Initialize TaskMaster
776 | const taskMaster = initTaskMaster({
777 | tasksPath: options.file || true,
778 | tag: options.tag
779 | });
780 | const tasksPath = taskMaster.getTasksPath();
781 | const tag = taskMaster.getCurrentTag();
782 |
783 | // Show current tag context
784 | displayCurrentTagIndicator(tag);
785 |
786 | // Validate required parameters
787 | if (!options.id) {
788 | console.error(chalk.red('Error: --id parameter is required'));
789 | console.log(
790 | chalk.yellow(
791 | 'Usage example: task-master scope-up --id=1,2,3 --strength=regular'
792 | )
793 | );
794 | process.exit(1);
795 | }
796 |
797 | // Parse and validate task IDs
798 | const taskIds = options.id.split(',').map((id) => {
799 | const parsed = parseInt(id.trim(), 10);
800 | if (Number.isNaN(parsed) || parsed <= 0) {
801 | console.error(chalk.red(`Error: Invalid task ID: ${id.trim()}`));
802 | process.exit(1);
803 | }
804 | return parsed;
805 | });
806 |
807 | // Validate strength level
808 | if (!validateStrength(options.strength)) {
809 | console.error(
810 | chalk.red(
811 | `Error: Invalid strength level: ${options.strength}. Must be one of: light, regular, heavy`
812 | )
813 | );
814 | process.exit(1);
815 | }
816 |
817 | // Validate tasks file exists
818 | if (!fs.existsSync(tasksPath)) {
819 | console.error(
820 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
821 | );
822 | process.exit(1);
823 | }
824 |
825 | console.log(
826 | chalk.blue(
827 | `Scoping up ${taskIds.length} task(s): ${taskIds.join(', ')}`
828 | )
829 | );
830 | console.log(chalk.blue(`Strength level: ${options.strength}`));
831 | if (options.prompt) {
832 | console.log(chalk.blue(`Custom instructions: ${options.prompt}`));
833 | }
834 |
835 | const context = {
836 | projectRoot: taskMaster.getProjectRoot(),
837 | tag,
838 | commandName: 'scope-up',
839 | outputType: 'cli',
840 | research: options.research || false
841 | };
842 |
843 | const result = await scopeUpTask(
844 | tasksPath,
845 | taskIds,
846 | options.strength,
847 | options.prompt || null,
848 | context,
849 | 'text'
850 | );
851 |
852 | console.log(
853 | chalk.green(
854 | `✅ Successfully scoped up ${result.updatedTasks.length} task(s)`
855 | )
856 | );
857 | } catch (error) {
858 | console.error(chalk.red(`Error: ${error.message}`));
859 |
860 | if (error.message.includes('not found')) {
861 | console.log(chalk.yellow('\nTo fix this issue:'));
862 | console.log(
863 | ' 1. Run task-master list to see all available task IDs'
864 | );
865 | console.log(' 2. Use valid task IDs with the --id parameter');
866 | }
867 |
868 | if (getDebugFlag()) {
869 | console.error(error);
870 | }
871 |
872 | process.exit(1);
873 | }
874 | });
875 |
876 | // scope-down command
877 | programInstance
878 | .command('scope-down')
879 | .description('Decrease task complexity with AI assistance')
880 | .option(
881 | '-f, --file <file>',
882 | 'Path to the tasks file',
883 | TASKMASTER_TASKS_FILE
884 | )
885 | .option(
886 | '-i, --id <ids>',
887 | 'Comma-separated task/subtask IDs to scope down (required)'
888 | )
889 | .option(
890 | '-s, --strength <level>',
891 | 'Complexity decrease strength: light, regular, heavy',
892 | 'regular'
893 | )
894 | .option(
895 | '-p, --prompt <text>',
896 | 'Custom instructions for targeted scope adjustments'
897 | )
898 | .option('-r, --research', 'Use research AI for more informed adjustments')
899 | .option('--tag <tag>', 'Specify tag context for task operations')
900 | .action(async (options) => {
901 | try {
902 | // Initialize TaskMaster
903 | const taskMaster = initTaskMaster({
904 | tasksPath: options.file || true,
905 | tag: options.tag
906 | });
907 | const tasksPath = taskMaster.getTasksPath();
908 | const tag = taskMaster.getCurrentTag();
909 |
910 | // Show current tag context
911 | displayCurrentTagIndicator(tag);
912 |
913 | // Validate required parameters
914 | if (!options.id) {
915 | console.error(chalk.red('Error: --id parameter is required'));
916 | console.log(
917 | chalk.yellow(
918 | 'Usage example: task-master scope-down --id=1,2,3 --strength=regular'
919 | )
920 | );
921 | process.exit(1);
922 | }
923 |
924 | // Parse and validate task IDs
925 | const taskIds = options.id.split(',').map((id) => {
926 | const parsed = parseInt(id.trim(), 10);
927 | if (Number.isNaN(parsed) || parsed <= 0) {
928 | console.error(chalk.red(`Error: Invalid task ID: ${id.trim()}`));
929 | process.exit(1);
930 | }
931 | return parsed;
932 | });
933 |
934 | // Validate strength level
935 | if (!validateStrength(options.strength)) {
936 | console.error(
937 | chalk.red(
938 | `Error: Invalid strength level: ${options.strength}. Must be one of: light, regular, heavy`
939 | )
940 | );
941 | process.exit(1);
942 | }
943 |
944 | // Validate tasks file exists
945 | if (!fs.existsSync(tasksPath)) {
946 | console.error(
947 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
948 | );
949 | process.exit(1);
950 | }
951 |
952 | console.log(
953 | chalk.blue(
954 | `Scoping down ${taskIds.length} task(s): ${taskIds.join(', ')}`
955 | )
956 | );
957 | console.log(chalk.blue(`Strength level: ${options.strength}`));
958 | if (options.prompt) {
959 | console.log(chalk.blue(`Custom instructions: ${options.prompt}`));
960 | }
961 |
962 | const context = {
963 | projectRoot: taskMaster.getProjectRoot(),
964 | tag,
965 | commandName: 'scope-down',
966 | outputType: 'cli',
967 | research: options.research || false
968 | };
969 |
970 | const result = await scopeDownTask(
971 | tasksPath,
972 | taskIds,
973 | options.strength,
974 | options.prompt || null,
975 | context,
976 | 'text'
977 | );
978 |
979 | console.log(
980 | chalk.green(
981 | `✅ Successfully scoped down ${result.updatedTasks.length} task(s)`
982 | )
983 | );
984 | } catch (error) {
985 | console.error(chalk.red(`Error: ${error.message}`));
986 |
987 | if (error.message.includes('not found')) {
988 | console.log(chalk.yellow('\nTo fix this issue:'));
989 | console.log(
990 | ' 1. Run task-master list to see all available task IDs'
991 | );
992 | console.log(' 2. Use valid task IDs with the --id parameter');
993 | }
994 |
995 | if (getDebugFlag()) {
996 | console.error(error);
997 | }
998 |
999 | process.exit(1);
1000 | }
1001 | });
1002 |
1003 | // generate command
1004 | programInstance
1005 | .command('generate')
1006 | .description('Generate task files from tasks.json')
1007 | .option(
1008 | '-f, --file <file>',
1009 | 'Path to the tasks file',
1010 | TASKMASTER_TASKS_FILE
1011 | )
1012 | .option(
1013 | '-o, --output <dir>',
1014 | 'Output directory',
1015 | path.dirname(TASKMASTER_TASKS_FILE)
1016 | )
1017 | .option('--tag <tag>', 'Specify tag context for task operations')
1018 | .action(async (options) => {
1019 | // Initialize TaskMaster
1020 | const taskMaster = initTaskMaster({
1021 | tasksPath: options.file || true,
1022 | tag: options.tag
1023 | });
1024 |
1025 | const outputDir = options.output;
1026 | const tag = taskMaster.getCurrentTag();
1027 |
1028 | console.log(
1029 | chalk.blue(`Generating task files from: ${taskMaster.getTasksPath()}`)
1030 | );
1031 | console.log(chalk.blue(`Output directory: ${outputDir}`));
1032 |
1033 | await generateTaskFiles(taskMaster.getTasksPath(), outputDir, {
1034 | projectRoot: taskMaster.getProjectRoot(),
1035 | tag
1036 | });
1037 | });
1038 |
1039 | // ========================================
1040 | // Register All Commands from @tm/cli
1041 | // ========================================
1042 | // Use the centralized command registry to register all CLI commands
1043 | // This replaces individual command registrations and reduces duplication
1044 | registerAllCommands(programInstance);
1045 |
1046 | // expand command
1047 | programInstance
1048 | .command('expand')
1049 | .description('Expand a task into subtasks using AI')
1050 | .option('-i, --id <id>', 'ID of the task to expand')
1051 | .option(
1052 | '-a, --all',
1053 | 'Expand all pending tasks based on complexity analysis'
1054 | )
1055 | .option(
1056 | '-n, --num <number>',
1057 | 'Number of subtasks to generate (uses complexity analysis by default if available)'
1058 | )
1059 | .option(
1060 | '-r, --research',
1061 | 'Enable research-backed generation (e.g., using Perplexity)',
1062 | false
1063 | )
1064 | .option('-p, --prompt <text>', 'Additional context for subtask generation')
1065 | .option('-f, --force', 'Force expansion even if subtasks exist', false) // Ensure force option exists
1066 | .option(
1067 | '--file <file>',
1068 | 'Path to the tasks file (relative to project root)',
1069 | TASKMASTER_TASKS_FILE // Allow file override
1070 | ) // Allow file override
1071 | .option(
1072 | '-cr, --complexity-report <file>',
1073 | 'Path to the complexity report file (use this to specify the complexity report, not --file)'
1074 | // Removed default value to allow tag-specific auto-detection
1075 | )
1076 | .option('--tag <tag>', 'Specify tag context for task operations')
1077 | .action(async (options) => {
1078 | // Initialize TaskMaster
1079 | const initOptions = {
1080 | tasksPath: options.file || true,
1081 | tag: options.tag
1082 | };
1083 |
1084 | if (options.complexityReport) {
1085 | initOptions.complexityReportPath = options.complexityReport;
1086 | }
1087 |
1088 | const taskMaster = initTaskMaster(initOptions);
1089 |
1090 | const tag = taskMaster.getCurrentTag();
1091 |
1092 | // Show current tag context
1093 | displayCurrentTagIndicator(tag);
1094 |
1095 | if (options.all) {
1096 | // --- Handle expand --all ---
1097 | console.log(chalk.blue('Expanding all pending tasks...'));
1098 | // Updated call to the refactored expandAllTasks
1099 | try {
1100 | const result = await expandAllTasks(
1101 | taskMaster.getTasksPath(),
1102 | options.num, // Pass num
1103 | options.research, // Pass research flag
1104 | options.prompt, // Pass additional context
1105 | options.force, // Pass force flag
1106 | {
1107 | projectRoot: taskMaster.getProjectRoot(),
1108 | tag,
1109 | complexityReportPath: taskMaster.getComplexityReportPath()
1110 | } // Pass context with projectRoot and tag
1111 | // outputFormat defaults to 'text' in expandAllTasks for CLI
1112 | );
1113 | } catch (error) {
1114 | console.error(
1115 | chalk.red(`Error expanding all tasks: ${error.message}`)
1116 | );
1117 | process.exit(1);
1118 | }
1119 | } else if (options.id) {
1120 | // --- Handle expand --id <id> (Should be correct from previous refactor) ---
1121 | if (!options.id) {
1122 | console.error(
1123 | chalk.red('Error: Task ID is required unless using --all.')
1124 | );
1125 | process.exit(1);
1126 | }
1127 |
1128 | console.log(chalk.blue(`Expanding task ${options.id}...`));
1129 | try {
1130 | // Call the refactored expandTask function
1131 | await expandTask(
1132 | taskMaster.getTasksPath(),
1133 | options.id,
1134 | options.num,
1135 | options.research,
1136 | options.prompt,
1137 | {
1138 | projectRoot: taskMaster.getProjectRoot(),
1139 | tag,
1140 | complexityReportPath: taskMaster.getComplexityReportPath()
1141 | }, // Pass context with projectRoot and tag
1142 | options.force // Pass the force flag down
1143 | );
1144 | // expandTask logs its own success/failure for single task
1145 | } catch (error) {
1146 | console.error(
1147 | chalk.red(`Error expanding task ${options.id}: ${error.message}`)
1148 | );
1149 | process.exit(1);
1150 | }
1151 | } else {
1152 | console.error(
1153 | chalk.red('Error: You must specify either a task ID (--id) or --all.')
1154 | );
1155 | programInstance.help(); // Show help
1156 | }
1157 | });
1158 |
1159 | // analyze-complexity command
1160 | programInstance
1161 | .command('analyze-complexity')
1162 | .description(
1163 | `Analyze tasks and generate expansion recommendations${chalk.reset('')}`
1164 | )
1165 | .option('-o, --output <file>', 'Output file path for the report')
1166 | .option(
1167 | '-m, --model <model>',
1168 | 'LLM model to use for analysis (defaults to configured model)'
1169 | )
1170 | .option(
1171 | '-t, --threshold <number>',
1172 | 'Minimum complexity score to recommend expansion (1-10)',
1173 | '5'
1174 | )
1175 | .option(
1176 | '-f, --file <file>',
1177 | 'Path to the tasks file',
1178 | TASKMASTER_TASKS_FILE
1179 | )
1180 | .option(
1181 | '-r, --research',
1182 | 'Use configured research model for research-backed complexity analysis'
1183 | )
1184 | .option(
1185 | '-i, --id <ids>',
1186 | 'Comma-separated list of specific task IDs to analyze (e.g., "1,3,5")'
1187 | )
1188 | .option('--from <id>', 'Starting task ID in a range to analyze')
1189 | .option('--to <id>', 'Ending task ID in a range to analyze')
1190 | .option('--tag <tag>', 'Specify tag context for task operations')
1191 | .action(async (options) => {
1192 | // Initialize TaskMaster
1193 | const initOptions = {
1194 | tasksPath: options.file || true, // Tasks file is required to analyze
1195 | tag: options.tag
1196 | };
1197 | // Only include complexityReportPath if output is explicitly specified
1198 | if (options.output) {
1199 | initOptions.complexityReportPath = options.output;
1200 | }
1201 |
1202 | const taskMaster = initTaskMaster(initOptions);
1203 |
1204 | const modelOverride = options.model;
1205 | const thresholdScore = parseFloat(options.threshold);
1206 | const useResearch = options.research || false;
1207 |
1208 | // Use the provided tag, or the current active tag, or default to 'master'
1209 | const targetTag = taskMaster.getCurrentTag();
1210 |
1211 | // Show current tag context
1212 | displayCurrentTagIndicator(targetTag);
1213 |
1214 | // Use user's explicit output path if provided, otherwise use tag-aware default
1215 | const outputPath = taskMaster.getComplexityReportPath();
1216 |
1217 | console.log(
1218 | chalk.blue(
1219 | `Analyzing task complexity from: ${taskMaster.getTasksPath()}`
1220 | )
1221 | );
1222 | console.log(chalk.blue(`Output report will be saved to: ${outputPath}`));
1223 |
1224 | if (options.id) {
1225 | console.log(chalk.blue(`Analyzing specific task IDs: ${options.id}`));
1226 | } else if (options.from || options.to) {
1227 | const fromStr = options.from ? options.from : 'first';
1228 | const toStr = options.to ? options.to : 'last';
1229 | console.log(
1230 | chalk.blue(`Analyzing tasks in range: ${fromStr} to ${toStr}`)
1231 | );
1232 | }
1233 |
1234 | if (useResearch) {
1235 | console.log(
1236 | chalk.blue(
1237 | 'Using Perplexity AI for research-backed complexity analysis'
1238 | )
1239 | );
1240 | }
1241 |
1242 | // Update options with tag-aware output path and context
1243 | const updatedOptions = {
1244 | ...options,
1245 | output: outputPath,
1246 | tag: targetTag,
1247 | projectRoot: taskMaster.getProjectRoot(),
1248 | file: taskMaster.getTasksPath()
1249 | };
1250 |
1251 | await analyzeTaskComplexity(updatedOptions);
1252 | });
1253 |
1254 | // research command
1255 | programInstance
1256 | .command('research')
1257 | .description('Perform AI-powered research queries with project context')
1258 | .argument('[prompt]', 'Research prompt to investigate')
1259 | .option('--file <file>', 'Path to the tasks file')
1260 | .option(
1261 | '-i, --id <ids>',
1262 | 'Comma-separated task/subtask IDs to include as context (e.g., "15,16.2")'
1263 | )
1264 | .option(
1265 | '-f, --files <paths>',
1266 | 'Comma-separated file paths to include as context'
1267 | )
1268 | .option(
1269 | '-c, --context <text>',
1270 | 'Additional custom context to include in the research prompt'
1271 | )
1272 | .option(
1273 | '-t, --tree',
1274 | 'Include project file tree structure in the research context'
1275 | )
1276 | .option(
1277 | '-s, --save <file>',
1278 | 'Save research results to the specified task/subtask(s)'
1279 | )
1280 | .option(
1281 | '-d, --detail <level>',
1282 | 'Output detail level: low, medium, high',
1283 | 'medium'
1284 | )
1285 | .option(
1286 | '--save-to <id>',
1287 | 'Automatically save research results to specified task/subtask ID (e.g., "15" or "15.2")'
1288 | )
1289 | .option(
1290 | '--save-file',
1291 | 'Save research results to .taskmaster/docs/research/ directory'
1292 | )
1293 | .option('--tag <tag>', 'Specify tag context for task operations')
1294 | .action(async (prompt, options) => {
1295 | // Initialize TaskMaster
1296 | const initOptions = {
1297 | tasksPath: options.file || true,
1298 | tag: options.tag
1299 | };
1300 |
1301 | const taskMaster = initTaskMaster(initOptions);
1302 |
1303 | // Parameter validation
1304 | if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
1305 | console.error(
1306 | chalk.red('Error: Research prompt is required and cannot be empty')
1307 | );
1308 | showResearchHelp();
1309 | process.exit(1);
1310 | }
1311 |
1312 | // Validate detail level
1313 | const validDetailLevels = ['low', 'medium', 'high'];
1314 | if (
1315 | options.detail &&
1316 | !validDetailLevels.includes(options.detail.toLowerCase())
1317 | ) {
1318 | console.error(
1319 | chalk.red(
1320 | `Error: Detail level must be one of: ${validDetailLevels.join(', ')}`
1321 | )
1322 | );
1323 | process.exit(1);
1324 | }
1325 |
1326 | // Validate and parse task IDs if provided
1327 | let taskIds = [];
1328 | if (options.id) {
1329 | try {
1330 | taskIds = options.id.split(',').map((id) => {
1331 | const trimmedId = id.trim();
1332 | // Support both task IDs (e.g., "15") and subtask IDs (e.g., "15.2")
1333 | if (!/^\d+(\.\d+)?$/.test(trimmedId)) {
1334 | throw new Error(
1335 | `Invalid task ID format: "${trimmedId}". Expected format: "15" or "15.2"`
1336 | );
1337 | }
1338 | return trimmedId;
1339 | });
1340 | } catch (error) {
1341 | console.error(chalk.red(`Error parsing task IDs: ${error.message}`));
1342 | process.exit(1);
1343 | }
1344 | }
1345 |
1346 | // Validate and parse file paths if provided
1347 | let filePaths = [];
1348 | if (options.files) {
1349 | try {
1350 | filePaths = options.files.split(',').map((filePath) => {
1351 | const trimmedPath = filePath.trim();
1352 | if (trimmedPath.length === 0) {
1353 | throw new Error('Empty file path provided');
1354 | }
1355 | return trimmedPath;
1356 | });
1357 | } catch (error) {
1358 | console.error(
1359 | chalk.red(`Error parsing file paths: ${error.message}`)
1360 | );
1361 | process.exit(1);
1362 | }
1363 | }
1364 |
1365 | // Validate save-to option if provided
1366 | if (options.saveTo) {
1367 | const saveToId = options.saveTo.trim();
1368 | if (saveToId.length === 0) {
1369 | console.error(chalk.red('Error: Save-to ID cannot be empty'));
1370 | process.exit(1);
1371 | }
1372 | // Validate ID format: number or number.number
1373 | if (!/^\d+(\.\d+)?$/.test(saveToId)) {
1374 | console.error(
1375 | chalk.red(
1376 | 'Error: Save-to ID must be in format "15" for task or "15.2" for subtask'
1377 | )
1378 | );
1379 | process.exit(1);
1380 | }
1381 | }
1382 |
1383 | // Validate save option if provided (legacy file save)
1384 | if (options.save) {
1385 | const saveTarget = options.save.trim();
1386 | if (saveTarget.length === 0) {
1387 | console.error(chalk.red('Error: Save target cannot be empty'));
1388 | process.exit(1);
1389 | }
1390 | // Check if it's a valid file path (basic validation)
1391 | if (saveTarget.includes('..') || saveTarget.startsWith('/')) {
1392 | console.error(
1393 | chalk.red(
1394 | 'Error: Save path must be relative and cannot contain ".."'
1395 | )
1396 | );
1397 | process.exit(1);
1398 | }
1399 | }
1400 |
1401 | const tag = taskMaster.getCurrentTag();
1402 |
1403 | // Show current tag context
1404 | displayCurrentTagIndicator(tag);
1405 |
1406 | // Validate tasks file exists if task IDs are specified
1407 | if (taskIds.length > 0) {
1408 | try {
1409 | const tasksData = readJSON(
1410 | taskMaster.getTasksPath(),
1411 | taskMaster.getProjectRoot(),
1412 | tag
1413 | );
1414 | if (!tasksData || !tasksData.tasks) {
1415 | console.error(
1416 | chalk.red(
1417 | `Error: No valid tasks found in ${taskMaster.getTasksPath()} for tag '${tag}'`
1418 | )
1419 | );
1420 | process.exit(1);
1421 | }
1422 | } catch (error) {
1423 | console.error(
1424 | chalk.red(`Error reading tasks file: ${error.message}`)
1425 | );
1426 | process.exit(1);
1427 | }
1428 | }
1429 |
1430 | // Validate file paths exist if specified
1431 | if (filePaths.length > 0) {
1432 | for (const filePath of filePaths) {
1433 | const fullPath = path.isAbsolute(filePath)
1434 | ? filePath
1435 | : path.join(taskMaster.getProjectRoot(), filePath);
1436 | if (!fs.existsSync(fullPath)) {
1437 | console.error(chalk.red(`Error: File not found: ${filePath}`));
1438 | process.exit(1);
1439 | }
1440 | }
1441 | }
1442 |
1443 | // Create validated parameters object
1444 | const validatedParams = {
1445 | prompt: prompt.trim(),
1446 | taskIds: taskIds,
1447 | filePaths: filePaths,
1448 | customContext: options.context ? options.context.trim() : null,
1449 | includeProjectTree: !!options.tree,
1450 | saveTarget: options.save ? options.save.trim() : null,
1451 | saveToId: options.saveTo ? options.saveTo.trim() : null,
1452 | allowFollowUp: true, // Always allow follow-up in CLI
1453 | detailLevel: options.detail ? options.detail.toLowerCase() : 'medium',
1454 | tasksPath: taskMaster.getTasksPath(),
1455 | projectRoot: taskMaster.getProjectRoot()
1456 | };
1457 |
1458 | // Display what we're about to do
1459 | console.log(chalk.blue(`Researching: "${validatedParams.prompt}"`));
1460 |
1461 | if (validatedParams.taskIds.length > 0) {
1462 | console.log(
1463 | chalk.gray(`Task context: ${validatedParams.taskIds.join(', ')}`)
1464 | );
1465 | }
1466 |
1467 | if (validatedParams.filePaths.length > 0) {
1468 | console.log(
1469 | chalk.gray(`File context: ${validatedParams.filePaths.join(', ')}`)
1470 | );
1471 | }
1472 |
1473 | if (validatedParams.customContext) {
1474 | console.log(
1475 | chalk.gray(
1476 | `Custom context: ${validatedParams.customContext.substring(0, 50)}${validatedParams.customContext.length > 50 ? '...' : ''}`
1477 | )
1478 | );
1479 | }
1480 |
1481 | if (validatedParams.includeProjectTree) {
1482 | console.log(chalk.gray('Including project file tree'));
1483 | }
1484 |
1485 | console.log(chalk.gray(`Detail level: ${validatedParams.detailLevel}`));
1486 |
1487 | try {
1488 | // Import the research function
1489 | const { performResearch } = await import('./task-manager/research.js');
1490 |
1491 | // Prepare research options
1492 | const researchOptions = {
1493 | taskIds: validatedParams.taskIds,
1494 | filePaths: validatedParams.filePaths,
1495 | customContext: validatedParams.customContext || '',
1496 | includeProjectTree: validatedParams.includeProjectTree,
1497 | detailLevel: validatedParams.detailLevel,
1498 | projectRoot: validatedParams.projectRoot,
1499 | saveToFile: !!options.saveFile,
1500 | tag: tag
1501 | };
1502 |
1503 | // Execute research
1504 | const result = await performResearch(
1505 | validatedParams.prompt,
1506 | researchOptions,
1507 | {
1508 | commandName: 'research',
1509 | outputType: 'cli',
1510 | tag: tag
1511 | },
1512 | 'text',
1513 | validatedParams.allowFollowUp // Pass follow-up flag
1514 | );
1515 |
1516 | // Auto-save to task/subtask if requested and no interactive save occurred
1517 | if (validatedParams.saveToId && !result.interactiveSaveOccurred) {
1518 | try {
1519 | const isSubtask = validatedParams.saveToId.includes('.');
1520 |
1521 | // Format research content for saving
1522 | const researchContent = `## Research Query: ${validatedParams.prompt}
1523 |
1524 | **Detail Level:** ${result.detailLevel}
1525 | **Context Size:** ${result.contextSize} characters
1526 | **Timestamp:** ${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}
1527 |
1528 | ### Results
1529 |
1530 | ${result.result}`;
1531 |
1532 | if (isSubtask) {
1533 | // Save to subtask
1534 | const { updateSubtaskById } = await import(
1535 | './task-manager/update-subtask-by-id.js'
1536 | );
1537 |
1538 | await updateSubtaskById(
1539 | validatedParams.tasksPath,
1540 | validatedParams.saveToId,
1541 | researchContent,
1542 | false, // useResearch = false for simple append
1543 | {
1544 | commandName: 'research-save',
1545 | outputType: 'cli',
1546 | projectRoot: validatedParams.projectRoot,
1547 | tag: tag
1548 | },
1549 | 'text'
1550 | );
1551 |
1552 | console.log(
1553 | chalk.green(
1554 | `✅ Research saved to subtask ${validatedParams.saveToId}`
1555 | )
1556 | );
1557 | } else {
1558 | // Save to task
1559 | const updateTaskById = (
1560 | await import('./task-manager/update-task-by-id.js')
1561 | ).default;
1562 |
1563 | const taskIdNum = parseInt(validatedParams.saveToId, 10);
1564 | await updateTaskById(
1565 | validatedParams.tasksPath,
1566 | taskIdNum,
1567 | researchContent,
1568 | false, // useResearch = false for simple append
1569 | {
1570 | commandName: 'research-save',
1571 | outputType: 'cli',
1572 | projectRoot: validatedParams.projectRoot,
1573 | tag: tag
1574 | },
1575 | 'text',
1576 | true // appendMode = true
1577 | );
1578 |
1579 | console.log(
1580 | chalk.green(
1581 | `✅ Research saved to task ${validatedParams.saveToId}`
1582 | )
1583 | );
1584 | }
1585 | } catch (saveError) {
1586 | console.log(
1587 | chalk.red(`❌ Error saving to task/subtask: ${saveError.message}`)
1588 | );
1589 | }
1590 | }
1591 |
1592 | // Save results to file if requested (legacy)
1593 | if (validatedParams.saveTarget) {
1594 | const saveContent = `# Research Query: ${validatedParams.prompt}
1595 |
1596 | **Detail Level:** ${result.detailLevel}
1597 | **Context Size:** ${result.contextSize} characters
1598 | **Timestamp:** ${new Date().toISOString()}
1599 |
1600 | ## Results
1601 |
1602 | ${result.result}
1603 | `;
1604 |
1605 | fs.writeFileSync(validatedParams.saveTarget, saveContent, 'utf-8');
1606 | console.log(
1607 | chalk.green(`\n💾 Results saved to: ${validatedParams.saveTarget}`)
1608 | );
1609 | }
1610 | } catch (error) {
1611 | console.error(chalk.red(`\n❌ Research failed: ${error.message}`));
1612 | process.exit(1);
1613 | }
1614 | });
1615 |
1616 | // clear-subtasks command
1617 | programInstance
1618 | .command('clear-subtasks')
1619 | .description('Clear subtasks from specified tasks')
1620 | .option(
1621 | '-f, --file <file>',
1622 | 'Path to the tasks file',
1623 | TASKMASTER_TASKS_FILE
1624 | )
1625 | .option(
1626 | '-i, --id <ids>',
1627 | 'Task IDs (comma-separated) to clear subtasks from'
1628 | )
1629 | .option('--all', 'Clear subtasks from all tasks')
1630 | .option('--tag <tag>', 'Specify tag context for task operations')
1631 | .action(async (options) => {
1632 | const taskIds = options.id;
1633 | const all = options.all;
1634 |
1635 | // Initialize TaskMaster
1636 | const taskMaster = initTaskMaster({
1637 | tasksPath: options.file || true,
1638 | tag: options.tag
1639 | });
1640 |
1641 | const tag = taskMaster.getCurrentTag();
1642 |
1643 | // Show current tag context
1644 | displayCurrentTagIndicator(tag);
1645 |
1646 | if (!taskIds && !all) {
1647 | console.error(
1648 | chalk.red(
1649 | 'Error: Please specify task IDs with --id=<ids> or use --all to clear all tasks'
1650 | )
1651 | );
1652 | process.exit(1);
1653 | }
1654 |
1655 | if (all) {
1656 | // If --all is specified, get all task IDs
1657 | const data = readJSON(
1658 | taskMaster.getTasksPath(),
1659 | taskMaster.getProjectRoot(),
1660 | tag
1661 | );
1662 | if (!data || !data.tasks) {
1663 | console.error(chalk.red('Error: No valid tasks found'));
1664 | process.exit(1);
1665 | }
1666 | const allIds = data.tasks.map((t) => t.id).join(',');
1667 | clearSubtasks(taskMaster.getTasksPath(), allIds, {
1668 | projectRoot: taskMaster.getProjectRoot(),
1669 | tag
1670 | });
1671 | } else {
1672 | clearSubtasks(taskMaster.getTasksPath(), taskIds, {
1673 | projectRoot: taskMaster.getProjectRoot(),
1674 | tag
1675 | });
1676 | }
1677 | });
1678 |
1679 | // add-task command
1680 | programInstance
1681 | .command('add-task')
1682 | .description('Add a new task using AI, optionally providing manual details')
1683 | .option(
1684 | '-f, --file <file>',
1685 | 'Path to the tasks file',
1686 | TASKMASTER_TASKS_FILE
1687 | )
1688 | .option(
1689 | '-p, --prompt <prompt>',
1690 | 'Description of the task to add (required if not using manual fields)'
1691 | )
1692 | .option('-t, --title <title>', 'Task title (for manual task creation)')
1693 | .option(
1694 | '-d, --description <description>',
1695 | 'Task description (for manual task creation)'
1696 | )
1697 | .option(
1698 | '--details <details>',
1699 | 'Implementation details (for manual task creation)'
1700 | )
1701 | .option(
1702 | '--dependencies <dependencies>',
1703 | 'Comma-separated list of task IDs this task depends on'
1704 | )
1705 | .option(
1706 | '--priority <priority>',
1707 | 'Task priority (high, medium, low)',
1708 | 'medium'
1709 | )
1710 | .option(
1711 | '-r, --research',
1712 | 'Whether to use research capabilities for task creation'
1713 | )
1714 | .option('--tag <tag>', 'Specify tag context for task operations')
1715 | .action(async (options) => {
1716 | const isManualCreation = options.title && options.description;
1717 |
1718 | // Validate that either prompt or title+description are provided
1719 | if (!options.prompt && !isManualCreation) {
1720 | console.error(
1721 | chalk.red(
1722 | 'Error: Either --prompt or both --title and --description must be provided'
1723 | )
1724 | );
1725 | process.exit(1);
1726 | }
1727 |
1728 | const tasksPath = options.file || TASKMASTER_TASKS_FILE;
1729 |
1730 | if (!fs.existsSync(tasksPath)) {
1731 | console.error(
1732 | `❌ No tasks.json file found. Please run "task-master init" or create a tasks.json file at ${TASKMASTER_TASKS_FILE}`
1733 | );
1734 | process.exit(1);
1735 | }
1736 |
1737 | // Correctly determine projectRoot
1738 | // Initialize TaskMaster
1739 | const taskMaster = initTaskMaster({
1740 | tasksPath: options.file || true,
1741 | tag: options.tag
1742 | });
1743 |
1744 | const projectRoot = taskMaster.getProjectRoot();
1745 |
1746 | const tag = taskMaster.getCurrentTag();
1747 |
1748 | // Show current tag context
1749 | displayCurrentTagIndicator(tag);
1750 |
1751 | let manualTaskData = null;
1752 | if (isManualCreation) {
1753 | manualTaskData = {
1754 | title: options.title,
1755 | description: options.description,
1756 | details: options.details || '',
1757 | testStrategy: options.testStrategy || ''
1758 | };
1759 | // Restore specific logging for manual creation
1760 | console.log(
1761 | chalk.blue(`Creating task manually with title: "${options.title}"`)
1762 | );
1763 | } else {
1764 | // Restore specific logging for AI creation
1765 | console.log(
1766 | chalk.blue(`Creating task with AI using prompt: "${options.prompt}"`)
1767 | );
1768 | }
1769 |
1770 | // Log dependencies and priority if provided (restored)
1771 | const dependenciesArray = options.dependencies
1772 | ? options.dependencies.split(',').map((id) => id.trim())
1773 | : [];
1774 | if (dependenciesArray.length > 0) {
1775 | console.log(
1776 | chalk.blue(`Dependencies: [${dependenciesArray.join(', ')}]`)
1777 | );
1778 | }
1779 | if (options.priority) {
1780 | console.log(chalk.blue(`Priority: ${options.priority}`));
1781 | }
1782 |
1783 | const context = {
1784 | projectRoot,
1785 | tag,
1786 | commandName: 'add-task',
1787 | outputType: 'cli'
1788 | };
1789 |
1790 | try {
1791 | const { newTaskId, telemetryData } = await addTask(
1792 | taskMaster.getTasksPath(),
1793 | options.prompt,
1794 | dependenciesArray,
1795 | options.priority,
1796 | context,
1797 | 'text',
1798 | manualTaskData,
1799 | options.research
1800 | );
1801 |
1802 | // addTask handles detailed CLI success logging AND telemetry display when outputFormat is 'text'
1803 | // No need to call displayAiUsageSummary here anymore.
1804 | } catch (error) {
1805 | console.error(chalk.red(`Error adding task: ${error.message}`));
1806 | if (error.details) {
1807 | console.error(chalk.red(error.details));
1808 | }
1809 | process.exit(1);
1810 | }
1811 | });
1812 |
1813 | // add-dependency command
1814 | programInstance
1815 | .command('add-dependency')
1816 | .description('Add a dependency to a task')
1817 | .option('-i, --id <id>', 'Task ID to add dependency to')
1818 | .option('-d, --depends-on <id>', 'Task ID that will become a dependency')
1819 | .option(
1820 | '-f, --file <file>',
1821 | 'Path to the tasks file',
1822 | TASKMASTER_TASKS_FILE
1823 | )
1824 | .option('--tag <tag>', 'Specify tag context for task operations')
1825 | .action(async (options) => {
1826 | const initOptions = {
1827 | tasksPath: options.file || true,
1828 | tag: options.tag
1829 | };
1830 |
1831 | // Initialize TaskMaster
1832 | const taskMaster = initTaskMaster(initOptions);
1833 |
1834 | const taskId = options.id;
1835 | const dependencyId = options.dependsOn;
1836 |
1837 | // Resolve tag using standard pattern
1838 | const tag = taskMaster.getCurrentTag();
1839 |
1840 | // Show current tag context
1841 | displayCurrentTagIndicator(tag);
1842 |
1843 | if (!taskId || !dependencyId) {
1844 | console.error(
1845 | chalk.red('Error: Both --id and --depends-on are required')
1846 | );
1847 | process.exit(1);
1848 | }
1849 |
1850 | // Handle subtask IDs correctly by preserving the string format for IDs containing dots
1851 | // Only use parseInt for simple numeric IDs
1852 | const formattedTaskId = taskId.includes('.')
1853 | ? taskId
1854 | : parseInt(taskId, 10);
1855 | const formattedDependencyId = dependencyId.includes('.')
1856 | ? dependencyId
1857 | : parseInt(dependencyId, 10);
1858 |
1859 | await addDependency(
1860 | taskMaster.getTasksPath(),
1861 | formattedTaskId,
1862 | formattedDependencyId,
1863 | {
1864 | projectRoot: taskMaster.getProjectRoot(),
1865 | tag
1866 | }
1867 | );
1868 | });
1869 |
1870 | // remove-dependency command
1871 | programInstance
1872 | .command('remove-dependency')
1873 | .description('Remove a dependency from a task')
1874 | .option('-i, --id <id>', 'Task ID to remove dependency from')
1875 | .option('-d, --depends-on <id>', 'Task ID to remove as a dependency')
1876 | .option(
1877 | '-f, --file <file>',
1878 | 'Path to the tasks file',
1879 | TASKMASTER_TASKS_FILE
1880 | )
1881 | .option('--tag <tag>', 'Specify tag context for task operations')
1882 | .action(async (options) => {
1883 | const initOptions = {
1884 | tasksPath: options.file || true,
1885 | tag: options.tag
1886 | };
1887 |
1888 | // Initialize TaskMaster
1889 | const taskMaster = initTaskMaster(initOptions);
1890 |
1891 | const taskId = options.id;
1892 | const dependencyId = options.dependsOn;
1893 |
1894 | // Resolve tag using standard pattern
1895 | const tag = taskMaster.getCurrentTag();
1896 |
1897 | // Show current tag context
1898 | displayCurrentTagIndicator(tag);
1899 |
1900 | if (!taskId || !dependencyId) {
1901 | console.error(
1902 | chalk.red('Error: Both --id and --depends-on are required')
1903 | );
1904 | process.exit(1);
1905 | }
1906 |
1907 | // Handle subtask IDs correctly by preserving the string format for IDs containing dots
1908 | // Only use parseInt for simple numeric IDs
1909 | const formattedTaskId = taskId.includes('.')
1910 | ? taskId
1911 | : parseInt(taskId, 10);
1912 | const formattedDependencyId = dependencyId.includes('.')
1913 | ? dependencyId
1914 | : parseInt(dependencyId, 10);
1915 |
1916 | await removeDependency(
1917 | taskMaster.getTasksPath(),
1918 | formattedTaskId,
1919 | formattedDependencyId,
1920 | {
1921 | projectRoot: taskMaster.getProjectRoot(),
1922 | tag
1923 | }
1924 | );
1925 | });
1926 |
1927 | // validate-dependencies command
1928 | programInstance
1929 | .command('validate-dependencies')
1930 | .description(
1931 | `Identify invalid dependencies without fixing them${chalk.reset('')}`
1932 | )
1933 | .option(
1934 | '-f, --file <file>',
1935 | 'Path to the tasks file',
1936 | TASKMASTER_TASKS_FILE
1937 | )
1938 | .option('--tag <tag>', 'Specify tag context for task operations')
1939 | .action(async (options) => {
1940 | const initOptions = {
1941 | tasksPath: options.file || true,
1942 | tag: options.tag
1943 | };
1944 |
1945 | // Initialize TaskMaster
1946 | const taskMaster = initTaskMaster(initOptions);
1947 |
1948 | // Resolve tag using standard pattern
1949 | const tag = taskMaster.getCurrentTag();
1950 |
1951 | // Show current tag context
1952 | displayCurrentTagIndicator(tag);
1953 |
1954 | await validateDependenciesCommand(taskMaster.getTasksPath(), {
1955 | context: { projectRoot: taskMaster.getProjectRoot(), tag }
1956 | });
1957 | });
1958 |
1959 | // fix-dependencies command
1960 | programInstance
1961 | .command('fix-dependencies')
1962 | .description(`Fix invalid dependencies automatically${chalk.reset('')}`)
1963 | .option(
1964 | '-f, --file <file>',
1965 | 'Path to the tasks file',
1966 | TASKMASTER_TASKS_FILE
1967 | )
1968 | .option('--tag <tag>', 'Specify tag context for task operations')
1969 | .action(async (options) => {
1970 | const initOptions = {
1971 | tasksPath: options.file || true,
1972 | tag: options.tag
1973 | };
1974 |
1975 | // Initialize TaskMaster
1976 | const taskMaster = initTaskMaster(initOptions);
1977 |
1978 | // Resolve tag using standard pattern
1979 | const tag = taskMaster.getCurrentTag();
1980 |
1981 | // Show current tag context
1982 | displayCurrentTagIndicator(tag);
1983 |
1984 | await fixDependenciesCommand(taskMaster.getTasksPath(), {
1985 | context: { projectRoot: taskMaster.getProjectRoot(), tag }
1986 | });
1987 | });
1988 |
1989 | // complexity-report command
1990 | programInstance
1991 | .command('complexity-report')
1992 | .description(`Display the complexity analysis report${chalk.reset('')}`)
1993 | .option(
1994 | '-f, --file <file>',
1995 | 'Path to the report file',
1996 | COMPLEXITY_REPORT_FILE
1997 | )
1998 | .option('--tag <tag>', 'Specify tag context for task operations')
1999 | .action(async (options) => {
2000 | const initOptions = {
2001 | tag: options.tag
2002 | };
2003 |
2004 | if (options.file && options.file !== COMPLEXITY_REPORT_FILE) {
2005 | initOptions.complexityReportPath = options.file;
2006 | }
2007 |
2008 | // Initialize TaskMaster
2009 | const taskMaster = initTaskMaster(initOptions);
2010 |
2011 | // Show current tag context
2012 | displayCurrentTagIndicator(taskMaster.getCurrentTag());
2013 |
2014 | await displayComplexityReport(taskMaster.getComplexityReportPath());
2015 | });
2016 |
2017 | // add-subtask command
2018 | programInstance
2019 | .command('add-subtask')
2020 | .description('Add a subtask to an existing task')
2021 | .option(
2022 | '-f, --file <file>',
2023 | 'Path to the tasks file',
2024 | TASKMASTER_TASKS_FILE
2025 | )
2026 | .option('-p, --parent <id>', 'Parent task ID (required)')
2027 | .option('-i, --task-id <id>', 'Existing task ID to convert to subtask')
2028 | .option(
2029 | '-t, --title <title>',
2030 | 'Title for the new subtask (when creating a new subtask)'
2031 | )
2032 | .option('-d, --description <text>', 'Description for the new subtask')
2033 | .option('--details <text>', 'Implementation details for the new subtask')
2034 | .option(
2035 | '--dependencies <ids>',
2036 | 'Comma-separated list of dependency IDs for the new subtask'
2037 | )
2038 | .option('-s, --status <status>', 'Status for the new subtask', 'pending')
2039 | .option('--generate', 'Regenerate task files after adding subtask')
2040 | .option('--tag <tag>', 'Specify tag context for task operations')
2041 | .action(async (options) => {
2042 | // Initialize TaskMaster
2043 | const taskMaster = initTaskMaster({
2044 | tasksPath: options.file || true,
2045 | tag: options.tag
2046 | });
2047 |
2048 | const parentId = options.parent;
2049 | const existingTaskId = options.taskId;
2050 | const generateFiles = options.generate || false;
2051 |
2052 | // Resolve tag using standard pattern
2053 | const tag = taskMaster.getCurrentTag();
2054 |
2055 | // Show current tag context
2056 | displayCurrentTagIndicator(tag);
2057 |
2058 | if (!parentId) {
2059 | console.error(
2060 | chalk.red(
2061 | 'Error: --parent parameter is required. Please provide a parent task ID.'
2062 | )
2063 | );
2064 | showAddSubtaskHelp();
2065 | process.exit(1);
2066 | }
2067 |
2068 | // Parse dependencies if provided
2069 | let dependencies = [];
2070 | if (options.dependencies) {
2071 | dependencies = options.dependencies.split(',').map((id) => {
2072 | // Handle both regular IDs and dot notation
2073 | return id.includes('.') ? id.trim() : parseInt(id.trim(), 10);
2074 | });
2075 | }
2076 |
2077 | try {
2078 | if (existingTaskId) {
2079 | // Convert existing task to subtask
2080 | console.log(
2081 | chalk.blue(
2082 | `Converting task ${existingTaskId} to a subtask of ${parentId}...`
2083 | )
2084 | );
2085 | await addSubtask(
2086 | taskMaster.getTasksPath(),
2087 | parentId,
2088 | existingTaskId,
2089 | null,
2090 | generateFiles,
2091 | { projectRoot: taskMaster.getProjectRoot(), tag }
2092 | );
2093 | console.log(
2094 | chalk.green(
2095 | `✓ Task ${existingTaskId} successfully converted to a subtask of task ${parentId}`
2096 | )
2097 | );
2098 | } else if (options.title) {
2099 | // Create new subtask with provided data
2100 | console.log(
2101 | chalk.blue(`Creating new subtask for parent task ${parentId}...`)
2102 | );
2103 |
2104 | const newSubtaskData = {
2105 | title: options.title,
2106 | description: options.description || '',
2107 | details: options.details || '',
2108 | status: options.status || 'pending',
2109 | dependencies: dependencies
2110 | };
2111 |
2112 | const subtask = await addSubtask(
2113 | taskMaster.getTasksPath(),
2114 | parentId,
2115 | null,
2116 | newSubtaskData,
2117 | generateFiles,
2118 | { projectRoot: taskMaster.getProjectRoot(), tag }
2119 | );
2120 | console.log(
2121 | chalk.green(
2122 | `✓ New subtask ${parentId}.${subtask.id} successfully created`
2123 | )
2124 | );
2125 |
2126 | // Display success message and suggested next steps
2127 | console.log(
2128 | boxen(
2129 | chalk.white.bold(
2130 | `Subtask ${parentId}.${subtask.id} Added Successfully`
2131 | ) +
2132 | '\n\n' +
2133 | chalk.white(`Title: ${subtask.title}`) +
2134 | '\n' +
2135 | chalk.white(`Status: ${getStatusWithColor(subtask.status)}`) +
2136 | '\n' +
2137 | (dependencies.length > 0
2138 | ? chalk.white(`Dependencies: ${dependencies.join(', ')}`) +
2139 | '\n'
2140 | : '') +
2141 | '\n' +
2142 | chalk.white.bold('Next Steps:') +
2143 | '\n' +
2144 | chalk.cyan(
2145 | `1. Run ${chalk.yellow(`task-master show ${parentId}`)} to see the parent task with all subtasks`
2146 | ) +
2147 | '\n' +
2148 | chalk.cyan(
2149 | `2. Run ${chalk.yellow(`task-master set-status --id=${parentId}.${subtask.id} --status=in-progress`)} to start working on it`
2150 | ),
2151 | {
2152 | padding: 1,
2153 | borderColor: 'green',
2154 | borderStyle: 'round',
2155 | margin: { top: 1 }
2156 | }
2157 | )
2158 | );
2159 | } else {
2160 | console.error(
2161 | chalk.red('Error: Either --task-id or --title must be provided.')
2162 | );
2163 | console.log(
2164 | boxen(
2165 | chalk.white.bold('Usage Examples:') +
2166 | '\n\n' +
2167 | chalk.white('Convert existing task to subtask:') +
2168 | '\n' +
2169 | chalk.yellow(
2170 | ` task-master add-subtask --parent=5 --task-id=8`
2171 | ) +
2172 | '\n\n' +
2173 | chalk.white('Create new subtask:') +
2174 | '\n' +
2175 | chalk.yellow(
2176 | ` task-master add-subtask --parent=5 --title="Implement login UI" --description="Create the login form"`
2177 | ) +
2178 | '\n\n',
2179 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2180 | )
2181 | );
2182 | process.exit(1);
2183 | }
2184 | } catch (error) {
2185 | console.error(chalk.red(`Error: ${error.message}`));
2186 | showAddSubtaskHelp();
2187 | process.exit(1);
2188 | }
2189 | })
2190 | .on('error', function (err) {
2191 | console.error(chalk.red(`Error: ${err.message}`));
2192 | showAddSubtaskHelp();
2193 | process.exit(1);
2194 | });
2195 |
2196 | // Helper function to show add-subtask command help
2197 | function showAddSubtaskHelp() {
2198 | console.log(
2199 | boxen(
2200 | `${chalk.white.bold('Add Subtask Command Help')}\n\n${chalk.cyan('Usage:')}\n task-master add-subtask --parent=<id> [options]\n\n${chalk.cyan('Options:')}\n -p, --parent <id> Parent task ID (required)\n -i, --task-id <id> Existing task ID to convert to subtask\n -t, --title <title> Title for the new subtask\n -d, --description <text> Description for the new subtask\n --details <text> Implementation details for the new subtask\n --dependencies <ids> Comma-separated list of dependency IDs\n -s, --status <status> Status for the new subtask (default: "pending")\n -f, --file <file> Path to the tasks file (default: "${TASKMASTER_TASKS_FILE}")\n --generate Regenerate task files after adding subtask\n\n${chalk.cyan('Examples:')}\n task-master add-subtask --parent=5 --task-id=8\n task-master add-subtask -p 5 -t "Implement login UI" -d "Create the login form" --generate`,
2201 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2202 | )
2203 | );
2204 | }
2205 |
2206 | // remove-subtask command
2207 | programInstance
2208 | .command('remove-subtask')
2209 | .description('Remove a subtask from its parent task')
2210 | .option(
2211 | '-f, --file <file>',
2212 | 'Path to the tasks file',
2213 | TASKMASTER_TASKS_FILE
2214 | )
2215 | .option(
2216 | '-i, --id <id>',
2217 | 'Subtask ID(s) to remove in format "parentId.subtaskId" (can be comma-separated for multiple subtasks)'
2218 | )
2219 | .option(
2220 | '-c, --convert',
2221 | 'Convert the subtask to a standalone task instead of deleting it'
2222 | )
2223 | .option('--generate', 'Regenerate task files after removing subtask')
2224 | .option('--tag <tag>', 'Specify tag context for task operations')
2225 | .action(async (options) => {
2226 | // Initialize TaskMaster
2227 | const taskMaster = initTaskMaster({
2228 | tasksPath: options.file || true,
2229 | tag: options.tag
2230 | });
2231 |
2232 | const subtaskIds = options.id;
2233 | const convertToTask = options.convert || false;
2234 | const generateFiles = options.generate || false;
2235 | const tag = taskMaster.getCurrentTag();
2236 |
2237 | if (!subtaskIds) {
2238 | console.error(
2239 | chalk.red(
2240 | 'Error: --id parameter is required. Please provide subtask ID(s) in format "parentId.subtaskId".'
2241 | )
2242 | );
2243 | showRemoveSubtaskHelp();
2244 | process.exit(1);
2245 | }
2246 |
2247 | try {
2248 | // Split by comma to support multiple subtask IDs
2249 | const subtaskIdArray = subtaskIds.split(',').map((id) => id.trim());
2250 |
2251 | for (const subtaskId of subtaskIdArray) {
2252 | // Validate subtask ID format
2253 | if (!subtaskId.includes('.')) {
2254 | console.error(
2255 | chalk.red(
2256 | `Error: Subtask ID "${subtaskId}" must be in format "parentId.subtaskId"`
2257 | )
2258 | );
2259 | showRemoveSubtaskHelp();
2260 | process.exit(1);
2261 | }
2262 |
2263 | console.log(chalk.blue(`Removing subtask ${subtaskId}...`));
2264 | if (convertToTask) {
2265 | console.log(
2266 | chalk.blue('The subtask will be converted to a standalone task')
2267 | );
2268 | }
2269 |
2270 | const result = await removeSubtask(
2271 | taskMaster.getTasksPath(),
2272 | subtaskId,
2273 | convertToTask,
2274 | generateFiles,
2275 | { projectRoot: taskMaster.getProjectRoot(), tag }
2276 | );
2277 |
2278 | if (convertToTask && result) {
2279 | // Display success message and next steps for converted task
2280 | console.log(
2281 | boxen(
2282 | chalk.white.bold(
2283 | `Subtask ${subtaskId} Converted to Task #${result.id}`
2284 | ) +
2285 | '\n\n' +
2286 | chalk.white(`Title: ${result.title}`) +
2287 | '\n' +
2288 | chalk.white(`Status: ${getStatusWithColor(result.status)}`) +
2289 | '\n' +
2290 | chalk.white(
2291 | `Dependencies: ${result.dependencies.join(', ')}`
2292 | ) +
2293 | '\n\n' +
2294 | chalk.white.bold('Next Steps:') +
2295 | '\n' +
2296 | chalk.cyan(
2297 | `1. Run ${chalk.yellow(`task-master show ${result.id}`)} to see details of the new task`
2298 | ) +
2299 | '\n' +
2300 | chalk.cyan(
2301 | `2. Run ${chalk.yellow(`task-master set-status --id=${result.id} --status=in-progress`)} to start working on it`
2302 | ),
2303 | {
2304 | padding: 1,
2305 | borderColor: 'green',
2306 | borderStyle: 'round',
2307 | margin: { top: 1 }
2308 | }
2309 | )
2310 | );
2311 | } else {
2312 | // Display success message for deleted subtask
2313 | console.log(
2314 | boxen(
2315 | chalk.white.bold(`Subtask ${subtaskId} Removed`) +
2316 | '\n\n' +
2317 | chalk.white('The subtask has been successfully deleted.'),
2318 | {
2319 | padding: 1,
2320 | borderColor: 'green',
2321 | borderStyle: 'round',
2322 | margin: { top: 1 }
2323 | }
2324 | )
2325 | );
2326 | }
2327 | }
2328 | } catch (error) {
2329 | console.error(chalk.red(`Error: ${error.message}`));
2330 | showRemoveSubtaskHelp();
2331 | process.exit(1);
2332 | }
2333 | })
2334 | .on('error', function (err) {
2335 | console.error(chalk.red(`Error: ${err.message}`));
2336 | showRemoveSubtaskHelp();
2337 | process.exit(1);
2338 | });
2339 |
2340 | // Helper function to show remove-subtask command help
2341 | function showRemoveSubtaskHelp() {
2342 | console.log(
2343 | boxen(
2344 | chalk.white.bold('Remove Subtask Command Help') +
2345 | '\n\n' +
2346 | chalk.cyan('Usage:') +
2347 | '\n' +
2348 | ` task-master remove-subtask --id=<parentId.subtaskId> [options]\n\n` +
2349 | chalk.cyan('Options:') +
2350 | '\n' +
2351 | ' -i, --id <id> Subtask ID(s) to remove in format "parentId.subtaskId" (can be comma-separated, required)\n' +
2352 | ' -c, --convert Convert the subtask to a standalone task instead of deleting it\n' +
2353 | ' -f, --file <file> Path to the tasks file (default: "' +
2354 | TASKMASTER_TASKS_FILE +
2355 | '")\n' +
2356 | ' --skip-generate Skip regenerating task files\n\n' +
2357 | chalk.cyan('Examples:') +
2358 | '\n' +
2359 | ' task-master remove-subtask --id=5.2\n' +
2360 | ' task-master remove-subtask --id=5.2,6.3,7.1\n' +
2361 | ' task-master remove-subtask --id=5.2 --convert',
2362 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2363 | )
2364 | );
2365 | }
2366 |
2367 | // Helper function to show tags command help
2368 | function showTagsHelp() {
2369 | console.log(
2370 | boxen(
2371 | chalk.white.bold('Tags Command Help') +
2372 | '\n\n' +
2373 | chalk.cyan('Usage:') +
2374 | '\n' +
2375 | ` task-master tags [options]\n\n` +
2376 | chalk.cyan('Options:') +
2377 | '\n' +
2378 | ' -f, --file <file> Path to the tasks file (default: "' +
2379 | TASKMASTER_TASKS_FILE +
2380 | '")\n' +
2381 | ' --show-metadata Show detailed metadata for each tag\n\n' +
2382 | chalk.cyan('Examples:') +
2383 | '\n' +
2384 | ' task-master tags\n' +
2385 | ' task-master tags --show-metadata\n\n' +
2386 | chalk.cyan('Related Commands:') +
2387 | '\n' +
2388 | ' task-master add-tag <name> Create a new tag\n' +
2389 | ' task-master use-tag <name> Switch to a tag\n' +
2390 | ' task-master delete-tag <name> Delete a tag',
2391 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2392 | )
2393 | );
2394 | }
2395 |
2396 | // Helper function to show add-tag command help
2397 | function showAddTagHelp() {
2398 | console.log(
2399 | boxen(
2400 | chalk.white.bold('Add Tag Command Help') +
2401 | '\n\n' +
2402 | chalk.cyan('Usage:') +
2403 | '\n' +
2404 | ` task-master add-tag <tagName> [options]\n\n` +
2405 | chalk.cyan('Options:') +
2406 | '\n' +
2407 | ' -f, --file <file> Path to the tasks file (default: "' +
2408 | TASKMASTER_TASKS_FILE +
2409 | '")\n' +
2410 | ' --copy-from-current Copy tasks from the current tag to the new tag\n' +
2411 | ' --copy-from <tag> Copy tasks from the specified tag to the new tag\n' +
2412 | ' -d, --description <text> Optional description for the tag\n\n' +
2413 | chalk.cyan('Examples:') +
2414 | '\n' +
2415 | ' task-master add-tag feature-xyz\n' +
2416 | ' task-master add-tag feature-xyz --copy-from-current\n' +
2417 | ' task-master add-tag feature-xyz --copy-from master\n' +
2418 | ' task-master add-tag feature-xyz -d "Feature XYZ development"',
2419 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2420 | )
2421 | );
2422 | }
2423 |
2424 | // Helper function to show delete-tag command help
2425 | function showDeleteTagHelp() {
2426 | console.log(
2427 | boxen(
2428 | chalk.white.bold('Delete Tag Command Help') +
2429 | '\n\n' +
2430 | chalk.cyan('Usage:') +
2431 | '\n' +
2432 | ` task-master delete-tag <tagName> [options]\n\n` +
2433 | chalk.cyan('Options:') +
2434 | '\n' +
2435 | ' -f, --file <file> Path to the tasks file (default: "' +
2436 | TASKMASTER_TASKS_FILE +
2437 | '")\n' +
2438 | ' -y, --yes Skip confirmation prompts\n\n' +
2439 | chalk.cyan('Examples:') +
2440 | '\n' +
2441 | ' task-master delete-tag feature-xyz\n' +
2442 | ' task-master delete-tag feature-xyz --yes\n\n' +
2443 | chalk.yellow('Warning:') +
2444 | '\n' +
2445 | ' This will permanently delete the tag and all its tasks!',
2446 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2447 | )
2448 | );
2449 | }
2450 |
2451 | // Helper function to show use-tag command help
2452 | function showUseTagHelp() {
2453 | console.log(
2454 | boxen(
2455 | chalk.white.bold('Use Tag Command Help') +
2456 | '\n\n' +
2457 | chalk.cyan('Usage:') +
2458 | '\n' +
2459 | ` task-master use-tag <tagName> [options]\n\n` +
2460 | chalk.cyan('Options:') +
2461 | '\n' +
2462 | ' -f, --file <file> Path to the tasks file (default: "' +
2463 | TASKMASTER_TASKS_FILE +
2464 | '")\n\n' +
2465 | chalk.cyan('Examples:') +
2466 | '\n' +
2467 | ' task-master use-tag feature-xyz\n' +
2468 | ' task-master use-tag master\n\n' +
2469 | chalk.cyan('Related Commands:') +
2470 | '\n' +
2471 | ' task-master tags List all available tags\n' +
2472 | ' task-master add-tag <name> Create a new tag',
2473 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2474 | )
2475 | );
2476 | }
2477 |
2478 | // Helper function to show research command help
2479 | function showResearchHelp() {
2480 | console.log(
2481 | boxen(
2482 | chalk.white.bold('Research Command Help') +
2483 | '\n\n' +
2484 | chalk.cyan('Usage:') +
2485 | '\n' +
2486 | ` task-master research "<query>" [options]\n\n` +
2487 | chalk.cyan('Required:') +
2488 | '\n' +
2489 | ' <query> Research question or prompt (required)\n\n' +
2490 | chalk.cyan('Context Options:') +
2491 | '\n' +
2492 | ' -i, --id <ids> Comma-separated task/subtask IDs for context (e.g., "15,23.2")\n' +
2493 | ' -f, --files <paths> Comma-separated file paths for context\n' +
2494 | ' -c, --context <text> Additional custom context text\n' +
2495 | ' --tree Include project file tree structure\n\n' +
2496 | chalk.cyan('Output Options:') +
2497 | '\n' +
2498 | ' -d, --detail <level> Detail level: low, medium, high (default: medium)\n' +
2499 | ' --save-to <id> Auto-save results to task/subtask ID (e.g., "15" or "15.2")\n' +
2500 | ' --tag <tag> Specify tag context for task operations\n\n' +
2501 | chalk.cyan('Examples:') +
2502 | '\n' +
2503 | ' task-master research "How should I implement user authentication?"\n' +
2504 | ' task-master research "What\'s the best approach?" --id=15,23.2\n' +
2505 | ' task-master research "How does auth work?" --files=src/auth.js --tree\n' +
2506 | ' task-master research "Implementation steps?" --save-to=15.2 --detail=high',
2507 | { padding: 1, borderColor: 'blue', borderStyle: 'round' }
2508 | )
2509 | );
2510 | }
2511 |
2512 | // remove-task command
2513 | programInstance
2514 | .command('remove-task')
2515 | .description('Remove one or more tasks or subtasks permanently')
2516 | .option(
2517 | '-i, --id <ids>',
2518 | 'ID(s) of the task(s) or subtask(s) to remove (e.g., "5", "5.2", or "5,6.1,7")'
2519 | )
2520 | .option(
2521 | '-f, --file <file>',
2522 | 'Path to the tasks file',
2523 | TASKMASTER_TASKS_FILE
2524 | )
2525 | .option('-y, --yes', 'Skip confirmation prompt', false)
2526 | .option('--tag <tag>', 'Specify tag context for task operations')
2527 | .action(async (options) => {
2528 | // Initialize TaskMaster
2529 | const taskMaster = initTaskMaster({
2530 | tasksPath: options.file || true,
2531 | tag: options.tag
2532 | });
2533 |
2534 | const taskIdsString = options.id;
2535 |
2536 | // Resolve tag using standard pattern
2537 | const tag = taskMaster.getCurrentTag();
2538 |
2539 | // Show current tag context
2540 | displayCurrentTagIndicator(tag);
2541 |
2542 | if (!taskIdsString) {
2543 | console.error(chalk.red('Error: Task ID(s) are required'));
2544 | console.error(
2545 | chalk.yellow(
2546 | 'Usage: task-master remove-task --id=<taskId1,taskId2...>'
2547 | )
2548 | );
2549 | process.exit(1);
2550 | }
2551 |
2552 | const taskIdsToRemove = taskIdsString
2553 | .split(',')
2554 | .map((id) => id.trim())
2555 | .filter(Boolean);
2556 |
2557 | if (taskIdsToRemove.length === 0) {
2558 | console.error(chalk.red('Error: No valid task IDs provided.'));
2559 | process.exit(1);
2560 | }
2561 |
2562 | try {
2563 | // Read data once for checks and confirmation
2564 | const data = readJSON(
2565 | taskMaster.getTasksPath(),
2566 | taskMaster.getProjectRoot(),
2567 | tag
2568 | );
2569 | if (!data || !data.tasks) {
2570 | console.error(
2571 | chalk.red(`Error: No valid tasks found in ${tasksPath}`)
2572 | );
2573 | process.exit(1);
2574 | }
2575 |
2576 | const existingTasksToRemove = [];
2577 | const nonExistentIds = [];
2578 | let totalSubtasksToDelete = 0;
2579 | const dependentTaskMessages = [];
2580 |
2581 | for (const taskId of taskIdsToRemove) {
2582 | if (!taskExists(data.tasks, taskId)) {
2583 | nonExistentIds.push(taskId);
2584 | } else {
2585 | // Correctly extract the task object from the result of findTaskById
2586 | const findResult = findTaskById(data.tasks, taskId);
2587 | const taskObject = findResult.task; // Get the actual task/subtask object
2588 |
2589 | if (taskObject) {
2590 | existingTasksToRemove.push({ id: taskId, task: taskObject }); // Push the actual task object
2591 |
2592 | // If it's a main task, count its subtasks and check dependents
2593 | if (!taskObject.isSubtask) {
2594 | // Check the actual task object
2595 | if (taskObject.subtasks && taskObject.subtasks.length > 0) {
2596 | totalSubtasksToDelete += taskObject.subtasks.length;
2597 | }
2598 | const dependentTasks = data.tasks.filter(
2599 | (t) =>
2600 | t.dependencies &&
2601 | t.dependencies.includes(parseInt(taskId, 10))
2602 | );
2603 | if (dependentTasks.length > 0) {
2604 | dependentTaskMessages.push(
2605 | ` - Task ${taskId}: ${dependentTasks.length} dependent tasks (${dependentTasks.map((t) => t.id).join(', ')})`
2606 | );
2607 | }
2608 | }
2609 | } else {
2610 | // Handle case where findTaskById returned null for the task property (should be rare)
2611 | nonExistentIds.push(`${taskId} (error finding details)`);
2612 | }
2613 | }
2614 | }
2615 |
2616 | if (nonExistentIds.length > 0) {
2617 | console.warn(
2618 | chalk.yellow(
2619 | `Warning: The following task IDs were not found: ${nonExistentIds.join(', ')}`
2620 | )
2621 | );
2622 | }
2623 |
2624 | if (existingTasksToRemove.length === 0) {
2625 | console.log(chalk.blue('No existing tasks found to remove.'));
2626 | process.exit(0);
2627 | }
2628 |
2629 | // Skip confirmation if --yes flag is provided
2630 | if (!options.yes) {
2631 | console.log();
2632 | console.log(
2633 | chalk.red.bold(
2634 | `⚠️ WARNING: This will permanently delete the following ${existingTasksToRemove.length} item(s):`
2635 | )
2636 | );
2637 | console.log();
2638 |
2639 | existingTasksToRemove.forEach(({ id, task }) => {
2640 | if (!task) return; // Should not happen due to taskExists check, but safeguard
2641 | if (task.isSubtask) {
2642 | // Subtask - title is directly on the task object
2643 | console.log(
2644 | chalk.white(` Subtask ${id}: ${task.title || '(no title)'}`)
2645 | );
2646 | // Optionally show parent context if available
2647 | if (task.parentTask) {
2648 | console.log(
2649 | chalk.gray(
2650 | ` (Parent: ${task.parentTask.id} - ${task.parentTask.title || '(no title)'})`
2651 | )
2652 | );
2653 | }
2654 | } else {
2655 | // Main task - title is directly on the task object
2656 | console.log(
2657 | chalk.white.bold(` Task ${id}: ${task.title || '(no title)'}`)
2658 | );
2659 | }
2660 | });
2661 |
2662 | if (totalSubtasksToDelete > 0) {
2663 | console.log(
2664 | chalk.yellow(
2665 | `⚠️ This will also delete ${totalSubtasksToDelete} subtasks associated with the selected main tasks!`
2666 | )
2667 | );
2668 | }
2669 |
2670 | if (dependentTaskMessages.length > 0) {
2671 | console.log(
2672 | chalk.yellow(
2673 | '⚠️ Warning: Dependencies on the following tasks will be removed:'
2674 | )
2675 | );
2676 | dependentTaskMessages.forEach((msg) =>
2677 | console.log(chalk.yellow(msg))
2678 | );
2679 | }
2680 |
2681 | console.log();
2682 |
2683 | const { confirm } = await inquirer.prompt([
2684 | {
2685 | type: 'confirm',
2686 | name: 'confirm',
2687 | message: chalk.red.bold(
2688 | `Are you sure you want to permanently delete these ${existingTasksToRemove.length} item(s)?`
2689 | ),
2690 | default: false
2691 | }
2692 | ]);
2693 |
2694 | if (!confirm) {
2695 | console.log(chalk.blue('Task deletion cancelled.'));
2696 | process.exit(0);
2697 | }
2698 | }
2699 |
2700 | const indicator = startLoadingIndicator(
2701 | `Removing ${existingTasksToRemove.length} task(s)/subtask(s)...`
2702 | );
2703 |
2704 | // Use the string of existing IDs for the core function
2705 | const existingIdsString = existingTasksToRemove
2706 | .map(({ id }) => id)
2707 | .join(',');
2708 | const result = await removeTask(
2709 | taskMaster.getTasksPath(),
2710 | existingIdsString,
2711 | {
2712 | projectRoot: taskMaster.getProjectRoot(),
2713 | tag
2714 | }
2715 | );
2716 |
2717 | stopLoadingIndicator(indicator);
2718 |
2719 | if (result.success) {
2720 | console.log(
2721 | boxen(
2722 | chalk.green(
2723 | `Successfully removed ${result.removedTasks.length} task(s)/subtask(s).`
2724 | ) +
2725 | (result.message ? `\n\nDetails:\n${result.message}` : '') +
2726 | (result.error
2727 | ? `\n\nWarnings:\n${chalk.yellow(result.error)}`
2728 | : ''),
2729 | { padding: 1, borderColor: 'green', borderStyle: 'round' }
2730 | )
2731 | );
2732 | } else {
2733 | console.error(
2734 | boxen(
2735 | chalk.red(
2736 | `Operation completed with errors. Removed ${result.removedTasks.length} task(s)/subtask(s).`
2737 | ) +
2738 | (result.message ? `\n\nDetails:\n${result.message}` : '') +
2739 | (result.error ? `\n\nErrors:\n${chalk.red(result.error)}` : ''),
2740 | {
2741 | padding: 1,
2742 | borderColor: 'red',
2743 | borderStyle: 'round'
2744 | }
2745 | )
2746 | );
2747 | process.exit(1); // Exit with error code if any part failed
2748 | }
2749 |
2750 | // Log any initially non-existent IDs again for clarity
2751 | if (nonExistentIds.length > 0) {
2752 | console.warn(
2753 | chalk.yellow(
2754 | `Note: The following IDs were not found initially and were skipped: ${nonExistentIds.join(', ')}`
2755 | )
2756 | );
2757 |
2758 | // Exit with error if any removals failed
2759 | if (result.removedTasks.length === 0) {
2760 | process.exit(1);
2761 | }
2762 | }
2763 | } catch (error) {
2764 | console.error(
2765 | chalk.red(`Error: ${error.message || 'An unknown error occurred'}`)
2766 | );
2767 | process.exit(1);
2768 | }
2769 | });
2770 |
2771 | // init command (Directly calls the implementation from init.js)
2772 | programInstance
2773 | .command('init')
2774 | .description('Initialize a new project with Task Master structure')
2775 | .option('-y, --yes', 'Skip prompts and use default values')
2776 | .option('-n, --name <name>', 'Project name')
2777 | .option('-d, --description <description>', 'Project description')
2778 | .option('-v, --version <version>', 'Project version', '0.1.0') // Set default here
2779 | .option('-a, --author <author>', 'Author name')
2780 | .option(
2781 | '-r, --rules <rules...>',
2782 | 'List of rules to add (roo, windsurf, cursor, ...). Accepts comma or space separated values.'
2783 | )
2784 | .option('--skip-install', 'Skip installing dependencies')
2785 | .option('--dry-run', 'Show what would be done without making changes')
2786 | .option('--aliases', 'Add shell aliases (tm, taskmaster)')
2787 | .option('--no-aliases', 'Skip shell aliases (tm, taskmaster)')
2788 | .option('--git', 'Initialize Git repository')
2789 | .option('--no-git', 'Skip Git repository initialization')
2790 | .option('--git-tasks', 'Store tasks in Git')
2791 | .option('--no-git-tasks', 'No Git storage of tasks')
2792 | .action(async (cmdOptions) => {
2793 | // cmdOptions contains parsed arguments
2794 | // Parse rules: accept space or comma separated, default to all available rules
2795 | let selectedProfiles = RULE_PROFILES;
2796 | let rulesExplicitlyProvided = false;
2797 |
2798 | if (cmdOptions.rules && Array.isArray(cmdOptions.rules)) {
2799 | const userSpecifiedProfiles = cmdOptions.rules
2800 | .flatMap((r) => r.split(','))
2801 | .map((r) => r.trim())
2802 | .filter(Boolean);
2803 | // Only override defaults if user specified valid rules
2804 | if (userSpecifiedProfiles.length > 0) {
2805 | selectedProfiles = userSpecifiedProfiles;
2806 | rulesExplicitlyProvided = true;
2807 | }
2808 | }
2809 |
2810 | cmdOptions.rules = selectedProfiles;
2811 | cmdOptions.rulesExplicitlyProvided = rulesExplicitlyProvided;
2812 |
2813 | try {
2814 | // Directly call the initializeProject function, passing the parsed options
2815 | await initializeProject(cmdOptions);
2816 | // initializeProject handles its own flow, including potential process.exit()
2817 | } catch (error) {
2818 | console.error(
2819 | chalk.red(`Error during initialization: ${error.message}`)
2820 | );
2821 | process.exit(1);
2822 | }
2823 | });
2824 |
2825 | // models command
2826 | programInstance
2827 | .command('models')
2828 | .description('Manage AI model configurations')
2829 | .option(
2830 | '--set-main <model_id>',
2831 | 'Set the primary model for task generation/updates'
2832 | )
2833 | .option(
2834 | '--set-research <model_id>',
2835 | 'Set the model for research-backed operations'
2836 | )
2837 | .option(
2838 | '--set-fallback <model_id>',
2839 | 'Set the model to use if the primary fails'
2840 | )
2841 | .option('--setup', 'Run interactive setup to configure models')
2842 | .option(
2843 | '--openrouter',
2844 | 'Allow setting a custom OpenRouter model ID (use with --set-*) '
2845 | )
2846 | .option(
2847 | '--ollama',
2848 | 'Allow setting a custom Ollama model ID (use with --set-*) '
2849 | )
2850 | .option(
2851 | '--bedrock',
2852 | 'Allow setting a custom Bedrock model ID (use with --set-*) '
2853 | )
2854 | .option(
2855 | '--claude-code',
2856 | 'Allow setting a Claude Code model ID (use with --set-*)'
2857 | )
2858 | .option(
2859 | '--azure',
2860 | 'Allow setting a custom Azure OpenAI model ID (use with --set-*) '
2861 | )
2862 | .option(
2863 | '--vertex',
2864 | 'Allow setting a custom Vertex AI model ID (use with --set-*) '
2865 | )
2866 | .option(
2867 | '--gemini-cli',
2868 | 'Allow setting a Gemini CLI model ID (use with --set-*)'
2869 | )
2870 | .option(
2871 | '--codex-cli',
2872 | 'Allow setting a Codex CLI model ID (use with --set-*)'
2873 | )
2874 | .option(
2875 | '--lmstudio',
2876 | 'Allow setting a custom LM Studio model ID (use with --set-*)'
2877 | )
2878 | .option(
2879 | '--openai-compatible',
2880 | 'Allow setting a custom OpenAI-compatible model ID (use with --set-*)'
2881 | )
2882 | .option(
2883 | '--baseURL <url>',
2884 | 'Custom base URL for openai-compatible, lmstudio, or ollama providers (e.g., http://localhost:8000/v1)'
2885 | )
2886 | .addHelpText(
2887 | 'after',
2888 | `
2889 | Examples:
2890 | $ task-master models # View current configuration
2891 | $ task-master models --set-main gpt-4o # Set main model (provider inferred)
2892 | $ task-master models --set-research sonar-pro # Set research model
2893 | $ task-master models --set-fallback claude-3-5-sonnet-20241022 # Set fallback
2894 | $ task-master models --set-main my-custom-model --ollama # Set custom Ollama model for main role
2895 | $ task-master models --set-main anthropic.claude-3-sonnet-20240229-v1:0 --bedrock # Set custom Bedrock model for main role
2896 | $ task-master models --set-main some/other-model --openrouter # Set custom OpenRouter model for main role
2897 | $ task-master models --set-main sonnet --claude-code # Set Claude Code model for main role
2898 | $ task-master models --set-main gpt-4o --azure # Set custom Azure OpenAI model for main role
2899 | $ task-master models --set-main claude-3-5-sonnet@20241022 --vertex # Set custom Vertex AI model for main role
2900 | $ task-master models --set-main gemini-2.5-pro --gemini-cli # Set Gemini CLI model for main role
2901 | $ task-master models --set-main gpt-5-codex --codex-cli # Set Codex CLI model for main role
2902 | $ task-master models --set-main qwen3-vl-4b --lmstudio # Set LM Studio model for main role (defaults to http://localhost:1234/v1)
2903 | $ task-master models --set-main qwen3-vl-4b --lmstudio --baseURL http://localhost:8000/v1 # Set LM Studio model with custom base URL
2904 | $ task-master models --set-main my-model --openai-compatible --baseURL http://localhost:8000/v1 # Set custom OpenAI-compatible model with custom endpoint
2905 | $ task-master models --setup # Run interactive setup`
2906 | )
2907 | .action(async (options) => {
2908 | // Initialize TaskMaster
2909 | const taskMaster = initTaskMaster({
2910 | tasksPath: options.file || false
2911 | });
2912 |
2913 | const projectRoot = taskMaster.getProjectRoot();
2914 |
2915 | // Validate flags: cannot use multiple provider flags simultaneously
2916 | const providerFlags = [
2917 | options.openrouter,
2918 | options.ollama,
2919 | options.bedrock,
2920 | options.claudeCode,
2921 | options.geminiCli,
2922 | options.codexCli,
2923 | options.lmstudio,
2924 | options.openaiCompatible
2925 | ].filter(Boolean).length;
2926 | if (providerFlags > 1) {
2927 | console.error(
2928 | chalk.red(
2929 | 'Error: Cannot use multiple provider flags (--openrouter, --ollama, --bedrock, --claude-code, --gemini-cli, --codex-cli, --lmstudio, --openai-compatible) simultaneously.'
2930 | )
2931 | );
2932 | process.exit(1);
2933 | }
2934 |
2935 | // Determine the primary action based on flags
2936 | const isSetup = options.setup;
2937 | const isSetOperation =
2938 | options.setMain || options.setResearch || options.setFallback;
2939 |
2940 | // --- Execute Action ---
2941 |
2942 | if (isSetup) {
2943 | // Action 1: Run Interactive Setup
2944 | console.log(chalk.blue('Starting interactive model setup...')); // Added feedback
2945 | try {
2946 | await runInteractiveSetup(taskMaster.getProjectRoot());
2947 | // runInteractiveSetup logs its own completion/error messages
2948 | } catch (setupError) {
2949 | console.error(
2950 | chalk.red('\\nInteractive setup failed unexpectedly:'),
2951 | setupError.message
2952 | );
2953 | }
2954 | // --- IMPORTANT: Exit after setup ---
2955 | return; // Stop execution here
2956 | }
2957 |
2958 | if (isSetOperation) {
2959 | // Action 2: Perform Direct Set Operations
2960 | let updateOccurred = false; // Track if any update actually happened
2961 |
2962 | if (options.setMain) {
2963 | const result = await setModel('main', options.setMain, {
2964 | projectRoot,
2965 | providerHint: options.openrouter
2966 | ? 'openrouter'
2967 | : options.ollama
2968 | ? 'ollama'
2969 | : options.bedrock
2970 | ? 'bedrock'
2971 | : options.claudeCode
2972 | ? 'claude-code'
2973 | : options.geminiCli
2974 | ? 'gemini-cli'
2975 | : options.codexCli
2976 | ? 'codex-cli'
2977 | : options.lmstudio
2978 | ? 'lmstudio'
2979 | : options.openaiCompatible
2980 | ? 'openai-compatible'
2981 | : undefined,
2982 | baseURL: options.baseURL
2983 | });
2984 | if (result.success) {
2985 | console.log(chalk.green(`✅ ${result.data.message}`));
2986 | if (result.data.warning)
2987 | console.log(chalk.yellow(result.data.warning));
2988 | updateOccurred = true;
2989 | } else {
2990 | console.error(
2991 | chalk.red(`❌ Error setting main model: ${result.error.message}`)
2992 | );
2993 | }
2994 | }
2995 | if (options.setResearch) {
2996 | const result = await setModel('research', options.setResearch, {
2997 | projectRoot,
2998 | providerHint: options.openrouter
2999 | ? 'openrouter'
3000 | : options.ollama
3001 | ? 'ollama'
3002 | : options.bedrock
3003 | ? 'bedrock'
3004 | : options.claudeCode
3005 | ? 'claude-code'
3006 | : options.geminiCli
3007 | ? 'gemini-cli'
3008 | : options.codexCli
3009 | ? 'codex-cli'
3010 | : options.lmstudio
3011 | ? 'lmstudio'
3012 | : options.openaiCompatible
3013 | ? 'openai-compatible'
3014 | : undefined,
3015 | baseURL: options.baseURL
3016 | });
3017 | if (result.success) {
3018 | console.log(chalk.green(`✅ ${result.data.message}`));
3019 | if (result.data.warning)
3020 | console.log(chalk.yellow(result.data.warning));
3021 | updateOccurred = true;
3022 | } else {
3023 | console.error(
3024 | chalk.red(
3025 | `❌ Error setting research model: ${result.error.message}`
3026 | )
3027 | );
3028 | }
3029 | }
3030 | if (options.setFallback) {
3031 | const result = await setModel('fallback', options.setFallback, {
3032 | projectRoot,
3033 | providerHint: options.openrouter
3034 | ? 'openrouter'
3035 | : options.ollama
3036 | ? 'ollama'
3037 | : options.bedrock
3038 | ? 'bedrock'
3039 | : options.claudeCode
3040 | ? 'claude-code'
3041 | : options.geminiCli
3042 | ? 'gemini-cli'
3043 | : options.codexCli
3044 | ? 'codex-cli'
3045 | : options.lmstudio
3046 | ? 'lmstudio'
3047 | : options.openaiCompatible
3048 | ? 'openai-compatible'
3049 | : undefined,
3050 | baseURL: options.baseURL
3051 | });
3052 | if (result.success) {
3053 | console.log(chalk.green(`✅ ${result.data.message}`));
3054 | if (result.data.warning)
3055 | console.log(chalk.yellow(result.data.warning));
3056 | updateOccurred = true;
3057 | } else {
3058 | console.error(
3059 | chalk.red(
3060 | `❌ Error setting fallback model: ${result.error.message}`
3061 | )
3062 | );
3063 | }
3064 | }
3065 |
3066 | // Optional: Add a final confirmation if any update occurred
3067 | if (updateOccurred) {
3068 | console.log(chalk.blue('\nModel configuration updated.'));
3069 | } else {
3070 | console.log(
3071 | chalk.yellow(
3072 | '\nNo model configuration changes were made (or errors occurred).'
3073 | )
3074 | );
3075 | }
3076 |
3077 | // --- IMPORTANT: Exit after set operations ---
3078 | return; // Stop execution here
3079 | }
3080 |
3081 | // Action 3: Display Full Status (Only runs if no setup and no set flags)
3082 | console.log(chalk.blue('Fetching current model configuration...')); // Added feedback
3083 | const configResult = await getModelConfiguration({ projectRoot });
3084 | const availableResult = await getAvailableModelsList({ projectRoot });
3085 | const apiKeyStatusResult = await getApiKeyStatusReport({ projectRoot });
3086 |
3087 | // 1. Display Active Models
3088 | if (!configResult.success) {
3089 | console.error(
3090 | chalk.red(
3091 | `❌ Error fetching configuration: ${configResult.error.message}`
3092 | )
3093 | );
3094 | } else {
3095 | displayModelConfiguration(
3096 | configResult.data,
3097 | availableResult.data?.models || []
3098 | );
3099 | }
3100 |
3101 | // 2. Display API Key Status
3102 | if (apiKeyStatusResult.success) {
3103 | displayApiKeyStatus(apiKeyStatusResult.data.report);
3104 | } else {
3105 | console.error(
3106 | chalk.yellow(
3107 | `⚠️ Warning: Could not display API Key status: ${apiKeyStatusResult.error.message}`
3108 | )
3109 | );
3110 | }
3111 |
3112 | // 3. Display Other Available Models (Filtered)
3113 | if (availableResult.success) {
3114 | const activeIds = configResult.success
3115 | ? [
3116 | configResult.data.activeModels.main.modelId,
3117 | configResult.data.activeModels.research.modelId,
3118 | configResult.data.activeModels.fallback?.modelId
3119 | ].filter(Boolean)
3120 | : [];
3121 | const displayableAvailable = availableResult.data.models.filter(
3122 | (m) => !activeIds.includes(m.modelId) && !m.modelId.startsWith('[')
3123 | );
3124 | displayAvailableModels(displayableAvailable);
3125 | } else {
3126 | console.error(
3127 | chalk.yellow(
3128 | `⚠️ Warning: Could not display available models: ${availableResult.error.message}`
3129 | )
3130 | );
3131 | }
3132 |
3133 | // 4. Conditional Hint if Config File is Missing
3134 | const configExists = isConfigFilePresent(projectRoot);
3135 | if (!configExists) {
3136 | console.log(
3137 | chalk.yellow(
3138 | "\\nHint: Run 'task-master models --setup' to create or update your configuration."
3139 | )
3140 | );
3141 | }
3142 | // --- IMPORTANT: Exit after displaying status ---
3143 | return; // Stop execution here
3144 | });
3145 |
3146 | // response-language command
3147 | programInstance
3148 | .command('lang')
3149 | .description('Manage response language settings')
3150 | .option('--response <response_language>', 'Set the response language')
3151 | .option('--setup', 'Run interactive setup to configure response language')
3152 | .action(async (options) => {
3153 | const taskMaster = initTaskMaster({});
3154 | const projectRoot = taskMaster.getProjectRoot(); // Find project root for context
3155 | const { response, setup } = options;
3156 | let responseLanguage = response !== undefined ? response : 'English';
3157 | if (setup) {
3158 | console.log(
3159 | chalk.blue('Starting interactive response language setup...')
3160 | );
3161 | try {
3162 | const userResponse = await inquirer.prompt([
3163 | {
3164 | type: 'input',
3165 | name: 'responseLanguage',
3166 | message: 'Input your preferred response language',
3167 | default: 'English'
3168 | }
3169 | ]);
3170 |
3171 | console.log(
3172 | chalk.blue(
3173 | 'Response language set to:',
3174 | userResponse.responseLanguage
3175 | )
3176 | );
3177 | responseLanguage = userResponse.responseLanguage;
3178 | } catch (setupError) {
3179 | console.error(
3180 | chalk.red('\\nInteractive setup failed unexpectedly:'),
3181 | setupError.message
3182 | );
3183 | }
3184 | }
3185 |
3186 | const result = setResponseLanguage(responseLanguage, {
3187 | projectRoot
3188 | });
3189 |
3190 | if (result.success) {
3191 | console.log(chalk.green(`✅ ${result.data.message}`));
3192 | } else {
3193 | console.error(
3194 | chalk.red(
3195 | `❌ Error setting response language: ${result.error.message}`
3196 | )
3197 | );
3198 | process.exit(1);
3199 | }
3200 | });
3201 |
3202 | // move-task command
3203 | programInstance
3204 | .command('move')
3205 | .description(
3206 | 'Move tasks between tags or reorder within tags. Supports cross-tag moves with dependency resolution options.'
3207 | )
3208 | .option(
3209 | '-f, --file <file>',
3210 | 'Path to the tasks file',
3211 | TASKMASTER_TASKS_FILE
3212 | )
3213 | .option(
3214 | '--from <id>',
3215 | 'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")'
3216 | )
3217 | .option(
3218 | '--to <id>',
3219 | 'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated'
3220 | )
3221 | .option('--tag <tag>', 'Specify tag context for task operations')
3222 | .option('--from-tag <tag>', 'Source tag for cross-tag moves')
3223 | .option('--to-tag <tag>', 'Target tag for cross-tag moves')
3224 | .option('--with-dependencies', 'Move dependent tasks along with main task')
3225 | .option('--ignore-dependencies', 'Break cross-tag dependencies during move')
3226 | .action(async (options) => {
3227 | // Helper function to show move command help - defined in scope for proper encapsulation
3228 | function showMoveHelp() {
3229 | console.log(
3230 | chalk.white.bold('Move Command Help') +
3231 | '\n\n' +
3232 | chalk.cyan('Move tasks between tags or reorder within tags.') +
3233 | '\n\n' +
3234 | chalk.yellow.bold('Within-Tag Moves:') +
3235 | '\n' +
3236 | chalk.white(' task-master move --from=5 --to=7') +
3237 | '\n' +
3238 | chalk.white(' task-master move --from=5.2 --to=7.3') +
3239 | '\n' +
3240 | chalk.white(' task-master move --from=5,6,7 --to=10,11,12') +
3241 | '\n\n' +
3242 | chalk.yellow.bold('Cross-Tag Moves:') +
3243 | '\n' +
3244 | chalk.white(
3245 | ' task-master move --from=5 --from-tag=backlog --to-tag=in-progress'
3246 | ) +
3247 | '\n' +
3248 | chalk.white(
3249 | ' task-master move --from=5,6 --from-tag=backlog --to-tag=done'
3250 | ) +
3251 | '\n\n' +
3252 | chalk.yellow.bold('Dependency Resolution:') +
3253 | '\n' +
3254 | chalk.white(' # Move with dependencies') +
3255 | '\n' +
3256 | chalk.white(
3257 | ' task-master move --from=5 --from-tag=backlog --to-tag=in-progress --with-dependencies'
3258 | ) +
3259 | '\n\n' +
3260 | chalk.white(' # Break dependencies') +
3261 | '\n' +
3262 | chalk.white(
3263 | ' task-master move --from=5 --from-tag=backlog --to-tag=in-progress --ignore-dependencies'
3264 | ) +
3265 | '\n\n' +
3266 | '\n' +
3267 | chalk.yellow.bold('Best Practices:') +
3268 | '\n' +
3269 | chalk.white(
3270 | ' • Use --with-dependencies to move dependent tasks together'
3271 | ) +
3272 | '\n' +
3273 | chalk.white(
3274 | ' • Use --ignore-dependencies to break cross-tag dependencies'
3275 | ) +
3276 | '\n' +
3277 | chalk.white(
3278 | ' • Check dependencies first: task-master validate-dependencies'
3279 | ) +
3280 | '\n' +
3281 | chalk.white(
3282 | ' • Fix dependency issues: task-master fix-dependencies'
3283 | ) +
3284 | '\n\n' +
3285 | chalk.yellow.bold('Error Resolution:') +
3286 | '\n' +
3287 | chalk.white(
3288 | ' • Cross-tag dependency conflicts: Use --with-dependencies or --ignore-dependencies'
3289 | ) +
3290 | '\n' +
3291 | chalk.white(
3292 | ' • Subtask movement: Promote subtask first with remove-subtask --convert'
3293 | ) +
3294 | '\n' +
3295 | chalk.white(
3296 | ' • Invalid tags: Check available tags with task-master tags'
3297 | ) +
3298 | '\n\n' +
3299 | chalk.gray('For more help, run: task-master move --help')
3300 | );
3301 | }
3302 |
3303 | // Helper function to handle cross-tag move logic
3304 | async function handleCrossTagMove(moveContext, options) {
3305 | const { sourceId, sourceTag, toTag, taskMaster } = moveContext;
3306 |
3307 | if (!sourceId) {
3308 | console.error(
3309 | chalk.red('Error: --from parameter is required for cross-tag moves')
3310 | );
3311 | showMoveHelp();
3312 | process.exit(1);
3313 | }
3314 |
3315 | const sourceIds = sourceId.split(',').map((id) => id.trim());
3316 | const moveOptions = {
3317 | withDependencies: options.withDependencies || false,
3318 | ignoreDependencies: options.ignoreDependencies || false
3319 | };
3320 |
3321 | console.log(
3322 | chalk.blue(
3323 | `Moving tasks ${sourceIds.join(', ')} from "${sourceTag}" to "${toTag}"...`
3324 | )
3325 | );
3326 |
3327 | const result = await moveTasksBetweenTags(
3328 | taskMaster.getTasksPath(),
3329 | sourceIds,
3330 | sourceTag,
3331 | toTag,
3332 | moveOptions,
3333 | { projectRoot: taskMaster.getProjectRoot() }
3334 | );
3335 |
3336 | console.log(chalk.green(`✓ ${result.message}`));
3337 |
3338 | // Print any tips returned from the move operation (e.g., after ignoring dependencies)
3339 | if (Array.isArray(result.tips) && result.tips.length > 0) {
3340 | console.log('\n' + chalk.yellow.bold('Next Steps:'));
3341 | result.tips.forEach((t) => console.log(chalk.white(` • ${t}`)));
3342 | }
3343 |
3344 | // Check if source tag still contains tasks before regenerating files
3345 | const tasksData = readJSON(
3346 | taskMaster.getTasksPath(),
3347 | taskMaster.getProjectRoot(),
3348 | sourceTag
3349 | );
3350 | const sourceTagHasTasks =
3351 | tasksData &&
3352 | Array.isArray(tasksData.tasks) &&
3353 | tasksData.tasks.length > 0;
3354 |
3355 | // Generate task files for the affected tags
3356 | await generateTaskFiles(
3357 | taskMaster.getTasksPath(),
3358 | path.dirname(taskMaster.getTasksPath()),
3359 | { tag: toTag, projectRoot: taskMaster.getProjectRoot() }
3360 | );
3361 |
3362 | // Only regenerate source tag files if it still contains tasks
3363 | if (sourceTagHasTasks) {
3364 | await generateTaskFiles(
3365 | taskMaster.getTasksPath(),
3366 | path.dirname(taskMaster.getTasksPath()),
3367 | { tag: sourceTag, projectRoot: taskMaster.getProjectRoot() }
3368 | );
3369 | }
3370 | }
3371 |
3372 | // Helper function to handle within-tag move logic
3373 | async function handleWithinTagMove(moveContext) {
3374 | const { sourceId, destinationId, tag, taskMaster } = moveContext;
3375 |
3376 | if (!sourceId || !destinationId) {
3377 | console.error(
3378 | chalk.red(
3379 | 'Error: Both --from and --to parameters are required for within-tag moves'
3380 | )
3381 | );
3382 | console.log(
3383 | chalk.yellow(
3384 | 'Usage: task-master move --from=<sourceId> --to=<destinationId>'
3385 | )
3386 | );
3387 | process.exit(1);
3388 | }
3389 |
3390 | // Check if we're moving multiple tasks (comma-separated IDs)
3391 | const sourceIds = sourceId.split(',').map((id) => id.trim());
3392 | const destinationIds = destinationId.split(',').map((id) => id.trim());
3393 |
3394 | // Validate that the number of source and destination IDs match
3395 | if (sourceIds.length !== destinationIds.length) {
3396 | console.error(
3397 | chalk.red(
3398 | 'Error: The number of source and destination IDs must match'
3399 | )
3400 | );
3401 | console.log(
3402 | chalk.yellow('Example: task-master move --from=5,6,7 --to=10,11,12')
3403 | );
3404 | process.exit(1);
3405 | }
3406 |
3407 | // If moving multiple tasks
3408 | if (sourceIds.length > 1) {
3409 | console.log(
3410 | chalk.blue(
3411 | `Moving multiple tasks: ${sourceIds.join(', ')} to ${destinationIds.join(', ')}...`
3412 | )
3413 | );
3414 |
3415 | // Read tasks data once to validate destination IDs
3416 | const tasksData = readJSON(
3417 | taskMaster.getTasksPath(),
3418 | taskMaster.getProjectRoot(),
3419 | tag
3420 | );
3421 | if (!tasksData || !tasksData.tasks) {
3422 | console.error(
3423 | chalk.red(
3424 | `Error: Invalid or missing tasks file at ${taskMaster.getTasksPath()}`
3425 | )
3426 | );
3427 | process.exit(1);
3428 | }
3429 |
3430 | // Collect errors during move attempts
3431 | const moveErrors = [];
3432 | const successfulMoves = [];
3433 |
3434 | // Move tasks one by one
3435 | for (let i = 0; i < sourceIds.length; i++) {
3436 | const fromId = sourceIds[i];
3437 | const toId = destinationIds[i];
3438 |
3439 | // Skip if source and destination are the same
3440 | if (fromId === toId) {
3441 | console.log(
3442 | chalk.yellow(`Skipping ${fromId} -> ${toId} (same ID)`)
3443 | );
3444 | continue;
3445 | }
3446 |
3447 | console.log(
3448 | chalk.blue(`Moving task/subtask ${fromId} to ${toId}...`)
3449 | );
3450 | try {
3451 | await moveTask(
3452 | taskMaster.getTasksPath(),
3453 | fromId,
3454 | toId,
3455 | i === sourceIds.length - 1,
3456 | { projectRoot: taskMaster.getProjectRoot(), tag }
3457 | );
3458 | console.log(
3459 | chalk.green(
3460 | `✓ Successfully moved task/subtask ${fromId} to ${toId}`
3461 | )
3462 | );
3463 | successfulMoves.push({ fromId, toId });
3464 | } catch (error) {
3465 | const errorInfo = {
3466 | fromId,
3467 | toId,
3468 | error: error.message
3469 | };
3470 | moveErrors.push(errorInfo);
3471 | console.error(
3472 | chalk.red(`Error moving ${fromId} to ${toId}: ${error.message}`)
3473 | );
3474 | // Continue with the next task rather than exiting
3475 | }
3476 | }
3477 |
3478 | // Display summary after all moves are attempted
3479 | if (moveErrors.length > 0) {
3480 | console.log(chalk.yellow('\n--- Move Operation Summary ---'));
3481 | console.log(
3482 | chalk.green(
3483 | `✓ Successfully moved: ${successfulMoves.length} tasks`
3484 | )
3485 | );
3486 | console.log(
3487 | chalk.red(`✗ Failed to move: ${moveErrors.length} tasks`)
3488 | );
3489 |
3490 | if (successfulMoves.length > 0) {
3491 | console.log(chalk.cyan('\nSuccessful moves:'));
3492 | successfulMoves.forEach(({ fromId, toId }) => {
3493 | console.log(chalk.cyan(` ${fromId} → ${toId}`));
3494 | });
3495 | }
3496 |
3497 | console.log(chalk.red('\nFailed moves:'));
3498 | moveErrors.forEach(({ fromId, toId, error }) => {
3499 | console.log(chalk.red(` ${fromId} → ${toId}: ${error}`));
3500 | });
3501 |
3502 | console.log(
3503 | chalk.yellow(
3504 | '\nNote: Some tasks were moved successfully. Check the errors above for failed moves.'
3505 | )
3506 | );
3507 | } else {
3508 | console.log(chalk.green('\n✓ All tasks moved successfully!'));
3509 | }
3510 | } else {
3511 | // Moving a single task (existing logic)
3512 | console.log(
3513 | chalk.blue(`Moving task/subtask ${sourceId} to ${destinationId}...`)
3514 | );
3515 |
3516 | const result = await moveTask(
3517 | taskMaster.getTasksPath(),
3518 | sourceId,
3519 | destinationId,
3520 | true,
3521 | { projectRoot: taskMaster.getProjectRoot(), tag }
3522 | );
3523 | console.log(
3524 | chalk.green(
3525 | `✓ Successfully moved task/subtask ${sourceId} to ${destinationId}`
3526 | )
3527 | );
3528 | }
3529 | }
3530 |
3531 | // Helper function to handle move errors
3532 | function handleMoveError(error, moveContext) {
3533 | console.error(chalk.red(`Error: ${error.message}`));
3534 |
3535 | // Enhanced error handling with structured error objects
3536 | if (error.code === 'CROSS_TAG_DEPENDENCY_CONFLICTS') {
3537 | // Use structured error data
3538 | const conflicts = error.data.conflicts || [];
3539 | const taskIds = error.data.taskIds || [];
3540 | displayCrossTagDependencyError(
3541 | conflicts,
3542 | moveContext.sourceTag,
3543 | moveContext.toTag,
3544 | taskIds.join(', ')
3545 | );
3546 | } else if (error.code === 'CANNOT_MOVE_SUBTASK') {
3547 | // Use structured error data
3548 | const taskId =
3549 | error.data.taskId || moveContext.sourceId?.split(',')[0];
3550 | displaySubtaskMoveError(
3551 | taskId,
3552 | moveContext.sourceTag,
3553 | moveContext.toTag
3554 | );
3555 | } else if (
3556 | error.code === 'SOURCE_TARGET_TAGS_SAME' ||
3557 | error.code === 'SAME_SOURCE_TARGET_TAG'
3558 | ) {
3559 | displayInvalidTagCombinationError(
3560 | moveContext.sourceTag,
3561 | moveContext.toTag,
3562 | 'Source and target tags are identical'
3563 | );
3564 | } else {
3565 | // General error - show dependency validation hints
3566 | displayDependencyValidationHints('after-error');
3567 | }
3568 |
3569 | process.exit(1);
3570 | }
3571 |
3572 | // Initialize TaskMaster
3573 | const taskMaster = initTaskMaster({
3574 | tasksPath: options.file || true,
3575 | tag: options.tag
3576 | });
3577 |
3578 | const sourceId = options.from;
3579 | const destinationId = options.to;
3580 | const fromTag = options.fromTag;
3581 | const toTag = options.toTag;
3582 |
3583 | const tag = taskMaster.getCurrentTag();
3584 |
3585 | // Get the source tag - fallback to current tag if not provided
3586 | const sourceTag = fromTag || taskMaster.getCurrentTag();
3587 |
3588 | // Check if this is a cross-tag move (different tags)
3589 | const isCrossTagMove = sourceTag && toTag && sourceTag !== toTag;
3590 |
3591 | // Initialize move context with all relevant data
3592 | const moveContext = {
3593 | sourceId,
3594 | destinationId,
3595 | sourceTag,
3596 | toTag,
3597 | tag,
3598 | taskMaster
3599 | };
3600 |
3601 | try {
3602 | if (isCrossTagMove) {
3603 | // Cross-tag move logic
3604 | await handleCrossTagMove(moveContext, options);
3605 | } else {
3606 | // Within-tag move logic
3607 | await handleWithinTagMove(moveContext);
3608 | }
3609 | } catch (error) {
3610 | const errMsg = String(error && (error.message || error));
3611 | if (errMsg.includes('already exists in target tag')) {
3612 | console.error(chalk.red(`Error: ${errMsg}`));
3613 | console.log(
3614 | '\n' +
3615 | chalk.yellow.bold('Conflict: ID already exists in target tag') +
3616 | '\n' +
3617 | chalk.white(
3618 | ' • Choose a different target tag without conflicting IDs'
3619 | ) +
3620 | '\n' +
3621 | chalk.white(
3622 | ' • Move a different set of IDs (avoid existing ones)'
3623 | ) +
3624 | '\n' +
3625 | chalk.white(
3626 | ' • If needed, move within-tag to a new ID first, then cross-tag move'
3627 | )
3628 | );
3629 | process.exit(1);
3630 | }
3631 | handleMoveError(error, moveContext);
3632 | }
3633 | });
3634 |
3635 | // Add/remove profile rules command
3636 | programInstance
3637 | .command('rules [action] [profiles...]')
3638 | .description(
3639 | `Add or remove rules for one or more profiles. Valid actions: ${Object.values(RULES_ACTIONS).join(', ')} (e.g., task-master rules ${RULES_ACTIONS.ADD} windsurf roo)`
3640 | )
3641 | .option(
3642 | '-f, --force',
3643 | 'Skip confirmation prompt when removing rules (dangerous)'
3644 | )
3645 | .option(
3646 | `--${RULES_SETUP_ACTION}`,
3647 | 'Run interactive setup to select rule profiles to add'
3648 | )
3649 | .addHelpText(
3650 | 'after',
3651 | `
3652 | Examples:
3653 | $ task-master rules ${RULES_ACTIONS.ADD} windsurf roo # Add Windsurf and Roo rule sets
3654 | $ task-master rules ${RULES_ACTIONS.REMOVE} windsurf # Remove Windsurf rule set
3655 | $ task-master rules --${RULES_SETUP_ACTION} # Interactive setup to select rule profiles`
3656 | )
3657 | .action(async (action, profiles, options) => {
3658 | const taskMaster = initTaskMaster({});
3659 | const projectRoot = taskMaster.getProjectRoot();
3660 | if (!projectRoot) {
3661 | console.error(chalk.red('Error: Could not find project root.'));
3662 | process.exit(1);
3663 | }
3664 |
3665 | /**
3666 | * 'task-master rules --setup' action:
3667 | *
3668 | * Launches an interactive prompt to select which rule profiles to add to the current project.
3669 | * This does NOT perform project initialization or ask about shell aliases—only rules selection.
3670 | *
3671 | * Example usage:
3672 | * $ task-master rules --setup
3673 | *
3674 | * Useful for adding rules after project creation.
3675 | *
3676 | * The list of profiles is always up-to-date with the available profiles.
3677 | */
3678 | if (options[RULES_SETUP_ACTION]) {
3679 | // Run interactive rules setup ONLY (no project init)
3680 | const selectedRuleProfiles = await runInteractiveProfilesSetup();
3681 |
3682 | if (!selectedRuleProfiles || selectedRuleProfiles.length === 0) {
3683 | console.log(chalk.yellow('No profiles selected. Exiting.'));
3684 | return;
3685 | }
3686 |
3687 | console.log(
3688 | chalk.blue(
3689 | `Installing ${selectedRuleProfiles.length} selected profile(s)...`
3690 | )
3691 | );
3692 |
3693 | for (let i = 0; i < selectedRuleProfiles.length; i++) {
3694 | const profile = selectedRuleProfiles[i];
3695 | console.log(
3696 | chalk.blue(
3697 | `Processing profile ${i + 1}/${selectedRuleProfiles.length}: ${profile}...`
3698 | )
3699 | );
3700 |
3701 | if (!isValidProfile(profile)) {
3702 | console.warn(
3703 | `Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
3704 | );
3705 | continue;
3706 | }
3707 | const profileConfig = getRulesProfile(profile);
3708 |
3709 | const addResult = convertAllRulesToProfileRules(
3710 | projectRoot,
3711 | profileConfig
3712 | );
3713 |
3714 | console.log(chalk.green(generateProfileSummary(profile, addResult)));
3715 | }
3716 |
3717 | console.log(
3718 | chalk.green(
3719 | `\nCompleted installation of all ${selectedRuleProfiles.length} profile(s).`
3720 | )
3721 | );
3722 | return;
3723 | }
3724 |
3725 | // Validate action for non-setup mode
3726 | if (!action || !isValidRulesAction(action)) {
3727 | console.error(
3728 | chalk.red(
3729 | `Error: Invalid or missing action '${action || 'none'}'. Valid actions are: ${Object.values(RULES_ACTIONS).join(', ')}`
3730 | )
3731 | );
3732 | console.error(
3733 | chalk.yellow(
3734 | `For interactive setup, use: task-master rules --${RULES_SETUP_ACTION}`
3735 | )
3736 | );
3737 | process.exit(1);
3738 | }
3739 |
3740 | if (!profiles || profiles.length === 0) {
3741 | console.error(
3742 | 'Please specify at least one rule profile (e.g., windsurf, roo).'
3743 | );
3744 | process.exit(1);
3745 | }
3746 |
3747 | // Support both space- and comma-separated profile lists
3748 | const expandedProfiles = profiles
3749 | .flatMap((b) => b.split(',').map((s) => s.trim()))
3750 | .filter(Boolean);
3751 |
3752 | if (action === RULES_ACTIONS.REMOVE) {
3753 | let confirmed = true;
3754 | if (!options.force) {
3755 | // Check if this removal would leave no profiles remaining
3756 | if (wouldRemovalLeaveNoProfiles(projectRoot, expandedProfiles)) {
3757 | const installedProfiles = getInstalledProfiles(projectRoot);
3758 | confirmed = await confirmRemoveAllRemainingProfiles(
3759 | expandedProfiles,
3760 | installedProfiles
3761 | );
3762 | } else {
3763 | confirmed = await confirmProfilesRemove(expandedProfiles);
3764 | }
3765 | }
3766 | if (!confirmed) {
3767 | console.log(chalk.yellow('Aborted: No rules were removed.'));
3768 | return;
3769 | }
3770 | }
3771 |
3772 | const removalResults = [];
3773 | const addResults = [];
3774 |
3775 | for (const profile of expandedProfiles) {
3776 | if (!isValidProfile(profile)) {
3777 | console.warn(
3778 | `Rule profile for "${profile}" not found. Valid profiles: ${RULE_PROFILES.join(', ')}. Skipping.`
3779 | );
3780 | continue;
3781 | }
3782 | const profileConfig = getRulesProfile(profile);
3783 |
3784 | if (action === RULES_ACTIONS.ADD) {
3785 | console.log(chalk.blue(`Adding rules for profile: ${profile}...`));
3786 | const addResult = convertAllRulesToProfileRules(
3787 | projectRoot,
3788 | profileConfig
3789 | );
3790 | console.log(
3791 | chalk.blue(`Completed adding rules for profile: ${profile}`)
3792 | );
3793 |
3794 | // Store result with profile name for summary
3795 | addResults.push({
3796 | profileName: profile,
3797 | success: addResult.success,
3798 | failed: addResult.failed
3799 | });
3800 |
3801 | console.log(chalk.green(generateProfileSummary(profile, addResult)));
3802 | } else if (action === RULES_ACTIONS.REMOVE) {
3803 | console.log(chalk.blue(`Removing rules for profile: ${profile}...`));
3804 | const result = removeProfileRules(projectRoot, profileConfig);
3805 | removalResults.push(result);
3806 | console.log(
3807 | chalk.green(generateProfileRemovalSummary(profile, result))
3808 | );
3809 | } else {
3810 | console.error(
3811 | `Unknown action. Use "${RULES_ACTIONS.ADD}" or "${RULES_ACTIONS.REMOVE}".`
3812 | );
3813 | process.exit(1);
3814 | }
3815 | }
3816 |
3817 | // Print summary for additions
3818 | if (action === RULES_ACTIONS.ADD && addResults.length > 0) {
3819 | const { allSuccessfulProfiles, totalSuccess, totalFailed } =
3820 | categorizeProfileResults(addResults);
3821 |
3822 | if (allSuccessfulProfiles.length > 0) {
3823 | console.log(
3824 | chalk.green(
3825 | `\nSuccessfully processed profiles: ${allSuccessfulProfiles.join(', ')}`
3826 | )
3827 | );
3828 |
3829 | // Create a descriptive summary
3830 | if (totalSuccess > 0) {
3831 | console.log(
3832 | chalk.green(
3833 | `Total: ${totalSuccess} files processed, ${totalFailed} failed.`
3834 | )
3835 | );
3836 | } else {
3837 | console.log(
3838 | chalk.green(
3839 | `Total: ${allSuccessfulProfiles.length} profile(s) set up successfully.`
3840 | )
3841 | );
3842 | }
3843 | }
3844 | }
3845 |
3846 | // Print summary for removals
3847 | if (action === RULES_ACTIONS.REMOVE && removalResults.length > 0) {
3848 | const {
3849 | successfulRemovals,
3850 | skippedRemovals,
3851 | failedRemovals,
3852 | removalsWithNotices
3853 | } = categorizeRemovalResults(removalResults);
3854 |
3855 | if (successfulRemovals.length > 0) {
3856 | console.log(
3857 | chalk.green(
3858 | `\nSuccessfully removed profiles for: ${successfulRemovals.join(', ')}`
3859 | )
3860 | );
3861 | }
3862 | if (skippedRemovals.length > 0) {
3863 | console.log(
3864 | chalk.yellow(
3865 | `Skipped (default or protected): ${skippedRemovals.join(', ')}`
3866 | )
3867 | );
3868 | }
3869 | if (failedRemovals.length > 0) {
3870 | console.log(chalk.red('\nErrors occurred:'));
3871 | failedRemovals.forEach((r) => {
3872 | console.log(chalk.red(` ${r.profileName}: ${r.error}`));
3873 | });
3874 | }
3875 | // Display notices about preserved files/configurations
3876 | if (removalsWithNotices.length > 0) {
3877 | console.log(chalk.cyan('\nNotices:'));
3878 | removalsWithNotices.forEach((r) => {
3879 | console.log(chalk.cyan(` ${r.profileName}: ${r.notice}`));
3880 | });
3881 | }
3882 |
3883 | // Overall summary
3884 | const totalProcessed = removalResults.length;
3885 | const totalSuccessful = successfulRemovals.length;
3886 | const totalSkipped = skippedRemovals.length;
3887 | const totalFailed = failedRemovals.length;
3888 |
3889 | console.log(
3890 | chalk.blue(
3891 | `\nTotal: ${totalProcessed} profile(s) processed - ${totalSuccessful} removed, ${totalSkipped} skipped, ${totalFailed} failed.`
3892 | )
3893 | );
3894 | }
3895 | });
3896 |
3897 | programInstance
3898 | .command('migrate')
3899 | .description(
3900 | 'Migrate existing project to use the new .taskmaster directory structure'
3901 | )
3902 | .option(
3903 | '-f, --force',
3904 | 'Force migration even if .taskmaster directory already exists'
3905 | )
3906 | .option(
3907 | '--backup',
3908 | 'Create backup of old files before migration (default: false)',
3909 | false
3910 | )
3911 | .option(
3912 | '--cleanup',
3913 | 'Remove old files after successful migration (default: true)',
3914 | true
3915 | )
3916 | .option('-y, --yes', 'Skip confirmation prompts')
3917 | .option(
3918 | '--dry-run',
3919 | 'Show what would be migrated without actually moving files'
3920 | )
3921 | .action(async (options) => {
3922 | try {
3923 | await migrateProject(options);
3924 | } catch (error) {
3925 | console.error(chalk.red('Error during migration:'), error.message);
3926 | process.exit(1);
3927 | }
3928 | });
3929 |
3930 | // sync-readme command
3931 | programInstance
3932 | .command('sync-readme')
3933 | .description('Sync the current task list to README.md in the project root')
3934 | .option(
3935 | '-f, --file <file>',
3936 | 'Path to the tasks file',
3937 | TASKMASTER_TASKS_FILE
3938 | )
3939 | .option('--with-subtasks', 'Include subtasks in the README output')
3940 | .option(
3941 | '-s, --status <status>',
3942 | 'Show only tasks matching this status (e.g., pending, done)'
3943 | )
3944 | .option('-t, --tag <tag>', 'Tag to use for the task list (default: master)')
3945 | .action(async (options) => {
3946 | // Initialize TaskMaster
3947 | const taskMaster = initTaskMaster({
3948 | tasksPath: options.file || true,
3949 | tag: options.tag
3950 | });
3951 |
3952 | const withSubtasks = options.withSubtasks || false;
3953 | const status = options.status || null;
3954 |
3955 | const tag = taskMaster.getCurrentTag();
3956 |
3957 | console.log(
3958 | chalk.blue(
3959 | `📝 Syncing tasks to README.md${withSubtasks ? ' (with subtasks)' : ''}${status ? ` (status: ${status})` : ''}...`
3960 | )
3961 | );
3962 |
3963 | const success = await syncTasksToReadme(taskMaster.getProjectRoot(), {
3964 | withSubtasks,
3965 | status,
3966 | tasksPath: taskMaster.getTasksPath(),
3967 | tag
3968 | });
3969 |
3970 | if (!success) {
3971 | console.error(chalk.red('❌ Failed to sync tasks to README.md'));
3972 | process.exit(1);
3973 | }
3974 | });
3975 |
3976 | // ===== TAG MANAGEMENT COMMANDS =====
3977 |
3978 | // add-tag command (DEPRECATED - use `tm tags add` instead)
3979 | programInstance
3980 | .command('add-tag')
3981 | .description(
3982 | '[DEPRECATED] Create a new tag context for organizing tasks (use "tm tags add" instead)'
3983 | )
3984 | .argument(
3985 | '[tagName]',
3986 | 'Name of the new tag to create (optional when using --from-branch)'
3987 | )
3988 | .option(
3989 | '-f, --file <file>',
3990 | 'Path to the tasks file',
3991 | TASKMASTER_TASKS_FILE
3992 | )
3993 | .option(
3994 | '--copy-from-current',
3995 | 'Copy tasks from the current tag to the new tag'
3996 | )
3997 | .option(
3998 | '--copy-from <tag>',
3999 | 'Copy tasks from the specified tag to the new tag'
4000 | )
4001 | .option(
4002 | '--from-branch',
4003 | 'Create tag name from current git branch (ignores tagName argument)'
4004 | )
4005 | .option('-d, --description <text>', 'Optional description for the tag')
4006 | .action(async (tagName, options) => {
4007 | // Show deprecation warning
4008 | console.warn(
4009 | chalk.yellow(
4010 | '⚠ Warning: "tm add-tag" is deprecated. Use "tm tags add" instead.'
4011 | )
4012 | );
4013 | console.log(
4014 | chalk.gray(' This command will be removed in a future version.\n')
4015 | );
4016 |
4017 | try {
4018 | // Initialize TaskMaster
4019 | const taskMaster = initTaskMaster({
4020 | tasksPath: options.file || true
4021 | });
4022 | const tasksPath = taskMaster.getTasksPath();
4023 |
4024 | // Validate tasks file exists
4025 | if (!fs.existsSync(tasksPath)) {
4026 | console.error(
4027 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
4028 | );
4029 | console.log(
4030 | chalk.yellow(
4031 | 'Hint: Run task-master init or task-master parse-prd to create tasks.json first'
4032 | )
4033 | );
4034 | process.exit(1);
4035 | }
4036 |
4037 | // Validate that either tagName is provided or --from-branch is used
4038 | if (!tagName && !options.fromBranch) {
4039 | console.error(
4040 | chalk.red(
4041 | 'Error: Either tagName argument or --from-branch option is required.'
4042 | )
4043 | );
4044 | console.log(chalk.yellow('Usage examples:'));
4045 | console.log(chalk.cyan(' task-master add-tag my-tag'));
4046 | console.log(chalk.cyan(' task-master add-tag --from-branch'));
4047 | process.exit(1);
4048 | }
4049 |
4050 | const context = {
4051 | projectRoot: taskMaster.getProjectRoot(),
4052 | commandName: 'add-tag',
4053 | outputType: 'cli'
4054 | };
4055 |
4056 | // Handle --from-branch option
4057 | if (options.fromBranch) {
4058 | const { createTagFromBranch } = await import(
4059 | './task-manager/tag-management.js'
4060 | );
4061 | const gitUtils = await import('./utils/git-utils.js');
4062 |
4063 | // Check if we're in a git repository
4064 | if (!(await gitUtils.isGitRepository(context.projectRoot))) {
4065 | console.error(
4066 | chalk.red(
4067 | 'Error: Not in a git repository. Cannot use --from-branch option.'
4068 | )
4069 | );
4070 | process.exit(1);
4071 | }
4072 |
4073 | // Get current git branch
4074 | const currentBranch = await gitUtils.getCurrentBranch(
4075 | context.projectRoot
4076 | );
4077 | if (!currentBranch) {
4078 | console.error(
4079 | chalk.red('Error: Could not determine current git branch.')
4080 | );
4081 | process.exit(1);
4082 | }
4083 |
4084 | // Create tag from branch
4085 | const branchOptions = {
4086 | copyFromCurrent: options.copyFromCurrent || false,
4087 | copyFromTag: options.copyFrom,
4088 | description:
4089 | options.description ||
4090 | `Tag created from git branch "${currentBranch}"`
4091 | };
4092 |
4093 | await createTagFromBranch(
4094 | taskMaster.getTasksPath(),
4095 | currentBranch,
4096 | branchOptions,
4097 | context,
4098 | 'text'
4099 | );
4100 | } else {
4101 | // Regular tag creation
4102 | const createOptions = {
4103 | copyFromCurrent: options.copyFromCurrent || false,
4104 | copyFromTag: options.copyFrom,
4105 | description: options.description
4106 | };
4107 |
4108 | await createTag(
4109 | taskMaster.getTasksPath(),
4110 | tagName,
4111 | createOptions,
4112 | context,
4113 | 'text'
4114 | );
4115 | }
4116 |
4117 | // Handle auto-switch if requested
4118 | if (options.autoSwitch) {
4119 | const { useTag } = await import('./task-manager/tag-management.js');
4120 | const finalTagName = options.fromBranch
4121 | ? (await import('./utils/git-utils.js')).sanitizeBranchNameForTag(
4122 | await (await import('./utils/git-utils.js')).getCurrentBranch(
4123 | projectRoot
4124 | )
4125 | )
4126 | : tagName;
4127 | await useTag(
4128 | taskMaster.getTasksPath(),
4129 | finalTagName,
4130 | {},
4131 | context,
4132 | 'text'
4133 | );
4134 | }
4135 | } catch (error) {
4136 | console.error(chalk.red(`Error creating tag: ${error.message}`));
4137 | showAddTagHelp();
4138 | process.exit(1);
4139 | }
4140 | })
4141 | .on('error', function (err) {
4142 | console.error(chalk.red(`Error: ${err.message}`));
4143 | showAddTagHelp();
4144 | process.exit(1);
4145 | });
4146 |
4147 | // delete-tag command (DEPRECATED - use `tm tags remove` instead)
4148 | programInstance
4149 | .command('delete-tag')
4150 | .description(
4151 | '[DEPRECATED] Delete an existing tag and all its tasks (use "tm tags remove" instead)'
4152 | )
4153 | .argument('<tagName>', 'Name of the tag to delete')
4154 | .option(
4155 | '-f, --file <file>',
4156 | 'Path to the tasks file',
4157 | TASKMASTER_TASKS_FILE
4158 | )
4159 | .option('-y, --yes', 'Skip confirmation prompts')
4160 | .action(async (tagName, options) => {
4161 | // Show deprecation warning
4162 | console.warn(
4163 | chalk.yellow(
4164 | '⚠ Warning: "tm delete-tag" is deprecated. Use "tm tags remove" instead.'
4165 | )
4166 | );
4167 | console.log(
4168 | chalk.gray(' This command will be removed in a future version.\n')
4169 | );
4170 |
4171 | try {
4172 | // Initialize TaskMaster
4173 | const taskMaster = initTaskMaster({
4174 | tasksPath: options.file || true
4175 | });
4176 | const tasksPath = taskMaster.getTasksPath();
4177 |
4178 | // Validate tasks file exists
4179 | if (!fs.existsSync(tasksPath)) {
4180 | console.error(
4181 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
4182 | );
4183 | process.exit(1);
4184 | }
4185 |
4186 | const deleteOptions = {
4187 | yes: options.yes || false
4188 | };
4189 |
4190 | const context = {
4191 | projectRoot: taskMaster.getProjectRoot(),
4192 | commandName: 'delete-tag',
4193 | outputType: 'cli'
4194 | };
4195 |
4196 | await deleteTag(
4197 | taskMaster.getTasksPath(),
4198 | tagName,
4199 | deleteOptions,
4200 | context,
4201 | 'text'
4202 | );
4203 | } catch (error) {
4204 | console.error(chalk.red(`Error deleting tag: ${error.message}`));
4205 | showDeleteTagHelp();
4206 | process.exit(1);
4207 | }
4208 | })
4209 | .on('error', function (err) {
4210 | console.error(chalk.red(`Error: ${err.message}`));
4211 | showDeleteTagHelp();
4212 | process.exit(1);
4213 | });
4214 |
4215 | // tags command - REMOVED
4216 | // This command has been replaced by the new CommandRegistry-based TagsCommand
4217 | // in apps/cli/src/commands/tags.command.ts
4218 | // The old implementation is no longer needed
4219 |
4220 | // use-tag command (DEPRECATED - use `tm tags use` instead)
4221 | programInstance
4222 | .command('use-tag')
4223 | .description(
4224 | '[DEPRECATED] Switch to a different tag context (use "tm tags use" instead)'
4225 | )
4226 | .argument('<tagName>', 'Name of the tag to switch to')
4227 | .option(
4228 | '-f, --file <file>',
4229 | 'Path to the tasks file',
4230 | TASKMASTER_TASKS_FILE
4231 | )
4232 | .action(async (tagName, options) => {
4233 | // Show deprecation warning
4234 | console.warn(
4235 | chalk.yellow(
4236 | '⚠ Warning: "tm use-tag" is deprecated. Use "tm tags use" instead.'
4237 | )
4238 | );
4239 | console.log(
4240 | chalk.gray(' This command will be removed in a future version.\n')
4241 | );
4242 |
4243 | try {
4244 | // Initialize TaskMaster
4245 | const taskMaster = initTaskMaster({
4246 | tasksPath: options.file || true
4247 | });
4248 | const tasksPath = taskMaster.getTasksPath();
4249 |
4250 | // Validate tasks file exists
4251 | if (!fs.existsSync(tasksPath)) {
4252 | console.error(
4253 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
4254 | );
4255 | process.exit(1);
4256 | }
4257 |
4258 | const context = {
4259 | projectRoot: taskMaster.getProjectRoot(),
4260 | commandName: 'use-tag',
4261 | outputType: 'cli'
4262 | };
4263 |
4264 | await useTag(taskMaster.getTasksPath(), tagName, {}, context, 'text');
4265 | } catch (error) {
4266 | console.error(chalk.red(`Error switching tag: ${error.message}`));
4267 | showUseTagHelp();
4268 | process.exit(1);
4269 | }
4270 | })
4271 | .on('error', function (err) {
4272 | console.error(chalk.red(`Error: ${err.message}`));
4273 | showUseTagHelp();
4274 | process.exit(1);
4275 | });
4276 |
4277 | // rename-tag command (DEPRECATED - use `tm tags rename` instead)
4278 | programInstance
4279 | .command('rename-tag')
4280 | .description(
4281 | '[DEPRECATED] Rename an existing tag (use "tm tags rename" instead)'
4282 | )
4283 | .argument('<oldName>', 'Current name of the tag')
4284 | .argument('<newName>', 'New name for the tag')
4285 | .option(
4286 | '-f, --file <file>',
4287 | 'Path to the tasks file',
4288 | TASKMASTER_TASKS_FILE
4289 | )
4290 | .action(async (oldName, newName, options) => {
4291 | // Show deprecation warning
4292 | console.warn(
4293 | chalk.yellow(
4294 | '⚠ Warning: "tm rename-tag" is deprecated. Use "tm tags rename" instead.'
4295 | )
4296 | );
4297 | console.log(
4298 | chalk.gray(' This command will be removed in a future version.\n')
4299 | );
4300 |
4301 | try {
4302 | // Initialize TaskMaster
4303 | const taskMaster = initTaskMaster({
4304 | tasksPath: options.file || true
4305 | });
4306 | const tasksPath = taskMaster.getTasksPath();
4307 |
4308 | // Validate tasks file exists
4309 | if (!fs.existsSync(tasksPath)) {
4310 | console.error(
4311 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
4312 | );
4313 | process.exit(1);
4314 | }
4315 |
4316 | const context = {
4317 | projectRoot: taskMaster.getProjectRoot(),
4318 | commandName: 'rename-tag',
4319 | outputType: 'cli'
4320 | };
4321 |
4322 | await renameTag(
4323 | taskMaster.getTasksPath(),
4324 | oldName,
4325 | newName,
4326 | {},
4327 | context,
4328 | 'text'
4329 | );
4330 | } catch (error) {
4331 | console.error(chalk.red(`Error renaming tag: ${error.message}`));
4332 | process.exit(1);
4333 | }
4334 | })
4335 | .on('error', function (err) {
4336 | console.error(chalk.red(`Error: ${err.message}`));
4337 | process.exit(1);
4338 | });
4339 |
4340 | // copy-tag command (DEPRECATED - use `tm tags copy` instead)
4341 | programInstance
4342 | .command('copy-tag')
4343 | .description(
4344 | '[DEPRECATED] Copy an existing tag to create a new tag with the same tasks (use "tm tags copy" instead)'
4345 | )
4346 | .argument('<sourceName>', 'Name of the source tag to copy from')
4347 | .argument('<targetName>', 'Name of the new tag to create')
4348 | .option(
4349 | '-f, --file <file>',
4350 | 'Path to the tasks file',
4351 | TASKMASTER_TASKS_FILE
4352 | )
4353 | .option('-d, --description <text>', 'Optional description for the new tag')
4354 | .action(async (sourceName, targetName, options) => {
4355 | // Show deprecation warning
4356 | console.warn(
4357 | chalk.yellow(
4358 | '⚠ Warning: "tm copy-tag" is deprecated. Use "tm tags copy" instead.'
4359 | )
4360 | );
4361 | console.log(
4362 | chalk.gray(' This command will be removed in a future version.\n')
4363 | );
4364 |
4365 | try {
4366 | // Initialize TaskMaster
4367 | const taskMaster = initTaskMaster({
4368 | tasksPath: options.file || true
4369 | });
4370 | const tasksPath = taskMaster.getTasksPath();
4371 |
4372 | // Validate tasks file exists
4373 | if (!fs.existsSync(tasksPath)) {
4374 | console.error(
4375 | chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
4376 | );
4377 | process.exit(1);
4378 | }
4379 |
4380 | const copyOptions = {
4381 | description: options.description
4382 | };
4383 |
4384 | const context = {
4385 | projectRoot: taskMaster.getProjectRoot(),
4386 | commandName: 'copy-tag',
4387 | outputType: 'cli'
4388 | };
4389 |
4390 | await copyTag(
4391 | tasksPath,
4392 | sourceName,
4393 | targetName,
4394 | copyOptions,
4395 | context,
4396 | 'text'
4397 | );
4398 | } catch (error) {
4399 | console.error(chalk.red(`Error copying tag: ${error.message}`));
4400 | process.exit(1);
4401 | }
4402 | })
4403 | .on('error', function (err) {
4404 | console.error(chalk.red(`Error: ${err.message}`));
4405 | process.exit(1);
4406 | });
4407 |
4408 | return programInstance;
4409 | }
4410 |
4411 | /**
4412 | * Setup the CLI application
4413 | * @returns {Object} Configured Commander program
4414 | */
4415 | function setupCLI() {
4416 | // Create a new program instance
4417 | const programInstance = new Command()
4418 | .name('task-master')
4419 | .description('AI-driven development task management')
4420 | .version(process.env.TM_PUBLIC_VERSION || 'unknown')
4421 | .helpOption('-h, --help', 'Display help')
4422 | .addHelpCommand(false); // Disable default help command
4423 |
4424 | // Only override help for the main program, not for individual commands
4425 | const originalHelpInformation =
4426 | programInstance.helpInformation.bind(programInstance);
4427 | programInstance.helpInformation = function () {
4428 | // If this is being called for a subcommand, use the default Commander.js help
4429 | if (this.parent && this.parent !== programInstance) {
4430 | return originalHelpInformation();
4431 | }
4432 | // If this is the main program help, use our custom display
4433 | displayHelp();
4434 | return '';
4435 | };
4436 |
4437 | // Register commands
4438 | registerCommands(programInstance);
4439 |
4440 | return programInstance;
4441 | }
4442 |
4443 | /**
4444 | * Parse arguments and run the CLI
4445 | * @param {Array} argv - Command-line arguments
4446 | */
4447 | async function runCLI(argv = process.argv) {
4448 | try {
4449 | // Display banner if not in a pipe (except for init command which has its own banner)
4450 | const isInitCommand = argv.includes('init');
4451 | if (process.stdout.isTTY && !isInitCommand) {
4452 | displayBanner();
4453 | }
4454 |
4455 | // If no arguments provided, show help
4456 | if (argv.length <= 2) {
4457 | displayHelp();
4458 | process.exit(0);
4459 | }
4460 |
4461 | // Check for updates BEFORE executing the command
4462 | const currentVersion = getTaskMasterVersion();
4463 | const updateInfo = await checkForUpdate(currentVersion);
4464 |
4465 | if (updateInfo.needsUpdate) {
4466 | // Display the upgrade notification first
4467 | displayUpgradeNotification(
4468 | updateInfo.currentVersion,
4469 | updateInfo.latestVersion,
4470 | updateInfo.highlights
4471 | );
4472 |
4473 | // Automatically perform the update
4474 | const updateSuccess = await performAutoUpdate(updateInfo.latestVersion);
4475 | if (updateSuccess) {
4476 | // Restart with the new version - this will execute the user's command
4477 | restartWithNewVersion(argv);
4478 | return; // Never reached, but for clarity
4479 | }
4480 | // If update fails, continue with current version
4481 | }
4482 |
4483 | // Setup and parse
4484 | // NOTE: getConfig() might be called during setupCLI->registerCommands if commands need config
4485 | // This means the ConfigurationError might be thrown here if configuration file is missing.
4486 | const programInstance = setupCLI();
4487 | await programInstance.parseAsync(argv);
4488 |
4489 | // Check if migration has occurred and show FYI notice once
4490 | try {
4491 | // Use initTaskMaster with no required fields - will only fail if no project root
4492 | const taskMaster = initTaskMaster({});
4493 |
4494 | const tasksPath = taskMaster.getTasksPath();
4495 | const statePath = taskMaster.getStatePath();
4496 |
4497 | if (tasksPath && fs.existsSync(tasksPath)) {
4498 | // Read raw file to check if it has master key (bypassing tag resolution)
4499 | const rawData = fs.readFileSync(tasksPath, 'utf8');
4500 | const parsedData = JSON.parse(rawData);
4501 |
4502 | if (parsedData && parsedData.master) {
4503 | // Migration has occurred, check if we've shown the notice
4504 | let stateData = { migrationNoticeShown: false };
4505 | if (statePath && fs.existsSync(statePath)) {
4506 | // Read state.json directly without tag resolution since it's not a tagged file
4507 | const rawStateData = fs.readFileSync(statePath, 'utf8');
4508 | stateData = JSON.parse(rawStateData) || stateData;
4509 | }
4510 |
4511 | if (!stateData.migrationNoticeShown) {
4512 | displayTaggedTasksFYI({ _migrationHappened: true });
4513 |
4514 | // Mark as shown
4515 | stateData.migrationNoticeShown = true;
4516 | // Write state.json directly without tag resolution since it's not a tagged file
4517 | if (statePath) {
4518 | fs.writeFileSync(statePath, JSON.stringify(stateData, null, 2));
4519 | }
4520 | }
4521 | }
4522 | }
4523 | } catch (error) {
4524 | // Silently ignore errors checking for migration notice
4525 | }
4526 | } catch (error) {
4527 | // ** Specific catch block for missing configuration file **
4528 | if (error instanceof ConfigurationError) {
4529 | console.error(
4530 | boxen(
4531 | chalk.red.bold('Configuration Update Required!') +
4532 | '\n\n' +
4533 | chalk.white('Taskmaster now uses a ') +
4534 | chalk.yellow.bold('configuration file') +
4535 | chalk.white(
4536 | ' in your project for AI model choices and settings.\n\n' +
4537 | 'This file appears to be '
4538 | ) +
4539 | chalk.red.bold('missing') +
4540 | chalk.white('. No worries though.\n\n') +
4541 | chalk.cyan.bold('To create this file, run the interactive setup:') +
4542 | '\n' +
4543 | chalk.green(' task-master models --setup') +
4544 | '\n\n' +
4545 | chalk.white.bold('Key Points:') +
4546 | '\n' +
4547 | chalk.white('* ') +
4548 | chalk.yellow.bold('Configuration file') +
4549 | chalk.white(
4550 | ': Stores your AI model settings (do not manually edit)\n'
4551 | ) +
4552 | chalk.white('* ') +
4553 | chalk.yellow.bold('.env & .mcp.json') +
4554 | chalk.white(': Still used ') +
4555 | chalk.red.bold('only') +
4556 | chalk.white(' for your AI provider API keys.\n\n') +
4557 | chalk.cyan(
4558 | '`task-master models` to check your config & available models\n'
4559 | ) +
4560 | chalk.cyan(
4561 | '`task-master models --setup` to adjust the AI models used by Taskmaster'
4562 | ),
4563 | {
4564 | padding: 1,
4565 | margin: { top: 1 },
4566 | borderColor: 'red',
4567 | borderStyle: 'round'
4568 | }
4569 | )
4570 | );
4571 | } else {
4572 | // Generic error handling for other errors
4573 | displayError(error);
4574 | }
4575 |
4576 | process.exit(1);
4577 | }
4578 | }
4579 |
4580 | /**
4581 | * Resolve the final complexity-report path.
4582 | * Rules:
4583 | * 1. If caller passes --output, always respect it.
4584 | * 2. If no explicit output AND tag === 'master' → default report file
4585 | * 3. If no explicit output AND tag !== 'master' → append _<tag>.json
4586 | *
4587 | * @param {string|undefined} outputOpt --output value from CLI (may be undefined)
4588 | * @param {string} targetTag resolved tag (defaults to 'master')
4589 | * @param {string} projectRoot absolute project root
4590 | * @returns {string} absolute path for the report
4591 | */
4592 | export function resolveComplexityReportPath({
4593 | projectRoot,
4594 | tag = 'master',
4595 | output // may be undefined
4596 | }) {
4597 | // 1. user knows best
4598 | if (output) {
4599 | return path.isAbsolute(output) ? output : path.join(projectRoot, output);
4600 | }
4601 |
4602 | // 2. default naming
4603 | const base = path.join(projectRoot, COMPLEXITY_REPORT_FILE);
4604 | return tag !== 'master' ? base.replace('.json', `_${tag}.json`) : base;
4605 | }
4606 |
4607 | export { registerCommands, setupCLI, runCLI };
4608 |
```