#
tokens: 68569/50000 1/821 files (page 50/52)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 50 of 52. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── agents
│   │   ├── task-checker.md
│   │   ├── task-executor.md
│   │   └── task-orchestrator.md
│   ├── commands
│   │   ├── dedupe.md
│   │   └── tm
│   │       ├── add-dependency
│   │       │   └── add-dependency.md
│   │       ├── add-subtask
│   │       │   ├── add-subtask.md
│   │       │   └── convert-task-to-subtask.md
│   │       ├── add-task
│   │       │   └── add-task.md
│   │       ├── analyze-complexity
│   │       │   └── analyze-complexity.md
│   │       ├── complexity-report
│   │       │   └── complexity-report.md
│   │       ├── expand
│   │       │   ├── expand-all-tasks.md
│   │       │   └── expand-task.md
│   │       ├── fix-dependencies
│   │       │   └── fix-dependencies.md
│   │       ├── generate
│   │       │   └── generate-tasks.md
│   │       ├── help.md
│   │       ├── init
│   │       │   ├── init-project-quick.md
│   │       │   └── init-project.md
│   │       ├── learn.md
│   │       ├── list
│   │       │   ├── list-tasks-by-status.md
│   │       │   ├── list-tasks-with-subtasks.md
│   │       │   └── list-tasks.md
│   │       ├── models
│   │       │   ├── setup-models.md
│   │       │   └── view-models.md
│   │       ├── next
│   │       │   └── next-task.md
│   │       ├── parse-prd
│   │       │   ├── parse-prd-with-research.md
│   │       │   └── parse-prd.md
│   │       ├── remove-dependency
│   │       │   └── remove-dependency.md
│   │       ├── remove-subtask
│   │       │   └── remove-subtask.md
│   │       ├── remove-subtasks
│   │       │   ├── remove-all-subtasks.md
│   │       │   └── remove-subtasks.md
│   │       ├── remove-task
│   │       │   └── remove-task.md
│   │       ├── set-status
│   │       │   ├── to-cancelled.md
│   │       │   ├── to-deferred.md
│   │       │   ├── to-done.md
│   │       │   ├── to-in-progress.md
│   │       │   ├── to-pending.md
│   │       │   └── to-review.md
│   │       ├── setup
│   │       │   ├── install-taskmaster.md
│   │       │   └── quick-install-taskmaster.md
│   │       ├── show
│   │       │   └── show-task.md
│   │       ├── status
│   │       │   └── project-status.md
│   │       ├── sync-readme
│   │       │   └── sync-readme.md
│   │       ├── tm-main.md
│   │       ├── update
│   │       │   ├── update-single-task.md
│   │       │   ├── update-task.md
│   │       │   └── update-tasks-from-id.md
│   │       ├── utils
│   │       │   └── analyze-project.md
│   │       ├── validate-dependencies
│   │       │   └── validate-dependencies.md
│   │       └── workflows
│   │           ├── auto-implement-tasks.md
│   │           ├── command-pipeline.md
│   │           └── smart-workflow.md
│   └── TM_COMMANDS_GUIDE.md
├── .coderabbit.yaml
├── .cursor
│   ├── mcp.json
│   └── rules
│       ├── ai_providers.mdc
│       ├── ai_services.mdc
│       ├── architecture.mdc
│       ├── changeset.mdc
│       ├── commands.mdc
│       ├── context_gathering.mdc
│       ├── cursor_rules.mdc
│       ├── dependencies.mdc
│       ├── dev_workflow.mdc
│       ├── git_workflow.mdc
│       ├── glossary.mdc
│       ├── mcp.mdc
│       ├── new_features.mdc
│       ├── self_improve.mdc
│       ├── tags.mdc
│       ├── taskmaster.mdc
│       ├── tasks.mdc
│       ├── telemetry.mdc
│       ├── test_workflow.mdc
│       ├── tests.mdc
│       ├── ui.mdc
│       └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   ├── enhancements---feature-requests.md
│   │   └── feedback.md
│   ├── PULL_REQUEST_TEMPLATE
│   │   ├── bugfix.md
│   │   ├── config.yml
│   │   ├── feature.md
│   │   └── integration.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── scripts
│   │   ├── auto-close-duplicates.mjs
│   │   ├── backfill-duplicate-comments.mjs
│   │   ├── check-pre-release-mode.mjs
│   │   ├── parse-metrics.mjs
│   │   ├── release.mjs
│   │   ├── tag-extension.mjs
│   │   └── utils.mjs
│   └── workflows
│       ├── auto-close-duplicates.yml
│       ├── backfill-duplicate-comments.yml
│       ├── ci.yml
│       ├── claude-dedupe-issues.yml
│       ├── claude-docs-trigger.yml
│       ├── claude-docs-updater.yml
│       ├── claude-issue-triage.yml
│       ├── claude.yml
│       ├── extension-ci.yml
│       ├── extension-release.yml
│       ├── log-issue-events.yml
│       ├── pre-release.yml
│       ├── release-check.yml
│       ├── release.yml
│       ├── update-models-md.yml
│       └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│   ├── hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── settings
│   │   └── mcp.json
│   └── steering
│       ├── dev_workflow.md
│       ├── kiro_rules.md
│       ├── self_improve.md
│       ├── taskmaster_hooks_workflow.md
│       └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│   ├── CLAUDE.md
│   ├── config.json
│   ├── docs
│   │   ├── MIGRATION-ROADMAP.md
│   │   ├── prd-tm-start.txt
│   │   ├── prd.txt
│   │   ├── README.md
│   │   ├── research
│   │   │   ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│   │   │   ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│   │   │   ├── 2025-06-14_test-save-functionality.md
│   │   │   ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│   │   │   └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│   │   ├── task-template-importing-prd.txt
│   │   ├── test-prd.txt
│   │   └── tm-core-phase-1.txt
│   ├── reports
│   │   ├── task-complexity-report_cc-kiro-hooks.json
│   │   ├── task-complexity-report_test-prd-tag.json
│   │   ├── task-complexity-report_tm-core-phase-1.json
│   │   ├── task-complexity-report.json
│   │   └── tm-core-complexity.json
│   ├── state.json
│   ├── tasks
│   │   ├── task_001_tm-start.txt
│   │   ├── task_002_tm-start.txt
│   │   ├── task_003_tm-start.txt
│   │   ├── task_004_tm-start.txt
│   │   ├── task_007_tm-start.txt
│   │   └── tasks.json
│   └── templates
│       └── example_prd.txt
├── .vscode
│   ├── extensions.json
│   └── settings.json
├── apps
│   ├── cli
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── commands
│   │   │   │   ├── auth.command.ts
│   │   │   │   ├── context.command.ts
│   │   │   │   ├── list.command.ts
│   │   │   │   ├── set-status.command.ts
│   │   │   │   ├── show.command.ts
│   │   │   │   └── start.command.ts
│   │   │   ├── index.ts
│   │   │   ├── ui
│   │   │   │   ├── components
│   │   │   │   │   ├── dashboard.component.ts
│   │   │   │   │   ├── header.component.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── next-task.component.ts
│   │   │   │   │   ├── suggested-steps.component.ts
│   │   │   │   │   └── task-detail.component.ts
│   │   │   │   └── index.ts
│   │   │   └── utils
│   │   │       ├── auto-update.ts
│   │   │       └── ui.ts
│   │   └── tsconfig.json
│   ├── docs
│   │   ├── archive
│   │   │   ├── ai-client-utils-example.mdx
│   │   │   ├── ai-development-workflow.mdx
│   │   │   ├── command-reference.mdx
│   │   │   ├── configuration.mdx
│   │   │   ├── cursor-setup.mdx
│   │   │   ├── examples.mdx
│   │   │   └── Installation.mdx
│   │   ├── best-practices
│   │   │   ├── advanced-tasks.mdx
│   │   │   ├── configuration-advanced.mdx
│   │   │   └── index.mdx
│   │   ├── capabilities
│   │   │   ├── cli-root-commands.mdx
│   │   │   ├── index.mdx
│   │   │   ├── mcp.mdx
│   │   │   └── task-structure.mdx
│   │   ├── CHANGELOG.md
│   │   ├── docs.json
│   │   ├── favicon.svg
│   │   ├── getting-started
│   │   │   ├── contribute.mdx
│   │   │   ├── faq.mdx
│   │   │   └── quick-start
│   │   │       ├── configuration-quick.mdx
│   │   │       ├── execute-quick.mdx
│   │   │       ├── installation.mdx
│   │   │       ├── moving-forward.mdx
│   │   │       ├── prd-quick.mdx
│   │   │       ├── quick-start.mdx
│   │   │       ├── requirements.mdx
│   │   │       ├── rules-quick.mdx
│   │   │       └── tasks-quick.mdx
│   │   ├── introduction.mdx
│   │   ├── licensing.md
│   │   ├── logo
│   │   │   ├── dark.svg
│   │   │   ├── light.svg
│   │   │   └── task-master-logo.png
│   │   ├── package.json
│   │   ├── README.md
│   │   ├── style.css
│   │   ├── vercel.json
│   │   └── whats-new.mdx
│   └── extension
│       ├── .vscodeignore
│       ├── assets
│       │   ├── banner.png
│       │   ├── icon-dark.svg
│       │   ├── icon-light.svg
│       │   ├── icon.png
│       │   ├── screenshots
│       │   │   ├── kanban-board.png
│       │   │   └── task-details.png
│       │   └── sidebar-icon.svg
│       ├── CHANGELOG.md
│       ├── components.json
│       ├── docs
│       │   ├── extension-CI-setup.md
│       │   └── extension-development-guide.md
│       ├── esbuild.js
│       ├── LICENSE
│       ├── package.json
│       ├── package.mjs
│       ├── package.publish.json
│       ├── README.md
│       ├── src
│       │   ├── components
│       │   │   ├── ConfigView.tsx
│       │   │   ├── constants.ts
│       │   │   ├── TaskDetails
│       │   │   │   ├── AIActionsSection.tsx
│       │   │   │   ├── DetailsSection.tsx
│       │   │   │   ├── PriorityBadge.tsx
│       │   │   │   ├── SubtasksSection.tsx
│       │   │   │   ├── TaskMetadataSidebar.tsx
│       │   │   │   └── useTaskDetails.ts
│       │   │   ├── TaskDetailsView.tsx
│       │   │   ├── TaskMasterLogo.tsx
│       │   │   └── ui
│       │   │       ├── badge.tsx
│       │   │       ├── breadcrumb.tsx
│       │   │       ├── button.tsx
│       │   │       ├── card.tsx
│       │   │       ├── collapsible.tsx
│       │   │       ├── CollapsibleSection.tsx
│       │   │       ├── dropdown-menu.tsx
│       │   │       ├── label.tsx
│       │   │       ├── scroll-area.tsx
│       │   │       ├── separator.tsx
│       │   │       ├── shadcn-io
│       │   │       │   └── kanban
│       │   │       │       └── index.tsx
│       │   │       └── textarea.tsx
│       │   ├── extension.ts
│       │   ├── index.ts
│       │   ├── lib
│       │   │   └── utils.ts
│       │   ├── services
│       │   │   ├── config-service.ts
│       │   │   ├── error-handler.ts
│       │   │   ├── notification-preferences.ts
│       │   │   ├── polling-service.ts
│       │   │   ├── polling-strategies.ts
│       │   │   ├── sidebar-webview-manager.ts
│       │   │   ├── task-repository.ts
│       │   │   ├── terminal-manager.ts
│       │   │   └── webview-manager.ts
│       │   ├── test
│       │   │   └── extension.test.ts
│       │   ├── utils
│       │   │   ├── configManager.ts
│       │   │   ├── connectionManager.ts
│       │   │   ├── errorHandler.ts
│       │   │   ├── event-emitter.ts
│       │   │   ├── logger.ts
│       │   │   ├── mcpClient.ts
│       │   │   ├── notificationPreferences.ts
│       │   │   └── task-master-api
│       │   │       ├── cache
│       │   │       │   └── cache-manager.ts
│       │   │       ├── index.ts
│       │   │       ├── mcp-client.ts
│       │   │       ├── transformers
│       │   │       │   └── task-transformer.ts
│       │   │       └── types
│       │   │           └── index.ts
│       │   └── webview
│       │       ├── App.tsx
│       │       ├── components
│       │       │   ├── AppContent.tsx
│       │       │   ├── EmptyState.tsx
│       │       │   ├── ErrorBoundary.tsx
│       │       │   ├── PollingStatus.tsx
│       │       │   ├── PriorityBadge.tsx
│       │       │   ├── SidebarView.tsx
│       │       │   ├── TagDropdown.tsx
│       │       │   ├── TaskCard.tsx
│       │       │   ├── TaskEditModal.tsx
│       │       │   ├── TaskMasterKanban.tsx
│       │       │   ├── ToastContainer.tsx
│       │       │   └── ToastNotification.tsx
│       │       ├── constants
│       │       │   └── index.ts
│       │       ├── contexts
│       │       │   └── VSCodeContext.tsx
│       │       ├── hooks
│       │       │   ├── useTaskQueries.ts
│       │       │   ├── useVSCodeMessages.ts
│       │       │   └── useWebviewHeight.ts
│       │       ├── index.css
│       │       ├── index.tsx
│       │       ├── providers
│       │       │   └── QueryProvider.tsx
│       │       ├── reducers
│       │       │   └── appReducer.ts
│       │       ├── sidebar.tsx
│       │       ├── types
│       │       │   └── index.ts
│       │       └── utils
│       │           ├── logger.ts
│       │           └── toast.ts
│       └── tsconfig.json
├── assets
│   ├── .windsurfrules
│   ├── AGENTS.md
│   ├── claude
│   │   ├── agents
│   │   │   ├── task-checker.md
│   │   │   ├── task-executor.md
│   │   │   └── task-orchestrator.md
│   │   ├── commands
│   │   │   └── tm
│   │   │       ├── add-dependency
│   │   │       │   └── add-dependency.md
│   │   │       ├── add-subtask
│   │   │       │   ├── add-subtask.md
│   │   │       │   └── convert-task-to-subtask.md
│   │   │       ├── add-task
│   │   │       │   └── add-task.md
│   │   │       ├── analyze-complexity
│   │   │       │   └── analyze-complexity.md
│   │   │       ├── clear-subtasks
│   │   │       │   ├── clear-all-subtasks.md
│   │   │       │   └── clear-subtasks.md
│   │   │       ├── complexity-report
│   │   │       │   └── complexity-report.md
│   │   │       ├── expand
│   │   │       │   ├── expand-all-tasks.md
│   │   │       │   └── expand-task.md
│   │   │       ├── fix-dependencies
│   │   │       │   └── fix-dependencies.md
│   │   │       ├── generate
│   │   │       │   └── generate-tasks.md
│   │   │       ├── help.md
│   │   │       ├── init
│   │   │       │   ├── init-project-quick.md
│   │   │       │   └── init-project.md
│   │   │       ├── learn.md
│   │   │       ├── list
│   │   │       │   ├── list-tasks-by-status.md
│   │   │       │   ├── list-tasks-with-subtasks.md
│   │   │       │   └── list-tasks.md
│   │   │       ├── models
│   │   │       │   ├── setup-models.md
│   │   │       │   └── view-models.md
│   │   │       ├── next
│   │   │       │   └── next-task.md
│   │   │       ├── parse-prd
│   │   │       │   ├── parse-prd-with-research.md
│   │   │       │   └── parse-prd.md
│   │   │       ├── remove-dependency
│   │   │       │   └── remove-dependency.md
│   │   │       ├── remove-subtask
│   │   │       │   └── remove-subtask.md
│   │   │       ├── remove-subtasks
│   │   │       │   ├── remove-all-subtasks.md
│   │   │       │   └── remove-subtasks.md
│   │   │       ├── remove-task
│   │   │       │   └── remove-task.md
│   │   │       ├── set-status
│   │   │       │   ├── to-cancelled.md
│   │   │       │   ├── to-deferred.md
│   │   │       │   ├── to-done.md
│   │   │       │   ├── to-in-progress.md
│   │   │       │   ├── to-pending.md
│   │   │       │   └── to-review.md
│   │   │       ├── setup
│   │   │       │   ├── install-taskmaster.md
│   │   │       │   └── quick-install-taskmaster.md
│   │   │       ├── show
│   │   │       │   └── show-task.md
│   │   │       ├── status
│   │   │       │   └── project-status.md
│   │   │       ├── sync-readme
│   │   │       │   └── sync-readme.md
│   │   │       ├── tm-main.md
│   │   │       ├── update
│   │   │       │   ├── update-single-task.md
│   │   │       │   ├── update-task.md
│   │   │       │   └── update-tasks-from-id.md
│   │   │       ├── utils
│   │   │       │   └── analyze-project.md
│   │   │       ├── validate-dependencies
│   │   │       │   └── validate-dependencies.md
│   │   │       └── workflows
│   │   │           ├── auto-implement-tasks.md
│   │   │           ├── command-pipeline.md
│   │   │           └── smart-workflow.md
│   │   └── TM_COMMANDS_GUIDE.md
│   ├── config.json
│   ├── env.example
│   ├── example_prd.txt
│   ├── gitignore
│   ├── kiro-hooks
│   │   ├── tm-code-change-task-tracker.kiro.hook
│   │   ├── tm-complexity-analyzer.kiro.hook
│   │   ├── tm-daily-standup-assistant.kiro.hook
│   │   ├── tm-git-commit-task-linker.kiro.hook
│   │   ├── tm-pr-readiness-checker.kiro.hook
│   │   ├── tm-task-dependency-auto-progression.kiro.hook
│   │   └── tm-test-success-task-completer.kiro.hook
│   ├── roocode
│   │   ├── .roo
│   │   │   ├── rules-architect
│   │   │   │   └── architect-rules
│   │   │   ├── rules-ask
│   │   │   │   └── ask-rules
│   │   │   ├── rules-code
│   │   │   │   └── code-rules
│   │   │   ├── rules-debug
│   │   │   │   └── debug-rules
│   │   │   ├── rules-orchestrator
│   │   │   │   └── orchestrator-rules
│   │   │   └── rules-test
│   │   │       └── test-rules
│   │   └── .roomodes
│   ├── rules
│   │   ├── cursor_rules.mdc
│   │   ├── dev_workflow.mdc
│   │   ├── self_improve.mdc
│   │   ├── taskmaster_hooks_workflow.mdc
│   │   └── taskmaster.mdc
│   └── scripts_README.md
├── bin
│   └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE.md
├── context
│   ├── chats
│   │   ├── add-task-dependencies-1.md
│   │   └── max-min-tokens.txt.md
│   ├── fastmcp-core.txt
│   ├── fastmcp-docs.txt
│   ├── MCP_INTEGRATION.md
│   ├── mcp-js-sdk-docs.txt
│   ├── mcp-protocol-repo.txt
│   ├── mcp-protocol-schema-03262025.json
│   └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│   ├── CLI-COMMANDER-PATTERN.md
│   ├── command-reference.md
│   ├── configuration.md
│   ├── contributor-docs
│   │   └── testing-roo-integration.md
│   ├── cross-tag-task-movement.md
│   ├── examples
│   │   └── claude-code-usage.md
│   ├── examples.md
│   ├── licensing.md
│   ├── mcp-provider-guide.md
│   ├── mcp-provider.md
│   ├── migration-guide.md
│   ├── models.md
│   ├── providers
│   │   └── gemini-cli.md
│   ├── README.md
│   ├── scripts
│   │   └── models-json-to-markdown.js
│   ├── task-structure.md
│   └── tutorial.md
├── images
│   └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│   ├── server.js
│   └── src
│       ├── core
│       │   ├── __tests__
│       │   │   └── context-manager.test.js
│       │   ├── context-manager.js
│       │   ├── direct-functions
│       │   │   ├── add-dependency.js
│       │   │   ├── add-subtask.js
│       │   │   ├── add-tag.js
│       │   │   ├── add-task.js
│       │   │   ├── analyze-task-complexity.js
│       │   │   ├── cache-stats.js
│       │   │   ├── clear-subtasks.js
│       │   │   ├── complexity-report.js
│       │   │   ├── copy-tag.js
│       │   │   ├── create-tag-from-branch.js
│       │   │   ├── delete-tag.js
│       │   │   ├── expand-all-tasks.js
│       │   │   ├── expand-task.js
│       │   │   ├── fix-dependencies.js
│       │   │   ├── generate-task-files.js
│       │   │   ├── initialize-project.js
│       │   │   ├── list-tags.js
│       │   │   ├── list-tasks.js
│       │   │   ├── models.js
│       │   │   ├── move-task-cross-tag.js
│       │   │   ├── move-task.js
│       │   │   ├── next-task.js
│       │   │   ├── parse-prd.js
│       │   │   ├── remove-dependency.js
│       │   │   ├── remove-subtask.js
│       │   │   ├── remove-task.js
│       │   │   ├── rename-tag.js
│       │   │   ├── research.js
│       │   │   ├── response-language.js
│       │   │   ├── rules.js
│       │   │   ├── scope-down.js
│       │   │   ├── scope-up.js
│       │   │   ├── set-task-status.js
│       │   │   ├── show-task.js
│       │   │   ├── update-subtask-by-id.js
│       │   │   ├── update-task-by-id.js
│       │   │   ├── update-tasks.js
│       │   │   ├── use-tag.js
│       │   │   └── validate-dependencies.js
│       │   ├── task-master-core.js
│       │   └── utils
│       │       ├── env-utils.js
│       │       └── path-utils.js
│       ├── custom-sdk
│       │   ├── errors.js
│       │   ├── index.js
│       │   ├── json-extractor.js
│       │   ├── language-model.js
│       │   ├── message-converter.js
│       │   └── schema-converter.js
│       ├── index.js
│       ├── logger.js
│       ├── providers
│       │   └── mcp-provider.js
│       └── tools
│           ├── add-dependency.js
│           ├── add-subtask.js
│           ├── add-tag.js
│           ├── add-task.js
│           ├── analyze.js
│           ├── clear-subtasks.js
│           ├── complexity-report.js
│           ├── copy-tag.js
│           ├── delete-tag.js
│           ├── expand-all.js
│           ├── expand-task.js
│           ├── fix-dependencies.js
│           ├── generate.js
│           ├── get-operation-status.js
│           ├── get-task.js
│           ├── get-tasks.js
│           ├── index.js
│           ├── initialize-project.js
│           ├── list-tags.js
│           ├── models.js
│           ├── move-task.js
│           ├── next-task.js
│           ├── parse-prd.js
│           ├── remove-dependency.js
│           ├── remove-subtask.js
│           ├── remove-task.js
│           ├── rename-tag.js
│           ├── research.js
│           ├── response-language.js
│           ├── rules.js
│           ├── scope-down.js
│           ├── scope-up.js
│           ├── set-task-status.js
│           ├── update-subtask.js
│           ├── update-task.js
│           ├── update.js
│           ├── use-tag.js
│           ├── utils.js
│           └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│   ├── build-config
│   │   ├── CHANGELOG.md
│   │   ├── package.json
│   │   ├── src
│   │   │   └── tsdown.base.ts
│   │   └── tsconfig.json
│   └── tm-core
│       ├── .gitignore
│       ├── CHANGELOG.md
│       ├── docs
│       │   └── listTasks-architecture.md
│       ├── package.json
│       ├── POC-STATUS.md
│       ├── README.md
│       ├── src
│       │   ├── auth
│       │   │   ├── auth-manager.test.ts
│       │   │   ├── auth-manager.ts
│       │   │   ├── config.ts
│       │   │   ├── credential-store.test.ts
│       │   │   ├── credential-store.ts
│       │   │   ├── index.ts
│       │   │   ├── oauth-service.ts
│       │   │   ├── supabase-session-storage.ts
│       │   │   └── types.ts
│       │   ├── clients
│       │   │   ├── index.ts
│       │   │   └── supabase-client.ts
│       │   ├── config
│       │   │   ├── config-manager.spec.ts
│       │   │   ├── config-manager.ts
│       │   │   ├── index.ts
│       │   │   └── services
│       │   │       ├── config-loader.service.spec.ts
│       │   │       ├── config-loader.service.ts
│       │   │       ├── config-merger.service.spec.ts
│       │   │       ├── config-merger.service.ts
│       │   │       ├── config-persistence.service.spec.ts
│       │   │       ├── config-persistence.service.ts
│       │   │       ├── environment-config-provider.service.spec.ts
│       │   │       ├── environment-config-provider.service.ts
│       │   │       ├── index.ts
│       │   │       ├── runtime-state-manager.service.spec.ts
│       │   │       └── runtime-state-manager.service.ts
│       │   ├── constants
│       │   │   └── index.ts
│       │   ├── entities
│       │   │   └── task.entity.ts
│       │   ├── errors
│       │   │   ├── index.ts
│       │   │   └── task-master-error.ts
│       │   ├── executors
│       │   │   ├── base-executor.ts
│       │   │   ├── claude-executor.ts
│       │   │   ├── executor-factory.ts
│       │   │   ├── executor-service.ts
│       │   │   ├── index.ts
│       │   │   └── types.ts
│       │   ├── index.ts
│       │   ├── interfaces
│       │   │   ├── ai-provider.interface.ts
│       │   │   ├── configuration.interface.ts
│       │   │   ├── index.ts
│       │   │   └── storage.interface.ts
│       │   ├── logger
│       │   │   ├── factory.ts
│       │   │   ├── index.ts
│       │   │   └── logger.ts
│       │   ├── mappers
│       │   │   └── TaskMapper.ts
│       │   ├── parser
│       │   │   └── index.ts
│       │   ├── providers
│       │   │   ├── ai
│       │   │   │   ├── base-provider.ts
│       │   │   │   └── index.ts
│       │   │   └── index.ts
│       │   ├── repositories
│       │   │   ├── supabase-task-repository.ts
│       │   │   └── task-repository.interface.ts
│       │   ├── services
│       │   │   ├── index.ts
│       │   │   ├── organization.service.ts
│       │   │   ├── task-execution-service.ts
│       │   │   └── task-service.ts
│       │   ├── storage
│       │   │   ├── api-storage.ts
│       │   │   ├── file-storage
│       │   │   │   ├── file-operations.ts
│       │   │   │   ├── file-storage.ts
│       │   │   │   ├── format-handler.ts
│       │   │   │   ├── index.ts
│       │   │   │   └── path-resolver.ts
│       │   │   ├── index.ts
│       │   │   └── storage-factory.ts
│       │   ├── subpath-exports.test.ts
│       │   ├── task-master-core.ts
│       │   ├── types
│       │   │   ├── database.types.ts
│       │   │   ├── index.ts
│       │   │   └── legacy.ts
│       │   └── utils
│       │       ├── id-generator.ts
│       │       └── index.ts
│       ├── tests
│       │   ├── integration
│       │   │   └── list-tasks.test.ts
│       │   ├── mocks
│       │   │   └── mock-provider.ts
│       │   ├── setup.ts
│       │   └── unit
│       │       ├── base-provider.test.ts
│       │       ├── executor.test.ts
│       │       └── smoke.test.ts
│       ├── tsconfig.json
│       └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│   ├── dev.js
│   ├── init.js
│   ├── modules
│   │   ├── ai-services-unified.js
│   │   ├── commands.js
│   │   ├── config-manager.js
│   │   ├── dependency-manager.js
│   │   ├── index.js
│   │   ├── prompt-manager.js
│   │   ├── supported-models.json
│   │   ├── sync-readme.js
│   │   ├── task-manager
│   │   │   ├── add-subtask.js
│   │   │   ├── add-task.js
│   │   │   ├── analyze-task-complexity.js
│   │   │   ├── clear-subtasks.js
│   │   │   ├── expand-all-tasks.js
│   │   │   ├── expand-task.js
│   │   │   ├── find-next-task.js
│   │   │   ├── generate-task-files.js
│   │   │   ├── is-task-dependent.js
│   │   │   ├── list-tasks.js
│   │   │   ├── migrate.js
│   │   │   ├── models.js
│   │   │   ├── move-task.js
│   │   │   ├── parse-prd
│   │   │   │   ├── index.js
│   │   │   │   ├── parse-prd-config.js
│   │   │   │   ├── parse-prd-helpers.js
│   │   │   │   ├── parse-prd-non-streaming.js
│   │   │   │   ├── parse-prd-streaming.js
│   │   │   │   └── parse-prd.js
│   │   │   ├── remove-subtask.js
│   │   │   ├── remove-task.js
│   │   │   ├── research.js
│   │   │   ├── response-language.js
│   │   │   ├── scope-adjustment.js
│   │   │   ├── set-task-status.js
│   │   │   ├── tag-management.js
│   │   │   ├── task-exists.js
│   │   │   ├── update-single-task-status.js
│   │   │   ├── update-subtask-by-id.js
│   │   │   ├── update-task-by-id.js
│   │   │   └── update-tasks.js
│   │   ├── task-manager.js
│   │   ├── ui.js
│   │   ├── update-config-tokens.js
│   │   ├── utils
│   │   │   ├── contextGatherer.js
│   │   │   ├── fuzzyTaskSearch.js
│   │   │   └── git-utils.js
│   │   └── utils.js
│   ├── task-complexity-report.json
│   ├── test-claude-errors.js
│   └── test-claude.js
├── src
│   ├── ai-providers
│   │   ├── anthropic.js
│   │   ├── azure.js
│   │   ├── base-provider.js
│   │   ├── bedrock.js
│   │   ├── claude-code.js
│   │   ├── custom-sdk
│   │   │   ├── claude-code
│   │   │   │   ├── errors.js
│   │   │   │   ├── index.js
│   │   │   │   ├── json-extractor.js
│   │   │   │   ├── language-model.js
│   │   │   │   ├── message-converter.js
│   │   │   │   └── types.js
│   │   │   └── grok-cli
│   │   │       ├── errors.js
│   │   │       ├── index.js
│   │   │       ├── json-extractor.js
│   │   │       ├── language-model.js
│   │   │       ├── message-converter.js
│   │   │       └── types.js
│   │   ├── gemini-cli.js
│   │   ├── google-vertex.js
│   │   ├── google.js
│   │   ├── grok-cli.js
│   │   ├── groq.js
│   │   ├── index.js
│   │   ├── ollama.js
│   │   ├── openai.js
│   │   ├── openrouter.js
│   │   ├── perplexity.js
│   │   └── xai.js
│   ├── constants
│   │   ├── commands.js
│   │   ├── paths.js
│   │   ├── profiles.js
│   │   ├── providers.js
│   │   ├── rules-actions.js
│   │   ├── task-priority.js
│   │   └── task-status.js
│   ├── profiles
│   │   ├── amp.js
│   │   ├── base-profile.js
│   │   ├── claude.js
│   │   ├── cline.js
│   │   ├── codex.js
│   │   ├── cursor.js
│   │   ├── gemini.js
│   │   ├── index.js
│   │   ├── kilo.js
│   │   ├── kiro.js
│   │   ├── opencode.js
│   │   ├── roo.js
│   │   ├── trae.js
│   │   ├── vscode.js
│   │   ├── windsurf.js
│   │   └── zed.js
│   ├── progress
│   │   ├── base-progress-tracker.js
│   │   ├── cli-progress-factory.js
│   │   ├── parse-prd-tracker.js
│   │   ├── progress-tracker-builder.js
│   │   └── tracker-ui.js
│   ├── prompts
│   │   ├── add-task.json
│   │   ├── analyze-complexity.json
│   │   ├── expand-task.json
│   │   ├── parse-prd.json
│   │   ├── README.md
│   │   ├── research.json
│   │   ├── schemas
│   │   │   ├── parameter.schema.json
│   │   │   ├── prompt-template.schema.json
│   │   │   ├── README.md
│   │   │   └── variant.schema.json
│   │   ├── update-subtask.json
│   │   ├── update-task.json
│   │   └── update-tasks.json
│   ├── provider-registry
│   │   └── index.js
│   ├── task-master.js
│   ├── ui
│   │   ├── confirm.js
│   │   ├── indicators.js
│   │   └── parse-prd.js
│   └── utils
│       ├── asset-resolver.js
│       ├── create-mcp-config.js
│       ├── format.js
│       ├── getVersion.js
│       ├── logger-utils.js
│       ├── manage-gitignore.js
│       ├── path-utils.js
│       ├── profiles.js
│       ├── rule-transformer.js
│       ├── stream-parser.js
│       └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│   ├── e2e
│   │   ├── e2e_helpers.sh
│   │   ├── parse_llm_output.cjs
│   │   ├── run_e2e.sh
│   │   ├── run_fallback_verification.sh
│   │   └── test_llm_analysis.sh
│   ├── fixture
│   │   └── test-tasks.json
│   ├── fixtures
│   │   ├── .taskmasterconfig
│   │   ├── sample-claude-response.js
│   │   ├── sample-prd.txt
│   │   └── sample-tasks.js
│   ├── integration
│   │   ├── claude-code-optional.test.js
│   │   ├── cli
│   │   │   ├── commands.test.js
│   │   │   ├── complex-cross-tag-scenarios.test.js
│   │   │   └── move-cross-tag.test.js
│   │   ├── manage-gitignore.test.js
│   │   ├── mcp-server
│   │   │   └── direct-functions.test.js
│   │   ├── move-task-cross-tag.integration.test.js
│   │   ├── move-task-simple.integration.test.js
│   │   └── profiles
│   │       ├── amp-init-functionality.test.js
│   │       ├── claude-init-functionality.test.js
│   │       ├── cline-init-functionality.test.js
│   │       ├── codex-init-functionality.test.js
│   │       ├── cursor-init-functionality.test.js
│   │       ├── gemini-init-functionality.test.js
│   │       ├── opencode-init-functionality.test.js
│   │       ├── roo-files-inclusion.test.js
│   │       ├── roo-init-functionality.test.js
│   │       ├── rules-files-inclusion.test.js
│   │       ├── trae-init-functionality.test.js
│   │       ├── vscode-init-functionality.test.js
│   │       └── windsurf-init-functionality.test.js
│   ├── manual
│   │   ├── progress
│   │   │   ├── parse-prd-analysis.js
│   │   │   ├── test-parse-prd.js
│   │   │   └── TESTING_GUIDE.md
│   │   └── prompts
│   │       ├── prompt-test.js
│   │       └── README.md
│   ├── README.md
│   ├── setup.js
│   └── unit
│       ├── ai-providers
│       │   ├── claude-code.test.js
│       │   ├── custom-sdk
│       │   │   └── claude-code
│       │   │       └── language-model.test.js
│       │   ├── gemini-cli.test.js
│       │   ├── mcp-components.test.js
│       │   └── openai.test.js
│       ├── ai-services-unified.test.js
│       ├── commands.test.js
│       ├── config-manager.test.js
│       ├── config-manager.test.mjs
│       ├── dependency-manager.test.js
│       ├── init.test.js
│       ├── initialize-project.test.js
│       ├── kebab-case-validation.test.js
│       ├── manage-gitignore.test.js
│       ├── mcp
│       │   └── tools
│       │       ├── __mocks__
│       │       │   └── move-task.js
│       │       ├── add-task.test.js
│       │       ├── analyze-complexity.test.js
│       │       ├── expand-all.test.js
│       │       ├── get-tasks.test.js
│       │       ├── initialize-project.test.js
│       │       ├── move-task-cross-tag-options.test.js
│       │       ├── move-task-cross-tag.test.js
│       │       └── remove-task.test.js
│       ├── mcp-providers
│       │   ├── mcp-components.test.js
│       │   └── mcp-provider.test.js
│       ├── parse-prd.test.js
│       ├── profiles
│       │   ├── amp-integration.test.js
│       │   ├── claude-integration.test.js
│       │   ├── cline-integration.test.js
│       │   ├── codex-integration.test.js
│       │   ├── cursor-integration.test.js
│       │   ├── gemini-integration.test.js
│       │   ├── kilo-integration.test.js
│       │   ├── kiro-integration.test.js
│       │   ├── mcp-config-validation.test.js
│       │   ├── opencode-integration.test.js
│       │   ├── profile-safety-check.test.js
│       │   ├── roo-integration.test.js
│       │   ├── rule-transformer-cline.test.js
│       │   ├── rule-transformer-cursor.test.js
│       │   ├── rule-transformer-gemini.test.js
│       │   ├── rule-transformer-kilo.test.js
│       │   ├── rule-transformer-kiro.test.js
│       │   ├── rule-transformer-opencode.test.js
│       │   ├── rule-transformer-roo.test.js
│       │   ├── rule-transformer-trae.test.js
│       │   ├── rule-transformer-vscode.test.js
│       │   ├── rule-transformer-windsurf.test.js
│       │   ├── rule-transformer-zed.test.js
│       │   ├── rule-transformer.test.js
│       │   ├── selective-profile-removal.test.js
│       │   ├── subdirectory-support.test.js
│       │   ├── trae-integration.test.js
│       │   ├── vscode-integration.test.js
│       │   ├── windsurf-integration.test.js
│       │   └── zed-integration.test.js
│       ├── progress
│       │   └── base-progress-tracker.test.js
│       ├── prompt-manager.test.js
│       ├── prompts
│       │   └── expand-task-prompt.test.js
│       ├── providers
│       │   └── provider-registry.test.js
│       ├── scripts
│       │   └── modules
│       │       ├── commands
│       │       │   ├── move-cross-tag.test.js
│       │       │   └── README.md
│       │       ├── dependency-manager
│       │       │   ├── circular-dependencies.test.js
│       │       │   ├── cross-tag-dependencies.test.js
│       │       │   └── fix-dependencies-command.test.js
│       │       ├── task-manager
│       │       │   ├── add-subtask.test.js
│       │       │   ├── add-task.test.js
│       │       │   ├── analyze-task-complexity.test.js
│       │       │   ├── clear-subtasks.test.js
│       │       │   ├── complexity-report-tag-isolation.test.js
│       │       │   ├── expand-all-tasks.test.js
│       │       │   ├── expand-task.test.js
│       │       │   ├── find-next-task.test.js
│       │       │   ├── generate-task-files.test.js
│       │       │   ├── list-tasks.test.js
│       │       │   ├── move-task-cross-tag.test.js
│       │       │   ├── move-task.test.js
│       │       │   ├── parse-prd.test.js
│       │       │   ├── remove-subtask.test.js
│       │       │   ├── remove-task.test.js
│       │       │   ├── research.test.js
│       │       │   ├── scope-adjustment.test.js
│       │       │   ├── set-task-status.test.js
│       │       │   ├── setup.js
│       │       │   ├── update-single-task-status.test.js
│       │       │   ├── update-subtask-by-id.test.js
│       │       │   ├── update-task-by-id.test.js
│       │       │   └── update-tasks.test.js
│       │       ├── ui
│       │       │   └── cross-tag-error-display.test.js
│       │       └── utils-tag-aware-paths.test.js
│       ├── task-finder.test.js
│       ├── task-manager
│       │   ├── clear-subtasks.test.js
│       │   ├── move-task.test.js
│       │   ├── tag-boundary.test.js
│       │   └── tag-management.test.js
│       ├── task-master.test.js
│       ├── ui
│       │   └── indicators.test.js
│       ├── ui.test.js
│       ├── utils-strip-ansi.test.js
│       └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
└── turbo.json
```

# Files

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