This is page 66 of 69. Use http://codebase.md/eyaltoledano/claude-task-master?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .changeset
│ ├── config.json
│ └── README.md
├── .claude
│ ├── commands
│ │ └── dedupe.md
│ └── TM_COMMANDS_GUIDE.md
├── .claude-plugin
│ └── marketplace.json
├── .coderabbit.yaml
├── .cursor
│ ├── mcp.json
│ └── rules
│ ├── ai_providers.mdc
│ ├── ai_services.mdc
│ ├── architecture.mdc
│ ├── changeset.mdc
│ ├── commands.mdc
│ ├── context_gathering.mdc
│ ├── cursor_rules.mdc
│ ├── dependencies.mdc
│ ├── dev_workflow.mdc
│ ├── git_workflow.mdc
│ ├── glossary.mdc
│ ├── mcp.mdc
│ ├── new_features.mdc
│ ├── self_improve.mdc
│ ├── tags.mdc
│ ├── taskmaster.mdc
│ ├── tasks.mdc
│ ├── telemetry.mdc
│ ├── test_workflow.mdc
│ ├── tests.mdc
│ ├── ui.mdc
│ └── utilities.mdc
├── .cursorignore
├── .env.example
├── .github
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.md
│ │ ├── enhancements---feature-requests.md
│ │ └── feedback.md
│ ├── PULL_REQUEST_TEMPLATE
│ │ ├── bugfix.md
│ │ ├── config.yml
│ │ ├── feature.md
│ │ └── integration.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── scripts
│ │ ├── auto-close-duplicates.mjs
│ │ ├── backfill-duplicate-comments.mjs
│ │ ├── check-pre-release-mode.mjs
│ │ ├── parse-metrics.mjs
│ │ ├── release.mjs
│ │ ├── tag-extension.mjs
│ │ ├── utils.mjs
│ │ └── validate-changesets.mjs
│ └── workflows
│ ├── auto-close-duplicates.yml
│ ├── backfill-duplicate-comments.yml
│ ├── ci.yml
│ ├── claude-dedupe-issues.yml
│ ├── claude-docs-trigger.yml
│ ├── claude-docs-updater.yml
│ ├── claude-issue-triage.yml
│ ├── claude.yml
│ ├── extension-ci.yml
│ ├── extension-release.yml
│ ├── log-issue-events.yml
│ ├── pre-release.yml
│ ├── release-check.yml
│ ├── release.yml
│ ├── update-models-md.yml
│ └── weekly-metrics-discord.yml
├── .gitignore
├── .kiro
│ ├── hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── settings
│ │ └── mcp.json
│ └── steering
│ ├── dev_workflow.md
│ ├── kiro_rules.md
│ ├── self_improve.md
│ ├── taskmaster_hooks_workflow.md
│ └── taskmaster.md
├── .manypkg.json
├── .mcp.json
├── .npmignore
├── .nvmrc
├── .taskmaster
│ ├── CLAUDE.md
│ ├── config.json
│ ├── docs
│ │ ├── autonomous-tdd-git-workflow.md
│ │ ├── MIGRATION-ROADMAP.md
│ │ ├── prd-tm-start.txt
│ │ ├── prd.txt
│ │ ├── README.md
│ │ ├── research
│ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md
│ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md
│ │ │ ├── 2025-06-14_test-save-functionality.md
│ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md
│ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md
│ │ ├── task-template-importing-prd.txt
│ │ ├── tdd-workflow-phase-0-spike.md
│ │ ├── tdd-workflow-phase-1-core-rails.md
│ │ ├── tdd-workflow-phase-1-orchestrator.md
│ │ ├── tdd-workflow-phase-2-pr-resumability.md
│ │ ├── tdd-workflow-phase-3-extensibility-guardrails.md
│ │ ├── test-prd.txt
│ │ └── tm-core-phase-1.txt
│ ├── reports
│ │ ├── task-complexity-report_autonomous-tdd-git-workflow.json
│ │ ├── task-complexity-report_cc-kiro-hooks.json
│ │ ├── task-complexity-report_tdd-phase-1-core-rails.json
│ │ ├── task-complexity-report_tdd-workflow-phase-0.json
│ │ ├── task-complexity-report_test-prd-tag.json
│ │ ├── task-complexity-report_tm-core-phase-1.json
│ │ ├── task-complexity-report.json
│ │ └── tm-core-complexity.json
│ ├── state.json
│ ├── tasks
│ │ ├── task_001_tm-start.txt
│ │ ├── task_002_tm-start.txt
│ │ ├── task_003_tm-start.txt
│ │ ├── task_004_tm-start.txt
│ │ ├── task_007_tm-start.txt
│ │ └── tasks.json
│ └── templates
│ ├── example_prd_rpg.md
│ └── example_prd.md
├── .vscode
│ ├── extensions.json
│ └── settings.json
├── apps
│ ├── cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── command-registry.ts
│ │ │ ├── commands
│ │ │ │ ├── auth.command.ts
│ │ │ │ ├── autopilot
│ │ │ │ │ ├── abort.command.ts
│ │ │ │ │ ├── commit.command.ts
│ │ │ │ │ ├── complete.command.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next.command.ts
│ │ │ │ │ ├── resume.command.ts
│ │ │ │ │ ├── shared.ts
│ │ │ │ │ ├── start.command.ts
│ │ │ │ │ └── status.command.ts
│ │ │ │ ├── briefs.command.ts
│ │ │ │ ├── context.command.ts
│ │ │ │ ├── export.command.ts
│ │ │ │ ├── list.command.ts
│ │ │ │ ├── models
│ │ │ │ │ ├── custom-providers.ts
│ │ │ │ │ ├── fetchers.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── prompts.ts
│ │ │ │ │ ├── setup.ts
│ │ │ │ │ └── types.ts
│ │ │ │ ├── next.command.ts
│ │ │ │ ├── set-status.command.ts
│ │ │ │ ├── show.command.ts
│ │ │ │ ├── start.command.ts
│ │ │ │ └── tags.command.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── model-management.ts
│ │ │ ├── types
│ │ │ │ └── tag-management.d.ts
│ │ │ ├── ui
│ │ │ │ ├── components
│ │ │ │ │ ├── cardBox.component.ts
│ │ │ │ │ ├── dashboard.component.ts
│ │ │ │ │ ├── header.component.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── next-task.component.ts
│ │ │ │ │ ├── suggested-steps.component.ts
│ │ │ │ │ └── task-detail.component.ts
│ │ │ │ ├── display
│ │ │ │ │ ├── messages.ts
│ │ │ │ │ └── tables.ts
│ │ │ │ ├── formatters
│ │ │ │ │ ├── complexity-formatters.ts
│ │ │ │ │ ├── dependency-formatters.ts
│ │ │ │ │ ├── priority-formatters.ts
│ │ │ │ │ ├── status-formatters.spec.ts
│ │ │ │ │ └── status-formatters.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ ├── helpers.spec.ts
│ │ │ │ └── helpers.ts
│ │ │ └── utils
│ │ │ ├── auth-helpers.ts
│ │ │ ├── auto-update.ts
│ │ │ ├── brief-selection.ts
│ │ │ ├── display-helpers.ts
│ │ │ ├── error-handler.ts
│ │ │ ├── index.ts
│ │ │ ├── project-root.ts
│ │ │ ├── task-status.ts
│ │ │ ├── ui.spec.ts
│ │ │ └── ui.ts
│ │ ├── tests
│ │ │ ├── integration
│ │ │ │ └── commands
│ │ │ │ └── autopilot
│ │ │ │ └── workflow.test.ts
│ │ │ └── unit
│ │ │ ├── commands
│ │ │ │ ├── autopilot
│ │ │ │ │ └── shared.test.ts
│ │ │ │ ├── list.command.spec.ts
│ │ │ │ └── show.command.spec.ts
│ │ │ └── ui
│ │ │ └── dashboard.component.spec.ts
│ │ ├── tsconfig.json
│ │ └── vitest.config.ts
│ ├── docs
│ │ ├── archive
│ │ │ ├── ai-client-utils-example.mdx
│ │ │ ├── ai-development-workflow.mdx
│ │ │ ├── command-reference.mdx
│ │ │ ├── configuration.mdx
│ │ │ ├── cursor-setup.mdx
│ │ │ ├── examples.mdx
│ │ │ └── Installation.mdx
│ │ ├── best-practices
│ │ │ ├── advanced-tasks.mdx
│ │ │ ├── configuration-advanced.mdx
│ │ │ └── index.mdx
│ │ ├── capabilities
│ │ │ ├── cli-root-commands.mdx
│ │ │ ├── index.mdx
│ │ │ ├── mcp.mdx
│ │ │ ├── rpg-method.mdx
│ │ │ └── task-structure.mdx
│ │ ├── CHANGELOG.md
│ │ ├── command-reference.mdx
│ │ ├── configuration.mdx
│ │ ├── docs.json
│ │ ├── favicon.svg
│ │ ├── getting-started
│ │ │ ├── api-keys.mdx
│ │ │ ├── contribute.mdx
│ │ │ ├── faq.mdx
│ │ │ └── quick-start
│ │ │ ├── configuration-quick.mdx
│ │ │ ├── execute-quick.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── moving-forward.mdx
│ │ │ ├── prd-quick.mdx
│ │ │ ├── quick-start.mdx
│ │ │ ├── requirements.mdx
│ │ │ ├── rules-quick.mdx
│ │ │ └── tasks-quick.mdx
│ │ ├── introduction.mdx
│ │ ├── licensing.md
│ │ ├── logo
│ │ │ ├── dark.svg
│ │ │ ├── light.svg
│ │ │ └── task-master-logo.png
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── style.css
│ │ ├── tdd-workflow
│ │ │ ├── ai-agent-integration.mdx
│ │ │ └── quickstart.mdx
│ │ ├── vercel.json
│ │ └── whats-new.mdx
│ ├── extension
│ │ ├── .vscodeignore
│ │ ├── assets
│ │ │ ├── banner.png
│ │ │ ├── icon-dark.svg
│ │ │ ├── icon-light.svg
│ │ │ ├── icon.png
│ │ │ ├── screenshots
│ │ │ │ ├── kanban-board.png
│ │ │ │ └── task-details.png
│ │ │ └── sidebar-icon.svg
│ │ ├── CHANGELOG.md
│ │ ├── components.json
│ │ ├── docs
│ │ │ ├── extension-CI-setup.md
│ │ │ └── extension-development-guide.md
│ │ ├── esbuild.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── package.mjs
│ │ ├── package.publish.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── components
│ │ │ │ ├── ConfigView.tsx
│ │ │ │ ├── constants.ts
│ │ │ │ ├── TaskDetails
│ │ │ │ │ ├── AIActionsSection.tsx
│ │ │ │ │ ├── DetailsSection.tsx
│ │ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ │ ├── SubtasksSection.tsx
│ │ │ │ │ ├── TaskMetadataSidebar.tsx
│ │ │ │ │ └── useTaskDetails.ts
│ │ │ │ ├── TaskDetailsView.tsx
│ │ │ │ ├── TaskMasterLogo.tsx
│ │ │ │ └── ui
│ │ │ │ ├── badge.tsx
│ │ │ │ ├── breadcrumb.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── collapsible.tsx
│ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── label.tsx
│ │ │ │ ├── scroll-area.tsx
│ │ │ │ ├── separator.tsx
│ │ │ │ ├── shadcn-io
│ │ │ │ │ └── kanban
│ │ │ │ │ └── index.tsx
│ │ │ │ └── textarea.tsx
│ │ │ ├── extension.ts
│ │ │ ├── index.ts
│ │ │ ├── lib
│ │ │ │ └── utils.ts
│ │ │ ├── services
│ │ │ │ ├── config-service.ts
│ │ │ │ ├── error-handler.ts
│ │ │ │ ├── notification-preferences.ts
│ │ │ │ ├── polling-service.ts
│ │ │ │ ├── polling-strategies.ts
│ │ │ │ ├── sidebar-webview-manager.ts
│ │ │ │ ├── task-repository.ts
│ │ │ │ ├── terminal-manager.ts
│ │ │ │ └── webview-manager.ts
│ │ │ ├── test
│ │ │ │ └── extension.test.ts
│ │ │ ├── utils
│ │ │ │ ├── configManager.ts
│ │ │ │ ├── connectionManager.ts
│ │ │ │ ├── errorHandler.ts
│ │ │ │ ├── event-emitter.ts
│ │ │ │ ├── logger.ts
│ │ │ │ ├── mcpClient.ts
│ │ │ │ ├── notificationPreferences.ts
│ │ │ │ └── task-master-api
│ │ │ │ ├── cache
│ │ │ │ │ └── cache-manager.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── mcp-client.ts
│ │ │ │ ├── transformers
│ │ │ │ │ └── task-transformer.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ └── webview
│ │ │ ├── App.tsx
│ │ │ ├── components
│ │ │ │ ├── AppContent.tsx
│ │ │ │ ├── EmptyState.tsx
│ │ │ │ ├── ErrorBoundary.tsx
│ │ │ │ ├── PollingStatus.tsx
│ │ │ │ ├── PriorityBadge.tsx
│ │ │ │ ├── SidebarView.tsx
│ │ │ │ ├── TagDropdown.tsx
│ │ │ │ ├── TaskCard.tsx
│ │ │ │ ├── TaskEditModal.tsx
│ │ │ │ ├── TaskMasterKanban.tsx
│ │ │ │ ├── ToastContainer.tsx
│ │ │ │ └── ToastNotification.tsx
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contexts
│ │ │ │ └── VSCodeContext.tsx
│ │ │ ├── hooks
│ │ │ │ ├── useTaskQueries.ts
│ │ │ │ ├── useVSCodeMessages.ts
│ │ │ │ └── useWebviewHeight.ts
│ │ │ ├── index.css
│ │ │ ├── index.tsx
│ │ │ ├── providers
│ │ │ │ └── QueryProvider.tsx
│ │ │ ├── reducers
│ │ │ │ └── appReducer.ts
│ │ │ ├── sidebar.tsx
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ └── utils
│ │ │ ├── logger.ts
│ │ │ └── toast.ts
│ │ └── tsconfig.json
│ └── mcp
│ ├── CHANGELOG.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ ├── shared
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ └── tools
│ │ ├── autopilot
│ │ │ ├── abort.tool.ts
│ │ │ ├── commit.tool.ts
│ │ │ ├── complete.tool.ts
│ │ │ ├── finalize.tool.ts
│ │ │ ├── index.ts
│ │ │ ├── next.tool.ts
│ │ │ ├── resume.tool.ts
│ │ │ ├── start.tool.ts
│ │ │ └── status.tool.ts
│ │ ├── README-ZOD-V3.md
│ │ └── tasks
│ │ ├── get-task.tool.ts
│ │ ├── get-tasks.tool.ts
│ │ └── index.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── assets
│ ├── .windsurfrules
│ ├── AGENTS.md
│ ├── claude
│ │ └── TM_COMMANDS_GUIDE.md
│ ├── config.json
│ ├── env.example
│ ├── example_prd_rpg.txt
│ ├── example_prd.txt
│ ├── GEMINI.md
│ ├── gitignore
│ ├── kiro-hooks
│ │ ├── tm-code-change-task-tracker.kiro.hook
│ │ ├── tm-complexity-analyzer.kiro.hook
│ │ ├── tm-daily-standup-assistant.kiro.hook
│ │ ├── tm-git-commit-task-linker.kiro.hook
│ │ ├── tm-pr-readiness-checker.kiro.hook
│ │ ├── tm-task-dependency-auto-progression.kiro.hook
│ │ └── tm-test-success-task-completer.kiro.hook
│ ├── roocode
│ │ ├── .roo
│ │ │ ├── rules-architect
│ │ │ │ └── architect-rules
│ │ │ ├── rules-ask
│ │ │ │ └── ask-rules
│ │ │ ├── rules-code
│ │ │ │ └── code-rules
│ │ │ ├── rules-debug
│ │ │ │ └── debug-rules
│ │ │ ├── rules-orchestrator
│ │ │ │ └── orchestrator-rules
│ │ │ └── rules-test
│ │ │ └── test-rules
│ │ └── .roomodes
│ ├── rules
│ │ ├── cursor_rules.mdc
│ │ ├── dev_workflow.mdc
│ │ ├── self_improve.mdc
│ │ ├── taskmaster_hooks_workflow.mdc
│ │ └── taskmaster.mdc
│ └── scripts_README.md
├── bin
│ └── task-master.js
├── biome.json
├── CHANGELOG.md
├── CLAUDE_CODE_PLUGIN.md
├── CLAUDE.md
├── context
│ ├── chats
│ │ ├── add-task-dependencies-1.md
│ │ └── max-min-tokens.txt.md
│ ├── fastmcp-core.txt
│ ├── fastmcp-docs.txt
│ ├── MCP_INTEGRATION.md
│ ├── mcp-js-sdk-docs.txt
│ ├── mcp-protocol-repo.txt
│ ├── mcp-protocol-schema-03262025.json
│ └── mcp-protocol-spec.txt
├── CONTRIBUTING.md
├── docs
│ ├── claude-code-integration.md
│ ├── CLI-COMMANDER-PATTERN.md
│ ├── command-reference.md
│ ├── configuration.md
│ ├── contributor-docs
│ │ ├── testing-roo-integration.md
│ │ └── worktree-setup.md
│ ├── cross-tag-task-movement.md
│ ├── examples
│ │ ├── claude-code-usage.md
│ │ └── codex-cli-usage.md
│ ├── examples.md
│ ├── licensing.md
│ ├── mcp-provider-guide.md
│ ├── mcp-provider.md
│ ├── migration-guide.md
│ ├── models.md
│ ├── providers
│ │ ├── codex-cli.md
│ │ └── gemini-cli.md
│ ├── README.md
│ ├── scripts
│ │ └── models-json-to-markdown.js
│ ├── task-structure.md
│ └── tutorial.md
├── images
│ ├── hamster-hiring.png
│ └── logo.png
├── index.js
├── jest.config.js
├── jest.resolver.cjs
├── LICENSE
├── llms-install.md
├── mcp-server
│ ├── server.js
│ └── src
│ ├── core
│ │ ├── __tests__
│ │ │ └── context-manager.test.js
│ │ ├── context-manager.js
│ │ ├── direct-functions
│ │ │ ├── add-dependency.js
│ │ │ ├── add-subtask.js
│ │ │ ├── add-tag.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── cache-stats.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── complexity-report.js
│ │ │ ├── copy-tag.js
│ │ │ ├── create-tag-from-branch.js
│ │ │ ├── delete-tag.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── fix-dependencies.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── initialize-project.js
│ │ │ ├── list-tags.js
│ │ │ ├── models.js
│ │ │ ├── move-task-cross-tag.js
│ │ │ ├── move-task.js
│ │ │ ├── next-task.js
│ │ │ ├── parse-prd.js
│ │ │ ├── remove-dependency.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── rename-tag.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── rules.js
│ │ │ ├── scope-down.js
│ │ │ ├── scope-up.js
│ │ │ ├── set-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ ├── update-tasks.js
│ │ │ ├── use-tag.js
│ │ │ └── validate-dependencies.js
│ │ ├── task-master-core.js
│ │ └── utils
│ │ ├── env-utils.js
│ │ └── path-utils.js
│ ├── custom-sdk
│ │ ├── errors.js
│ │ ├── index.js
│ │ ├── json-extractor.js
│ │ ├── language-model.js
│ │ ├── message-converter.js
│ │ └── schema-converter.js
│ ├── index.js
│ ├── logger.js
│ ├── providers
│ │ └── mcp-provider.js
│ └── tools
│ ├── add-dependency.js
│ ├── add-subtask.js
│ ├── add-tag.js
│ ├── add-task.js
│ ├── analyze.js
│ ├── clear-subtasks.js
│ ├── complexity-report.js
│ ├── copy-tag.js
│ ├── delete-tag.js
│ ├── expand-all.js
│ ├── expand-task.js
│ ├── fix-dependencies.js
│ ├── generate.js
│ ├── get-operation-status.js
│ ├── index.js
│ ├── initialize-project.js
│ ├── list-tags.js
│ ├── models.js
│ ├── move-task.js
│ ├── next-task.js
│ ├── parse-prd.js
│ ├── README-ZOD-V3.md
│ ├── remove-dependency.js
│ ├── remove-subtask.js
│ ├── remove-task.js
│ ├── rename-tag.js
│ ├── research.js
│ ├── response-language.js
│ ├── rules.js
│ ├── scope-down.js
│ ├── scope-up.js
│ ├── set-task-status.js
│ ├── tool-registry.js
│ ├── update-subtask.js
│ ├── update-task.js
│ ├── update.js
│ ├── use-tag.js
│ ├── utils.js
│ └── validate-dependencies.js
├── mcp-test.js
├── output.json
├── package-lock.json
├── package.json
├── packages
│ ├── ai-sdk-provider-grok-cli
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── errors.test.ts
│ │ │ ├── errors.ts
│ │ │ ├── grok-cli-language-model.ts
│ │ │ ├── grok-cli-provider.test.ts
│ │ │ ├── grok-cli-provider.ts
│ │ │ ├── index.ts
│ │ │ ├── json-extractor.test.ts
│ │ │ ├── json-extractor.ts
│ │ │ ├── message-converter.test.ts
│ │ │ ├── message-converter.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ ├── build-config
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── src
│ │ │ └── tsdown.base.ts
│ │ └── tsconfig.json
│ ├── claude-code-plugin
│ │ ├── .claude-plugin
│ │ │ └── plugin.json
│ │ ├── .gitignore
│ │ ├── agents
│ │ │ ├── task-checker.md
│ │ │ ├── task-executor.md
│ │ │ └── task-orchestrator.md
│ │ ├── CHANGELOG.md
│ │ ├── commands
│ │ │ ├── add-dependency.md
│ │ │ ├── add-subtask.md
│ │ │ ├── add-task.md
│ │ │ ├── analyze-complexity.md
│ │ │ ├── analyze-project.md
│ │ │ ├── auto-implement-tasks.md
│ │ │ ├── command-pipeline.md
│ │ │ ├── complexity-report.md
│ │ │ ├── convert-task-to-subtask.md
│ │ │ ├── expand-all-tasks.md
│ │ │ ├── expand-task.md
│ │ │ ├── fix-dependencies.md
│ │ │ ├── generate-tasks.md
│ │ │ ├── help.md
│ │ │ ├── init-project-quick.md
│ │ │ ├── init-project.md
│ │ │ ├── install-taskmaster.md
│ │ │ ├── learn.md
│ │ │ ├── list-tasks-by-status.md
│ │ │ ├── list-tasks-with-subtasks.md
│ │ │ ├── list-tasks.md
│ │ │ ├── next-task.md
│ │ │ ├── parse-prd-with-research.md
│ │ │ ├── parse-prd.md
│ │ │ ├── project-status.md
│ │ │ ├── quick-install-taskmaster.md
│ │ │ ├── remove-all-subtasks.md
│ │ │ ├── remove-dependency.md
│ │ │ ├── remove-subtask.md
│ │ │ ├── remove-subtasks.md
│ │ │ ├── remove-task.md
│ │ │ ├── setup-models.md
│ │ │ ├── show-task.md
│ │ │ ├── smart-workflow.md
│ │ │ ├── sync-readme.md
│ │ │ ├── tm-main.md
│ │ │ ├── to-cancelled.md
│ │ │ ├── to-deferred.md
│ │ │ ├── to-done.md
│ │ │ ├── to-in-progress.md
│ │ │ ├── to-pending.md
│ │ │ ├── to-review.md
│ │ │ ├── update-single-task.md
│ │ │ ├── update-task.md
│ │ │ ├── update-tasks-from-id.md
│ │ │ ├── validate-dependencies.md
│ │ │ └── view-models.md
│ │ ├── mcp.json
│ │ └── package.json
│ ├── tm-bridge
│ │ ├── CHANGELOG.md
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── add-tag-bridge.ts
│ │ │ ├── bridge-types.ts
│ │ │ ├── bridge-utils.ts
│ │ │ ├── expand-bridge.ts
│ │ │ ├── index.ts
│ │ │ ├── tags-bridge.ts
│ │ │ ├── update-bridge.ts
│ │ │ └── use-tag-bridge.ts
│ │ └── tsconfig.json
│ └── tm-core
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── docs
│ │ └── listTasks-architecture.md
│ ├── package.json
│ ├── POC-STATUS.md
│ ├── README.md
│ ├── src
│ │ ├── common
│ │ │ ├── constants
│ │ │ │ ├── index.ts
│ │ │ │ ├── paths.ts
│ │ │ │ └── providers.ts
│ │ │ ├── errors
│ │ │ │ ├── index.ts
│ │ │ │ └── task-master-error.ts
│ │ │ ├── interfaces
│ │ │ │ ├── configuration.interface.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── storage.interface.ts
│ │ │ ├── logger
│ │ │ │ ├── factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── logger.spec.ts
│ │ │ │ └── logger.ts
│ │ │ ├── mappers
│ │ │ │ ├── TaskMapper.test.ts
│ │ │ │ └── TaskMapper.ts
│ │ │ ├── types
│ │ │ │ ├── database.types.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── legacy.ts
│ │ │ │ └── repository-types.ts
│ │ │ └── utils
│ │ │ ├── git-utils.ts
│ │ │ ├── id-generator.ts
│ │ │ ├── index.ts
│ │ │ ├── path-helpers.ts
│ │ │ ├── path-normalizer.spec.ts
│ │ │ ├── path-normalizer.ts
│ │ │ ├── project-root-finder.spec.ts
│ │ │ ├── project-root-finder.ts
│ │ │ ├── run-id-generator.spec.ts
│ │ │ └── run-id-generator.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── ai
│ │ │ │ ├── index.ts
│ │ │ │ ├── interfaces
│ │ │ │ │ └── ai-provider.interface.ts
│ │ │ │ └── providers
│ │ │ │ ├── base-provider.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth
│ │ │ │ ├── auth-domain.spec.ts
│ │ │ │ ├── auth-domain.ts
│ │ │ │ ├── config.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── auth-manager.spec.ts
│ │ │ │ │ └── auth-manager.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── context-store.ts
│ │ │ │ │ ├── oauth-service.ts
│ │ │ │ │ ├── organization.service.ts
│ │ │ │ │ ├── supabase-session-storage.spec.ts
│ │ │ │ │ └── supabase-session-storage.ts
│ │ │ │ └── types.ts
│ │ │ ├── briefs
│ │ │ │ ├── briefs-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── brief-service.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── utils
│ │ │ │ └── url-parser.ts
│ │ │ ├── commands
│ │ │ │ └── index.ts
│ │ │ ├── config
│ │ │ │ ├── config-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ ├── config-manager.spec.ts
│ │ │ │ │ └── config-manager.ts
│ │ │ │ └── services
│ │ │ │ ├── config-loader.service.spec.ts
│ │ │ │ ├── config-loader.service.ts
│ │ │ │ ├── config-merger.service.spec.ts
│ │ │ │ ├── config-merger.service.ts
│ │ │ │ ├── config-persistence.service.spec.ts
│ │ │ │ ├── config-persistence.service.ts
│ │ │ │ ├── environment-config-provider.service.spec.ts
│ │ │ │ ├── environment-config-provider.service.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── runtime-state-manager.service.spec.ts
│ │ │ │ └── runtime-state-manager.service.ts
│ │ │ ├── dependencies
│ │ │ │ └── index.ts
│ │ │ ├── execution
│ │ │ │ ├── executors
│ │ │ │ │ ├── base-executor.ts
│ │ │ │ │ ├── claude-executor.ts
│ │ │ │ │ └── executor-factory.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── executor-service.ts
│ │ │ │ └── types.ts
│ │ │ ├── git
│ │ │ │ ├── adapters
│ │ │ │ │ ├── git-adapter.test.ts
│ │ │ │ │ └── git-adapter.ts
│ │ │ │ ├── git-domain.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── services
│ │ │ │ ├── branch-name-generator.spec.ts
│ │ │ │ ├── branch-name-generator.ts
│ │ │ │ ├── commit-message-generator.test.ts
│ │ │ │ ├── commit-message-generator.ts
│ │ │ │ ├── scope-detector.test.ts
│ │ │ │ ├── scope-detector.ts
│ │ │ │ ├── template-engine.test.ts
│ │ │ │ └── template-engine.ts
│ │ │ ├── integration
│ │ │ │ ├── clients
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── supabase-client.ts
│ │ │ │ ├── integration-domain.ts
│ │ │ │ └── services
│ │ │ │ ├── export.service.ts
│ │ │ │ ├── task-expansion.service.ts
│ │ │ │ └── task-retrieval.service.ts
│ │ │ ├── reports
│ │ │ │ ├── index.ts
│ │ │ │ ├── managers
│ │ │ │ │ └── complexity-report-manager.ts
│ │ │ │ └── types.ts
│ │ │ ├── storage
│ │ │ │ ├── adapters
│ │ │ │ │ ├── activity-logger.ts
│ │ │ │ │ ├── api-storage.ts
│ │ │ │ │ └── file-storage
│ │ │ │ │ ├── file-operations.ts
│ │ │ │ │ ├── file-storage.ts
│ │ │ │ │ ├── format-handler.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── path-resolver.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── services
│ │ │ │ │ └── storage-factory.ts
│ │ │ │ └── utils
│ │ │ │ └── api-client.ts
│ │ │ ├── tasks
│ │ │ │ ├── entities
│ │ │ │ │ └── task.entity.ts
│ │ │ │ ├── parser
│ │ │ │ │ └── index.ts
│ │ │ │ ├── repositories
│ │ │ │ │ ├── supabase
│ │ │ │ │ │ ├── dependency-fetcher.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── supabase-repository.ts
│ │ │ │ │ └── task-repository.interface.ts
│ │ │ │ ├── services
│ │ │ │ │ ├── preflight-checker.service.ts
│ │ │ │ │ ├── tag.service.ts
│ │ │ │ │ ├── task-execution-service.ts
│ │ │ │ │ ├── task-loader.service.ts
│ │ │ │ │ └── task-service.ts
│ │ │ │ └── tasks-domain.ts
│ │ │ ├── ui
│ │ │ │ └── index.ts
│ │ │ └── workflow
│ │ │ ├── managers
│ │ │ │ ├── workflow-state-manager.spec.ts
│ │ │ │ └── workflow-state-manager.ts
│ │ │ ├── orchestrators
│ │ │ │ ├── workflow-orchestrator.test.ts
│ │ │ │ └── workflow-orchestrator.ts
│ │ │ ├── services
│ │ │ │ ├── test-result-validator.test.ts
│ │ │ │ ├── test-result-validator.ts
│ │ │ │ ├── test-result-validator.types.ts
│ │ │ │ ├── workflow-activity-logger.ts
│ │ │ │ └── workflow.service.ts
│ │ │ ├── types.ts
│ │ │ └── workflow-domain.ts
│ │ ├── subpath-exports.test.ts
│ │ ├── tm-core.ts
│ │ └── utils
│ │ └── time.utils.ts
│ ├── tests
│ │ ├── auth
│ │ │ └── auth-refresh.test.ts
│ │ ├── integration
│ │ │ ├── auth-token-refresh.test.ts
│ │ │ ├── list-tasks.test.ts
│ │ │ └── storage
│ │ │ └── activity-logger.test.ts
│ │ ├── mocks
│ │ │ └── mock-provider.ts
│ │ ├── setup.ts
│ │ └── unit
│ │ ├── base-provider.test.ts
│ │ ├── executor.test.ts
│ │ └── smoke.test.ts
│ ├── tsconfig.json
│ └── vitest.config.ts
├── README-task-master.md
├── README.md
├── scripts
│ ├── create-worktree.sh
│ ├── dev.js
│ ├── init.js
│ ├── list-worktrees.sh
│ ├── modules
│ │ ├── ai-services-unified.js
│ │ ├── bridge-utils.js
│ │ ├── commands.js
│ │ ├── config-manager.js
│ │ ├── dependency-manager.js
│ │ ├── index.js
│ │ ├── prompt-manager.js
│ │ ├── supported-models.json
│ │ ├── sync-readme.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.js
│ │ │ ├── add-task.js
│ │ │ ├── analyze-task-complexity.js
│ │ │ ├── clear-subtasks.js
│ │ │ ├── expand-all-tasks.js
│ │ │ ├── expand-task.js
│ │ │ ├── find-next-task.js
│ │ │ ├── generate-task-files.js
│ │ │ ├── is-task-dependent.js
│ │ │ ├── list-tasks.js
│ │ │ ├── migrate.js
│ │ │ ├── models.js
│ │ │ ├── move-task.js
│ │ │ ├── parse-prd
│ │ │ │ ├── index.js
│ │ │ │ ├── parse-prd-config.js
│ │ │ │ ├── parse-prd-helpers.js
│ │ │ │ ├── parse-prd-non-streaming.js
│ │ │ │ ├── parse-prd-streaming.js
│ │ │ │ └── parse-prd.js
│ │ │ ├── remove-subtask.js
│ │ │ ├── remove-task.js
│ │ │ ├── research.js
│ │ │ ├── response-language.js
│ │ │ ├── scope-adjustment.js
│ │ │ ├── set-task-status.js
│ │ │ ├── tag-management.js
│ │ │ ├── task-exists.js
│ │ │ ├── update-single-task-status.js
│ │ │ ├── update-subtask-by-id.js
│ │ │ ├── update-task-by-id.js
│ │ │ └── update-tasks.js
│ │ ├── task-manager.js
│ │ ├── ui.js
│ │ ├── update-config-tokens.js
│ │ ├── utils
│ │ │ ├── contextGatherer.js
│ │ │ ├── fuzzyTaskSearch.js
│ │ │ └── git-utils.js
│ │ └── utils.js
│ ├── task-complexity-report.json
│ ├── test-claude-errors.js
│ └── test-claude.js
├── sonar-project.properties
├── src
│ ├── ai-providers
│ │ ├── anthropic.js
│ │ ├── azure.js
│ │ ├── base-provider.js
│ │ ├── bedrock.js
│ │ ├── claude-code.js
│ │ ├── codex-cli.js
│ │ ├── gemini-cli.js
│ │ ├── google-vertex.js
│ │ ├── google.js
│ │ ├── grok-cli.js
│ │ ├── groq.js
│ │ ├── index.js
│ │ ├── lmstudio.js
│ │ ├── ollama.js
│ │ ├── openai-compatible.js
│ │ ├── openai.js
│ │ ├── openrouter.js
│ │ ├── perplexity.js
│ │ ├── xai.js
│ │ ├── zai-coding.js
│ │ └── zai.js
│ ├── constants
│ │ ├── commands.js
│ │ ├── paths.js
│ │ ├── profiles.js
│ │ ├── rules-actions.js
│ │ ├── task-priority.js
│ │ └── task-status.js
│ ├── profiles
│ │ ├── amp.js
│ │ ├── base-profile.js
│ │ ├── claude.js
│ │ ├── cline.js
│ │ ├── codex.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── index.js
│ │ ├── kilo.js
│ │ ├── kiro.js
│ │ ├── opencode.js
│ │ ├── roo.js
│ │ ├── trae.js
│ │ ├── vscode.js
│ │ ├── windsurf.js
│ │ └── zed.js
│ ├── progress
│ │ ├── base-progress-tracker.js
│ │ ├── cli-progress-factory.js
│ │ ├── parse-prd-tracker.js
│ │ ├── progress-tracker-builder.js
│ │ └── tracker-ui.js
│ ├── prompts
│ │ ├── add-task.json
│ │ ├── analyze-complexity.json
│ │ ├── expand-task.json
│ │ ├── parse-prd.json
│ │ ├── README.md
│ │ ├── research.json
│ │ ├── schemas
│ │ │ ├── parameter.schema.json
│ │ │ ├── prompt-template.schema.json
│ │ │ ├── README.md
│ │ │ └── variant.schema.json
│ │ ├── update-subtask.json
│ │ ├── update-task.json
│ │ └── update-tasks.json
│ ├── provider-registry
│ │ └── index.js
│ ├── schemas
│ │ ├── add-task.js
│ │ ├── analyze-complexity.js
│ │ ├── base-schemas.js
│ │ ├── expand-task.js
│ │ ├── parse-prd.js
│ │ ├── registry.js
│ │ ├── update-subtask.js
│ │ ├── update-task.js
│ │ └── update-tasks.js
│ ├── task-master.js
│ ├── ui
│ │ ├── confirm.js
│ │ ├── indicators.js
│ │ └── parse-prd.js
│ └── utils
│ ├── asset-resolver.js
│ ├── create-mcp-config.js
│ ├── format.js
│ ├── getVersion.js
│ ├── logger-utils.js
│ ├── manage-gitignore.js
│ ├── path-utils.js
│ ├── profiles.js
│ ├── rule-transformer.js
│ ├── stream-parser.js
│ └── timeout-manager.js
├── test-clean-tags.js
├── test-config-manager.js
├── test-prd.txt
├── test-tag-functions.js
├── test-version-check-full.js
├── test-version-check.js
├── tests
│ ├── e2e
│ │ ├── e2e_helpers.sh
│ │ ├── parse_llm_output.cjs
│ │ ├── run_e2e.sh
│ │ ├── run_fallback_verification.sh
│ │ └── test_llm_analysis.sh
│ ├── fixtures
│ │ ├── .taskmasterconfig
│ │ ├── sample-claude-response.js
│ │ ├── sample-prd.txt
│ │ └── sample-tasks.js
│ ├── helpers
│ │ └── tool-counts.js
│ ├── integration
│ │ ├── claude-code-error-handling.test.js
│ │ ├── claude-code-optional.test.js
│ │ ├── cli
│ │ │ ├── commands.test.js
│ │ │ ├── complex-cross-tag-scenarios.test.js
│ │ │ └── move-cross-tag.test.js
│ │ ├── manage-gitignore.test.js
│ │ ├── mcp-server
│ │ │ └── direct-functions.test.js
│ │ ├── move-task-cross-tag.integration.test.js
│ │ ├── move-task-simple.integration.test.js
│ │ ├── profiles
│ │ │ ├── amp-init-functionality.test.js
│ │ │ ├── claude-init-functionality.test.js
│ │ │ ├── cline-init-functionality.test.js
│ │ │ ├── codex-init-functionality.test.js
│ │ │ ├── cursor-init-functionality.test.js
│ │ │ ├── gemini-init-functionality.test.js
│ │ │ ├── opencode-init-functionality.test.js
│ │ │ ├── roo-files-inclusion.test.js
│ │ │ ├── roo-init-functionality.test.js
│ │ │ ├── rules-files-inclusion.test.js
│ │ │ ├── trae-init-functionality.test.js
│ │ │ ├── vscode-init-functionality.test.js
│ │ │ └── windsurf-init-functionality.test.js
│ │ └── providers
│ │ └── temperature-support.test.js
│ ├── manual
│ │ ├── progress
│ │ │ ├── parse-prd-analysis.js
│ │ │ ├── test-parse-prd.js
│ │ │ └── TESTING_GUIDE.md
│ │ └── prompts
│ │ ├── prompt-test.js
│ │ └── README.md
│ ├── README.md
│ ├── setup.js
│ └── unit
│ ├── ai-providers
│ │ ├── base-provider.test.js
│ │ ├── claude-code.test.js
│ │ ├── codex-cli.test.js
│ │ ├── gemini-cli.test.js
│ │ ├── lmstudio.test.js
│ │ ├── mcp-components.test.js
│ │ ├── openai-compatible.test.js
│ │ ├── openai.test.js
│ │ ├── provider-registry.test.js
│ │ ├── zai-coding.test.js
│ │ ├── zai-provider.test.js
│ │ ├── zai-schema-introspection.test.js
│ │ └── zai.test.js
│ ├── ai-services-unified.test.js
│ ├── commands.test.js
│ ├── config-manager.test.js
│ ├── config-manager.test.mjs
│ ├── dependency-manager.test.js
│ ├── init.test.js
│ ├── initialize-project.test.js
│ ├── kebab-case-validation.test.js
│ ├── manage-gitignore.test.js
│ ├── mcp
│ │ └── tools
│ │ ├── __mocks__
│ │ │ └── move-task.js
│ │ ├── add-task.test.js
│ │ ├── analyze-complexity.test.js
│ │ ├── expand-all.test.js
│ │ ├── get-tasks.test.js
│ │ ├── initialize-project.test.js
│ │ ├── move-task-cross-tag-options.test.js
│ │ ├── move-task-cross-tag.test.js
│ │ ├── remove-task.test.js
│ │ └── tool-registration.test.js
│ ├── mcp-providers
│ │ ├── mcp-components.test.js
│ │ └── mcp-provider.test.js
│ ├── parse-prd.test.js
│ ├── profiles
│ │ ├── amp-integration.test.js
│ │ ├── claude-integration.test.js
│ │ ├── cline-integration.test.js
│ │ ├── codex-integration.test.js
│ │ ├── cursor-integration.test.js
│ │ ├── gemini-integration.test.js
│ │ ├── kilo-integration.test.js
│ │ ├── kiro-integration.test.js
│ │ ├── mcp-config-validation.test.js
│ │ ├── opencode-integration.test.js
│ │ ├── profile-safety-check.test.js
│ │ ├── roo-integration.test.js
│ │ ├── rule-transformer-cline.test.js
│ │ ├── rule-transformer-cursor.test.js
│ │ ├── rule-transformer-gemini.test.js
│ │ ├── rule-transformer-kilo.test.js
│ │ ├── rule-transformer-kiro.test.js
│ │ ├── rule-transformer-opencode.test.js
│ │ ├── rule-transformer-roo.test.js
│ │ ├── rule-transformer-trae.test.js
│ │ ├── rule-transformer-vscode.test.js
│ │ ├── rule-transformer-windsurf.test.js
│ │ ├── rule-transformer-zed.test.js
│ │ ├── rule-transformer.test.js
│ │ ├── selective-profile-removal.test.js
│ │ ├── subdirectory-support.test.js
│ │ ├── trae-integration.test.js
│ │ ├── vscode-integration.test.js
│ │ ├── windsurf-integration.test.js
│ │ └── zed-integration.test.js
│ ├── progress
│ │ └── base-progress-tracker.test.js
│ ├── prompt-manager.test.js
│ ├── prompts
│ │ ├── expand-task-prompt.test.js
│ │ └── prompt-migration.test.js
│ ├── scripts
│ │ └── modules
│ │ ├── commands
│ │ │ ├── move-cross-tag.test.js
│ │ │ └── README.md
│ │ ├── dependency-manager
│ │ │ ├── circular-dependencies.test.js
│ │ │ ├── cross-tag-dependencies.test.js
│ │ │ └── fix-dependencies-command.test.js
│ │ ├── task-manager
│ │ │ ├── add-subtask.test.js
│ │ │ ├── add-task.test.js
│ │ │ ├── analyze-task-complexity.test.js
│ │ │ ├── clear-subtasks.test.js
│ │ │ ├── complexity-report-tag-isolation.test.js
│ │ │ ├── expand-all-tasks.test.js
│ │ │ ├── expand-task.test.js
│ │ │ ├── find-next-task.test.js
│ │ │ ├── generate-task-files.test.js
│ │ │ ├── list-tasks.test.js
│ │ │ ├── models-baseurl.test.js
│ │ │ ├── move-task-cross-tag.test.js
│ │ │ ├── move-task.test.js
│ │ │ ├── parse-prd-schema.test.js
│ │ │ ├── parse-prd.test.js
│ │ │ ├── remove-subtask.test.js
│ │ │ ├── remove-task.test.js
│ │ │ ├── research.test.js
│ │ │ ├── scope-adjustment.test.js
│ │ │ ├── set-task-status.test.js
│ │ │ ├── setup.js
│ │ │ ├── update-single-task-status.test.js
│ │ │ ├── update-subtask-by-id.test.js
│ │ │ ├── update-task-by-id.test.js
│ │ │ └── update-tasks.test.js
│ │ ├── ui
│ │ │ └── cross-tag-error-display.test.js
│ │ └── utils-tag-aware-paths.test.js
│ ├── task-finder.test.js
│ ├── task-manager
│ │ ├── clear-subtasks.test.js
│ │ ├── move-task.test.js
│ │ ├── tag-boundary.test.js
│ │ └── tag-management.test.js
│ ├── task-master.test.js
│ ├── ui
│ │ └── indicators.test.js
│ ├── ui.test.js
│ ├── utils-strip-ansi.test.js
│ └── utils.test.js
├── tsconfig.json
├── tsdown.config.ts
├── turbo.json
└── update-task-migration-plan.md
```
# Files
--------------------------------------------------------------------------------
/scripts/modules/ui.js:
--------------------------------------------------------------------------------
```javascript
1 | /**
2 | * ui.js
3 | * User interface functions for the Task Master CLI
4 | */
5 |
6 | import chalk from 'chalk';
7 | import figlet from 'figlet';
8 | import boxen from 'boxen';
9 | import ora from 'ora';
10 | import Table from 'cli-table3';
11 | import gradient from 'gradient-string';
12 | import readline from 'readline';
13 | import {
14 | log,
15 | findTaskById,
16 | readJSON,
17 | truncate,
18 | isSilentMode,
19 | formatTaskId
20 | } from './utils.js';
21 | import fs from 'fs';
22 | import {
23 | findNextTask,
24 | analyzeTaskComplexity,
25 | readComplexityReport
26 | } from './task-manager.js';
27 | import { getProjectName, getDefaultSubtasks } from './config-manager.js';
28 | import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js';
29 | import {
30 | TASKMASTER_CONFIG_FILE,
31 | TASKMASTER_TASKS_FILE
32 | } from '../../src/constants/paths.js';
33 | import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
34 |
35 | // Create a color gradient for the banner
36 | const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']);
37 | const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']);
38 |
39 | /**
40 | * Display FYI notice about tagged task lists (only if migration occurred)
41 | * @param {Object} data - Data object that may contain _migrationHappened flag
42 | */
43 | function displayTaggedTasksFYI(data) {
44 | if (isSilentMode() || !data || !data._migrationHappened) return;
45 |
46 | console.log(
47 | boxen(
48 | chalk.white.bold('FYI: ') +
49 | chalk.gray('Taskmaster now supports separate task lists per tag. ') +
50 | chalk.cyan(
51 | 'Use the --tag flag to create/read/update/filter tasks by tag.'
52 | ),
53 | {
54 | padding: { top: 0, bottom: 0, left: 2, right: 2 },
55 | borderColor: 'cyan',
56 | borderStyle: 'round',
57 | margin: { top: 1, bottom: 1 }
58 | }
59 | )
60 | );
61 | }
62 |
63 | /**
64 | * Display a small, non-intrusive indicator showing the current tag context
65 | * @param {string} tagName - The tag name to display
66 | * @param {Object} options - Display options
67 | * @param {boolean} [options.skipIfMaster=false] - Don't show indicator if tag is 'master'
68 | * @param {boolean} [options.dim=false] - Use dimmed styling
69 | */
70 | function displayCurrentTagIndicator(tag, options = {}) {
71 | if (isSilentMode()) return;
72 |
73 | const { skipIfMaster = false, dim = false } = options;
74 |
75 | // Skip display for master tag only if explicitly requested
76 | if (skipIfMaster && tag === 'master') return;
77 |
78 | // Create a small, tasteful tag indicator
79 | const tagIcon = '🏷️';
80 | const tagText = dim
81 | ? chalk.gray(`${tagIcon} tag: ${tag}`)
82 | : chalk.dim(`${tagIcon} tag: `) + chalk.cyan(tag);
83 |
84 | console.log(tagText);
85 | }
86 |
87 | /**
88 | * Display a fancy banner for the CLI
89 | */
90 | function displayBanner() {
91 | if (isSilentMode()) return;
92 |
93 | // console.clear(); // Removing this to avoid clearing the terminal per command
94 | const bannerText = figlet.textSync('Task Master', {
95 | font: 'Standard',
96 | horizontalLayout: 'default',
97 | verticalLayout: 'default'
98 | });
99 |
100 | console.log(coolGradient(bannerText));
101 |
102 | // Add creator credit line below the banner
103 | console.log(
104 | chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano')
105 | );
106 |
107 | // Read version directly from package.json
108 | const version = getTaskMasterVersion();
109 |
110 | console.log(
111 | boxen(
112 | chalk.white(
113 | `${chalk.bold('Version:')} ${version} ${chalk.bold('Project:')} ${getProjectName(null)}`
114 | ),
115 | {
116 | padding: 1,
117 | margin: { top: 0, bottom: 1 },
118 | borderStyle: 'round',
119 | borderColor: 'cyan'
120 | }
121 | )
122 | );
123 | }
124 |
125 | /**
126 | * Start a loading indicator with an animated spinner
127 | * @param {string} message - Message to display next to the spinner
128 | * @returns {Object} Spinner object
129 | */
130 | function startLoadingIndicator(message) {
131 | if (isSilentMode()) return null;
132 |
133 | const spinner = ora({
134 | text: message,
135 | color: 'cyan'
136 | }).start();
137 |
138 | return spinner;
139 | }
140 |
141 | /**
142 | * Stop a loading indicator (basic stop, no success/fail indicator)
143 | * @param {Object} spinner - Spinner object to stop
144 | */
145 | function stopLoadingIndicator(spinner) {
146 | if (spinner && typeof spinner.stop === 'function') {
147 | spinner.stop();
148 | }
149 | }
150 |
151 | /**
152 | * Complete a loading indicator with success (shows checkmark)
153 | * @param {Object} spinner - Spinner object to complete
154 | * @param {string} message - Optional success message (defaults to current text)
155 | */
156 | function succeedLoadingIndicator(spinner, message = null) {
157 | if (spinner && typeof spinner.succeed === 'function') {
158 | if (message) {
159 | spinner.succeed(message);
160 | } else {
161 | spinner.succeed();
162 | }
163 | }
164 | }
165 |
166 | /**
167 | * Complete a loading indicator with failure (shows X)
168 | * @param {Object} spinner - Spinner object to fail
169 | * @param {string} message - Optional failure message (defaults to current text)
170 | */
171 | function failLoadingIndicator(spinner, message = null) {
172 | if (spinner && typeof spinner.fail === 'function') {
173 | if (message) {
174 | spinner.fail(message);
175 | } else {
176 | spinner.fail();
177 | }
178 | }
179 | }
180 |
181 | /**
182 | * Complete a loading indicator with warning (shows warning symbol)
183 | * @param {Object} spinner - Spinner object to warn
184 | * @param {string} message - Optional warning message (defaults to current text)
185 | */
186 | function warnLoadingIndicator(spinner, message = null) {
187 | if (spinner && typeof spinner.warn === 'function') {
188 | if (message) {
189 | spinner.warn(message);
190 | } else {
191 | spinner.warn();
192 | }
193 | }
194 | }
195 |
196 | /**
197 | * Complete a loading indicator with info (shows info symbol)
198 | * @param {Object} spinner - Spinner object to complete with info
199 | * @param {string} message - Optional info message (defaults to current text)
200 | */
201 | function infoLoadingIndicator(spinner, message = null) {
202 | if (spinner && typeof spinner.info === 'function') {
203 | if (message) {
204 | spinner.info(message);
205 | } else {
206 | spinner.info();
207 | }
208 | }
209 | }
210 |
211 | /**
212 | * Create a colored progress bar
213 | * @param {number} percent - The completion percentage
214 | * @param {number} length - The total length of the progress bar in characters
215 | * @param {Object} statusBreakdown - Optional breakdown of non-complete statuses (e.g., {pending: 20, 'in-progress': 10})
216 | * @returns {string} The formatted progress bar
217 | */
218 | function createProgressBar(percent, length = 30, statusBreakdown = null) {
219 | // Adjust the percent to treat deferred and cancelled as complete
220 | const effectivePercent = statusBreakdown
221 | ? Math.min(
222 | 100,
223 | percent +
224 | (statusBreakdown.deferred || 0) +
225 | (statusBreakdown.cancelled || 0)
226 | )
227 | : percent;
228 |
229 | // Calculate how many characters to fill for "true completion"
230 | const trueCompletedFilled = Math.round((percent * length) / 100);
231 |
232 | // Calculate how many characters to fill for "effective completion" (including deferred/cancelled)
233 | const effectiveCompletedFilled = Math.round(
234 | (effectivePercent * length) / 100
235 | );
236 |
237 | // The "deferred/cancelled" section (difference between true and effective)
238 | const deferredCancelledFilled =
239 | effectiveCompletedFilled - trueCompletedFilled;
240 |
241 | // Set the empty section (remaining after effective completion)
242 | const empty = length - effectiveCompletedFilled;
243 |
244 | // Determine color based on percentage for the completed section
245 | let completedColor;
246 | if (percent < 25) {
247 | completedColor = chalk.red;
248 | } else if (percent < 50) {
249 | completedColor = chalk.hex('#FFA500'); // Orange
250 | } else if (percent < 75) {
251 | completedColor = chalk.yellow;
252 | } else if (percent < 100) {
253 | completedColor = chalk.green;
254 | } else {
255 | completedColor = chalk.hex('#006400'); // Dark green
256 | }
257 |
258 | // Create colored sections
259 | const completedSection = completedColor('█'.repeat(trueCompletedFilled));
260 |
261 | // Gray section for deferred/cancelled items
262 | const deferredCancelledSection = chalk.gray(
263 | '█'.repeat(deferredCancelledFilled)
264 | );
265 |
266 | // If we have a status breakdown, create a multi-colored remaining section
267 | let remainingSection = '';
268 |
269 | if (statusBreakdown && empty > 0) {
270 | // Status colors (matching the statusConfig colors in getStatusWithColor)
271 | const statusColors = {
272 | pending: chalk.yellow,
273 | 'in-progress': chalk.hex('#FFA500'), // Orange
274 | blocked: chalk.red,
275 | review: chalk.magenta
276 | // Deferred and cancelled are treated as part of the completed section
277 | };
278 |
279 | // Calculate proportions for each status
280 | const totalRemaining = Object.entries(statusBreakdown)
281 | .filter(
282 | ([status]) =>
283 | !['deferred', 'cancelled', 'done', 'completed'].includes(status)
284 | )
285 | .reduce((sum, [_, val]) => sum + val, 0);
286 |
287 | // If no remaining tasks with tracked statuses, just use gray
288 | if (totalRemaining <= 0) {
289 | remainingSection = chalk.gray('░'.repeat(empty));
290 | } else {
291 | // Track how many characters we've added
292 | let addedChars = 0;
293 |
294 | // Add each status section proportionally
295 | for (const [status, percentage] of Object.entries(statusBreakdown)) {
296 | // Skip statuses that are considered complete
297 | if (['deferred', 'cancelled', 'done', 'completed'].includes(status))
298 | continue;
299 |
300 | // Calculate how many characters this status should fill
301 | const statusChars = Math.round((percentage / totalRemaining) * empty);
302 |
303 | // Make sure we don't exceed the total length due to rounding
304 | const actualChars = Math.min(statusChars, empty - addedChars);
305 |
306 | // Add colored section for this status
307 | const colorFn = statusColors[status] || chalk.gray;
308 | remainingSection += colorFn('░'.repeat(actualChars));
309 |
310 | addedChars += actualChars;
311 | }
312 |
313 | // If we have any remaining space due to rounding, fill with gray
314 | if (addedChars < empty) {
315 | remainingSection += chalk.gray('░'.repeat(empty - addedChars));
316 | }
317 | }
318 | } else {
319 | // Default to gray for the empty section if no breakdown provided
320 | remainingSection = chalk.gray('░'.repeat(empty));
321 | }
322 |
323 | // Effective percentage text color should reflect the highest category
324 | const percentTextColor =
325 | percent === 100
326 | ? chalk.hex('#006400') // Dark green for 100%
327 | : effectivePercent === 100
328 | ? chalk.gray // Gray for 100% with deferred/cancelled
329 | : completedColor; // Otherwise match the completed color
330 |
331 | // Build the complete progress bar
332 | return `${completedSection}${deferredCancelledSection}${remainingSection} ${percentTextColor(`${effectivePercent.toFixed(0)}%`)}`;
333 | }
334 |
335 | /**
336 | * Get a colored status string based on the status value
337 | * @param {string} status - Task status (e.g., "done", "pending", "in-progress")
338 | * @param {boolean} forTable - Whether the status is being displayed in a table
339 | * @returns {string} Colored status string
340 | */
341 | function getStatusWithColor(status, forTable = false) {
342 | if (!status) {
343 | return chalk.gray('❓ unknown');
344 | }
345 |
346 | const statusConfig = {
347 | done: { color: chalk.green, icon: '✓', tableIcon: '✓' },
348 | completed: { color: chalk.green, icon: '✓', tableIcon: '✓' },
349 | pending: { color: chalk.yellow, icon: '○', tableIcon: '⏱' },
350 | 'in-progress': { color: chalk.hex('#FFA500'), icon: '🔄', tableIcon: '►' },
351 | deferred: { color: chalk.gray, icon: 'x', tableIcon: '⏱' },
352 | blocked: { color: chalk.red, icon: '!', tableIcon: '✗' },
353 | review: { color: chalk.magenta, icon: '?', tableIcon: '?' },
354 | cancelled: { color: chalk.gray, icon: '❌', tableIcon: 'x' }
355 | };
356 |
357 | const config = statusConfig[status.toLowerCase()] || {
358 | color: chalk.red,
359 | icon: '❌',
360 | tableIcon: '✗'
361 | };
362 |
363 | // Use simpler icons for table display to prevent border issues
364 | if (forTable) {
365 | // Use ASCII characters instead of Unicode for completely stable display
366 | const simpleIcons = {
367 | done: '✓',
368 | completed: '✓',
369 | pending: '○',
370 | 'in-progress': '►',
371 | deferred: 'x',
372 | blocked: '!', // Using plain x character for better compatibility
373 | review: '?' // Using circled dot symbol
374 | };
375 | const simpleIcon = simpleIcons[status.toLowerCase()] || 'x';
376 | return config.color(`${simpleIcon} ${status}`);
377 | }
378 |
379 | return config.color(`${config.icon} ${status}`);
380 | }
381 |
382 | /**
383 | * Format dependencies list with status indicators
384 | * @param {Array} dependencies - Array of dependency IDs
385 | * @param {Array} allTasks - Array of all tasks
386 | * @param {boolean} forConsole - Whether the output is for console display
387 | * @param {Object|null} complexityReport - Optional pre-loaded complexity report
388 | * @returns {string} Formatted dependencies string
389 | */
390 | function formatDependenciesWithStatus(
391 | dependencies,
392 | allTasks,
393 | forConsole = false,
394 | complexityReport = null // Add complexityReport parameter
395 | ) {
396 | if (
397 | !dependencies ||
398 | !Array.isArray(dependencies) ||
399 | dependencies.length === 0
400 | ) {
401 | return forConsole ? chalk.gray('None') : 'None';
402 | }
403 |
404 | const formattedDeps = dependencies.map((depId) => {
405 | const depIdStr = depId.toString(); // Ensure string format for display
406 |
407 | // Check if it's already a fully qualified subtask ID (like "22.1")
408 | if (depIdStr.includes('.')) {
409 | const parts = depIdStr.split('.');
410 | // Validate that it's a proper subtask format (parentId.subtaskId)
411 | if (parts.length !== 2 || !parts[0] || !parts[1]) {
412 | // Invalid format - treat as regular dependency
413 | const numericDepId =
414 | typeof depId === 'string' ? parseInt(depId, 10) : depId;
415 | const depTaskResult = findTaskById(
416 | allTasks,
417 | numericDepId,
418 | complexityReport
419 | );
420 | const depTask = depTaskResult.task;
421 |
422 | if (!depTask) {
423 | return forConsole
424 | ? chalk.red(`${depIdStr} (Not found)`)
425 | : `${depIdStr} (Not found)`;
426 | }
427 |
428 | const status = depTask.status || 'pending';
429 | const isDone =
430 | status.toLowerCase() === 'done' ||
431 | status.toLowerCase() === 'completed';
432 | const isInProgress = status.toLowerCase() === 'in-progress';
433 |
434 | if (forConsole) {
435 | if (isDone) {
436 | return chalk.green.bold(depIdStr);
437 | } else if (isInProgress) {
438 | return chalk.yellow.bold(depIdStr);
439 | } else {
440 | return chalk.red.bold(depIdStr);
441 | }
442 | }
443 | return depIdStr;
444 | }
445 |
446 | const [parentId, subtaskId] = parts.map((id) => parseInt(id, 10));
447 |
448 | // Find the parent task
449 | const parentTask = allTasks.find((t) => t.id === parentId);
450 | if (!parentTask || !parentTask.subtasks) {
451 | return forConsole
452 | ? chalk.red(`${depIdStr} (Not found)`)
453 | : `${depIdStr} (Not found)`;
454 | }
455 |
456 | // Find the subtask
457 | const subtask = parentTask.subtasks.find((st) => st.id === subtaskId);
458 | if (!subtask) {
459 | return forConsole
460 | ? chalk.red(`${depIdStr} (Not found)`)
461 | : `${depIdStr} (Not found)`;
462 | }
463 |
464 | // Format with status
465 | const status = subtask.status || 'pending';
466 | const isDone =
467 | status.toLowerCase() === 'done' || status.toLowerCase() === 'completed';
468 | const isInProgress = status.toLowerCase() === 'in-progress';
469 |
470 | if (forConsole) {
471 | if (isDone) {
472 | return chalk.green.bold(depIdStr);
473 | } else if (isInProgress) {
474 | return chalk.hex('#FFA500').bold(depIdStr);
475 | } else {
476 | return chalk.red.bold(depIdStr);
477 | }
478 | }
479 |
480 | // For plain text output (task files), return just the ID without any formatting or emoji
481 | return depIdStr;
482 | }
483 |
484 | // If depId is a number less than 100, it's likely a reference to a subtask ID in the current task
485 | // This case is typically handled elsewhere (in task-specific code) before calling this function
486 |
487 | // For regular task dependencies (not subtasks)
488 | // Convert string depId to number if needed
489 | const numericDepId =
490 | typeof depId === 'string' ? parseInt(depId, 10) : depId;
491 |
492 | // Look up the task using the numeric ID
493 | const depTaskResult = findTaskById(
494 | allTasks,
495 | numericDepId,
496 | complexityReport
497 | );
498 | const depTask = depTaskResult.task; // Access the task object from the result
499 |
500 | if (!depTask) {
501 | return forConsole
502 | ? chalk.red(`${depIdStr} (Not found)`)
503 | : `${depIdStr} (Not found)`;
504 | }
505 |
506 | // Format with status
507 | const status = depTask.status || 'pending';
508 | const isDone =
509 | status.toLowerCase() === 'done' || status.toLowerCase() === 'completed';
510 | const isInProgress = status.toLowerCase() === 'in-progress';
511 |
512 | if (forConsole) {
513 | if (isDone) {
514 | return chalk.green.bold(depIdStr);
515 | } else if (isInProgress) {
516 | return chalk.yellow.bold(depIdStr);
517 | } else {
518 | return chalk.red.bold(depIdStr);
519 | }
520 | }
521 |
522 | // For plain text output (task files), return just the ID without any formatting or emoji
523 | return depIdStr;
524 | });
525 |
526 | return formattedDeps.join(', ');
527 | }
528 |
529 | /**
530 | * Display a comprehensive help guide
531 | */
532 | function displayHelp() {
533 | // Get terminal width - moved to top of function to make it available throughout
534 | const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect
535 |
536 | console.log(
537 | boxen(chalk.white.bold('Task Master CLI'), {
538 | padding: 1,
539 | borderColor: 'blue',
540 | borderStyle: 'round',
541 | margin: { top: 1, bottom: 1 }
542 | })
543 | );
544 |
545 | // Command categories
546 | const commandCategories = [
547 | {
548 | title: 'Project Setup & Configuration',
549 | color: 'blue',
550 | commands: [
551 | {
552 | name: 'init',
553 | args: '[--name=<name>] [--description=<desc>] [-y]',
554 | desc: 'Initialize a new project with Task Master structure'
555 | },
556 | {
557 | name: 'models',
558 | args: '',
559 | desc: 'View current AI model configuration and available models'
560 | },
561 | {
562 | name: 'models --setup',
563 | args: '',
564 | desc: 'Run interactive setup to configure AI models'
565 | },
566 | {
567 | name: 'models --set-main',
568 | args: '<model_id>',
569 | desc: 'Set the primary model for task generation'
570 | },
571 | {
572 | name: 'models --set-research',
573 | args: '<model_id>',
574 | desc: 'Set the model for research operations'
575 | },
576 | {
577 | name: 'models --set-fallback',
578 | args: '<model_id>',
579 | desc: 'Set the fallback model (optional)'
580 | }
581 | ]
582 | },
583 | {
584 | title: 'Task Generation',
585 | color: 'cyan',
586 | commands: [
587 | {
588 | name: 'parse-prd',
589 | args: '--input=<file.txt> [--num-tasks=10]',
590 | desc: 'Generate tasks from a PRD document'
591 | },
592 | {
593 | name: 'generate',
594 | args: '',
595 | desc: 'Create individual task files from tasks.json'
596 | }
597 | ]
598 | },
599 | {
600 | title: 'Task Management',
601 | color: 'green',
602 | commands: [
603 | {
604 | name: 'list',
605 | args: '[--status=<status>] [--with-subtasks]',
606 | desc: 'List all tasks with their status'
607 | },
608 | {
609 | name: 'set-status',
610 | args: '--id=<id> --status=<status>',
611 | desc: `Update task status (${TASK_STATUS_OPTIONS.join(', ')})`
612 | },
613 | {
614 | name: 'sync-readme',
615 | args: '[--with-subtasks] [--status=<status>]',
616 | desc: 'Export tasks to README.md with professional formatting'
617 | },
618 | {
619 | name: 'update',
620 | args: '--from=<id> --prompt="<context>"',
621 | desc: 'Update multiple tasks based on new requirements'
622 | },
623 | {
624 | name: 'update-task',
625 | args: '--id=<id> --prompt="<context>"',
626 | desc: 'Update a single specific task with new information'
627 | },
628 | {
629 | name: 'update-subtask',
630 | args: '--id=<parentId.subtaskId> --prompt="<context>"',
631 | desc: 'Append additional information to a subtask'
632 | },
633 | {
634 | name: 'add-task',
635 | args: '--prompt="<text>" [--dependencies=<ids>] [--priority=<priority>]',
636 | desc: 'Add a new task using AI'
637 | },
638 | {
639 | name: 'remove-task',
640 | args: '--id=<id> [-y]',
641 | desc: 'Permanently remove a task or subtask'
642 | }
643 | ]
644 | },
645 | {
646 | title: 'Subtask Management',
647 | color: 'yellow',
648 | commands: [
649 | {
650 | name: 'add-subtask',
651 | args: '--parent=<id> --title="<title>" [--description="<desc>"]',
652 | desc: 'Add a new subtask to a parent task'
653 | },
654 | {
655 | name: 'add-subtask',
656 | args: '--parent=<id> --task-id=<id>',
657 | desc: 'Convert an existing task into a subtask'
658 | },
659 | {
660 | name: 'remove-subtask',
661 | args: '--id=<parentId.subtaskId> [--convert]',
662 | desc: 'Remove a subtask (optionally convert to standalone task)'
663 | },
664 | {
665 | name: 'clear-subtasks',
666 | args: '--id=<id>',
667 | desc: 'Remove all subtasks from specified tasks'
668 | },
669 | {
670 | name: 'clear-subtasks --all',
671 | args: '',
672 | desc: 'Remove subtasks from all tasks'
673 | }
674 | ]
675 | },
676 | {
677 | title: 'Task Analysis & Breakdown',
678 | color: 'magenta',
679 | commands: [
680 | {
681 | name: 'analyze-complexity',
682 | args: '[--research] [--threshold=5]',
683 | desc: 'Analyze tasks and generate expansion recommendations'
684 | },
685 | {
686 | name: 'complexity-report',
687 | args: '[--file=<path>]',
688 | desc: 'Display the complexity analysis report'
689 | },
690 | {
691 | name: 'expand',
692 | args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]',
693 | desc: 'Break down tasks into detailed subtasks'
694 | },
695 | {
696 | name: 'expand --all',
697 | args: '[--force] [--research]',
698 | desc: 'Expand all pending tasks with subtasks'
699 | },
700 | {
701 | name: 'research',
702 | args: '"<prompt>" [-i=<task_ids>] [-f=<file_paths>] [-c="<context>"] [--tree] [-s=<save_file>] [-d=<detail_level>]',
703 | desc: 'Perform AI-powered research queries with project context'
704 | }
705 | ]
706 | },
707 | {
708 | title: 'Task Navigation & Viewing',
709 | color: 'cyan',
710 | commands: [
711 | {
712 | name: 'next',
713 | args: '',
714 | desc: 'Show the next task to work on based on dependencies'
715 | },
716 | {
717 | name: 'show',
718 | args: '<id>',
719 | desc: 'Display detailed information about a specific task'
720 | }
721 | ]
722 | },
723 | {
724 | title: 'Tag Management',
725 | color: 'magenta',
726 | commands: [
727 | {
728 | name: 'tags',
729 | args: '[--show-metadata]',
730 | desc: 'List all available tags with task counts'
731 | },
732 | {
733 | name: 'add-tag',
734 | args: '<tagName> [--copy-from-current] [--copy-from=<tag>] [-d="<desc>"]',
735 | desc: 'Create a new tag context for organizing tasks'
736 | },
737 | {
738 | name: 'use-tag',
739 | args: '<tagName>',
740 | desc: 'Switch to a different tag context'
741 | },
742 | {
743 | name: 'delete-tag',
744 | args: '<tagName> [--yes]',
745 | desc: 'Delete an existing tag and all its tasks'
746 | },
747 | {
748 | name: 'rename-tag',
749 | args: '<oldName> <newName>',
750 | desc: 'Rename an existing tag'
751 | },
752 | {
753 | name: 'copy-tag',
754 | args: '<sourceName> <targetName> [-d="<desc>"]',
755 | desc: 'Copy an existing tag to create a new tag with the same tasks'
756 | }
757 | ]
758 | },
759 | {
760 | title: 'Dependency Management',
761 | color: 'blue',
762 | commands: [
763 | {
764 | name: 'add-dependency',
765 | args: '--id=<id> --depends-on=<id>',
766 | desc: 'Add a dependency to a task'
767 | },
768 | {
769 | name: 'remove-dependency',
770 | args: '--id=<id> --depends-on=<id>',
771 | desc: 'Remove a dependency from a task'
772 | },
773 | {
774 | name: 'validate-dependencies',
775 | args: '',
776 | desc: 'Identify invalid dependencies without fixing them'
777 | },
778 | {
779 | name: 'fix-dependencies',
780 | args: '',
781 | desc: 'Fix invalid dependencies automatically'
782 | }
783 | ]
784 | }
785 | ];
786 |
787 | // Display each category
788 | commandCategories.forEach((category) => {
789 | console.log(
790 | boxen(chalk[category.color].bold(category.title), {
791 | padding: { left: 2, right: 2, top: 0, bottom: 0 },
792 | margin: { top: 1, bottom: 0 },
793 | borderColor: category.color,
794 | borderStyle: 'round'
795 | })
796 | );
797 |
798 | // Calculate dynamic column widths - adjust ratios as needed
799 | const nameWidth = Math.max(25, Math.floor(terminalWidth * 0.2)); // 20% of width but min 25
800 | const argsWidth = Math.max(40, Math.floor(terminalWidth * 0.35)); // 35% of width but min 40
801 | const descWidth = Math.max(45, Math.floor(terminalWidth * 0.45) - 10); // 45% of width but min 45, minus some buffer
802 |
803 | const commandTable = new Table({
804 | colWidths: [nameWidth, argsWidth, descWidth],
805 | chars: {
806 | top: '',
807 | 'top-mid': '',
808 | 'top-left': '',
809 | 'top-right': '',
810 | bottom: '',
811 | 'bottom-mid': '',
812 | 'bottom-left': '',
813 | 'bottom-right': '',
814 | left: '',
815 | 'left-mid': '',
816 | mid: '',
817 | 'mid-mid': '',
818 | right: '',
819 | 'right-mid': '',
820 | middle: ' '
821 | },
822 | style: { border: [], 'padding-left': 4 },
823 | wordWrap: true
824 | });
825 |
826 | category.commands.forEach((cmd, index) => {
827 | commandTable.push([
828 | `${chalk.yellow.bold(cmd.name)}${chalk.reset('')}`,
829 | `${chalk.white(cmd.args)}${chalk.reset('')}`,
830 | `${chalk.dim(cmd.desc)}${chalk.reset('')}`
831 | ]);
832 | });
833 |
834 | console.log(commandTable.toString());
835 | console.log('');
836 | });
837 |
838 | // Display configuration section
839 | console.log(
840 | boxen(chalk.cyan.bold('Configuration'), {
841 | padding: { left: 2, right: 2, top: 0, bottom: 0 },
842 | margin: { top: 1, bottom: 0 },
843 | borderColor: 'cyan',
844 | borderStyle: 'round'
845 | })
846 | );
847 |
848 | // Get terminal width if not already defined
849 | const configTerminalWidth = terminalWidth || process.stdout.columns || 100;
850 |
851 | // Calculate dynamic column widths for config table
852 | const configKeyWidth = Math.max(30, Math.floor(configTerminalWidth * 0.25));
853 | const configDescWidth = Math.max(50, Math.floor(configTerminalWidth * 0.45));
854 | const configValueWidth = Math.max(
855 | 30,
856 | Math.floor(configTerminalWidth * 0.3) - 10
857 | );
858 |
859 | const configTable = new Table({
860 | colWidths: [configKeyWidth, configDescWidth, configValueWidth],
861 | chars: {
862 | top: '',
863 | 'top-mid': '',
864 | 'top-left': '',
865 | 'top-right': '',
866 | bottom: '',
867 | 'bottom-mid': '',
868 | 'bottom-left': '',
869 | 'bottom-right': '',
870 | left: '',
871 | 'left-mid': '',
872 | mid: '',
873 | 'mid-mid': '',
874 | right: '',
875 | 'right-mid': '',
876 | middle: ' '
877 | },
878 | style: { border: [], 'padding-left': 4 },
879 | wordWrap: true
880 | });
881 |
882 | configTable.push(
883 | [
884 | `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset('')}`,
885 | `${chalk.white('AI model configuration file (project root)')}${chalk.reset('')}`,
886 | `${chalk.dim('Managed by models cmd')}${chalk.reset('')}`
887 | ],
888 | [
889 | `${chalk.yellow('API Keys (.env)')}${chalk.reset('')}`,
890 | `${chalk.white('API keys for AI providers (ANTHROPIC_API_KEY, etc.)')}${chalk.reset('')}`,
891 | `${chalk.dim('Required in .env file')}${chalk.reset('')}`
892 | ],
893 | [
894 | `${chalk.yellow('MCP Keys (mcp.json)')}${chalk.reset('')}`,
895 | `${chalk.white('API keys for Cursor integration')}${chalk.reset('')}`,
896 | `${chalk.dim('Required in .cursor/')}${chalk.reset('')}`
897 | ]
898 | );
899 |
900 | console.log(configTable.toString());
901 | console.log('');
902 |
903 | // Show helpful hints
904 | console.log(
905 | boxen(
906 | chalk.white.bold('Quick Start:') +
907 | '\n\n' +
908 | chalk.cyan('1. Create Project: ') +
909 | chalk.white('task-master init') +
910 | '\n' +
911 | chalk.cyan('2. Setup Models: ') +
912 | chalk.white('task-master models --setup') +
913 | '\n' +
914 | chalk.cyan('3. Parse PRD: ') +
915 | chalk.white('task-master parse-prd --input=<prd-file>') +
916 | '\n' +
917 | chalk.cyan('4. List Tasks: ') +
918 | chalk.white('task-master list') +
919 | '\n' +
920 | chalk.cyan('5. Find Next Task: ') +
921 | chalk.white('task-master next'),
922 | {
923 | padding: 1,
924 | borderColor: 'yellow',
925 | borderStyle: 'round',
926 | margin: { top: 1 },
927 | width: Math.min(configTerminalWidth - 10, 100) // Limit width to terminal width minus padding, max 100
928 | }
929 | )
930 | );
931 | }
932 |
933 | /**
934 | * Get colored complexity score
935 | * @param {number} score - Complexity score (1-10)
936 | * @returns {string} Colored complexity score
937 | */
938 | function getComplexityWithColor(score) {
939 | if (score <= 3) return chalk.green(`● ${score}`);
940 | if (score <= 6) return chalk.yellow(`● ${score}`);
941 | return chalk.red(`● ${score}`);
942 | }
943 |
944 | /**
945 | * Truncate a string to a maximum length and add ellipsis if needed
946 | * @param {string} str - The string to truncate
947 | * @param {number} maxLength - Maximum length
948 | * @returns {string} Truncated string
949 | */
950 | function truncateString(str, maxLength) {
951 | if (!str) return '';
952 | if (str.length <= maxLength) return str;
953 | return str.substring(0, maxLength - 3) + '...';
954 | }
955 |
956 | /**
957 | * Display the next task to work on
958 | * @param {string} tasksPath - Path to the tasks.json file
959 | * @param {string} complexityReportPath - Path to the complexity report file
960 | * @param {string} tag - Optional tag to override current tag resolution
961 | */
962 | async function displayNextTask(
963 | tasksPath,
964 | complexityReportPath = null,
965 | context = {}
966 | ) {
967 | // Extract parameters from context
968 | const { projectRoot, tag } = context;
969 |
970 | // Read the tasks file with proper projectRoot for tag resolution
971 | const data = readJSON(tasksPath, projectRoot, tag);
972 | if (!data || !data.tasks) {
973 | log('error', 'No valid tasks found.');
974 | process.exit(1);
975 | }
976 |
977 | // Read complexity report once
978 | const complexityReport = readComplexityReport(complexityReportPath);
979 |
980 | // Find the next task
981 | const nextTask = findNextTask(data.tasks, complexityReport);
982 |
983 | if (!nextTask) {
984 | console.log(
985 | boxen(
986 | chalk.yellow('No eligible tasks found!\n\n') +
987 | 'All pending tasks have unsatisfied dependencies, or all tasks are completed.',
988 | {
989 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
990 | borderColor: 'yellow',
991 | borderStyle: 'round',
992 | margin: { top: 1 }
993 | }
994 | )
995 | );
996 | return;
997 | }
998 |
999 | // Display the task in a nice format
1000 | console.log(
1001 | boxen(chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`), {
1002 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1003 | borderColor: 'blue',
1004 | borderStyle: 'round',
1005 | margin: { top: 1, bottom: 0 }
1006 | })
1007 | );
1008 |
1009 | // Create a table with task details
1010 | const taskTable = new Table({
1011 | style: {
1012 | head: [],
1013 | border: [],
1014 | 'padding-top': 0,
1015 | 'padding-bottom': 0,
1016 | compact: true
1017 | },
1018 | chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
1019 | colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)],
1020 | wordWrap: true
1021 | });
1022 |
1023 | // Priority with color
1024 | const priorityColors = {
1025 | high: chalk.red.bold,
1026 | medium: chalk.yellow,
1027 | low: chalk.gray
1028 | };
1029 | const priorityColor =
1030 | priorityColors[nextTask.priority || 'medium'] || chalk.white;
1031 |
1032 | // Add task details to table
1033 | taskTable.push(
1034 | [chalk.cyan.bold('ID:'), nextTask.id.toString()],
1035 | [chalk.cyan.bold('Title:'), nextTask.title],
1036 | [
1037 | chalk.cyan.bold('Priority:'),
1038 | priorityColor(nextTask.priority || 'medium')
1039 | ],
1040 | [
1041 | chalk.cyan.bold('Dependencies:'),
1042 | formatDependenciesWithStatus(
1043 | nextTask.dependencies,
1044 | data.tasks,
1045 | true,
1046 | complexityReport
1047 | )
1048 | ],
1049 | [
1050 | chalk.cyan.bold('Complexity:'),
1051 | nextTask.complexityScore
1052 | ? getComplexityWithColor(nextTask.complexityScore)
1053 | : chalk.gray('N/A')
1054 | ],
1055 | [chalk.cyan.bold('Description:'), nextTask.description]
1056 | );
1057 |
1058 | console.log(taskTable.toString());
1059 |
1060 | // If task has details, show them in a separate box
1061 | if (nextTask.details && nextTask.details.trim().length > 0) {
1062 | console.log(
1063 | boxen(
1064 | chalk.white.bold('Implementation Details:') + '\n\n' + nextTask.details,
1065 | {
1066 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1067 | borderColor: 'cyan',
1068 | borderStyle: 'round',
1069 | margin: { top: 1, bottom: 0 }
1070 | }
1071 | )
1072 | );
1073 | }
1074 |
1075 | // Determine if the nextTask is a subtask
1076 | const isSubtask = !!nextTask.parentId;
1077 |
1078 | // Show subtasks if they exist (only for parent tasks)
1079 | if (!isSubtask && nextTask.subtasks && nextTask.subtasks.length > 0) {
1080 | console.log(
1081 | boxen(chalk.white.bold('Subtasks'), {
1082 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1083 | margin: { top: 1, bottom: 0 },
1084 | borderColor: 'magenta',
1085 | borderStyle: 'round'
1086 | })
1087 | );
1088 |
1089 | // Calculate available width for the subtask table
1090 | const availableWidth = process.stdout.columns - 10 || 100; // Default to 100 if can't detect
1091 |
1092 | // Define percentage-based column widths
1093 | const idWidthPct = 8;
1094 | const statusWidthPct = 15;
1095 | const depsWidthPct = 25;
1096 | const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct;
1097 |
1098 | // Calculate actual column widths
1099 | const idWidth = Math.floor(availableWidth * (idWidthPct / 100));
1100 | const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100));
1101 | const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100));
1102 | const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100));
1103 |
1104 | // Create a table for subtasks with improved handling
1105 | const subtaskTable = new Table({
1106 | head: [
1107 | chalk.magenta.bold('ID'),
1108 | chalk.magenta.bold('Status'),
1109 | chalk.magenta.bold('Title'),
1110 | chalk.magenta.bold('Deps')
1111 | ],
1112 | colWidths: [idWidth, statusWidth, titleWidth, depsWidth],
1113 | style: {
1114 | head: [],
1115 | border: [],
1116 | 'padding-top': 0,
1117 | 'padding-bottom': 0,
1118 | compact: true
1119 | },
1120 | chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
1121 | wordWrap: true
1122 | });
1123 |
1124 | // Add subtasks to table
1125 | nextTask.subtasks.forEach((st) => {
1126 | const statusColor =
1127 | {
1128 | done: chalk.green,
1129 | completed: chalk.green,
1130 | pending: chalk.yellow,
1131 | 'in-progress': chalk.blue
1132 | }[st.status || 'pending'] || chalk.white;
1133 |
1134 | // Format subtask dependencies
1135 | let subtaskDeps = 'None';
1136 | if (st.dependencies && st.dependencies.length > 0) {
1137 | // Format dependencies with correct notation
1138 | const formattedDeps = st.dependencies.map((depId) => {
1139 | if (typeof depId === 'number' && depId < 100) {
1140 | const foundSubtask = nextTask.subtasks.find(
1141 | (st) => st.id === depId
1142 | );
1143 | if (foundSubtask) {
1144 | const isDone =
1145 | foundSubtask.status === 'done' ||
1146 | foundSubtask.status === 'completed';
1147 | const isInProgress = foundSubtask.status === 'in-progress';
1148 |
1149 | // Use consistent color formatting instead of emojis
1150 | if (isDone) {
1151 | return chalk.green.bold(`${nextTask.id}.${depId}`);
1152 | } else if (isInProgress) {
1153 | return chalk.hex('#FFA500').bold(`${nextTask.id}.${depId}`);
1154 | } else {
1155 | return chalk.red.bold(`${nextTask.id}.${depId}`);
1156 | }
1157 | }
1158 | return chalk.red(`${nextTask.id}.${depId} (Not found)`);
1159 | }
1160 | return depId;
1161 | });
1162 |
1163 | // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again
1164 | subtaskDeps =
1165 | formattedDeps.length === 1
1166 | ? formattedDeps[0]
1167 | : formattedDeps.join(chalk.white(', '));
1168 | }
1169 |
1170 | subtaskTable.push([
1171 | `${nextTask.id}.${st.id}`,
1172 | statusColor(st.status || 'pending'),
1173 | st.title,
1174 | subtaskDeps
1175 | ]);
1176 | });
1177 |
1178 | console.log(subtaskTable.toString());
1179 | }
1180 |
1181 | // Suggest expanding if no subtasks (only for parent tasks without subtasks)
1182 | if (!isSubtask && (!nextTask.subtasks || nextTask.subtasks.length === 0)) {
1183 | console.log(
1184 | boxen(
1185 | chalk.yellow('No subtasks found. Consider breaking down this task:') +
1186 | '\n' +
1187 | chalk.white(
1188 | `Run: ${chalk.cyan(`task-master expand --id=${nextTask.id}`)}`
1189 | ),
1190 | {
1191 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1192 | borderColor: 'yellow',
1193 | borderStyle: 'round',
1194 | margin: { top: 1, bottom: 0 }
1195 | }
1196 | )
1197 | );
1198 | }
1199 |
1200 | // Show action suggestions
1201 | let suggestedActionsContent = chalk.white.bold('Suggested Actions:') + '\n';
1202 | if (isSubtask) {
1203 | // Suggested actions for a subtask
1204 | suggestedActionsContent +=
1205 | `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` +
1206 | `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` +
1207 | `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${nextTask.parentId}`)}`;
1208 | } else {
1209 | // Suggested actions for a parent task
1210 | suggestedActionsContent +=
1211 | `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` +
1212 | `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` +
1213 | (nextTask.subtasks && nextTask.subtasks.length > 0
1214 | ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${nextTask.id}.1 --status=done`)}` // Example: first subtask
1215 | : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${nextTask.id}`)}`);
1216 | }
1217 |
1218 | console.log(
1219 | boxen(suggestedActionsContent, {
1220 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1221 | borderColor: 'green',
1222 | borderStyle: 'round',
1223 | margin: { top: 1 }
1224 | })
1225 | );
1226 |
1227 | // Show FYI notice if migration occurred
1228 | displayTaggedTasksFYI(data);
1229 | }
1230 |
1231 | /**
1232 | * Display a specific task by ID
1233 | * @param {string} tasksPath - Path to the tasks.json file
1234 | * @param {string|number} taskId - The ID of the task to display
1235 | * @param {string} complexityReportPath - Path to the complexity report file
1236 | * @param {string} [statusFilter] - Optional status to filter subtasks by
1237 | * @param {object} context - Context object containing projectRoot and tag
1238 | * @param {string} context.projectRoot - Project root path
1239 | * @param {string} context.tag - Tag for the task
1240 | */
1241 | async function displayTaskById(
1242 | tasksPath,
1243 | taskId,
1244 | complexityReportPath = null,
1245 | statusFilter = null,
1246 | context = {}
1247 | ) {
1248 | const { projectRoot, tag } = context;
1249 |
1250 | // Read the tasks file with proper projectRoot for tag resolution
1251 | const data = readJSON(tasksPath, projectRoot, tag);
1252 | if (!data || !data.tasks) {
1253 | log('error', 'No valid tasks found.');
1254 | process.exit(1);
1255 | }
1256 |
1257 | // Read complexity report once
1258 | const complexityReport = readComplexityReport(complexityReportPath);
1259 |
1260 | // Find the task by ID, applying the status filter if provided
1261 | // Returns { task, originalSubtaskCount, originalSubtasks }
1262 | const { task, originalSubtaskCount, originalSubtasks } = findTaskById(
1263 | data.tasks,
1264 | taskId,
1265 | complexityReport,
1266 | statusFilter
1267 | );
1268 |
1269 | if (!task) {
1270 | console.log(
1271 | boxen(chalk.yellow(`Task with ID ${taskId} not found!`), {
1272 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1273 | borderColor: 'yellow',
1274 | borderStyle: 'round',
1275 | margin: { top: 1 }
1276 | })
1277 | );
1278 | return;
1279 | }
1280 |
1281 | // Handle subtask display specially (This logic remains the same)
1282 | if (task.isSubtask || task.parentTask) {
1283 | console.log(
1284 | boxen(
1285 | chalk.white.bold(
1286 | `Subtask: #${task.parentTask.id}.${task.id} - ${task.title}`
1287 | ),
1288 | {
1289 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1290 | borderColor: 'magenta',
1291 | borderStyle: 'round',
1292 | margin: { top: 1, bottom: 0 }
1293 | }
1294 | )
1295 | );
1296 |
1297 | const subtaskTable = new Table({
1298 | style: {
1299 | head: [],
1300 | border: [],
1301 | 'padding-top': 0,
1302 | 'padding-bottom': 0,
1303 | compact: true
1304 | },
1305 | chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
1306 | colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)],
1307 | wordWrap: true
1308 | });
1309 | subtaskTable.push(
1310 | [chalk.cyan.bold('ID:'), `${task.parentTask.id}.${task.id}`],
1311 | [
1312 | chalk.cyan.bold('Parent Task:'),
1313 | `#${task.parentTask.id} - ${task.parentTask.title}`
1314 | ],
1315 | [chalk.cyan.bold('Title:'), task.title],
1316 | [
1317 | chalk.cyan.bold('Status:'),
1318 | getStatusWithColor(task.status || 'pending', true)
1319 | ],
1320 | [
1321 | chalk.cyan.bold('Complexity:'),
1322 | task.complexityScore
1323 | ? getComplexityWithColor(task.complexityScore)
1324 | : chalk.gray('N/A')
1325 | ],
1326 | [
1327 | chalk.cyan.bold('Description:'),
1328 | task.description || 'No description provided.'
1329 | ]
1330 | );
1331 | console.log(subtaskTable.toString());
1332 |
1333 | if (task.details && task.details.trim().length > 0) {
1334 | console.log(
1335 | boxen(
1336 | chalk.white.bold('Implementation Details:') + '\n\n' + task.details,
1337 | {
1338 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1339 | borderColor: 'cyan',
1340 | borderStyle: 'round',
1341 | margin: { top: 1, bottom: 0 }
1342 | }
1343 | )
1344 | );
1345 | }
1346 |
1347 | console.log(
1348 | boxen(
1349 | chalk.white.bold('Suggested Actions:') +
1350 | '\n' +
1351 | `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=in-progress`)}\n` +
1352 | `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=done`)}\n` +
1353 | `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${task.parentTask.id}`)}`,
1354 | {
1355 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1356 | borderColor: 'green',
1357 | borderStyle: 'round',
1358 | margin: { top: 1 }
1359 | }
1360 | )
1361 | );
1362 | return; // Exit after displaying subtask details
1363 | }
1364 |
1365 | // --- Display Regular Task Details ---
1366 | console.log(
1367 | boxen(chalk.white.bold(`Task: #${task.id} - ${task.title}`), {
1368 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1369 | borderColor: 'blue',
1370 | borderStyle: 'round',
1371 | margin: { top: 1, bottom: 0 }
1372 | })
1373 | );
1374 |
1375 | const taskTable = new Table({
1376 | style: {
1377 | head: [],
1378 | border: [],
1379 | 'padding-top': 0,
1380 | 'padding-bottom': 0,
1381 | compact: true
1382 | },
1383 | chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
1384 | colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)],
1385 | wordWrap: true
1386 | });
1387 | const priorityColors = {
1388 | high: chalk.red.bold,
1389 | medium: chalk.yellow,
1390 | low: chalk.gray
1391 | };
1392 | const priorityColor =
1393 | priorityColors[task.priority || 'medium'] || chalk.white;
1394 | taskTable.push(
1395 | [chalk.cyan.bold('ID:'), task.id.toString()],
1396 | [chalk.cyan.bold('Title:'), task.title],
1397 | [
1398 | chalk.cyan.bold('Status:'),
1399 | getStatusWithColor(task.status || 'pending', true)
1400 | ],
1401 | [chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')],
1402 | [
1403 | chalk.cyan.bold('Dependencies:'),
1404 | formatDependenciesWithStatus(
1405 | task.dependencies,
1406 | data.tasks,
1407 | true,
1408 | complexityReport
1409 | )
1410 | ],
1411 | [
1412 | chalk.cyan.bold('Complexity:'),
1413 | task.complexityScore
1414 | ? getComplexityWithColor(task.complexityScore)
1415 | : chalk.gray('N/A')
1416 | ],
1417 | [chalk.cyan.bold('Description:'), task.description]
1418 | );
1419 | console.log(taskTable.toString());
1420 |
1421 | if (task.details && task.details.trim().length > 0) {
1422 | console.log(
1423 | boxen(
1424 | chalk.white.bold('Implementation Details:') + '\n\n' + task.details,
1425 | {
1426 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1427 | borderColor: 'cyan',
1428 | borderStyle: 'round',
1429 | margin: { top: 1, bottom: 0 }
1430 | }
1431 | )
1432 | );
1433 | }
1434 | if (task.testStrategy && task.testStrategy.trim().length > 0) {
1435 | console.log(
1436 | boxen(chalk.white.bold('Test Strategy:') + '\n\n' + task.testStrategy, {
1437 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1438 | borderColor: 'cyan',
1439 | borderStyle: 'round',
1440 | margin: { top: 1, bottom: 0 }
1441 | })
1442 | );
1443 | }
1444 |
1445 | // --- Subtask Table Display (uses filtered list: task.subtasks) ---
1446 | if (task.subtasks && task.subtasks.length > 0) {
1447 | console.log(
1448 | boxen(chalk.white.bold('Subtasks'), {
1449 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1450 | margin: { top: 1, bottom: 0 },
1451 | borderColor: 'magenta',
1452 | borderStyle: 'round'
1453 | })
1454 | );
1455 |
1456 | const availableWidth = process.stdout.columns - 10 || 100;
1457 | const idWidthPct = 10;
1458 | const statusWidthPct = 15;
1459 | const depsWidthPct = 25;
1460 | const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct;
1461 | const idWidth = Math.floor(availableWidth * (idWidthPct / 100));
1462 | const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100));
1463 | const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100));
1464 | const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100));
1465 |
1466 | const subtaskTable = new Table({
1467 | head: [
1468 | chalk.magenta.bold('ID'),
1469 | chalk.magenta.bold('Status'),
1470 | chalk.magenta.bold('Title'),
1471 | chalk.magenta.bold('Deps')
1472 | ],
1473 | colWidths: [idWidth, statusWidth, titleWidth, depsWidth],
1474 | style: {
1475 | head: [],
1476 | border: [],
1477 | 'padding-top': 0,
1478 | 'padding-bottom': 0,
1479 | compact: true
1480 | },
1481 | chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
1482 | wordWrap: true
1483 | });
1484 |
1485 | // Populate table with the potentially filtered subtasks
1486 | task.subtasks.forEach((st) => {
1487 | const statusColorMap = {
1488 | done: chalk.green,
1489 | completed: chalk.green,
1490 | pending: chalk.yellow,
1491 | 'in-progress': chalk.blue
1492 | };
1493 | const statusColor = statusColorMap[st.status || 'pending'] || chalk.white;
1494 | let subtaskDeps = 'None';
1495 | if (st.dependencies && st.dependencies.length > 0) {
1496 | const formattedDeps = st.dependencies.map((depId) => {
1497 | // Use the original, unfiltered list for dependency status lookup
1498 | const sourceListForDeps = originalSubtasks || task.subtasks;
1499 | const foundDepSubtask =
1500 | typeof depId === 'number' && depId < 100
1501 | ? sourceListForDeps.find((sub) => sub.id === depId)
1502 | : null;
1503 |
1504 | if (foundDepSubtask) {
1505 | const isDone =
1506 | foundDepSubtask.status === 'done' ||
1507 | foundDepSubtask.status === 'completed';
1508 | const isInProgress = foundDepSubtask.status === 'in-progress';
1509 | const color = isDone
1510 | ? chalk.green.bold
1511 | : isInProgress
1512 | ? chalk.hex('#FFA500').bold
1513 | : chalk.red.bold;
1514 | return color(`${task.id}.${depId}`);
1515 | } else if (typeof depId === 'number' && depId < 100) {
1516 | return chalk.red(`${task.id}.${depId} (Not found)`);
1517 | }
1518 | return depId; // Assume it's a top-level task ID if not a number < 100
1519 | });
1520 | subtaskDeps =
1521 | formattedDeps.length === 1
1522 | ? formattedDeps[0]
1523 | : formattedDeps.join(chalk.white(', '));
1524 | }
1525 | subtaskTable.push([
1526 | `${task.id}.${st.id}`,
1527 | statusColor(st.status || 'pending'),
1528 | st.title,
1529 | subtaskDeps
1530 | ]);
1531 | });
1532 | console.log(subtaskTable.toString());
1533 |
1534 | // Display filter summary line *immediately after the table* if a filter was applied
1535 | if (statusFilter && originalSubtaskCount !== null) {
1536 | console.log(
1537 | chalk.cyan(
1538 | ` Filtered by status: ${chalk.bold(statusFilter)}. Showing ${chalk.bold(task.subtasks.length)} of ${chalk.bold(originalSubtaskCount)} subtasks.`
1539 | )
1540 | );
1541 | // Add a newline for spacing before the progress bar if the filter line was shown
1542 | console.log();
1543 | }
1544 | // --- Conditional Messages for No Subtasks Shown ---
1545 | } else if (statusFilter && originalSubtaskCount === 0) {
1546 | // Case where filter applied, but the parent task had 0 subtasks originally
1547 | console.log(
1548 | boxen(
1549 | chalk.yellow(
1550 | `No subtasks found matching status: ${statusFilter} (Task has no subtasks)`
1551 | ),
1552 | {
1553 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1554 | margin: { top: 1, bottom: 0 },
1555 | borderColor: 'yellow',
1556 | borderStyle: 'round'
1557 | }
1558 | )
1559 | );
1560 | } else if (
1561 | statusFilter &&
1562 | originalSubtaskCount > 0 &&
1563 | task.subtasks.length === 0
1564 | ) {
1565 | // Case where filter applied, original subtasks existed, but none matched
1566 | console.log(
1567 | boxen(
1568 | chalk.yellow(
1569 | `No subtasks found matching status: ${statusFilter} (out of ${originalSubtaskCount} total)`
1570 | ),
1571 | {
1572 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1573 | margin: { top: 1, bottom: 0 },
1574 | borderColor: 'yellow',
1575 | borderStyle: 'round'
1576 | }
1577 | )
1578 | );
1579 | } else if (
1580 | !statusFilter &&
1581 | (!originalSubtasks || originalSubtasks.length === 0)
1582 | ) {
1583 | // Case where NO filter applied AND the task genuinely has no subtasks
1584 | // Use the authoritative originalSubtasks if it exists (from filtering), else check task.subtasks
1585 | const actualSubtasks = originalSubtasks || task.subtasks;
1586 | if (!actualSubtasks || actualSubtasks.length === 0) {
1587 | console.log(
1588 | boxen(
1589 | chalk.yellow('No subtasks found. Consider breaking down this task:') +
1590 | '\n' +
1591 | chalk.white(
1592 | `Run: ${chalk.cyan(`task-master expand --id=${task.id}`)}`
1593 | ),
1594 | {
1595 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1596 | borderColor: 'yellow',
1597 | borderStyle: 'round',
1598 | margin: { top: 1, bottom: 0 }
1599 | }
1600 | )
1601 | );
1602 | }
1603 | }
1604 |
1605 | // --- Subtask Progress Bar Display (uses originalSubtasks or task.subtasks) ---
1606 | // Determine the list to use for progress calculation (always the original if available and filtering happened)
1607 | const subtasksForProgress = originalSubtasks || task.subtasks; // Use original if filtering occurred, else the potentially empty task.subtasks
1608 |
1609 | // Only show progress if there are actually subtasks
1610 | if (subtasksForProgress && subtasksForProgress.length > 0) {
1611 | const totalSubtasks = subtasksForProgress.length;
1612 | const completedSubtasks = subtasksForProgress.filter(
1613 | (st) => st.status === 'done' || st.status === 'completed'
1614 | ).length;
1615 |
1616 | // Count other statuses from the original/complete list
1617 | const inProgressSubtasks = subtasksForProgress.filter(
1618 | (st) => st.status === 'in-progress'
1619 | ).length;
1620 | const pendingSubtasks = subtasksForProgress.filter(
1621 | (st) => st.status === 'pending'
1622 | ).length;
1623 | const blockedSubtasks = subtasksForProgress.filter(
1624 | (st) => st.status === 'blocked'
1625 | ).length;
1626 | const deferredSubtasks = subtasksForProgress.filter(
1627 | (st) => st.status === 'deferred'
1628 | ).length;
1629 | const cancelledSubtasks = subtasksForProgress.filter(
1630 | (st) => st.status === 'cancelled'
1631 | ).length;
1632 |
1633 | const statusBreakdown = {
1634 | // Calculate breakdown based on the complete list
1635 | 'in-progress': (inProgressSubtasks / totalSubtasks) * 100,
1636 | pending: (pendingSubtasks / totalSubtasks) * 100,
1637 | blocked: (blockedSubtasks / totalSubtasks) * 100,
1638 | deferred: (deferredSubtasks / totalSubtasks) * 100,
1639 | cancelled: (cancelledSubtasks / totalSubtasks) * 100
1640 | };
1641 | const completionPercentage = (completedSubtasks / totalSubtasks) * 100;
1642 |
1643 | const availableWidth = process.stdout.columns || 80;
1644 | const boxPadding = 2;
1645 | const boxBorders = 2;
1646 | const percentTextLength = 5;
1647 | const progressBarLength = Math.max(
1648 | 20,
1649 | Math.min(
1650 | 60,
1651 | availableWidth - boxPadding - boxBorders - percentTextLength - 35
1652 | )
1653 | );
1654 |
1655 | const statusCounts =
1656 | `${chalk.green('✓ Done:')} ${completedSubtasks} ${chalk.hex('#FFA500')('► In Progress:')} ${inProgressSubtasks} ${chalk.yellow('○ Pending:')} ${pendingSubtasks}\n` +
1657 | `${chalk.red('! Blocked:')} ${blockedSubtasks} ${chalk.gray('⏱ Deferred:')} ${deferredSubtasks} ${chalk.gray('✗ Cancelled:')} ${cancelledSubtasks}`;
1658 |
1659 | console.log(
1660 | boxen(
1661 | chalk.white.bold('Subtask Progress:') +
1662 | '\n\n' +
1663 | `${chalk.cyan('Completed:')} ${completedSubtasks}/${totalSubtasks} (${completionPercentage.toFixed(1)}%)\n` +
1664 | `${statusCounts}\n` +
1665 | `${chalk.cyan('Progress:')} ${createProgressBar(completionPercentage, progressBarLength, statusBreakdown)}`,
1666 | {
1667 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1668 | borderColor: 'blue',
1669 | borderStyle: 'round',
1670 | margin: { top: 1, bottom: 0 },
1671 | width: Math.min(availableWidth - 10, 100),
1672 | textAlignment: 'left'
1673 | }
1674 | )
1675 | );
1676 | }
1677 |
1678 | // --- Suggested Actions ---
1679 | const actions = [];
1680 | let actionNumber = 1;
1681 |
1682 | // Basic actions
1683 | actions.push(
1684 | `${chalk.cyan(`${actionNumber}.`)} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}`
1685 | );
1686 | actionNumber++;
1687 | actions.push(
1688 | `${chalk.cyan(`${actionNumber}.`)} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}`
1689 | );
1690 | actionNumber++;
1691 |
1692 | // Subtask-related action
1693 | if (subtasksForProgress && subtasksForProgress.length > 0) {
1694 | actions.push(
1695 | `${chalk.cyan(`${actionNumber}.`)} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}`
1696 | );
1697 | } else {
1698 | actions.push(
1699 | `${chalk.cyan(`${actionNumber}.`)} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`
1700 | );
1701 | }
1702 | actionNumber++;
1703 |
1704 | // Complexity-based scope adjustment actions
1705 | if (task.complexityScore) {
1706 | const complexityScore = task.complexityScore;
1707 | actions.push(
1708 | `${chalk.cyan(`${actionNumber}.`)} Re-analyze complexity: ${chalk.yellow(`task-master analyze-complexity --id=${task.id}`)}`
1709 | );
1710 | actionNumber++;
1711 |
1712 | // Add scope adjustment suggestions based on current complexity
1713 | if (complexityScore >= 7) {
1714 | // High complexity - suggest scoping down
1715 | actions.push(
1716 | `${chalk.cyan(`${actionNumber}.`)} Scope down (simplify): ${chalk.yellow(`task-master scope-down --id=${task.id} --strength=regular`)}`
1717 | );
1718 | actionNumber++;
1719 | if (complexityScore >= 9) {
1720 | actions.push(
1721 | `${chalk.cyan(`${actionNumber}.`)} Heavy scope down: ${chalk.yellow(`task-master scope-down --id=${task.id} --strength=heavy`)}`
1722 | );
1723 | actionNumber++;
1724 | }
1725 | } else if (complexityScore <= 4) {
1726 | // Low complexity - suggest scoping up
1727 | actions.push(
1728 | `${chalk.cyan(`${actionNumber}.`)} Scope up (add detail): ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=regular`)}`
1729 | );
1730 | actionNumber++;
1731 | if (complexityScore <= 2) {
1732 | actions.push(
1733 | `${chalk.cyan(`${actionNumber}.`)} Heavy scope up: ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=heavy`)}`
1734 | );
1735 | actionNumber++;
1736 | }
1737 | } else {
1738 | // Medium complexity (5-6) - offer both options
1739 | actions.push(
1740 | `${chalk.cyan(`${actionNumber}.`)} Scope up/down: ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=light`)} or ${chalk.yellow(`scope-down --id=${task.id} --strength=light`)}`
1741 | );
1742 | actionNumber++;
1743 | }
1744 | }
1745 |
1746 | console.log(
1747 | boxen(chalk.white.bold('Suggested Actions:') + '\n' + actions.join('\n'), {
1748 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
1749 | borderColor: 'green',
1750 | borderStyle: 'round',
1751 | margin: { top: 1 }
1752 | })
1753 | );
1754 |
1755 | // Show FYI notice if migration occurred
1756 | displayTaggedTasksFYI(data);
1757 | }
1758 |
1759 | /**
1760 | * Display the complexity analysis report in a nice format
1761 | * @param {string} reportPath - Path to the complexity report file
1762 | */
1763 | async function displayComplexityReport(reportPath) {
1764 | // Check if the report exists
1765 | if (!fs.existsSync(reportPath)) {
1766 | console.log(
1767 | boxen(
1768 | chalk.yellow(`No complexity report found at ${reportPath}\n\n`) +
1769 | 'Would you like to generate one now?',
1770 | {
1771 | padding: 1,
1772 | borderColor: 'yellow',
1773 | borderStyle: 'round',
1774 | margin: { top: 1 }
1775 | }
1776 | )
1777 | );
1778 |
1779 | const rl = readline.createInterface({
1780 | input: process.stdin,
1781 | output: process.stdout
1782 | });
1783 |
1784 | const answer = await new Promise((resolve) => {
1785 | rl.question(chalk.cyan('Generate complexity report? (y/n): '), resolve);
1786 | });
1787 | rl.close();
1788 |
1789 | if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
1790 | // Call the analyze-complexity command
1791 | console.log(chalk.blue('Generating complexity report...'));
1792 | const tasksPath = TASKMASTER_TASKS_FILE;
1793 | if (!fs.existsSync(tasksPath)) {
1794 | console.error(
1795 | '❌ No tasks.json file found. Please run "task-master init" or create a tasks.json file.'
1796 | );
1797 | return null;
1798 | }
1799 |
1800 | await analyzeTaskComplexity({
1801 | output: reportPath,
1802 | research: false, // Default to no research for speed
1803 | file: tasksPath
1804 | });
1805 | // Read the newly generated report
1806 | return displayComplexityReport(reportPath);
1807 | } else {
1808 | console.log(chalk.yellow('Report generation cancelled.'));
1809 | return;
1810 | }
1811 | }
1812 |
1813 | // Read the report
1814 | let report;
1815 | try {
1816 | report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
1817 | } catch (error) {
1818 | log('error', `Error reading complexity report: ${error.message}`);
1819 | return;
1820 | }
1821 |
1822 | // Display report header
1823 | console.log(
1824 | boxen(chalk.white.bold('Task Complexity Analysis Report'), {
1825 | padding: 1,
1826 | borderColor: 'blue',
1827 | borderStyle: 'round',
1828 | margin: { top: 1, bottom: 1 }
1829 | })
1830 | );
1831 |
1832 | // Display metadata
1833 | const metaTable = new Table({
1834 | style: {
1835 | head: [],
1836 | border: [],
1837 | 'padding-top': 0,
1838 | 'padding-bottom': 0,
1839 | compact: true
1840 | },
1841 | chars: {
1842 | mid: '',
1843 | 'left-mid': '',
1844 | 'mid-mid': '',
1845 | 'right-mid': ''
1846 | },
1847 | colWidths: [20, 50]
1848 | });
1849 |
1850 | metaTable.push(
1851 | [
1852 | chalk.cyan.bold('Generated:'),
1853 | new Date(report.meta.generatedAt).toLocaleString()
1854 | ],
1855 | [chalk.cyan.bold('Tasks Analyzed:'), report.meta.tasksAnalyzed],
1856 | [chalk.cyan.bold('Threshold Score:'), report.meta.thresholdScore],
1857 | [chalk.cyan.bold('Project:'), report.meta.projectName],
1858 | [
1859 | chalk.cyan.bold('Research-backed:'),
1860 | report.meta.usedResearch ? 'Yes' : 'No'
1861 | ]
1862 | );
1863 |
1864 | console.log(metaTable.toString());
1865 |
1866 | // Sort tasks by complexity score (highest first)
1867 | const sortedTasks = [...report.complexityAnalysis].sort(
1868 | (a, b) => b.complexityScore - a.complexityScore
1869 | );
1870 |
1871 | // Determine which tasks need expansion based on threshold
1872 | const tasksNeedingExpansion = sortedTasks.filter(
1873 | (task) => task.complexityScore >= report.meta.thresholdScore
1874 | );
1875 | const simpleTasks = sortedTasks.filter(
1876 | (task) => task.complexityScore < report.meta.thresholdScore
1877 | );
1878 |
1879 | // Create progress bar to show complexity distribution
1880 | const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10)
1881 | sortedTasks.forEach((task) => {
1882 | if (task.complexityScore < 5) complexityDistribution[0]++;
1883 | else if (task.complexityScore < 8) complexityDistribution[1]++;
1884 | else complexityDistribution[2]++;
1885 | });
1886 |
1887 | const percentLow = Math.round(
1888 | (complexityDistribution[0] / sortedTasks.length) * 100
1889 | );
1890 | const percentMedium = Math.round(
1891 | (complexityDistribution[1] / sortedTasks.length) * 100
1892 | );
1893 | const percentHigh = Math.round(
1894 | (complexityDistribution[2] / sortedTasks.length) * 100
1895 | );
1896 |
1897 | console.log(
1898 | boxen(
1899 | chalk.white.bold('Complexity Distribution\n\n') +
1900 | `${chalk.green.bold('Low (1-4):')} ${complexityDistribution[0]} tasks (${percentLow}%)\n` +
1901 | `${chalk.yellow.bold('Medium (5-7):')} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` +
1902 | `${chalk.red.bold('High (8-10):')} ${complexityDistribution[2]} tasks (${percentHigh}%)`,
1903 | {
1904 | padding: 1,
1905 | borderColor: 'cyan',
1906 | borderStyle: 'round',
1907 | margin: { top: 1, bottom: 1 }
1908 | }
1909 | )
1910 | );
1911 |
1912 | // Get terminal width
1913 | const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect
1914 |
1915 | // Calculate dynamic column widths
1916 | const idWidth = 12;
1917 | const titleWidth = Math.floor(terminalWidth * 0.25); // 25% of width
1918 | const scoreWidth = 8;
1919 | const subtasksWidth = 8;
1920 | // Command column gets the remaining space (minus some buffer for borders)
1921 | const commandWidth =
1922 | terminalWidth - idWidth - titleWidth - scoreWidth - subtasksWidth - 10;
1923 |
1924 | // Create table with new column widths and word wrapping
1925 | const complexTable = new Table({
1926 | head: [
1927 | chalk.yellow.bold('ID'),
1928 | chalk.yellow.bold('Title'),
1929 | chalk.yellow.bold('Score'),
1930 | chalk.yellow.bold('Subtasks'),
1931 | chalk.yellow.bold('Expansion Command')
1932 | ],
1933 | colWidths: [idWidth, titleWidth, scoreWidth, subtasksWidth, commandWidth],
1934 | style: { head: [], border: [] },
1935 | wordWrap: true,
1936 | wrapOnWordBoundary: true
1937 | });
1938 |
1939 | // When adding rows, don't truncate the expansion command
1940 | tasksNeedingExpansion.forEach((task) => {
1941 | const expansionCommand = `task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}${task.expansionPrompt ? ` --prompt="${task.expansionPrompt}"` : ''}`;
1942 |
1943 | complexTable.push([
1944 | task.taskId,
1945 | truncate(task.taskTitle, titleWidth - 3), // Still truncate title for readability
1946 | getComplexityWithColor(task.complexityScore),
1947 | task.recommendedSubtasks,
1948 | chalk.cyan(expansionCommand) // Don't truncate - allow wrapping
1949 | ]);
1950 | });
1951 |
1952 | console.log(complexTable.toString());
1953 |
1954 | // Create table for simple tasks
1955 | if (simpleTasks.length > 0) {
1956 | console.log(
1957 | boxen(chalk.green.bold(`Simple Tasks (${simpleTasks.length})`), {
1958 | padding: { left: 2, right: 2, top: 0, bottom: 0 },
1959 | margin: { top: 1, bottom: 0 },
1960 | borderColor: 'green',
1961 | borderStyle: 'round'
1962 | })
1963 | );
1964 |
1965 | const simpleTable = new Table({
1966 | head: [
1967 | chalk.green.bold('ID'),
1968 | chalk.green.bold('Title'),
1969 | chalk.green.bold('Score'),
1970 | chalk.green.bold('Reasoning')
1971 | ],
1972 | colWidths: [5, 40, 8, 50],
1973 | style: { head: [], border: [] }
1974 | });
1975 |
1976 | simpleTasks.forEach((task) => {
1977 | simpleTable.push([
1978 | task.taskId,
1979 | truncate(task.taskTitle, 37),
1980 | getComplexityWithColor(task.complexityScore),
1981 | truncate(task.reasoning, 47)
1982 | ]);
1983 | });
1984 |
1985 | console.log(simpleTable.toString());
1986 | }
1987 |
1988 | // Show action suggestions
1989 | console.log(
1990 | boxen(
1991 | chalk.white.bold('Suggested Actions:') +
1992 | '\n\n' +
1993 | `${chalk.cyan('1.')} Expand all complex tasks: ${chalk.yellow(`task-master expand --all`)}\n` +
1994 | `${chalk.cyan('2.')} Expand a specific task: ${chalk.yellow(`task-master expand --id=<id>`)}\n` +
1995 | `${chalk.cyan('3.')} Regenerate with research: ${chalk.yellow(`task-master analyze-complexity --research`)}`,
1996 | {
1997 | padding: 1,
1998 | borderColor: 'cyan',
1999 | borderStyle: 'round',
2000 | margin: { top: 1 }
2001 | }
2002 | )
2003 | );
2004 | }
2005 |
2006 | /**
2007 | * Generate a prompt for complexity analysis
2008 | * @param {Object} tasksData - Tasks data object containing tasks array
2009 | * @returns {string} Generated prompt
2010 | */
2011 | function generateComplexityAnalysisPrompt(tasksData) {
2012 | const defaultSubtasks = getDefaultSubtasks(null); // Use the getter
2013 | return `Analyze the complexity of the following tasks and provide recommendations for subtask breakdown:
2014 |
2015 | ${tasksData.tasks
2016 | .map(
2017 | (task) => `
2018 | Task ID: ${task.id}
2019 | Title: ${task.title}
2020 | Description: ${task.description}
2021 | Details: ${task.details}
2022 | Dependencies: ${JSON.stringify(task.dependencies || [])}
2023 | Priority: ${task.priority || 'medium'}
2024 | `
2025 | )
2026 | .join('\n---\n')}
2027 |
2028 | Analyze each task and return a JSON array with the following structure for each task:
2029 | [
2030 | {
2031 | "taskId": number,
2032 | "taskTitle": string,
2033 | "complexityScore": number (1-10),
2034 | "recommendedSubtasks": number (${Math.max(3, defaultSubtasks - 1)}-${Math.min(8, defaultSubtasks + 2)}),
2035 | "expansionPrompt": string (a specific prompt for generating good subtasks),
2036 | "reasoning": string (brief explanation of your assessment)
2037 | },
2038 | ...
2039 | ]
2040 |
2041 | IMPORTANT: Make sure to include an analysis for EVERY task listed above, with the correct taskId matching each task's ID.
2042 | `;
2043 | }
2044 |
2045 | /**
2046 | * Confirm overwriting existing tasks.json file
2047 | * @param {string} tasksPath - Path to the tasks.json file
2048 | * @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise
2049 | */
2050 | async function confirmTaskOverwrite(tasksPath) {
2051 | console.log(
2052 | boxen(
2053 | chalk.yellow(
2054 | "It looks like you've already generated tasks for this project.\n"
2055 | ) +
2056 | chalk.yellow(
2057 | 'Executing this command will overwrite any existing tasks.'
2058 | ),
2059 | {
2060 | padding: 1,
2061 | borderColor: 'yellow',
2062 | borderStyle: 'round',
2063 | margin: { top: 1 }
2064 | }
2065 | )
2066 | );
2067 |
2068 | const rl = readline.createInterface({
2069 | input: process.stdin,
2070 | output: process.stdout
2071 | });
2072 |
2073 | const answer = await new Promise((resolve) => {
2074 | rl.question(
2075 | chalk.cyan('Are you sure you wish to continue? (y/N): '),
2076 | resolve
2077 | );
2078 | });
2079 | rl.close();
2080 |
2081 | return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
2082 | }
2083 |
2084 | /**
2085 | * Displays the API key status for different providers.
2086 | * @param {Array<{provider: string, cli: boolean, mcp: boolean}>} statusReport - The report generated by getApiKeyStatusReport.
2087 | */
2088 | function displayApiKeyStatus(statusReport) {
2089 | if (!statusReport || statusReport.length === 0) {
2090 | console.log(chalk.yellow('No API key status information available.'));
2091 | return;
2092 | }
2093 |
2094 | const table = new Table({
2095 | head: [
2096 | chalk.cyan('Provider'),
2097 | chalk.cyan('CLI Key (.env)'),
2098 | chalk.cyan('MCP Key (mcp.json)')
2099 | ],
2100 | colWidths: [15, 20, 25],
2101 | chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }
2102 | });
2103 |
2104 | statusReport.forEach(({ provider, cli, mcp }) => {
2105 | const cliStatus = cli ? chalk.green('✅ Found') : chalk.red('❌ Missing');
2106 | const mcpStatus = mcp ? chalk.green('✅ Found') : chalk.red('❌ Missing');
2107 | // Capitalize provider name for display
2108 | const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
2109 | table.push([providerName, cliStatus, mcpStatus]);
2110 | });
2111 |
2112 | console.log(chalk.bold('\n🔑 API Key Status:'));
2113 | console.log(table.toString());
2114 | console.log(
2115 | chalk.gray(
2116 | ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.`
2117 | )
2118 | );
2119 | }
2120 |
2121 | // --- Formatting Helpers (Potentially move some to utils.js if reusable) ---
2122 |
2123 | const formatSweScoreWithTertileStars = (score, allModels) => {
2124 | // ... (Implementation from previous version or refine) ...
2125 | if (score === null || score === undefined || score <= 0) return 'N/A';
2126 | const formattedPercentage = `${(score * 100).toFixed(1)}%`;
2127 |
2128 | const validScores = allModels
2129 | .map((m) => m.sweScore)
2130 | .filter((s) => s !== null && s !== undefined && s > 0);
2131 | const sortedScores = [...validScores].sort((a, b) => b - a);
2132 | const n = sortedScores.length;
2133 | let stars = chalk.gray('☆☆☆');
2134 |
2135 | if (n > 0) {
2136 | const topThirdIndex = Math.max(0, Math.floor(n / 3) - 1);
2137 | const midThirdIndex = Math.max(0, Math.floor((2 * n) / 3) - 1);
2138 | if (score >= sortedScores[topThirdIndex]) stars = chalk.yellow('★★★');
2139 | else if (score >= sortedScores[midThirdIndex])
2140 | stars = chalk.yellow('★★') + chalk.gray('☆');
2141 | else stars = chalk.yellow('★') + chalk.gray('☆☆');
2142 | }
2143 | return `${formattedPercentage} ${stars}`;
2144 | };
2145 |
2146 | const formatCost = (costObj) => {
2147 | // ... (Implementation from previous version or refine) ...
2148 | if (!costObj) return 'N/A';
2149 | if (costObj.input === 0 && costObj.output === 0) {
2150 | return chalk.green('Free');
2151 | }
2152 | const formatSingleCost = (costValue) => {
2153 | if (costValue === null || costValue === undefined) return 'N/A';
2154 | const isInteger = Number.isInteger(costValue);
2155 | return `$${costValue.toFixed(isInteger ? 0 : 2)}`;
2156 | };
2157 | return `${formatSingleCost(costObj.input)} in, ${formatSingleCost(costObj.output)} out`;
2158 | };
2159 |
2160 | // --- Display Functions ---
2161 |
2162 | /**
2163 | * Displays the currently configured active models.
2164 | * @param {ConfigData} configData - The active configuration data.
2165 | * @param {AvailableModel[]} allAvailableModels - Needed for SWE score tertiles.
2166 | */
2167 | function displayModelConfiguration(configData, allAvailableModels = []) {
2168 | console.log(chalk.cyan.bold('\nActive Model Configuration:'));
2169 | const active = configData.activeModels;
2170 | const activeTable = new Table({
2171 | head: [
2172 | 'Role',
2173 | 'Provider',
2174 | 'Model ID',
2175 | 'SWE Score',
2176 | 'Cost ($/1M tkns)'
2177 | // 'API Key Status' // Removed, handled by separate displayApiKeyStatus
2178 | ].map((h) => chalk.cyan.bold(h)),
2179 | colWidths: [10, 14, 30, 18, 20 /*, 28 */], // Adjusted widths
2180 | style: { head: ['cyan', 'bold'] }
2181 | });
2182 |
2183 | activeTable.push([
2184 | chalk.white('Main'),
2185 | active.main.provider,
2186 | active.main.modelId,
2187 | formatSweScoreWithTertileStars(active.main.sweScore, allAvailableModels),
2188 | formatCost(active.main.cost)
2189 | // getCombinedStatus(active.main.keyStatus) // Removed
2190 | ]);
2191 | activeTable.push([
2192 | chalk.white('Research'),
2193 | active.research.provider,
2194 | active.research.modelId,
2195 | formatSweScoreWithTertileStars(
2196 | active.research.sweScore,
2197 | allAvailableModels
2198 | ),
2199 | formatCost(active.research.cost)
2200 | // getCombinedStatus(active.research.keyStatus) // Removed
2201 | ]);
2202 | if (active.fallback && active.fallback.provider && active.fallback.modelId) {
2203 | activeTable.push([
2204 | chalk.white('Fallback'),
2205 | active.fallback.provider,
2206 | active.fallback.modelId,
2207 | formatSweScoreWithTertileStars(
2208 | active.fallback.sweScore,
2209 | allAvailableModels
2210 | ),
2211 | formatCost(active.fallback.cost)
2212 | // getCombinedStatus(active.fallback.keyStatus) // Removed
2213 | ]);
2214 | } else {
2215 | activeTable.push([
2216 | chalk.white('Fallback'),
2217 | chalk.gray('-'),
2218 | chalk.gray('(Not Set)'),
2219 | chalk.gray('-'),
2220 | chalk.gray('-')
2221 | // chalk.gray('-') // Removed
2222 | ]);
2223 | }
2224 | console.log(activeTable.toString());
2225 | }
2226 |
2227 | /**
2228 | * Displays the list of available models not currently configured.
2229 | * @param {AvailableModel[]} availableModels - List of available models.
2230 | */
2231 | function displayAvailableModels(availableModels) {
2232 | if (!availableModels || availableModels.length === 0) {
2233 | console.log(
2234 | chalk.gray('\n(No other models available or all are configured)')
2235 | );
2236 | return;
2237 | }
2238 |
2239 | console.log(chalk.cyan.bold('\nOther Available Models:'));
2240 | const availableTable = new Table({
2241 | head: ['Provider', 'Model ID', 'SWE Score', 'Cost ($/1M tkns)'].map((h) =>
2242 | chalk.cyan.bold(h)
2243 | ),
2244 | colWidths: [15, 40, 18, 25],
2245 | style: { head: ['cyan', 'bold'] }
2246 | });
2247 |
2248 | availableModels.forEach((model) => {
2249 | availableTable.push([
2250 | model.provider,
2251 | model.modelId,
2252 | formatSweScoreWithTertileStars(model.sweScore, availableModels), // Pass itself for comparison
2253 | formatCost(model.cost)
2254 | ]);
2255 | });
2256 | console.log(availableTable.toString());
2257 |
2258 | // --- Suggested Actions Section (moved here from models command) ---
2259 | console.log(
2260 | boxen(
2261 | chalk.white.bold('Next Steps:') +
2262 | '\n' +
2263 | chalk.cyan(
2264 | `1. Set main model: ${chalk.yellow('task-master models --set-main <model_id>')}`
2265 | ) +
2266 | '\n' +
2267 | chalk.cyan(
2268 | `2. Set research model: ${chalk.yellow('task-master models --set-research <model_id>')}`
2269 | ) +
2270 | '\n' +
2271 | chalk.cyan(
2272 | `3. Set fallback model: ${chalk.yellow('task-master models --set-fallback <model_id>')}`
2273 | ) +
2274 | '\n' +
2275 | chalk.cyan(
2276 | `4. Run interactive setup: ${chalk.yellow('task-master models --setup')}`
2277 | ) +
2278 | '\n' +
2279 | chalk.cyan(
2280 | `5. Use custom ollama/openrouter models: ${chalk.yellow('task-master models --openrouter|ollama --set-main|research|fallback <model_id>')}`
2281 | ),
2282 | {
2283 | padding: 1,
2284 | borderColor: 'yellow',
2285 | borderStyle: 'round',
2286 | margin: { top: 1 }
2287 | }
2288 | )
2289 | );
2290 | }
2291 |
2292 | /**
2293 | * Displays AI usage telemetry summary in the CLI.
2294 | * @param {object} telemetryData - The telemetry data object.
2295 | * @param {string} outputType - 'cli' or 'mcp' (though typically only called for 'cli').
2296 | */
2297 | function displayAiUsageSummary(telemetryData, outputType = 'cli') {
2298 | if (
2299 | (outputType !== 'cli' && outputType !== 'text') ||
2300 | !telemetryData ||
2301 | isSilentMode()
2302 | ) {
2303 | return; // Only display for CLI and if data exists and not in silent mode
2304 | }
2305 |
2306 | const {
2307 | modelUsed,
2308 | providerName,
2309 | inputTokens,
2310 | outputTokens,
2311 | totalTokens,
2312 | totalCost,
2313 | commandName,
2314 | isUnknownCost
2315 | } = telemetryData;
2316 |
2317 | let summary = chalk.bold.blue('AI Usage Summary:') + '\n';
2318 | summary += chalk.gray(` Command: ${commandName}\n`);
2319 | summary += chalk.gray(` Provider: ${providerName}\n`);
2320 | summary += chalk.gray(` Model: ${modelUsed}\n`);
2321 | summary += chalk.gray(
2322 | ` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n`
2323 | );
2324 |
2325 | // Show "Unknown" if pricing data is not available, otherwise show the cost
2326 | const costDisplay = isUnknownCost ? 'Unknown' : `$${totalCost.toFixed(6)}`;
2327 | summary += chalk.gray(` Est. Cost: ${costDisplay}`);
2328 |
2329 | console.log(
2330 | boxen(summary, {
2331 | padding: 1,
2332 | margin: { top: 1 },
2333 | borderColor: 'blue',
2334 | borderStyle: 'round',
2335 | title: '💡 Telemetry',
2336 | titleAlignment: 'center'
2337 | })
2338 | );
2339 | }
2340 |
2341 | /**
2342 | * Display multiple tasks in a compact summary format with interactive drill-down
2343 | * @param {string} tasksPath - Path to the tasks.json file
2344 | * @param {Array<string>} taskIds - Array of task IDs to display
2345 | * @param {string} complexityReportPath - Path to complexity report
2346 | * @param {string} statusFilter - Optional status filter for subtasks
2347 | * @param {Object} context - Context object containing projectRoot and tag
2348 | * @param {string} [context.projectRoot] - Project root path
2349 | * @param {string} [context.tag] - Tag for the task
2350 | */
2351 | async function displayMultipleTasksSummary(
2352 | tasksPath,
2353 | taskIds,
2354 | complexityReportPath = null,
2355 | statusFilter = null,
2356 | context = {}
2357 | ) {
2358 | displayBanner();
2359 |
2360 | // Extract projectRoot and tag from context
2361 | const projectRoot = context.projectRoot || null;
2362 | const tag = context.tag || null;
2363 |
2364 | // Read the tasks file with proper projectRoot for tag resolution
2365 | const data = readJSON(tasksPath, projectRoot, tag);
2366 | if (!data || !data.tasks) {
2367 | log('error', 'No valid tasks found.');
2368 | process.exit(1);
2369 | }
2370 |
2371 | // Read complexity report once
2372 | const complexityReport = readComplexityReport(complexityReportPath);
2373 |
2374 | // Find all requested tasks
2375 | const foundTasks = [];
2376 | const notFoundIds = [];
2377 |
2378 | taskIds.forEach((id) => {
2379 | const { task } = findTaskById(
2380 | data.tasks,
2381 | id,
2382 | complexityReport,
2383 | statusFilter
2384 | );
2385 | if (task) {
2386 | foundTasks.push(task);
2387 | } else {
2388 | notFoundIds.push(id);
2389 | }
2390 | });
2391 |
2392 | // Show not found tasks
2393 | if (notFoundIds.length > 0) {
2394 | console.log(
2395 | boxen(chalk.yellow(`Tasks not found: ${notFoundIds.join(', ')}`), {
2396 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
2397 | borderColor: 'yellow',
2398 | borderStyle: 'round',
2399 | margin: { top: 1, bottom: 1 }
2400 | })
2401 | );
2402 | }
2403 |
2404 | if (foundTasks.length === 0) {
2405 | console.log(
2406 | boxen(chalk.red('No valid tasks found to display'), {
2407 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
2408 | borderColor: 'red',
2409 | borderStyle: 'round',
2410 | margin: { top: 1 }
2411 | })
2412 | );
2413 | return;
2414 | }
2415 |
2416 | // Display header
2417 | console.log(
2418 | boxen(
2419 | chalk.white.bold(
2420 | `Task Summary (${foundTasks.length} task${foundTasks.length === 1 ? '' : 's'})`
2421 | ),
2422 | {
2423 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
2424 | borderColor: 'blue',
2425 | borderStyle: 'round',
2426 | margin: { top: 1, bottom: 0 }
2427 | }
2428 | )
2429 | );
2430 |
2431 | // Calculate terminal width for responsive layout
2432 | const terminalWidth = process.stdout.columns || 100;
2433 | const availableWidth = terminalWidth - 10;
2434 |
2435 | // Create compact summary table
2436 | const summaryTable = new Table({
2437 | head: [
2438 | chalk.cyan.bold('ID'),
2439 | chalk.cyan.bold('Title'),
2440 | chalk.cyan.bold('Status'),
2441 | chalk.cyan.bold('Priority'),
2442 | chalk.cyan.bold('Subtasks'),
2443 | chalk.cyan.bold('Progress')
2444 | ],
2445 | colWidths: [
2446 | Math.floor(availableWidth * 0.08), // ID: 8%
2447 | Math.floor(availableWidth * 0.35), // Title: 35%
2448 | Math.floor(availableWidth * 0.12), // Status: 12%
2449 | Math.floor(availableWidth * 0.1), // Priority: 10%
2450 | Math.floor(availableWidth * 0.15), // Subtasks: 15%
2451 | Math.floor(availableWidth * 0.2) // Progress: 20%
2452 | ],
2453 | style: {
2454 | head: [],
2455 | border: [],
2456 | 'padding-top': 0,
2457 | 'padding-bottom': 0,
2458 | compact: true
2459 | },
2460 | chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' },
2461 | wordWrap: true
2462 | });
2463 |
2464 | // Add each task to the summary table
2465 | foundTasks.forEach((task) => {
2466 | // Handle subtask case
2467 | if (task.isSubtask || task.parentTask) {
2468 | const parentId = task.parentTask ? task.parentTask.id : 'Unknown';
2469 | summaryTable.push([
2470 | `${parentId}.${task.id}`,
2471 | truncate(task.title, Math.floor(availableWidth * 0.35) - 3),
2472 | getStatusWithColor(task.status || 'pending', true),
2473 | chalk.gray('(subtask)'),
2474 | chalk.gray('N/A'),
2475 | chalk.gray('N/A')
2476 | ]);
2477 | return;
2478 | }
2479 |
2480 | // Handle regular task
2481 | const priorityColors = {
2482 | high: chalk.red.bold,
2483 | medium: chalk.yellow,
2484 | low: chalk.gray
2485 | };
2486 | const priorityColor =
2487 | priorityColors[task.priority || 'medium'] || chalk.white;
2488 |
2489 | // Calculate subtask summary
2490 | let subtaskSummary = chalk.gray('None');
2491 | let progressBar = chalk.gray('N/A');
2492 |
2493 | if (task.subtasks && task.subtasks.length > 0) {
2494 | const total = task.subtasks.length;
2495 | const completed = task.subtasks.filter(
2496 | (st) => st.status === 'done' || st.status === 'completed'
2497 | ).length;
2498 | const inProgress = task.subtasks.filter(
2499 | (st) => st.status === 'in-progress'
2500 | ).length;
2501 | const pending = task.subtasks.filter(
2502 | (st) => st.status === 'pending'
2503 | ).length;
2504 |
2505 | // Compact subtask count with status indicators
2506 | subtaskSummary = `${chalk.green(completed)}/${total}`;
2507 | if (inProgress > 0)
2508 | subtaskSummary += ` ${chalk.hex('#FFA500')(`+${inProgress}`)}`;
2509 | if (pending > 0) subtaskSummary += ` ${chalk.yellow(`(${pending})`)}`;
2510 |
2511 | // Mini progress bar (shorter than usual)
2512 | const completionPercentage = (completed / total) * 100;
2513 | const barLength = 8; // Compact bar
2514 | const statusBreakdown = {
2515 | 'in-progress': (inProgress / total) * 100,
2516 | pending: (pending / total) * 100
2517 | };
2518 | progressBar = createProgressBar(
2519 | completionPercentage,
2520 | barLength,
2521 | statusBreakdown
2522 | );
2523 | }
2524 |
2525 | summaryTable.push([
2526 | task.id.toString(),
2527 | truncate(task.title, Math.floor(availableWidth * 0.35) - 3),
2528 | getStatusWithColor(task.status || 'pending', true),
2529 | priorityColor(task.priority || 'medium'),
2530 | subtaskSummary,
2531 | progressBar
2532 | ]);
2533 | });
2534 |
2535 | console.log(summaryTable.toString());
2536 |
2537 | // Interactive drill-down prompt
2538 | if (foundTasks.length > 1) {
2539 | console.log(
2540 | boxen(
2541 | chalk.white.bold('Interactive Options:') +
2542 | '\n' +
2543 | chalk.cyan('• Press Enter to view available actions for all tasks') +
2544 | '\n' +
2545 | chalk.cyan(
2546 | '• Type a task ID (e.g., "3" or "3.2") to view that specific task'
2547 | ) +
2548 | '\n' +
2549 | chalk.cyan('• Type "q" to quit'),
2550 | {
2551 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
2552 | borderColor: 'green',
2553 | borderStyle: 'round',
2554 | margin: { top: 1 }
2555 | }
2556 | )
2557 | );
2558 |
2559 | const rl = readline.createInterface({
2560 | input: process.stdin,
2561 | output: process.stdout
2562 | });
2563 |
2564 | const choice = await new Promise((resolve) => {
2565 | rl.question(chalk.cyan('Your choice: '), resolve);
2566 | });
2567 | rl.close();
2568 |
2569 | if (choice.toLowerCase() === 'q') {
2570 | return;
2571 | } else if (choice.trim() === '') {
2572 | // Show action menu for selected tasks
2573 | console.log(
2574 | boxen(
2575 | chalk.white.bold('Available Actions for Selected Tasks:') +
2576 | '\n' +
2577 | chalk.cyan('1.') +
2578 | ' Mark all as in-progress' +
2579 | '\n' +
2580 | chalk.cyan('2.') +
2581 | ' Mark all as done' +
2582 | '\n' +
2583 | chalk.cyan('3.') +
2584 | ' Show next available task' +
2585 | '\n' +
2586 | chalk.cyan('4.') +
2587 | ' Expand all tasks (generate subtasks)' +
2588 | '\n' +
2589 | chalk.cyan('5.') +
2590 | ' View dependency relationships' +
2591 | '\n' +
2592 | chalk.cyan('6.') +
2593 | ' Generate task files' +
2594 | '\n' +
2595 | chalk.gray('Or type a task ID to view details'),
2596 | {
2597 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
2598 | borderColor: 'blue',
2599 | borderStyle: 'round',
2600 | margin: { top: 1 }
2601 | }
2602 | )
2603 | );
2604 |
2605 | const rl2 = readline.createInterface({
2606 | input: process.stdin,
2607 | output: process.stdout
2608 | });
2609 |
2610 | const actionChoice = await new Promise((resolve) => {
2611 | rl2.question(chalk.cyan('Choose action (1-6): '), resolve);
2612 | });
2613 | rl2.close();
2614 |
2615 | const taskIdList = foundTasks.map((t) => t.id).join(',');
2616 |
2617 | switch (actionChoice.trim()) {
2618 | case '1':
2619 | console.log(
2620 | chalk.blue(
2621 | `\n→ Command: task-master set-status --id=${taskIdList} --status=in-progress`
2622 | )
2623 | );
2624 | console.log(
2625 | chalk.green(
2626 | '✓ Copy and run this command to mark all tasks as in-progress'
2627 | )
2628 | );
2629 | break;
2630 | case '2':
2631 | console.log(
2632 | chalk.blue(
2633 | `\n→ Command: task-master set-status --id=${taskIdList} --status=done`
2634 | )
2635 | );
2636 | console.log(
2637 | chalk.green('✓ Copy and run this command to mark all tasks as done')
2638 | );
2639 | break;
2640 | case '3':
2641 | console.log(chalk.blue(`\n→ Command: task-master next`));
2642 | console.log(
2643 | chalk.green(
2644 | '✓ Copy and run this command to see the next available task'
2645 | )
2646 | );
2647 | break;
2648 | case '4':
2649 | console.log(
2650 | chalk.blue(
2651 | `\n→ Command: task-master expand --id=${taskIdList} --research`
2652 | )
2653 | );
2654 | console.log(
2655 | chalk.green(
2656 | '✓ Copy and run this command to expand all selected tasks into subtasks'
2657 | )
2658 | );
2659 | break;
2660 | case '5': {
2661 | // Show dependency visualization
2662 | console.log(chalk.white.bold('\nDependency Relationships:'));
2663 | let hasDependencies = false;
2664 | foundTasks.forEach((task) => {
2665 | if (task.dependencies && task.dependencies.length > 0) {
2666 | console.log(
2667 | chalk.cyan(
2668 | `Task ${task.id} depends on: ${task.dependencies.join(', ')}`
2669 | )
2670 | );
2671 | hasDependencies = true;
2672 | }
2673 | });
2674 | if (!hasDependencies) {
2675 | console.log(chalk.gray('No dependencies found for selected tasks'));
2676 | }
2677 | break;
2678 | }
2679 | case '6':
2680 | console.log(chalk.blue(`\n→ Command: task-master generate`));
2681 | console.log(
2682 | chalk.green('✓ Copy and run this command to generate task files')
2683 | );
2684 | break;
2685 | default:
2686 | if (actionChoice.trim().length > 0) {
2687 | console.log(chalk.yellow(`Invalid choice: ${actionChoice.trim()}`));
2688 | console.log(chalk.gray('Please choose 1-6 or type a task ID'));
2689 | }
2690 | }
2691 | } else {
2692 | // Show specific task
2693 | await displayTaskById(
2694 | tasksPath,
2695 | choice.trim(),
2696 | complexityReportPath,
2697 | statusFilter,
2698 | context
2699 | );
2700 | }
2701 | } else {
2702 | // Single task - show suggested actions
2703 | const task = foundTasks[0];
2704 | console.log(
2705 | boxen(
2706 | chalk.white.bold('Suggested Actions:') +
2707 | '\n' +
2708 | `${chalk.cyan('1.')} View full details: ${chalk.yellow(`task-master show ${task.id}`)}\n` +
2709 | `${chalk.cyan('2.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` +
2710 | `${chalk.cyan('3.')} Mark as done: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}`,
2711 | {
2712 | padding: { top: 0, bottom: 0, left: 1, right: 1 },
2713 | borderColor: 'green',
2714 | borderStyle: 'round',
2715 | margin: { top: 1 }
2716 | }
2717 | )
2718 | );
2719 | }
2720 | }
2721 |
2722 | /**
2723 | * Display context analysis results with beautiful formatting
2724 | * @param {Object} analysisData - Analysis data from ContextGatherer
2725 | * @param {string} semanticQuery - The original query used for semantic search
2726 | * @param {number} contextSize - Size of gathered context in characters
2727 | */
2728 | function displayContextAnalysis(analysisData, semanticQuery, contextSize) {
2729 | if (isSilentMode() || !analysisData) return;
2730 |
2731 | const { highRelevance, mediumRelevance, recentTasks, allRelevantTasks } =
2732 | analysisData;
2733 |
2734 | // Create the context analysis display
2735 | let analysisContent = chalk.white.bold('Context Analysis') + '\n\n';
2736 |
2737 | // Query info
2738 | analysisContent +=
2739 | chalk.gray('Query: ') + chalk.white(`"${semanticQuery}"`) + '\n';
2740 | analysisContent +=
2741 | chalk.gray('Context size: ') +
2742 | chalk.cyan(`${contextSize.toLocaleString()} characters`) +
2743 | '\n';
2744 | analysisContent +=
2745 | chalk.gray('Tasks found: ') +
2746 | chalk.yellow(`${allRelevantTasks.length} relevant tasks`) +
2747 | '\n\n';
2748 |
2749 | // High relevance matches
2750 | if (highRelevance.length > 0) {
2751 | analysisContent += chalk.green.bold('🎯 High Relevance Matches:') + '\n';
2752 | highRelevance.slice(0, 3).forEach((task) => {
2753 | analysisContent +=
2754 | chalk.green(` • Task ${task.id}: ${truncate(task.title, 50)}`) + '\n';
2755 | });
2756 | if (highRelevance.length > 3) {
2757 | analysisContent +=
2758 | chalk.green(
2759 | ` • ... and ${highRelevance.length - 3} more high relevance tasks`
2760 | ) + '\n';
2761 | }
2762 | analysisContent += '\n';
2763 | }
2764 |
2765 | // Medium relevance matches
2766 | if (mediumRelevance.length > 0) {
2767 | analysisContent += chalk.yellow.bold('📋 Medium Relevance Matches:') + '\n';
2768 | mediumRelevance.slice(0, 3).forEach((task) => {
2769 | analysisContent +=
2770 | chalk.yellow(` • Task ${task.id}: ${truncate(task.title, 50)}`) + '\n';
2771 | });
2772 | if (mediumRelevance.length > 3) {
2773 | analysisContent +=
2774 | chalk.yellow(
2775 | ` • ... and ${mediumRelevance.length - 3} more medium relevance tasks`
2776 | ) + '\n';
2777 | }
2778 | analysisContent += '\n';
2779 | }
2780 |
2781 | // Recent tasks (if they contributed)
2782 | const recentTasksNotInRelevance = recentTasks.filter(
2783 | (task) =>
2784 | !highRelevance.some((hr) => hr.id === task.id) &&
2785 | !mediumRelevance.some((mr) => mr.id === task.id)
2786 | );
2787 |
2788 | if (recentTasksNotInRelevance.length > 0) {
2789 | analysisContent += chalk.cyan.bold('🕒 Recent Tasks (for context):') + '\n';
2790 | recentTasksNotInRelevance.slice(0, 2).forEach((task) => {
2791 | analysisContent +=
2792 | chalk.cyan(` • Task ${task.id}: ${truncate(task.title, 50)}`) + '\n';
2793 | });
2794 | if (recentTasksNotInRelevance.length > 2) {
2795 | analysisContent +=
2796 | chalk.cyan(
2797 | ` • ... and ${recentTasksNotInRelevance.length - 2} more recent tasks`
2798 | ) + '\n';
2799 | }
2800 | }
2801 |
2802 | console.log(
2803 | boxen(analysisContent, {
2804 | padding: { top: 1, bottom: 1, left: 2, right: 2 },
2805 | margin: { top: 1, bottom: 0 },
2806 | borderStyle: 'round',
2807 | borderColor: 'blue',
2808 | title: chalk.blue('🔍 Context Gathering'),
2809 | titleAlignment: 'center'
2810 | })
2811 | );
2812 | }
2813 |
2814 | // Export UI functions
2815 | export {
2816 | displayBanner,
2817 | displayTaggedTasksFYI,
2818 | startLoadingIndicator,
2819 | stopLoadingIndicator,
2820 | createProgressBar,
2821 | getStatusWithColor,
2822 | formatDependenciesWithStatus,
2823 | displayHelp,
2824 | getComplexityWithColor,
2825 | displayNextTask,
2826 | displayTaskById,
2827 | displayComplexityReport,
2828 | generateComplexityAnalysisPrompt,
2829 | confirmTaskOverwrite,
2830 | displayApiKeyStatus,
2831 | displayModelConfiguration,
2832 | displayAvailableModels,
2833 | displayAiUsageSummary,
2834 | displayMultipleTasksSummary,
2835 | succeedLoadingIndicator,
2836 | failLoadingIndicator,
2837 | warnLoadingIndicator,
2838 | infoLoadingIndicator,
2839 | displayContextAnalysis,
2840 | displayCurrentTagIndicator,
2841 | formatTaskIdForDisplay
2842 | };
2843 |
2844 | /**
2845 | * Display enhanced error message for cross-tag dependency conflicts
2846 | * @param {Array} conflicts - Array of cross-tag dependency conflicts
2847 | * @param {string} sourceTag - Source tag name
2848 | * @param {string} targetTag - Target tag name
2849 | * @param {string} sourceIds - Source task IDs (comma-separated)
2850 | */
2851 | export function displayCrossTagDependencyError(
2852 | conflicts,
2853 | sourceTag,
2854 | targetTag,
2855 | sourceIds
2856 | ) {
2857 | console.log(
2858 | chalk.red(`\n❌ Cannot move tasks from "${sourceTag}" to "${targetTag}"`)
2859 | );
2860 | console.log(chalk.yellow(`\nCross-tag dependency conflicts detected:`));
2861 |
2862 | if (conflicts.length > 0) {
2863 | conflicts.forEach((conflict) => {
2864 | console.log(` • ${conflict.message}`);
2865 | });
2866 | }
2867 |
2868 | console.log(chalk.cyan(`\nResolution options:`));
2869 | console.log(
2870 | ` 1. Move with dependencies: task-master move --from=${sourceIds} --from-tag=${sourceTag} --to-tag=${targetTag} --with-dependencies`
2871 | );
2872 | console.log(
2873 | ` 2. Break dependencies: task-master move --from=${sourceIds} --from-tag=${sourceTag} --to-tag=${targetTag} --ignore-dependencies`
2874 | );
2875 | console.log(
2876 | ` 3. Validate and fix dependencies: task-master validate-dependencies && task-master fix-dependencies`
2877 | );
2878 | if (conflicts.length > 0) {
2879 | console.log(
2880 | ` 4. Move dependencies first: task-master move --from=${conflicts.map((c) => c.dependencyId).join(',')} --from-tag=${conflicts[0].dependencyTag} --to-tag=${targetTag}`
2881 | );
2882 | }
2883 | }
2884 |
2885 | /**
2886 | * Helper function to format task ID for display, handling edge cases with explicit labels
2887 | * Builds on the existing formatTaskId utility but adds user-friendly display for edge cases
2888 | * @param {*} taskId - The task ID to format
2889 | * @returns {string} Formatted task ID for display
2890 | */
2891 | function formatTaskIdForDisplay(taskId) {
2892 | if (taskId === null) return 'null';
2893 | if (taskId === undefined) return 'undefined';
2894 | if (taskId === '') return '(empty)';
2895 |
2896 | // Use existing formatTaskId for normal cases, with fallback to 'unknown'
2897 | return formatTaskId(taskId) || 'unknown';
2898 | }
2899 |
2900 | /**
2901 | * Display enhanced error message for subtask movement restriction
2902 | * @param {string} taskId - The subtask ID that cannot be moved
2903 | * @param {string} sourceTag - Source tag name
2904 | * @param {string} targetTag - Target tag name
2905 | */
2906 | export function displaySubtaskMoveError(taskId, sourceTag, targetTag) {
2907 | // Handle null/undefined taskId but preserve the actual value for display
2908 | const displayTaskId = formatTaskIdForDisplay(taskId);
2909 |
2910 | // Safe taskId for operations that need a valid string
2911 | const safeTaskId = taskId || 'unknown';
2912 |
2913 | // Validate taskId format before splitting
2914 | let parentId = safeTaskId;
2915 | if (safeTaskId.includes('.')) {
2916 | const parts = safeTaskId.split('.');
2917 | // Check if it's a valid subtask format (parentId.subtaskId)
2918 | if (parts.length === 2 && parts[0] && parts[1]) {
2919 | parentId = parts[0];
2920 | } else {
2921 | // Invalid format - log warning and use the original taskId
2922 | console.log(
2923 | chalk.yellow(
2924 | `\n⚠️ Warning: Unexpected taskId format "${safeTaskId}". Using as-is for command suggestions.`
2925 | )
2926 | );
2927 | parentId = safeTaskId;
2928 | }
2929 | }
2930 |
2931 | console.log(
2932 | chalk.red(`\n❌ Cannot move subtask ${displayTaskId} directly between tags`)
2933 | );
2934 | console.log(chalk.yellow(`\nSubtask movement restriction:`));
2935 | console.log(` • Subtasks cannot be moved directly between tags`);
2936 | console.log(` • They must be promoted to full tasks first`);
2937 | console.log(` • Source tag: "${sourceTag}"`);
2938 | console.log(` • Target tag: "${targetTag}"`);
2939 |
2940 | console.log(chalk.cyan(`\nResolution options:`));
2941 | console.log(
2942 | ` 1. Promote subtask to full task: task-master remove-subtask --id=${displayTaskId} --convert`
2943 | );
2944 | console.log(
2945 | ` 2. Then move the promoted task: task-master move --from=${parentId} --from-tag=${sourceTag} --to-tag=${targetTag}`
2946 | );
2947 | console.log(
2948 | ` 3. Or move the parent task with all subtasks: task-master move --from=${parentId} --from-tag=${sourceTag} --to-tag=${targetTag} --with-dependencies`
2949 | );
2950 | }
2951 |
2952 | /**
2953 | * Display enhanced error message for invalid tag combinations
2954 | * @param {string} sourceTag - Source tag name
2955 | * @param {string} targetTag - Target tag name
2956 | * @param {string} reason - Reason for the error
2957 | */
2958 | export function displayInvalidTagCombinationError(
2959 | sourceTag,
2960 | targetTag,
2961 | reason
2962 | ) {
2963 | console.log(chalk.red(`\n❌ Invalid tag combination`));
2964 | console.log(chalk.yellow(`\nError details:`));
2965 | console.log(` • Source tag: "${sourceTag}"`);
2966 | console.log(` • Target tag: "${targetTag}"`);
2967 | console.log(` • Reason: ${reason}`);
2968 |
2969 | console.log(chalk.cyan(`\nResolution options:`));
2970 | console.log(` 1. Use different tags for cross-tag moves`);
2971 | console.log(
2972 | ` 2. Use within-tag move: task-master move --from=<id> --to=<id> --tag=${sourceTag}`
2973 | );
2974 | console.log(` 3. Check available tags: task-master tags`);
2975 | }
2976 |
2977 | /**
2978 | * Display helpful hints for dependency validation commands
2979 | * @param {string} context - Context for the hints (e.g., 'before-move', 'after-error')
2980 | */
2981 | export function displayDependencyValidationHints(context = 'general') {
2982 | const hints = {
2983 | 'before-move': [
2984 | '💡 Tip: Run "task-master validate-dependencies" to check for dependency issues before moving tasks',
2985 | '💡 Tip: Use "task-master fix-dependencies" to automatically resolve common dependency problems',
2986 | '💡 Tip: Consider using --with-dependencies flag to move dependent tasks together'
2987 | ],
2988 | 'after-error': [
2989 | '🔧 Quick fix: Run "task-master validate-dependencies" to identify specific issues',
2990 | '🔧 Quick fix: Use "task-master fix-dependencies" to automatically resolve problems',
2991 | '🔧 Quick fix: Check "task-master show <id>" to see task dependencies before moving'
2992 | ],
2993 | general: [
2994 | '💡 Use "task-master validate-dependencies" to check for dependency issues',
2995 | '💡 Use "task-master fix-dependencies" to automatically resolve problems',
2996 | '💡 Use "task-master show <id>" to view task dependencies',
2997 | '💡 Use --with-dependencies flag to move dependent tasks together'
2998 | ]
2999 | };
3000 |
3001 | const relevantHints = hints[context] || hints.general;
3002 |
3003 | console.log(chalk.cyan(`\nHelpful hints:`));
3004 | // Convert to Set to ensure only unique hints are displayed
3005 | const uniqueHints = new Set(relevantHints);
3006 | uniqueHints.forEach((hint) => {
3007 | console.log(` ${hint}`);
3008 | });
3009 | }
3010 |
```