This is page 35 of 38. Use http://codebase.md/eyaltoledano/claude-task-master?page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ └── README.md ├── .claude │ ├── agents │ │ ├── task-checker.md │ │ ├── task-executor.md │ │ └── task-orchestrator.md │ ├── commands │ │ ├── dedupe.md │ │ └── tm │ │ ├── add-dependency │ │ │ └── add-dependency.md │ │ ├── add-subtask │ │ │ ├── add-subtask.md │ │ │ └── convert-task-to-subtask.md │ │ ├── add-task │ │ │ └── add-task.md │ │ ├── analyze-complexity │ │ │ └── analyze-complexity.md │ │ ├── complexity-report │ │ │ └── complexity-report.md │ │ ├── expand │ │ │ ├── expand-all-tasks.md │ │ │ └── expand-task.md │ │ ├── fix-dependencies │ │ │ └── fix-dependencies.md │ │ ├── generate │ │ │ └── generate-tasks.md │ │ ├── help.md │ │ ├── init │ │ │ ├── init-project-quick.md │ │ │ └── init-project.md │ │ ├── learn.md │ │ ├── list │ │ │ ├── list-tasks-by-status.md │ │ │ ├── list-tasks-with-subtasks.md │ │ │ └── list-tasks.md │ │ ├── models │ │ │ ├── setup-models.md │ │ │ └── view-models.md │ │ ├── next │ │ │ └── next-task.md │ │ ├── parse-prd │ │ │ ├── parse-prd-with-research.md │ │ │ └── parse-prd.md │ │ ├── remove-dependency │ │ │ └── remove-dependency.md │ │ ├── remove-subtask │ │ │ └── remove-subtask.md │ │ ├── remove-subtasks │ │ │ ├── remove-all-subtasks.md │ │ │ └── remove-subtasks.md │ │ ├── remove-task │ │ │ └── remove-task.md │ │ ├── set-status │ │ │ ├── to-cancelled.md │ │ │ ├── to-deferred.md │ │ │ ├── to-done.md │ │ │ ├── to-in-progress.md │ │ │ ├── to-pending.md │ │ │ └── to-review.md │ │ ├── setup │ │ │ ├── install-taskmaster.md │ │ │ └── quick-install-taskmaster.md │ │ ├── show │ │ │ └── show-task.md │ │ ├── status │ │ │ └── project-status.md │ │ ├── sync-readme │ │ │ └── sync-readme.md │ │ ├── tm-main.md │ │ ├── update │ │ │ ├── update-single-task.md │ │ │ ├── update-task.md │ │ │ └── update-tasks-from-id.md │ │ ├── utils │ │ │ └── analyze-project.md │ │ ├── validate-dependencies │ │ │ └── validate-dependencies.md │ │ └── workflows │ │ ├── auto-implement-tasks.md │ │ ├── command-pipeline.md │ │ └── smart-workflow.md │ └── TM_COMMANDS_GUIDE.md ├── .coderabbit.yaml ├── .cursor │ ├── mcp.json │ └── rules │ ├── ai_providers.mdc │ ├── ai_services.mdc │ ├── architecture.mdc │ ├── changeset.mdc │ ├── commands.mdc │ ├── context_gathering.mdc │ ├── cursor_rules.mdc │ ├── dependencies.mdc │ ├── dev_workflow.mdc │ ├── git_workflow.mdc │ ├── glossary.mdc │ ├── mcp.mdc │ ├── new_features.mdc │ ├── self_improve.mdc │ ├── tags.mdc │ ├── taskmaster.mdc │ ├── tasks.mdc │ ├── telemetry.mdc │ ├── test_workflow.mdc │ ├── tests.mdc │ ├── ui.mdc │ └── utilities.mdc ├── .cursorignore ├── .env.example ├── .github │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ ├── enhancements---feature-requests.md │ │ └── feedback.md │ ├── PULL_REQUEST_TEMPLATE │ │ ├── bugfix.md │ │ ├── config.yml │ │ ├── feature.md │ │ └── integration.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── scripts │ │ ├── auto-close-duplicates.mjs │ │ ├── backfill-duplicate-comments.mjs │ │ ├── check-pre-release-mode.mjs │ │ ├── parse-metrics.mjs │ │ ├── release.mjs │ │ ├── tag-extension.mjs │ │ └── utils.mjs │ └── workflows │ ├── auto-close-duplicates.yml │ ├── backfill-duplicate-comments.yml │ ├── ci.yml │ ├── claude-dedupe-issues.yml │ ├── claude-docs-trigger.yml │ ├── claude-docs-updater.yml │ ├── claude-issue-triage.yml │ ├── claude.yml │ ├── extension-ci.yml │ ├── extension-release.yml │ ├── log-issue-events.yml │ ├── pre-release.yml │ ├── release-check.yml │ ├── release.yml │ ├── update-models-md.yml │ └── weekly-metrics-discord.yml ├── .gitignore ├── .kiro │ ├── hooks │ │ ├── tm-code-change-task-tracker.kiro.hook │ │ ├── tm-complexity-analyzer.kiro.hook │ │ ├── tm-daily-standup-assistant.kiro.hook │ │ ├── tm-git-commit-task-linker.kiro.hook │ │ ├── tm-pr-readiness-checker.kiro.hook │ │ ├── tm-task-dependency-auto-progression.kiro.hook │ │ └── tm-test-success-task-completer.kiro.hook │ ├── settings │ │ └── mcp.json │ └── steering │ ├── dev_workflow.md │ ├── kiro_rules.md │ ├── self_improve.md │ ├── taskmaster_hooks_workflow.md │ └── taskmaster.md ├── .manypkg.json ├── .mcp.json ├── .npmignore ├── .nvmrc ├── .taskmaster │ ├── CLAUDE.md │ ├── config.json │ ├── docs │ │ ├── MIGRATION-ROADMAP.md │ │ ├── prd-tm-start.txt │ │ ├── prd.txt │ │ ├── README.md │ │ ├── research │ │ │ ├── 2025-06-14_how-can-i-improve-the-scope-up-and-scope-down-comm.md │ │ │ ├── 2025-06-14_should-i-be-using-any-specific-libraries-for-this.md │ │ │ ├── 2025-06-14_test-save-functionality.md │ │ │ ├── 2025-06-14_test-the-fix-for-duplicate-saves-final-test.md │ │ │ └── 2025-08-01_do-we-need-to-add-new-commands-or-can-we-just-weap.md │ │ ├── task-template-importing-prd.txt │ │ ├── test-prd.txt │ │ └── tm-core-phase-1.txt │ ├── reports │ │ ├── task-complexity-report_cc-kiro-hooks.json │ │ ├── task-complexity-report_test-prd-tag.json │ │ ├── task-complexity-report_tm-core-phase-1.json │ │ ├── task-complexity-report.json │ │ └── tm-core-complexity.json │ ├── state.json │ ├── tasks │ │ ├── task_001_tm-start.txt │ │ ├── task_002_tm-start.txt │ │ ├── task_003_tm-start.txt │ │ ├── task_004_tm-start.txt │ │ ├── task_007_tm-start.txt │ │ └── tasks.json │ └── templates │ └── example_prd.txt ├── .vscode │ ├── extensions.json │ └── settings.json ├── apps │ ├── cli │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ ├── commands │ │ │ │ ├── auth.command.ts │ │ │ │ ├── context.command.ts │ │ │ │ ├── list.command.ts │ │ │ │ ├── set-status.command.ts │ │ │ │ ├── show.command.ts │ │ │ │ └── start.command.ts │ │ │ ├── index.ts │ │ │ ├── ui │ │ │ │ ├── components │ │ │ │ │ ├── dashboard.component.ts │ │ │ │ │ ├── header.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── next-task.component.ts │ │ │ │ │ ├── suggested-steps.component.ts │ │ │ │ │ └── task-detail.component.ts │ │ │ │ └── index.ts │ │ │ └── utils │ │ │ ├── auto-update.ts │ │ │ └── ui.ts │ │ └── tsconfig.json │ ├── docs │ │ ├── archive │ │ │ ├── ai-client-utils-example.mdx │ │ │ ├── ai-development-workflow.mdx │ │ │ ├── command-reference.mdx │ │ │ ├── configuration.mdx │ │ │ ├── cursor-setup.mdx │ │ │ ├── examples.mdx │ │ │ └── Installation.mdx │ │ ├── best-practices │ │ │ ├── advanced-tasks.mdx │ │ │ ├── configuration-advanced.mdx │ │ │ └── index.mdx │ │ ├── capabilities │ │ │ ├── cli-root-commands.mdx │ │ │ ├── index.mdx │ │ │ ├── mcp.mdx │ │ │ └── task-structure.mdx │ │ ├── CHANGELOG.md │ │ ├── docs.json │ │ ├── favicon.svg │ │ ├── getting-started │ │ │ ├── contribute.mdx │ │ │ ├── faq.mdx │ │ │ └── quick-start │ │ │ ├── configuration-quick.mdx │ │ │ ├── execute-quick.mdx │ │ │ ├── installation.mdx │ │ │ ├── moving-forward.mdx │ │ │ ├── prd-quick.mdx │ │ │ ├── quick-start.mdx │ │ │ ├── requirements.mdx │ │ │ ├── rules-quick.mdx │ │ │ └── tasks-quick.mdx │ │ ├── introduction.mdx │ │ ├── licensing.md │ │ ├── logo │ │ │ ├── dark.svg │ │ │ ├── light.svg │ │ │ └── task-master-logo.png │ │ ├── package.json │ │ ├── README.md │ │ ├── style.css │ │ ├── vercel.json │ │ └── whats-new.mdx │ └── extension │ ├── .vscodeignore │ ├── assets │ │ ├── banner.png │ │ ├── icon-dark.svg │ │ ├── icon-light.svg │ │ ├── icon.png │ │ ├── screenshots │ │ │ ├── kanban-board.png │ │ │ └── task-details.png │ │ └── sidebar-icon.svg │ ├── CHANGELOG.md │ ├── components.json │ ├── docs │ │ ├── extension-CI-setup.md │ │ └── extension-development-guide.md │ ├── esbuild.js │ ├── LICENSE │ ├── package.json │ ├── package.mjs │ ├── package.publish.json │ ├── README.md │ ├── src │ │ ├── components │ │ │ ├── ConfigView.tsx │ │ │ ├── constants.ts │ │ │ ├── TaskDetails │ │ │ │ ├── AIActionsSection.tsx │ │ │ │ ├── DetailsSection.tsx │ │ │ │ ├── PriorityBadge.tsx │ │ │ │ ├── SubtasksSection.tsx │ │ │ │ ├── TaskMetadataSidebar.tsx │ │ │ │ └── useTaskDetails.ts │ │ │ ├── TaskDetailsView.tsx │ │ │ ├── TaskMasterLogo.tsx │ │ │ └── ui │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── CollapsibleSection.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── label.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── separator.tsx │ │ │ ├── shadcn-io │ │ │ │ └── kanban │ │ │ │ └── index.tsx │ │ │ └── textarea.tsx │ │ ├── extension.ts │ │ ├── index.ts │ │ ├── lib │ │ │ └── utils.ts │ │ ├── services │ │ │ ├── config-service.ts │ │ │ ├── error-handler.ts │ │ │ ├── notification-preferences.ts │ │ │ ├── polling-service.ts │ │ │ ├── polling-strategies.ts │ │ │ ├── sidebar-webview-manager.ts │ │ │ ├── task-repository.ts │ │ │ ├── terminal-manager.ts │ │ │ └── webview-manager.ts │ │ ├── test │ │ │ └── extension.test.ts │ │ ├── utils │ │ │ ├── configManager.ts │ │ │ ├── connectionManager.ts │ │ │ ├── errorHandler.ts │ │ │ ├── event-emitter.ts │ │ │ ├── logger.ts │ │ │ ├── mcpClient.ts │ │ │ ├── notificationPreferences.ts │ │ │ └── task-master-api │ │ │ ├── cache │ │ │ │ └── cache-manager.ts │ │ │ ├── index.ts │ │ │ ├── mcp-client.ts │ │ │ ├── transformers │ │ │ │ └── task-transformer.ts │ │ │ └── types │ │ │ └── index.ts │ │ └── webview │ │ ├── App.tsx │ │ ├── components │ │ │ ├── AppContent.tsx │ │ │ ├── EmptyState.tsx │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── PollingStatus.tsx │ │ │ ├── PriorityBadge.tsx │ │ │ ├── SidebarView.tsx │ │ │ ├── TagDropdown.tsx │ │ │ ├── TaskCard.tsx │ │ │ ├── TaskEditModal.tsx │ │ │ ├── TaskMasterKanban.tsx │ │ │ ├── ToastContainer.tsx │ │ │ └── ToastNotification.tsx │ │ ├── constants │ │ │ └── index.ts │ │ ├── contexts │ │ │ └── VSCodeContext.tsx │ │ ├── hooks │ │ │ ├── useTaskQueries.ts │ │ │ ├── useVSCodeMessages.ts │ │ │ └── useWebviewHeight.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── providers │ │ │ └── QueryProvider.tsx │ │ ├── reducers │ │ │ └── appReducer.ts │ │ ├── sidebar.tsx │ │ ├── types │ │ │ └── index.ts │ │ └── utils │ │ ├── logger.ts │ │ └── toast.ts │ └── tsconfig.json ├── assets │ ├── .windsurfrules │ ├── AGENTS.md │ ├── claude │ │ ├── agents │ │ │ ├── task-checker.md │ │ │ ├── task-executor.md │ │ │ └── task-orchestrator.md │ │ ├── commands │ │ │ └── tm │ │ │ ├── add-dependency │ │ │ │ └── add-dependency.md │ │ │ ├── add-subtask │ │ │ │ ├── add-subtask.md │ │ │ │ └── convert-task-to-subtask.md │ │ │ ├── add-task │ │ │ │ └── add-task.md │ │ │ ├── analyze-complexity │ │ │ │ └── analyze-complexity.md │ │ │ ├── clear-subtasks │ │ │ │ ├── clear-all-subtasks.md │ │ │ │ └── clear-subtasks.md │ │ │ ├── complexity-report │ │ │ │ └── complexity-report.md │ │ │ ├── expand │ │ │ │ ├── expand-all-tasks.md │ │ │ │ └── expand-task.md │ │ │ ├── fix-dependencies │ │ │ │ └── fix-dependencies.md │ │ │ ├── generate │ │ │ │ └── generate-tasks.md │ │ │ ├── help.md │ │ │ ├── init │ │ │ │ ├── init-project-quick.md │ │ │ │ └── init-project.md │ │ │ ├── learn.md │ │ │ ├── list │ │ │ │ ├── list-tasks-by-status.md │ │ │ │ ├── list-tasks-with-subtasks.md │ │ │ │ └── list-tasks.md │ │ │ ├── models │ │ │ │ ├── setup-models.md │ │ │ │ └── view-models.md │ │ │ ├── next │ │ │ │ └── next-task.md │ │ │ ├── parse-prd │ │ │ │ ├── parse-prd-with-research.md │ │ │ │ └── parse-prd.md │ │ │ ├── remove-dependency │ │ │ │ └── remove-dependency.md │ │ │ ├── remove-subtask │ │ │ │ └── remove-subtask.md │ │ │ ├── remove-subtasks │ │ │ │ ├── remove-all-subtasks.md │ │ │ │ └── remove-subtasks.md │ │ │ ├── remove-task │ │ │ │ └── remove-task.md │ │ │ ├── set-status │ │ │ │ ├── to-cancelled.md │ │ │ │ ├── to-deferred.md │ │ │ │ ├── to-done.md │ │ │ │ ├── to-in-progress.md │ │ │ │ ├── to-pending.md │ │ │ │ └── to-review.md │ │ │ ├── setup │ │ │ │ ├── install-taskmaster.md │ │ │ │ └── quick-install-taskmaster.md │ │ │ ├── show │ │ │ │ └── show-task.md │ │ │ ├── status │ │ │ │ └── project-status.md │ │ │ ├── sync-readme │ │ │ │ └── sync-readme.md │ │ │ ├── tm-main.md │ │ │ ├── update │ │ │ │ ├── update-single-task.md │ │ │ │ ├── update-task.md │ │ │ │ └── update-tasks-from-id.md │ │ │ ├── utils │ │ │ │ └── analyze-project.md │ │ │ ├── validate-dependencies │ │ │ │ └── validate-dependencies.md │ │ │ └── workflows │ │ │ ├── auto-implement-tasks.md │ │ │ ├── command-pipeline.md │ │ │ └── smart-workflow.md │ │ └── TM_COMMANDS_GUIDE.md │ ├── config.json │ ├── env.example │ ├── example_prd.txt │ ├── gitignore │ ├── kiro-hooks │ │ ├── tm-code-change-task-tracker.kiro.hook │ │ ├── tm-complexity-analyzer.kiro.hook │ │ ├── tm-daily-standup-assistant.kiro.hook │ │ ├── tm-git-commit-task-linker.kiro.hook │ │ ├── tm-pr-readiness-checker.kiro.hook │ │ ├── tm-task-dependency-auto-progression.kiro.hook │ │ └── tm-test-success-task-completer.kiro.hook │ ├── roocode │ │ ├── .roo │ │ │ ├── rules-architect │ │ │ │ └── architect-rules │ │ │ ├── rules-ask │ │ │ │ └── ask-rules │ │ │ ├── rules-code │ │ │ │ └── code-rules │ │ │ ├── rules-debug │ │ │ │ └── debug-rules │ │ │ ├── rules-orchestrator │ │ │ │ └── orchestrator-rules │ │ │ └── rules-test │ │ │ └── test-rules │ │ └── .roomodes │ ├── rules │ │ ├── cursor_rules.mdc │ │ ├── dev_workflow.mdc │ │ ├── self_improve.mdc │ │ ├── taskmaster_hooks_workflow.mdc │ │ └── taskmaster.mdc │ └── scripts_README.md ├── bin │ └── task-master.js ├── biome.json ├── CHANGELOG.md ├── CLAUDE.md ├── context │ ├── chats │ │ ├── add-task-dependencies-1.md │ │ └── max-min-tokens.txt.md │ ├── fastmcp-core.txt │ ├── fastmcp-docs.txt │ ├── MCP_INTEGRATION.md │ ├── mcp-js-sdk-docs.txt │ ├── mcp-protocol-repo.txt │ ├── mcp-protocol-schema-03262025.json │ └── mcp-protocol-spec.txt ├── CONTRIBUTING.md ├── docs │ ├── CLI-COMMANDER-PATTERN.md │ ├── command-reference.md │ ├── configuration.md │ ├── contributor-docs │ │ └── testing-roo-integration.md │ ├── cross-tag-task-movement.md │ ├── examples │ │ └── claude-code-usage.md │ ├── examples.md │ ├── licensing.md │ ├── mcp-provider-guide.md │ ├── mcp-provider.md │ ├── migration-guide.md │ ├── models.md │ ├── providers │ │ └── gemini-cli.md │ ├── README.md │ ├── scripts │ │ └── models-json-to-markdown.js │ ├── task-structure.md │ └── tutorial.md ├── images │ └── logo.png ├── index.js ├── jest.config.js ├── jest.resolver.cjs ├── LICENSE ├── llms-install.md ├── mcp-server │ ├── server.js │ └── src │ ├── core │ │ ├── __tests__ │ │ │ └── context-manager.test.js │ │ ├── context-manager.js │ │ ├── direct-functions │ │ │ ├── add-dependency.js │ │ │ ├── add-subtask.js │ │ │ ├── add-tag.js │ │ │ ├── add-task.js │ │ │ ├── analyze-task-complexity.js │ │ │ ├── cache-stats.js │ │ │ ├── clear-subtasks.js │ │ │ ├── complexity-report.js │ │ │ ├── copy-tag.js │ │ │ ├── create-tag-from-branch.js │ │ │ ├── delete-tag.js │ │ │ ├── expand-all-tasks.js │ │ │ ├── expand-task.js │ │ │ ├── fix-dependencies.js │ │ │ ├── generate-task-files.js │ │ │ ├── initialize-project.js │ │ │ ├── list-tags.js │ │ │ ├── list-tasks.js │ │ │ ├── models.js │ │ │ ├── move-task-cross-tag.js │ │ │ ├── move-task.js │ │ │ ├── next-task.js │ │ │ ├── parse-prd.js │ │ │ ├── remove-dependency.js │ │ │ ├── remove-subtask.js │ │ │ ├── remove-task.js │ │ │ ├── rename-tag.js │ │ │ ├── research.js │ │ │ ├── response-language.js │ │ │ ├── rules.js │ │ │ ├── scope-down.js │ │ │ ├── scope-up.js │ │ │ ├── set-task-status.js │ │ │ ├── show-task.js │ │ │ ├── update-subtask-by-id.js │ │ │ ├── update-task-by-id.js │ │ │ ├── update-tasks.js │ │ │ ├── use-tag.js │ │ │ └── validate-dependencies.js │ │ ├── task-master-core.js │ │ └── utils │ │ ├── env-utils.js │ │ └── path-utils.js │ ├── custom-sdk │ │ ├── errors.js │ │ ├── index.js │ │ ├── json-extractor.js │ │ ├── language-model.js │ │ ├── message-converter.js │ │ └── schema-converter.js │ ├── index.js │ ├── logger.js │ ├── providers │ │ └── mcp-provider.js │ └── tools │ ├── add-dependency.js │ ├── add-subtask.js │ ├── add-tag.js │ ├── add-task.js │ ├── analyze.js │ ├── clear-subtasks.js │ ├── complexity-report.js │ ├── copy-tag.js │ ├── delete-tag.js │ ├── expand-all.js │ ├── expand-task.js │ ├── fix-dependencies.js │ ├── generate.js │ ├── get-operation-status.js │ ├── get-task.js │ ├── get-tasks.js │ ├── index.js │ ├── initialize-project.js │ ├── list-tags.js │ ├── models.js │ ├── move-task.js │ ├── next-task.js │ ├── parse-prd.js │ ├── remove-dependency.js │ ├── remove-subtask.js │ ├── remove-task.js │ ├── rename-tag.js │ ├── research.js │ ├── response-language.js │ ├── rules.js │ ├── scope-down.js │ ├── scope-up.js │ ├── set-task-status.js │ ├── update-subtask.js │ ├── update-task.js │ ├── update.js │ ├── use-tag.js │ ├── utils.js │ └── validate-dependencies.js ├── mcp-test.js ├── output.json ├── package-lock.json ├── package.json ├── packages │ ├── build-config │ │ ├── CHANGELOG.md │ │ ├── package.json │ │ ├── src │ │ │ └── tsdown.base.ts │ │ └── tsconfig.json │ └── tm-core │ ├── .gitignore │ ├── CHANGELOG.md │ ├── docs │ │ └── listTasks-architecture.md │ ├── package.json │ ├── POC-STATUS.md │ ├── README.md │ ├── src │ │ ├── auth │ │ │ ├── auth-manager.test.ts │ │ │ ├── auth-manager.ts │ │ │ ├── config.ts │ │ │ ├── credential-store.test.ts │ │ │ ├── credential-store.ts │ │ │ ├── index.ts │ │ │ ├── oauth-service.ts │ │ │ ├── supabase-session-storage.ts │ │ │ └── types.ts │ │ ├── clients │ │ │ ├── index.ts │ │ │ └── supabase-client.ts │ │ ├── config │ │ │ ├── config-manager.spec.ts │ │ │ ├── config-manager.ts │ │ │ ├── index.ts │ │ │ └── services │ │ │ ├── config-loader.service.spec.ts │ │ │ ├── config-loader.service.ts │ │ │ ├── config-merger.service.spec.ts │ │ │ ├── config-merger.service.ts │ │ │ ├── config-persistence.service.spec.ts │ │ │ ├── config-persistence.service.ts │ │ │ ├── environment-config-provider.service.spec.ts │ │ │ ├── environment-config-provider.service.ts │ │ │ ├── index.ts │ │ │ ├── runtime-state-manager.service.spec.ts │ │ │ └── runtime-state-manager.service.ts │ │ ├── constants │ │ │ └── index.ts │ │ ├── entities │ │ │ └── task.entity.ts │ │ ├── errors │ │ │ ├── index.ts │ │ │ └── task-master-error.ts │ │ ├── executors │ │ │ ├── base-executor.ts │ │ │ ├── claude-executor.ts │ │ │ ├── executor-factory.ts │ │ │ ├── executor-service.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── interfaces │ │ │ ├── ai-provider.interface.ts │ │ │ ├── configuration.interface.ts │ │ │ ├── index.ts │ │ │ └── storage.interface.ts │ │ ├── logger │ │ │ ├── factory.ts │ │ │ ├── index.ts │ │ │ └── logger.ts │ │ ├── mappers │ │ │ └── TaskMapper.ts │ │ ├── parser │ │ │ └── index.ts │ │ ├── providers │ │ │ ├── ai │ │ │ │ ├── base-provider.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── repositories │ │ │ ├── supabase-task-repository.ts │ │ │ └── task-repository.interface.ts │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── organization.service.ts │ │ │ ├── task-execution-service.ts │ │ │ └── task-service.ts │ │ ├── storage │ │ │ ├── api-storage.ts │ │ │ ├── file-storage │ │ │ │ ├── file-operations.ts │ │ │ │ ├── file-storage.ts │ │ │ │ ├── format-handler.ts │ │ │ │ ├── index.ts │ │ │ │ └── path-resolver.ts │ │ │ ├── index.ts │ │ │ └── storage-factory.ts │ │ ├── subpath-exports.test.ts │ │ ├── task-master-core.ts │ │ ├── types │ │ │ ├── database.types.ts │ │ │ ├── index.ts │ │ │ └── legacy.ts │ │ └── utils │ │ ├── id-generator.ts │ │ └── index.ts │ ├── tests │ │ ├── integration │ │ │ └── list-tasks.test.ts │ │ ├── mocks │ │ │ └── mock-provider.ts │ │ ├── setup.ts │ │ └── unit │ │ ├── base-provider.test.ts │ │ ├── executor.test.ts │ │ └── smoke.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── README-task-master.md ├── README.md ├── scripts │ ├── dev.js │ ├── init.js │ ├── modules │ │ ├── ai-services-unified.js │ │ ├── commands.js │ │ ├── config-manager.js │ │ ├── dependency-manager.js │ │ ├── index.js │ │ ├── prompt-manager.js │ │ ├── supported-models.json │ │ ├── sync-readme.js │ │ ├── task-manager │ │ │ ├── add-subtask.js │ │ │ ├── add-task.js │ │ │ ├── analyze-task-complexity.js │ │ │ ├── clear-subtasks.js │ │ │ ├── expand-all-tasks.js │ │ │ ├── expand-task.js │ │ │ ├── find-next-task.js │ │ │ ├── generate-task-files.js │ │ │ ├── is-task-dependent.js │ │ │ ├── list-tasks.js │ │ │ ├── migrate.js │ │ │ ├── models.js │ │ │ ├── move-task.js │ │ │ ├── parse-prd │ │ │ │ ├── index.js │ │ │ │ ├── parse-prd-config.js │ │ │ │ ├── parse-prd-helpers.js │ │ │ │ ├── parse-prd-non-streaming.js │ │ │ │ ├── parse-prd-streaming.js │ │ │ │ └── parse-prd.js │ │ │ ├── remove-subtask.js │ │ │ ├── remove-task.js │ │ │ ├── research.js │ │ │ ├── response-language.js │ │ │ ├── scope-adjustment.js │ │ │ ├── set-task-status.js │ │ │ ├── tag-management.js │ │ │ ├── task-exists.js │ │ │ ├── update-single-task-status.js │ │ │ ├── update-subtask-by-id.js │ │ │ ├── update-task-by-id.js │ │ │ └── update-tasks.js │ │ ├── task-manager.js │ │ ├── ui.js │ │ ├── update-config-tokens.js │ │ ├── utils │ │ │ ├── contextGatherer.js │ │ │ ├── fuzzyTaskSearch.js │ │ │ └── git-utils.js │ │ └── utils.js │ ├── task-complexity-report.json │ ├── test-claude-errors.js │ └── test-claude.js ├── src │ ├── ai-providers │ │ ├── anthropic.js │ │ ├── azure.js │ │ ├── base-provider.js │ │ ├── bedrock.js │ │ ├── claude-code.js │ │ ├── custom-sdk │ │ │ ├── claude-code │ │ │ │ ├── errors.js │ │ │ │ ├── index.js │ │ │ │ ├── json-extractor.js │ │ │ │ ├── language-model.js │ │ │ │ ├── message-converter.js │ │ │ │ └── types.js │ │ │ └── grok-cli │ │ │ ├── errors.js │ │ │ ├── index.js │ │ │ ├── json-extractor.js │ │ │ ├── language-model.js │ │ │ ├── message-converter.js │ │ │ └── types.js │ │ ├── gemini-cli.js │ │ ├── google-vertex.js │ │ ├── google.js │ │ ├── grok-cli.js │ │ ├── groq.js │ │ ├── index.js │ │ ├── ollama.js │ │ ├── openai.js │ │ ├── openrouter.js │ │ ├── perplexity.js │ │ └── xai.js │ ├── constants │ │ ├── commands.js │ │ ├── paths.js │ │ ├── profiles.js │ │ ├── providers.js │ │ ├── rules-actions.js │ │ ├── task-priority.js │ │ └── task-status.js │ ├── profiles │ │ ├── amp.js │ │ ├── base-profile.js │ │ ├── claude.js │ │ ├── cline.js │ │ ├── codex.js │ │ ├── cursor.js │ │ ├── gemini.js │ │ ├── index.js │ │ ├── kilo.js │ │ ├── kiro.js │ │ ├── opencode.js │ │ ├── roo.js │ │ ├── trae.js │ │ ├── vscode.js │ │ ├── windsurf.js │ │ └── zed.js │ ├── progress │ │ ├── base-progress-tracker.js │ │ ├── cli-progress-factory.js │ │ ├── parse-prd-tracker.js │ │ ├── progress-tracker-builder.js │ │ └── tracker-ui.js │ ├── prompts │ │ ├── add-task.json │ │ ├── analyze-complexity.json │ │ ├── expand-task.json │ │ ├── parse-prd.json │ │ ├── README.md │ │ ├── research.json │ │ ├── schemas │ │ │ ├── parameter.schema.json │ │ │ ├── prompt-template.schema.json │ │ │ ├── README.md │ │ │ └── variant.schema.json │ │ ├── update-subtask.json │ │ ├── update-task.json │ │ └── update-tasks.json │ ├── provider-registry │ │ └── index.js │ ├── task-master.js │ ├── ui │ │ ├── confirm.js │ │ ├── indicators.js │ │ └── parse-prd.js │ └── utils │ ├── asset-resolver.js │ ├── create-mcp-config.js │ ├── format.js │ ├── getVersion.js │ ├── logger-utils.js │ ├── manage-gitignore.js │ ├── path-utils.js │ ├── profiles.js │ ├── rule-transformer.js │ ├── stream-parser.js │ └── timeout-manager.js ├── test-clean-tags.js ├── test-config-manager.js ├── test-prd.txt ├── test-tag-functions.js ├── test-version-check-full.js ├── test-version-check.js ├── tests │ ├── e2e │ │ ├── e2e_helpers.sh │ │ ├── parse_llm_output.cjs │ │ ├── run_e2e.sh │ │ ├── run_fallback_verification.sh │ │ └── test_llm_analysis.sh │ ├── fixture │ │ └── test-tasks.json │ ├── fixtures │ │ ├── .taskmasterconfig │ │ ├── sample-claude-response.js │ │ ├── sample-prd.txt │ │ └── sample-tasks.js │ ├── integration │ │ ├── claude-code-optional.test.js │ │ ├── cli │ │ │ ├── commands.test.js │ │ │ ├── complex-cross-tag-scenarios.test.js │ │ │ └── move-cross-tag.test.js │ │ ├── manage-gitignore.test.js │ │ ├── mcp-server │ │ │ └── direct-functions.test.js │ │ ├── move-task-cross-tag.integration.test.js │ │ ├── move-task-simple.integration.test.js │ │ └── profiles │ │ ├── amp-init-functionality.test.js │ │ ├── claude-init-functionality.test.js │ │ ├── cline-init-functionality.test.js │ │ ├── codex-init-functionality.test.js │ │ ├── cursor-init-functionality.test.js │ │ ├── gemini-init-functionality.test.js │ │ ├── opencode-init-functionality.test.js │ │ ├── roo-files-inclusion.test.js │ │ ├── roo-init-functionality.test.js │ │ ├── rules-files-inclusion.test.js │ │ ├── trae-init-functionality.test.js │ │ ├── vscode-init-functionality.test.js │ │ └── windsurf-init-functionality.test.js │ ├── manual │ │ ├── progress │ │ │ ├── parse-prd-analysis.js │ │ │ ├── test-parse-prd.js │ │ │ └── TESTING_GUIDE.md │ │ └── prompts │ │ ├── prompt-test.js │ │ └── README.md │ ├── README.md │ ├── setup.js │ └── unit │ ├── ai-providers │ │ ├── claude-code.test.js │ │ ├── custom-sdk │ │ │ └── claude-code │ │ │ └── language-model.test.js │ │ ├── gemini-cli.test.js │ │ ├── mcp-components.test.js │ │ └── openai.test.js │ ├── ai-services-unified.test.js │ ├── commands.test.js │ ├── config-manager.test.js │ ├── config-manager.test.mjs │ ├── dependency-manager.test.js │ ├── init.test.js │ ├── initialize-project.test.js │ ├── kebab-case-validation.test.js │ ├── manage-gitignore.test.js │ ├── mcp │ │ └── tools │ │ ├── __mocks__ │ │ │ └── move-task.js │ │ ├── add-task.test.js │ │ ├── analyze-complexity.test.js │ │ ├── expand-all.test.js │ │ ├── get-tasks.test.js │ │ ├── initialize-project.test.js │ │ ├── move-task-cross-tag-options.test.js │ │ ├── move-task-cross-tag.test.js │ │ └── remove-task.test.js │ ├── mcp-providers │ │ ├── mcp-components.test.js │ │ └── mcp-provider.test.js │ ├── parse-prd.test.js │ ├── profiles │ │ ├── amp-integration.test.js │ │ ├── claude-integration.test.js │ │ ├── cline-integration.test.js │ │ ├── codex-integration.test.js │ │ ├── cursor-integration.test.js │ │ ├── gemini-integration.test.js │ │ ├── kilo-integration.test.js │ │ ├── kiro-integration.test.js │ │ ├── mcp-config-validation.test.js │ │ ├── opencode-integration.test.js │ │ ├── profile-safety-check.test.js │ │ ├── roo-integration.test.js │ │ ├── rule-transformer-cline.test.js │ │ ├── rule-transformer-cursor.test.js │ │ ├── rule-transformer-gemini.test.js │ │ ├── rule-transformer-kilo.test.js │ │ ├── rule-transformer-kiro.test.js │ │ ├── rule-transformer-opencode.test.js │ │ ├── rule-transformer-roo.test.js │ │ ├── rule-transformer-trae.test.js │ │ ├── rule-transformer-vscode.test.js │ │ ├── rule-transformer-windsurf.test.js │ │ ├── rule-transformer-zed.test.js │ │ ├── rule-transformer.test.js │ │ ├── selective-profile-removal.test.js │ │ ├── subdirectory-support.test.js │ │ ├── trae-integration.test.js │ │ ├── vscode-integration.test.js │ │ ├── windsurf-integration.test.js │ │ └── zed-integration.test.js │ ├── progress │ │ └── base-progress-tracker.test.js │ ├── prompt-manager.test.js │ ├── prompts │ │ └── expand-task-prompt.test.js │ ├── providers │ │ └── provider-registry.test.js │ ├── scripts │ │ └── modules │ │ ├── commands │ │ │ ├── move-cross-tag.test.js │ │ │ └── README.md │ │ ├── dependency-manager │ │ │ ├── circular-dependencies.test.js │ │ │ ├── cross-tag-dependencies.test.js │ │ │ └── fix-dependencies-command.test.js │ │ ├── task-manager │ │ │ ├── add-subtask.test.js │ │ │ ├── add-task.test.js │ │ │ ├── analyze-task-complexity.test.js │ │ │ ├── clear-subtasks.test.js │ │ │ ├── complexity-report-tag-isolation.test.js │ │ │ ├── expand-all-tasks.test.js │ │ │ ├── expand-task.test.js │ │ │ ├── find-next-task.test.js │ │ │ ├── generate-task-files.test.js │ │ │ ├── list-tasks.test.js │ │ │ ├── move-task-cross-tag.test.js │ │ │ ├── move-task.test.js │ │ │ ├── parse-prd.test.js │ │ │ ├── remove-subtask.test.js │ │ │ ├── remove-task.test.js │ │ │ ├── research.test.js │ │ │ ├── scope-adjustment.test.js │ │ │ ├── set-task-status.test.js │ │ │ ├── setup.js │ │ │ ├── update-single-task-status.test.js │ │ │ ├── update-subtask-by-id.test.js │ │ │ ├── update-task-by-id.test.js │ │ │ └── update-tasks.test.js │ │ ├── ui │ │ │ └── cross-tag-error-display.test.js │ │ └── utils-tag-aware-paths.test.js │ ├── task-finder.test.js │ ├── task-manager │ │ ├── clear-subtasks.test.js │ │ ├── move-task.test.js │ │ ├── tag-boundary.test.js │ │ └── tag-management.test.js │ ├── task-master.test.js │ ├── ui │ │ └── indicators.test.js │ ├── ui.test.js │ ├── utils-strip-ansi.test.js │ └── utils.test.js ├── tsconfig.json ├── tsdown.config.ts └── turbo.json ``` # Files -------------------------------------------------------------------------------- /scripts/modules/ui.js: -------------------------------------------------------------------------------- ```javascript /** * ui.js * User interface functions for the Task Master CLI */ import chalk from 'chalk'; import figlet from 'figlet'; import boxen from 'boxen'; import ora from 'ora'; import Table from 'cli-table3'; import gradient from 'gradient-string'; import readline from 'readline'; import { log, findTaskById, readJSON, truncate, isSilentMode, formatTaskId } from './utils.js'; import fs from 'fs'; import { findNextTask, analyzeTaskComplexity, readComplexityReport } from './task-manager.js'; import { getProjectName, getDefaultSubtasks } from './config-manager.js'; import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; import { TASKMASTER_CONFIG_FILE, TASKMASTER_TASKS_FILE } from '../../src/constants/paths.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; // Create a color gradient for the banner const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']); const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']); /** * Display FYI notice about tagged task lists (only if migration occurred) * @param {Object} data - Data object that may contain _migrationHappened flag */ function displayTaggedTasksFYI(data) { if (isSilentMode() || !data || !data._migrationHappened) return; console.log( boxen( chalk.white.bold('FYI: ') + chalk.gray('Taskmaster now supports separate task lists per tag. ') + chalk.cyan( 'Use the --tag flag to create/read/update/filter tasks by tag.' ), { padding: { top: 0, bottom: 0, left: 2, right: 2 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 1 } } ) ); } /** * Display a small, non-intrusive indicator showing the current tag context * @param {string} tagName - The tag name to display * @param {Object} options - Display options * @param {boolean} [options.skipIfMaster=false] - Don't show indicator if tag is 'master' * @param {boolean} [options.dim=false] - Use dimmed styling */ function displayCurrentTagIndicator(tag, options = {}) { if (isSilentMode()) return; const { skipIfMaster = false, dim = false } = options; // Skip display for master tag only if explicitly requested if (skipIfMaster && tag === 'master') return; // Create a small, tasteful tag indicator const tagIcon = '🏷️'; const tagText = dim ? chalk.gray(`${tagIcon} tag: ${tag}`) : chalk.dim(`${tagIcon} tag: `) + chalk.cyan(tag); console.log(tagText); } /** * Display a fancy banner for the CLI */ function displayBanner() { if (isSilentMode()) return; // console.clear(); // Removing this to avoid clearing the terminal per command const bannerText = figlet.textSync('Task Master', { font: 'Standard', horizontalLayout: 'default', verticalLayout: 'default' }); console.log(coolGradient(bannerText)); // Add creator credit line below the banner console.log( chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano') ); // Read version directly from package.json const version = getTaskMasterVersion(); console.log( boxen( chalk.white( `${chalk.bold('Version:')} ${version} ${chalk.bold('Project:')} ${getProjectName(null)}` ), { padding: 1, margin: { top: 0, bottom: 1 }, borderStyle: 'round', borderColor: 'cyan' } ) ); } /** * Start a loading indicator with an animated spinner * @param {string} message - Message to display next to the spinner * @returns {Object} Spinner object */ function startLoadingIndicator(message) { if (isSilentMode()) return null; const spinner = ora({ text: message, color: 'cyan' }).start(); return spinner; } /** * Stop a loading indicator (basic stop, no success/fail indicator) * @param {Object} spinner - Spinner object to stop */ function stopLoadingIndicator(spinner) { if (spinner && typeof spinner.stop === 'function') { spinner.stop(); } } /** * Complete a loading indicator with success (shows checkmark) * @param {Object} spinner - Spinner object to complete * @param {string} message - Optional success message (defaults to current text) */ function succeedLoadingIndicator(spinner, message = null) { if (spinner && typeof spinner.succeed === 'function') { if (message) { spinner.succeed(message); } else { spinner.succeed(); } } } /** * Complete a loading indicator with failure (shows X) * @param {Object} spinner - Spinner object to fail * @param {string} message - Optional failure message (defaults to current text) */ function failLoadingIndicator(spinner, message = null) { if (spinner && typeof spinner.fail === 'function') { if (message) { spinner.fail(message); } else { spinner.fail(); } } } /** * Complete a loading indicator with warning (shows warning symbol) * @param {Object} spinner - Spinner object to warn * @param {string} message - Optional warning message (defaults to current text) */ function warnLoadingIndicator(spinner, message = null) { if (spinner && typeof spinner.warn === 'function') { if (message) { spinner.warn(message); } else { spinner.warn(); } } } /** * Complete a loading indicator with info (shows info symbol) * @param {Object} spinner - Spinner object to complete with info * @param {string} message - Optional info message (defaults to current text) */ function infoLoadingIndicator(spinner, message = null) { if (spinner && typeof spinner.info === 'function') { if (message) { spinner.info(message); } else { spinner.info(); } } } /** * Create a colored progress bar * @param {number} percent - The completion percentage * @param {number} length - The total length of the progress bar in characters * @param {Object} statusBreakdown - Optional breakdown of non-complete statuses (e.g., {pending: 20, 'in-progress': 10}) * @returns {string} The formatted progress bar */ function createProgressBar(percent, length = 30, statusBreakdown = null) { // Adjust the percent to treat deferred and cancelled as complete const effectivePercent = statusBreakdown ? Math.min( 100, percent + (statusBreakdown.deferred || 0) + (statusBreakdown.cancelled || 0) ) : percent; // Calculate how many characters to fill for "true completion" const trueCompletedFilled = Math.round((percent * length) / 100); // Calculate how many characters to fill for "effective completion" (including deferred/cancelled) const effectiveCompletedFilled = Math.round( (effectivePercent * length) / 100 ); // The "deferred/cancelled" section (difference between true and effective) const deferredCancelledFilled = effectiveCompletedFilled - trueCompletedFilled; // Set the empty section (remaining after effective completion) const empty = length - effectiveCompletedFilled; // Determine color based on percentage for the completed section let completedColor; if (percent < 25) { completedColor = chalk.red; } else if (percent < 50) { completedColor = chalk.hex('#FFA500'); // Orange } else if (percent < 75) { completedColor = chalk.yellow; } else if (percent < 100) { completedColor = chalk.green; } else { completedColor = chalk.hex('#006400'); // Dark green } // Create colored sections const completedSection = completedColor('█'.repeat(trueCompletedFilled)); // Gray section for deferred/cancelled items const deferredCancelledSection = chalk.gray( '█'.repeat(deferredCancelledFilled) ); // If we have a status breakdown, create a multi-colored remaining section let remainingSection = ''; if (statusBreakdown && empty > 0) { // Status colors (matching the statusConfig colors in getStatusWithColor) const statusColors = { pending: chalk.yellow, 'in-progress': chalk.hex('#FFA500'), // Orange blocked: chalk.red, review: chalk.magenta // Deferred and cancelled are treated as part of the completed section }; // Calculate proportions for each status const totalRemaining = Object.entries(statusBreakdown) .filter( ([status]) => !['deferred', 'cancelled', 'done', 'completed'].includes(status) ) .reduce((sum, [_, val]) => sum + val, 0); // If no remaining tasks with tracked statuses, just use gray if (totalRemaining <= 0) { remainingSection = chalk.gray('░'.repeat(empty)); } else { // Track how many characters we've added let addedChars = 0; // Add each status section proportionally for (const [status, percentage] of Object.entries(statusBreakdown)) { // Skip statuses that are considered complete if (['deferred', 'cancelled', 'done', 'completed'].includes(status)) continue; // Calculate how many characters this status should fill const statusChars = Math.round((percentage / totalRemaining) * empty); // Make sure we don't exceed the total length due to rounding const actualChars = Math.min(statusChars, empty - addedChars); // Add colored section for this status const colorFn = statusColors[status] || chalk.gray; remainingSection += colorFn('░'.repeat(actualChars)); addedChars += actualChars; } // If we have any remaining space due to rounding, fill with gray if (addedChars < empty) { remainingSection += chalk.gray('░'.repeat(empty - addedChars)); } } } else { // Default to gray for the empty section if no breakdown provided remainingSection = chalk.gray('░'.repeat(empty)); } // Effective percentage text color should reflect the highest category const percentTextColor = percent === 100 ? chalk.hex('#006400') // Dark green for 100% : effectivePercent === 100 ? chalk.gray // Gray for 100% with deferred/cancelled : completedColor; // Otherwise match the completed color // Build the complete progress bar return `${completedSection}${deferredCancelledSection}${remainingSection} ${percentTextColor(`${effectivePercent.toFixed(0)}%`)}`; } /** * Get a colored status string based on the status value * @param {string} status - Task status (e.g., "done", "pending", "in-progress") * @param {boolean} forTable - Whether the status is being displayed in a table * @returns {string} Colored status string */ function getStatusWithColor(status, forTable = false) { if (!status) { return chalk.gray('❓ unknown'); } const statusConfig = { done: { color: chalk.green, icon: '✓', tableIcon: '✓' }, completed: { color: chalk.green, icon: '✓', tableIcon: '✓' }, pending: { color: chalk.yellow, icon: '○', tableIcon: '⏱' }, 'in-progress': { color: chalk.hex('#FFA500'), icon: '🔄', tableIcon: '►' }, deferred: { color: chalk.gray, icon: 'x', tableIcon: '⏱' }, blocked: { color: chalk.red, icon: '!', tableIcon: '✗' }, review: { color: chalk.magenta, icon: '?', tableIcon: '?' }, cancelled: { color: chalk.gray, icon: '❌', tableIcon: 'x' } }; const config = statusConfig[status.toLowerCase()] || { color: chalk.red, icon: '❌', tableIcon: '✗' }; // Use simpler icons for table display to prevent border issues if (forTable) { // Use ASCII characters instead of Unicode for completely stable display const simpleIcons = { done: '✓', completed: '✓', pending: '○', 'in-progress': '►', deferred: 'x', blocked: '!', // Using plain x character for better compatibility review: '?' // Using circled dot symbol }; const simpleIcon = simpleIcons[status.toLowerCase()] || 'x'; return config.color(`${simpleIcon} ${status}`); } return config.color(`${config.icon} ${status}`); } /** * Format dependencies list with status indicators * @param {Array} dependencies - Array of dependency IDs * @param {Array} allTasks - Array of all tasks * @param {boolean} forConsole - Whether the output is for console display * @param {Object|null} complexityReport - Optional pre-loaded complexity report * @returns {string} Formatted dependencies string */ function formatDependenciesWithStatus( dependencies, allTasks, forConsole = false, complexityReport = null // Add complexityReport parameter ) { if ( !dependencies || !Array.isArray(dependencies) || dependencies.length === 0 ) { return forConsole ? chalk.gray('None') : 'None'; } const formattedDeps = dependencies.map((depId) => { const depIdStr = depId.toString(); // Ensure string format for display // Check if it's already a fully qualified subtask ID (like "22.1") if (depIdStr.includes('.')) { const parts = depIdStr.split('.'); // Validate that it's a proper subtask format (parentId.subtaskId) if (parts.length !== 2 || !parts[0] || !parts[1]) { // Invalid format - treat as regular dependency const numericDepId = typeof depId === 'string' ? parseInt(depId, 10) : depId; const depTaskResult = findTaskById( allTasks, numericDepId, complexityReport ); const depTask = depTaskResult.task; if (!depTask) { return forConsole ? chalk.red(`${depIdStr} (Not found)`) : `${depIdStr} (Not found)`; } const status = depTask.status || 'pending'; const isDone = status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; const isInProgress = status.toLowerCase() === 'in-progress'; if (forConsole) { if (isDone) { return chalk.green.bold(depIdStr); } else if (isInProgress) { return chalk.yellow.bold(depIdStr); } else { return chalk.red.bold(depIdStr); } } return depIdStr; } const [parentId, subtaskId] = parts.map((id) => parseInt(id, 10)); // Find the parent task const parentTask = allTasks.find((t) => t.id === parentId); if (!parentTask || !parentTask.subtasks) { return forConsole ? chalk.red(`${depIdStr} (Not found)`) : `${depIdStr} (Not found)`; } // Find the subtask const subtask = parentTask.subtasks.find((st) => st.id === subtaskId); if (!subtask) { return forConsole ? chalk.red(`${depIdStr} (Not found)`) : `${depIdStr} (Not found)`; } // Format with status const status = subtask.status || 'pending'; const isDone = status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; const isInProgress = status.toLowerCase() === 'in-progress'; if (forConsole) { if (isDone) { return chalk.green.bold(depIdStr); } else if (isInProgress) { return chalk.hex('#FFA500').bold(depIdStr); } else { return chalk.red.bold(depIdStr); } } // For plain text output (task files), return just the ID without any formatting or emoji return depIdStr; } // If depId is a number less than 100, it's likely a reference to a subtask ID in the current task // This case is typically handled elsewhere (in task-specific code) before calling this function // For regular task dependencies (not subtasks) // Convert string depId to number if needed const numericDepId = typeof depId === 'string' ? parseInt(depId, 10) : depId; // Look up the task using the numeric ID const depTaskResult = findTaskById( allTasks, numericDepId, complexityReport ); const depTask = depTaskResult.task; // Access the task object from the result if (!depTask) { return forConsole ? chalk.red(`${depIdStr} (Not found)`) : `${depIdStr} (Not found)`; } // Format with status const status = depTask.status || 'pending'; const isDone = status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; const isInProgress = status.toLowerCase() === 'in-progress'; if (forConsole) { if (isDone) { return chalk.green.bold(depIdStr); } else if (isInProgress) { return chalk.yellow.bold(depIdStr); } else { return chalk.red.bold(depIdStr); } } // For plain text output (task files), return just the ID without any formatting or emoji return depIdStr; }); return formattedDeps.join(', '); } /** * Display a comprehensive help guide */ function displayHelp() { // Get terminal width - moved to top of function to make it available throughout const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect console.log( boxen(chalk.white.bold('Task Master CLI'), { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }) ); // Command categories const commandCategories = [ { title: 'Project Setup & Configuration', color: 'blue', commands: [ { name: 'init', args: '[--name=<name>] [--description=<desc>] [-y]', desc: 'Initialize a new project with Task Master structure' }, { name: 'models', args: '', desc: 'View current AI model configuration and available models' }, { name: 'models --setup', args: '', desc: 'Run interactive setup to configure AI models' }, { name: 'models --set-main', args: '<model_id>', desc: 'Set the primary model for task generation' }, { name: 'models --set-research', args: '<model_id>', desc: 'Set the model for research operations' }, { name: 'models --set-fallback', args: '<model_id>', desc: 'Set the fallback model (optional)' } ] }, { title: 'Task Generation', color: 'cyan', commands: [ { name: 'parse-prd', args: '--input=<file.txt> [--num-tasks=10]', desc: 'Generate tasks from a PRD document' }, { name: 'generate', args: '', desc: 'Create individual task files from tasks.json' } ] }, { title: 'Task Management', color: 'green', commands: [ { name: 'list', args: '[--status=<status>] [--with-subtasks]', desc: 'List all tasks with their status' }, { name: 'set-status', args: '--id=<id> --status=<status>', desc: `Update task status (${TASK_STATUS_OPTIONS.join(', ')})` }, { name: 'sync-readme', args: '[--with-subtasks] [--status=<status>]', desc: 'Export tasks to README.md with professional formatting' }, { name: 'update', args: '--from=<id> --prompt="<context>"', desc: 'Update multiple tasks based on new requirements' }, { name: 'update-task', args: '--id=<id> --prompt="<context>"', desc: 'Update a single specific task with new information' }, { name: 'update-subtask', args: '--id=<parentId.subtaskId> --prompt="<context>"', desc: 'Append additional information to a subtask' }, { name: 'add-task', args: '--prompt="<text>" [--dependencies=<ids>] [--priority=<priority>]', desc: 'Add a new task using AI' }, { name: 'remove-task', args: '--id=<id> [-y]', desc: 'Permanently remove a task or subtask' } ] }, { title: 'Subtask Management', color: 'yellow', commands: [ { name: 'add-subtask', args: '--parent=<id> --title="<title>" [--description="<desc>"]', desc: 'Add a new subtask to a parent task' }, { name: 'add-subtask', args: '--parent=<id> --task-id=<id>', desc: 'Convert an existing task into a subtask' }, { name: 'remove-subtask', args: '--id=<parentId.subtaskId> [--convert]', desc: 'Remove a subtask (optionally convert to standalone task)' }, { name: 'clear-subtasks', args: '--id=<id>', desc: 'Remove all subtasks from specified tasks' }, { name: 'clear-subtasks --all', args: '', desc: 'Remove subtasks from all tasks' } ] }, { title: 'Task Analysis & Breakdown', color: 'magenta', commands: [ { name: 'analyze-complexity', args: '[--research] [--threshold=5]', desc: 'Analyze tasks and generate expansion recommendations' }, { name: 'complexity-report', args: '[--file=<path>]', desc: 'Display the complexity analysis report' }, { name: 'expand', args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]', desc: 'Break down tasks into detailed subtasks' }, { name: 'expand --all', args: '[--force] [--research]', desc: 'Expand all pending tasks with subtasks' }, { name: 'research', args: '"<prompt>" [-i=<task_ids>] [-f=<file_paths>] [-c="<context>"] [--tree] [-s=<save_file>] [-d=<detail_level>]', desc: 'Perform AI-powered research queries with project context' } ] }, { title: 'Task Navigation & Viewing', color: 'cyan', commands: [ { name: 'next', args: '', desc: 'Show the next task to work on based on dependencies' }, { name: 'show', args: '<id>', desc: 'Display detailed information about a specific task' } ] }, { title: 'Tag Management', color: 'magenta', commands: [ { name: 'tags', args: '[--show-metadata]', desc: 'List all available tags with task counts' }, { name: 'add-tag', args: '<tagName> [--copy-from-current] [--copy-from=<tag>] [-d="<desc>"]', desc: 'Create a new tag context for organizing tasks' }, { name: 'use-tag', args: '<tagName>', desc: 'Switch to a different tag context' }, { name: 'delete-tag', args: '<tagName> [--yes]', desc: 'Delete an existing tag and all its tasks' }, { name: 'rename-tag', args: '<oldName> <newName>', desc: 'Rename an existing tag' }, { name: 'copy-tag', args: '<sourceName> <targetName> [-d="<desc>"]', desc: 'Copy an existing tag to create a new tag with the same tasks' } ] }, { title: 'Dependency Management', color: 'blue', commands: [ { name: 'add-dependency', args: '--id=<id> --depends-on=<id>', desc: 'Add a dependency to a task' }, { name: 'remove-dependency', args: '--id=<id> --depends-on=<id>', desc: 'Remove a dependency from a task' }, { name: 'validate-dependencies', args: '', desc: 'Identify invalid dependencies without fixing them' }, { name: 'fix-dependencies', args: '', desc: 'Fix invalid dependencies automatically' } ] } ]; // Display each category commandCategories.forEach((category) => { console.log( boxen(chalk[category.color].bold(category.title), { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: category.color, borderStyle: 'round' }) ); // Calculate dynamic column widths - adjust ratios as needed const nameWidth = Math.max(25, Math.floor(terminalWidth * 0.2)); // 20% of width but min 25 const argsWidth = Math.max(40, Math.floor(terminalWidth * 0.35)); // 35% of width but min 40 const descWidth = Math.max(45, Math.floor(terminalWidth * 0.45) - 10); // 45% of width but min 45, minus some buffer const commandTable = new Table({ colWidths: [nameWidth, argsWidth, descWidth], chars: { top: '', 'top-mid': '', 'top-left': '', 'top-right': '', bottom: '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '', left: '', 'left-mid': '', mid: '', 'mid-mid': '', right: '', 'right-mid': '', middle: ' ' }, style: { border: [], 'padding-left': 4 }, wordWrap: true }); category.commands.forEach((cmd, index) => { commandTable.push([ `${chalk.yellow.bold(cmd.name)}${chalk.reset('')}`, `${chalk.white(cmd.args)}${chalk.reset('')}`, `${chalk.dim(cmd.desc)}${chalk.reset('')}` ]); }); console.log(commandTable.toString()); console.log(''); }); // Display configuration section console.log( boxen(chalk.cyan.bold('Configuration'), { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'cyan', borderStyle: 'round' }) ); // Get terminal width if not already defined const configTerminalWidth = terminalWidth || process.stdout.columns || 100; // Calculate dynamic column widths for config table const configKeyWidth = Math.max(30, Math.floor(configTerminalWidth * 0.25)); const configDescWidth = Math.max(50, Math.floor(configTerminalWidth * 0.45)); const configValueWidth = Math.max( 30, Math.floor(configTerminalWidth * 0.3) - 10 ); const configTable = new Table({ colWidths: [configKeyWidth, configDescWidth, configValueWidth], chars: { top: '', 'top-mid': '', 'top-left': '', 'top-right': '', bottom: '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '', left: '', 'left-mid': '', mid: '', 'mid-mid': '', right: '', 'right-mid': '', middle: ' ' }, style: { border: [], 'padding-left': 4 }, wordWrap: true }); configTable.push( [ `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset('')}`, `${chalk.white('AI model configuration file (project root)')}${chalk.reset('')}`, `${chalk.dim('Managed by models cmd')}${chalk.reset('')}` ], [ `${chalk.yellow('API Keys (.env)')}${chalk.reset('')}`, `${chalk.white('API keys for AI providers (ANTHROPIC_API_KEY, etc.)')}${chalk.reset('')}`, `${chalk.dim('Required in .env file')}${chalk.reset('')}` ], [ `${chalk.yellow('MCP Keys (mcp.json)')}${chalk.reset('')}`, `${chalk.white('API keys for Cursor integration')}${chalk.reset('')}`, `${chalk.dim('Required in .cursor/')}${chalk.reset('')}` ] ); console.log(configTable.toString()); console.log(''); // Show helpful hints console.log( boxen( chalk.white.bold('Quick Start:') + '\n\n' + chalk.cyan('1. Create Project: ') + chalk.white('task-master init') + '\n' + chalk.cyan('2. Setup Models: ') + chalk.white('task-master models --setup') + '\n' + chalk.cyan('3. Parse PRD: ') + chalk.white('task-master parse-prd --input=<prd-file>') + '\n' + chalk.cyan('4. List Tasks: ') + chalk.white('task-master list') + '\n' + chalk.cyan('5. Find Next Task: ') + chalk.white('task-master next'), { padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 }, width: Math.min(configTerminalWidth - 10, 100) // Limit width to terminal width minus padding, max 100 } ) ); } /** * Get colored complexity score * @param {number} score - Complexity score (1-10) * @returns {string} Colored complexity score */ function getComplexityWithColor(score) { if (score <= 3) return chalk.green(`● ${score}`); if (score <= 6) return chalk.yellow(`● ${score}`); return chalk.red(`● ${score}`); } /** * Truncate a string to a maximum length and add ellipsis if needed * @param {string} str - The string to truncate * @param {number} maxLength - Maximum length * @returns {string} Truncated string */ function truncateString(str, maxLength) { if (!str) return ''; if (str.length <= maxLength) return str; return str.substring(0, maxLength - 3) + '...'; } /** * Display the next task to work on * @param {string} tasksPath - Path to the tasks.json file * @param {string} complexityReportPath - Path to the complexity report file * @param {string} tag - Optional tag to override current tag resolution */ async function displayNextTask( tasksPath, complexityReportPath = null, context = {} ) { // Extract parameters from context const { projectRoot, tag } = context; // Read the tasks file with proper projectRoot for tag resolution const data = readJSON(tasksPath, projectRoot, tag); if (!data || !data.tasks) { log('error', 'No valid tasks found.'); process.exit(1); } // Read complexity report once const complexityReport = readComplexityReport(complexityReportPath); // Find the next task const nextTask = findNextTask(data.tasks, complexityReport); if (!nextTask) { console.log( boxen( chalk.yellow('No eligible tasks found!\n\n') + 'All pending tasks have unsatisfied dependencies, or all tasks are completed.', { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } } ) ); return; } // Display the task in a nice format console.log( boxen(chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } }) ); // Create a table with task details const taskTable = new Table({ style: { head: [], border: [], 'padding-top': 0, 'padding-bottom': 0, compact: true }, chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], wordWrap: true }); // Priority with color const priorityColors = { high: chalk.red.bold, medium: chalk.yellow, low: chalk.gray }; const priorityColor = priorityColors[nextTask.priority || 'medium'] || chalk.white; // Add task details to table taskTable.push( [chalk.cyan.bold('ID:'), nextTask.id.toString()], [chalk.cyan.bold('Title:'), nextTask.title], [ chalk.cyan.bold('Priority:'), priorityColor(nextTask.priority || 'medium') ], [ chalk.cyan.bold('Dependencies:'), formatDependenciesWithStatus( nextTask.dependencies, data.tasks, true, complexityReport ) ], [ chalk.cyan.bold('Complexity:'), nextTask.complexityScore ? getComplexityWithColor(nextTask.complexityScore) : chalk.gray('N/A') ], [chalk.cyan.bold('Description:'), nextTask.description] ); console.log(taskTable.toString()); // If task has details, show them in a separate box if (nextTask.details && nextTask.details.trim().length > 0) { console.log( boxen( chalk.white.bold('Implementation Details:') + '\n\n' + nextTask.details, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } } ) ); } // Determine if the nextTask is a subtask const isSubtask = !!nextTask.parentId; // Show subtasks if they exist (only for parent tasks) if (!isSubtask && nextTask.subtasks && nextTask.subtasks.length > 0) { console.log( boxen(chalk.white.bold('Subtasks'), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' }) ); // Calculate available width for the subtask table const availableWidth = process.stdout.columns - 10 || 100; // Default to 100 if can't detect // Define percentage-based column widths const idWidthPct = 8; const statusWidthPct = 15; const depsWidthPct = 25; const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; // Calculate actual column widths const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); // Create a table for subtasks with improved handling const subtaskTable = new Table({ head: [ chalk.magenta.bold('ID'), chalk.magenta.bold('Status'), chalk.magenta.bold('Title'), chalk.magenta.bold('Deps') ], colWidths: [idWidth, statusWidth, titleWidth, depsWidth], style: { head: [], border: [], 'padding-top': 0, 'padding-bottom': 0, compact: true }, chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, wordWrap: true }); // Add subtasks to table nextTask.subtasks.forEach((st) => { const statusColor = { done: chalk.green, completed: chalk.green, pending: chalk.yellow, 'in-progress': chalk.blue }[st.status || 'pending'] || chalk.white; // Format subtask dependencies let subtaskDeps = 'None'; if (st.dependencies && st.dependencies.length > 0) { // Format dependencies with correct notation const formattedDeps = st.dependencies.map((depId) => { if (typeof depId === 'number' && depId < 100) { const foundSubtask = nextTask.subtasks.find( (st) => st.id === depId ); if (foundSubtask) { const isDone = foundSubtask.status === 'done' || foundSubtask.status === 'completed'; const isInProgress = foundSubtask.status === 'in-progress'; // Use consistent color formatting instead of emojis if (isDone) { return chalk.green.bold(`${nextTask.id}.${depId}`); } else if (isInProgress) { return chalk.hex('#FFA500').bold(`${nextTask.id}.${depId}`); } else { return chalk.red.bold(`${nextTask.id}.${depId}`); } } return chalk.red(`${nextTask.id}.${depId} (Not found)`); } return depId; }); // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again subtaskDeps = formattedDeps.length === 1 ? formattedDeps[0] : formattedDeps.join(chalk.white(', ')); } subtaskTable.push([ `${nextTask.id}.${st.id}`, statusColor(st.status || 'pending'), st.title, subtaskDeps ]); }); console.log(subtaskTable.toString()); } // Suggest expanding if no subtasks (only for parent tasks without subtasks) if (!isSubtask && (!nextTask.subtasks || nextTask.subtasks.length === 0)) { console.log( boxen( chalk.yellow('No subtasks found. Consider breaking down this task:') + '\n' + chalk.white( `Run: ${chalk.cyan(`task-master expand --id=${nextTask.id}`)}` ), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1, bottom: 0 } } ) ); } // Show action suggestions let suggestedActionsContent = chalk.white.bold('Suggested Actions:') + '\n'; if (isSubtask) { // Suggested actions for a subtask suggestedActionsContent += `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${nextTask.parentId}`)}`; } else { // Suggested actions for a parent task suggestedActionsContent += `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + (nextTask.subtasks && nextTask.subtasks.length > 0 ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${nextTask.id}.1 --status=done`)}` // Example: first subtask : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${nextTask.id}`)}`); } console.log( boxen(suggestedActionsContent, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } }) ); // Show FYI notice if migration occurred displayTaggedTasksFYI(data); } /** * Display a specific task by ID * @param {string} tasksPath - Path to the tasks.json file * @param {string|number} taskId - The ID of the task to display * @param {string} complexityReportPath - Path to the complexity report file * @param {string} [statusFilter] - Optional status to filter subtasks by * @param {object} context - Context object containing projectRoot and tag * @param {string} context.projectRoot - Project root path * @param {string} context.tag - Tag for the task */ async function displayTaskById( tasksPath, taskId, complexityReportPath = null, statusFilter = null, context = {} ) { const { projectRoot, tag } = context; // Read the tasks file with proper projectRoot for tag resolution const data = readJSON(tasksPath, projectRoot, tag); if (!data || !data.tasks) { log('error', 'No valid tasks found.'); process.exit(1); } // Read complexity report once const complexityReport = readComplexityReport(complexityReportPath); // Find the task by ID, applying the status filter if provided // Returns { task, originalSubtaskCount, originalSubtasks } const { task, originalSubtaskCount, originalSubtasks } = findTaskById( data.tasks, taskId, complexityReport, statusFilter ); if (!task) { console.log( boxen(chalk.yellow(`Task with ID ${taskId} not found!`), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } }) ); return; } // Handle subtask display specially (This logic remains the same) if (task.isSubtask || task.parentTask) { console.log( boxen( chalk.white.bold( `Subtask: #${task.parentTask.id}.${task.id} - ${task.title}` ), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'magenta', borderStyle: 'round', margin: { top: 1, bottom: 0 } } ) ); const subtaskTable = new Table({ style: { head: [], border: [], 'padding-top': 0, 'padding-bottom': 0, compact: true }, chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], wordWrap: true }); subtaskTable.push( [chalk.cyan.bold('ID:'), `${task.parentTask.id}.${task.id}`], [ chalk.cyan.bold('Parent Task:'), `#${task.parentTask.id} - ${task.parentTask.title}` ], [chalk.cyan.bold('Title:'), task.title], [ chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending', true) ], [ chalk.cyan.bold('Complexity:'), task.complexityScore ? getComplexityWithColor(task.complexityScore) : chalk.gray('N/A') ], [ chalk.cyan.bold('Description:'), task.description || 'No description provided.' ] ); console.log(subtaskTable.toString()); if (task.details && task.details.trim().length > 0) { console.log( boxen( chalk.white.bold('Implementation Details:') + '\n\n' + task.details, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } } ) ); } console.log( boxen( chalk.white.bold('Suggested Actions:') + '\n' + `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=in-progress`)}\n` + `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=done`)}\n` + `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${task.parentTask.id}`)}`, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } ) ); return; // Exit after displaying subtask details } // --- Display Regular Task Details --- console.log( boxen(chalk.white.bold(`Task: #${task.id} - ${task.title}`), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } }) ); const taskTable = new Table({ style: { head: [], border: [], 'padding-top': 0, 'padding-bottom': 0, compact: true }, chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], wordWrap: true }); const priorityColors = { high: chalk.red.bold, medium: chalk.yellow, low: chalk.gray }; const priorityColor = priorityColors[task.priority || 'medium'] || chalk.white; taskTable.push( [chalk.cyan.bold('ID:'), task.id.toString()], [chalk.cyan.bold('Title:'), task.title], [ chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending', true) ], [chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')], [ chalk.cyan.bold('Dependencies:'), formatDependenciesWithStatus( task.dependencies, data.tasks, true, complexityReport ) ], [ chalk.cyan.bold('Complexity:'), task.complexityScore ? getComplexityWithColor(task.complexityScore) : chalk.gray('N/A') ], [chalk.cyan.bold('Description:'), task.description] ); console.log(taskTable.toString()); if (task.details && task.details.trim().length > 0) { console.log( boxen( chalk.white.bold('Implementation Details:') + '\n\n' + task.details, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } } ) ); } if (task.testStrategy && task.testStrategy.trim().length > 0) { console.log( boxen(chalk.white.bold('Test Strategy:') + '\n\n' + task.testStrategy, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } }) ); } // --- Subtask Table Display (uses filtered list: task.subtasks) --- if (task.subtasks && task.subtasks.length > 0) { console.log( boxen(chalk.white.bold('Subtasks'), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' }) ); const availableWidth = process.stdout.columns - 10 || 100; const idWidthPct = 10; const statusWidthPct = 15; const depsWidthPct = 25; const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); const subtaskTable = new Table({ head: [ chalk.magenta.bold('ID'), chalk.magenta.bold('Status'), chalk.magenta.bold('Title'), chalk.magenta.bold('Deps') ], colWidths: [idWidth, statusWidth, titleWidth, depsWidth], style: { head: [], border: [], 'padding-top': 0, 'padding-bottom': 0, compact: true }, chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, wordWrap: true }); // Populate table with the potentially filtered subtasks task.subtasks.forEach((st) => { const statusColorMap = { done: chalk.green, completed: chalk.green, pending: chalk.yellow, 'in-progress': chalk.blue }; const statusColor = statusColorMap[st.status || 'pending'] || chalk.white; let subtaskDeps = 'None'; if (st.dependencies && st.dependencies.length > 0) { const formattedDeps = st.dependencies.map((depId) => { // Use the original, unfiltered list for dependency status lookup const sourceListForDeps = originalSubtasks || task.subtasks; const foundDepSubtask = typeof depId === 'number' && depId < 100 ? sourceListForDeps.find((sub) => sub.id === depId) : null; if (foundDepSubtask) { const isDone = foundDepSubtask.status === 'done' || foundDepSubtask.status === 'completed'; const isInProgress = foundDepSubtask.status === 'in-progress'; const color = isDone ? chalk.green.bold : isInProgress ? chalk.hex('#FFA500').bold : chalk.red.bold; return color(`${task.id}.${depId}`); } else if (typeof depId === 'number' && depId < 100) { return chalk.red(`${task.id}.${depId} (Not found)`); } return depId; // Assume it's a top-level task ID if not a number < 100 }); subtaskDeps = formattedDeps.length === 1 ? formattedDeps[0] : formattedDeps.join(chalk.white(', ')); } subtaskTable.push([ `${task.id}.${st.id}`, statusColor(st.status || 'pending'), st.title, subtaskDeps ]); }); console.log(subtaskTable.toString()); // Display filter summary line *immediately after the table* if a filter was applied if (statusFilter && originalSubtaskCount !== null) { console.log( chalk.cyan( ` Filtered by status: ${chalk.bold(statusFilter)}. Showing ${chalk.bold(task.subtasks.length)} of ${chalk.bold(originalSubtaskCount)} subtasks.` ) ); // Add a newline for spacing before the progress bar if the filter line was shown console.log(); } // --- Conditional Messages for No Subtasks Shown --- } else if (statusFilter && originalSubtaskCount === 0) { // Case where filter applied, but the parent task had 0 subtasks originally console.log( boxen( chalk.yellow( `No subtasks found matching status: ${statusFilter} (Task has no subtasks)` ), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'yellow', borderStyle: 'round' } ) ); } else if ( statusFilter && originalSubtaskCount > 0 && task.subtasks.length === 0 ) { // Case where filter applied, original subtasks existed, but none matched console.log( boxen( chalk.yellow( `No subtasks found matching status: ${statusFilter} (out of ${originalSubtaskCount} total)` ), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'yellow', borderStyle: 'round' } ) ); } else if ( !statusFilter && (!originalSubtasks || originalSubtasks.length === 0) ) { // Case where NO filter applied AND the task genuinely has no subtasks // Use the authoritative originalSubtasks if it exists (from filtering), else check task.subtasks const actualSubtasks = originalSubtasks || task.subtasks; if (!actualSubtasks || actualSubtasks.length === 0) { console.log( boxen( chalk.yellow('No subtasks found. Consider breaking down this task:') + '\n' + chalk.white( `Run: ${chalk.cyan(`task-master expand --id=${task.id}`)}` ), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1, bottom: 0 } } ) ); } } // --- Subtask Progress Bar Display (uses originalSubtasks or task.subtasks) --- // Determine the list to use for progress calculation (always the original if available and filtering happened) const subtasksForProgress = originalSubtasks || task.subtasks; // Use original if filtering occurred, else the potentially empty task.subtasks // Only show progress if there are actually subtasks if (subtasksForProgress && subtasksForProgress.length > 0) { const totalSubtasks = subtasksForProgress.length; const completedSubtasks = subtasksForProgress.filter( (st) => st.status === 'done' || st.status === 'completed' ).length; // Count other statuses from the original/complete list const inProgressSubtasks = subtasksForProgress.filter( (st) => st.status === 'in-progress' ).length; const pendingSubtasks = subtasksForProgress.filter( (st) => st.status === 'pending' ).length; const blockedSubtasks = subtasksForProgress.filter( (st) => st.status === 'blocked' ).length; const deferredSubtasks = subtasksForProgress.filter( (st) => st.status === 'deferred' ).length; const cancelledSubtasks = subtasksForProgress.filter( (st) => st.status === 'cancelled' ).length; const statusBreakdown = { // Calculate breakdown based on the complete list 'in-progress': (inProgressSubtasks / totalSubtasks) * 100, pending: (pendingSubtasks / totalSubtasks) * 100, blocked: (blockedSubtasks / totalSubtasks) * 100, deferred: (deferredSubtasks / totalSubtasks) * 100, cancelled: (cancelledSubtasks / totalSubtasks) * 100 }; const completionPercentage = (completedSubtasks / totalSubtasks) * 100; const availableWidth = process.stdout.columns || 80; const boxPadding = 2; const boxBorders = 2; const percentTextLength = 5; const progressBarLength = Math.max( 20, Math.min( 60, availableWidth - boxPadding - boxBorders - percentTextLength - 35 ) ); const statusCounts = `${chalk.green('✓ Done:')} ${completedSubtasks} ${chalk.hex('#FFA500')('► In Progress:')} ${inProgressSubtasks} ${chalk.yellow('○ Pending:')} ${pendingSubtasks}\n` + `${chalk.red('! Blocked:')} ${blockedSubtasks} ${chalk.gray('⏱ Deferred:')} ${deferredSubtasks} ${chalk.gray('✗ Cancelled:')} ${cancelledSubtasks}`; console.log( boxen( chalk.white.bold('Subtask Progress:') + '\n\n' + `${chalk.cyan('Completed:')} ${completedSubtasks}/${totalSubtasks} (${completionPercentage.toFixed(1)}%)\n` + `${statusCounts}\n` + `${chalk.cyan('Progress:')} ${createProgressBar(completionPercentage, progressBarLength, statusBreakdown)}`, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 }, width: Math.min(availableWidth - 10, 100), textAlignment: 'left' } ) ); } // --- Suggested Actions --- const actions = []; let actionNumber = 1; // Basic actions actions.push( `${chalk.cyan(`${actionNumber}.`)} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}` ); actionNumber++; actions.push( `${chalk.cyan(`${actionNumber}.`)} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}` ); actionNumber++; // Subtask-related action if (subtasksForProgress && subtasksForProgress.length > 0) { actions.push( `${chalk.cyan(`${actionNumber}.`)} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}` ); } else { actions.push( `${chalk.cyan(`${actionNumber}.`)} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}` ); } actionNumber++; // Complexity-based scope adjustment actions if (task.complexityScore) { const complexityScore = task.complexityScore; actions.push( `${chalk.cyan(`${actionNumber}.`)} Re-analyze complexity: ${chalk.yellow(`task-master analyze-complexity --id=${task.id}`)}` ); actionNumber++; // Add scope adjustment suggestions based on current complexity if (complexityScore >= 7) { // High complexity - suggest scoping down actions.push( `${chalk.cyan(`${actionNumber}.`)} Scope down (simplify): ${chalk.yellow(`task-master scope-down --id=${task.id} --strength=regular`)}` ); actionNumber++; if (complexityScore >= 9) { actions.push( `${chalk.cyan(`${actionNumber}.`)} Heavy scope down: ${chalk.yellow(`task-master scope-down --id=${task.id} --strength=heavy`)}` ); actionNumber++; } } else if (complexityScore <= 4) { // Low complexity - suggest scoping up actions.push( `${chalk.cyan(`${actionNumber}.`)} Scope up (add detail): ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=regular`)}` ); actionNumber++; if (complexityScore <= 2) { actions.push( `${chalk.cyan(`${actionNumber}.`)} Heavy scope up: ${chalk.yellow(`task-master scope-up --id=${task.id} --strength=heavy`)}` ); actionNumber++; } } else { // Medium complexity (5-6) - offer both options actions.push( `${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`)}` ); actionNumber++; } } console.log( boxen(chalk.white.bold('Suggested Actions:') + '\n' + actions.join('\n'), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } }) ); // Show FYI notice if migration occurred displayTaggedTasksFYI(data); } /** * Display the complexity analysis report in a nice format * @param {string} reportPath - Path to the complexity report file */ async function displayComplexityReport(reportPath) { // Check if the report exists if (!fs.existsSync(reportPath)) { console.log( boxen( chalk.yellow(`No complexity report found at ${reportPath}\n\n`) + 'Would you like to generate one now?', { padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } } ) ); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise((resolve) => { rl.question(chalk.cyan('Generate complexity report? (y/n): '), resolve); }); rl.close(); if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { // Call the analyze-complexity command console.log(chalk.blue('Generating complexity report...')); const tasksPath = TASKMASTER_TASKS_FILE; if (!fs.existsSync(tasksPath)) { console.error( '❌ No tasks.json file found. Please run "task-master init" or create a tasks.json file.' ); return null; } await analyzeTaskComplexity({ output: reportPath, research: false, // Default to no research for speed file: tasksPath }); // Read the newly generated report return displayComplexityReport(reportPath); } else { console.log(chalk.yellow('Report generation cancelled.')); return; } } // Read the report let report; try { report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); } catch (error) { log('error', `Error reading complexity report: ${error.message}`); return; } // Display report header console.log( boxen(chalk.white.bold('Task Complexity Analysis Report'), { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }) ); // Display metadata const metaTable = new Table({ style: { head: [], border: [], 'padding-top': 0, 'padding-bottom': 0, compact: true }, chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, colWidths: [20, 50] }); metaTable.push( [ chalk.cyan.bold('Generated:'), new Date(report.meta.generatedAt).toLocaleString() ], [chalk.cyan.bold('Tasks Analyzed:'), report.meta.tasksAnalyzed], [chalk.cyan.bold('Threshold Score:'), report.meta.thresholdScore], [chalk.cyan.bold('Project:'), report.meta.projectName], [ chalk.cyan.bold('Research-backed:'), report.meta.usedResearch ? 'Yes' : 'No' ] ); console.log(metaTable.toString()); // Sort tasks by complexity score (highest first) const sortedTasks = [...report.complexityAnalysis].sort( (a, b) => b.complexityScore - a.complexityScore ); // Determine which tasks need expansion based on threshold const tasksNeedingExpansion = sortedTasks.filter( (task) => task.complexityScore >= report.meta.thresholdScore ); const simpleTasks = sortedTasks.filter( (task) => task.complexityScore < report.meta.thresholdScore ); // Create progress bar to show complexity distribution const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10) sortedTasks.forEach((task) => { if (task.complexityScore < 5) complexityDistribution[0]++; else if (task.complexityScore < 8) complexityDistribution[1]++; else complexityDistribution[2]++; }); const percentLow = Math.round( (complexityDistribution[0] / sortedTasks.length) * 100 ); const percentMedium = Math.round( (complexityDistribution[1] / sortedTasks.length) * 100 ); const percentHigh = Math.round( (complexityDistribution[2] / sortedTasks.length) * 100 ); console.log( boxen( chalk.white.bold('Complexity Distribution\n\n') + `${chalk.green.bold('Low (1-4):')} ${complexityDistribution[0]} tasks (${percentLow}%)\n` + `${chalk.yellow.bold('Medium (5-7):')} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` + `${chalk.red.bold('High (8-10):')} ${complexityDistribution[2]} tasks (${percentHigh}%)`, { padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 1 } } ) ); // Get terminal width const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect // Calculate dynamic column widths const idWidth = 12; const titleWidth = Math.floor(terminalWidth * 0.25); // 25% of width const scoreWidth = 8; const subtasksWidth = 8; // Command column gets the remaining space (minus some buffer for borders) const commandWidth = terminalWidth - idWidth - titleWidth - scoreWidth - subtasksWidth - 10; // Create table with new column widths and word wrapping const complexTable = new Table({ head: [ chalk.yellow.bold('ID'), chalk.yellow.bold('Title'), chalk.yellow.bold('Score'), chalk.yellow.bold('Subtasks'), chalk.yellow.bold('Expansion Command') ], colWidths: [idWidth, titleWidth, scoreWidth, subtasksWidth, commandWidth], style: { head: [], border: [] }, wordWrap: true, wrapOnWordBoundary: true }); // When adding rows, don't truncate the expansion command tasksNeedingExpansion.forEach((task) => { const expansionCommand = `task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}${task.expansionPrompt ? ` --prompt="${task.expansionPrompt}"` : ''}`; complexTable.push([ task.taskId, truncate(task.taskTitle, titleWidth - 3), // Still truncate title for readability getComplexityWithColor(task.complexityScore), task.recommendedSubtasks, chalk.cyan(expansionCommand) // Don't truncate - allow wrapping ]); }); console.log(complexTable.toString()); // Create table for simple tasks if (simpleTasks.length > 0) { console.log( boxen(chalk.green.bold(`Simple Tasks (${simpleTasks.length})`), { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'green', borderStyle: 'round' }) ); const simpleTable = new Table({ head: [ chalk.green.bold('ID'), chalk.green.bold('Title'), chalk.green.bold('Score'), chalk.green.bold('Reasoning') ], colWidths: [5, 40, 8, 50], style: { head: [], border: [] } }); simpleTasks.forEach((task) => { simpleTable.push([ task.taskId, truncate(task.taskTitle, 37), getComplexityWithColor(task.complexityScore), truncate(task.reasoning, 47) ]); }); console.log(simpleTable.toString()); } // Show action suggestions console.log( boxen( chalk.white.bold('Suggested Actions:') + '\n\n' + `${chalk.cyan('1.')} Expand all complex tasks: ${chalk.yellow(`task-master expand --all`)}\n` + `${chalk.cyan('2.')} Expand a specific task: ${chalk.yellow(`task-master expand --id=<id>`)}\n` + `${chalk.cyan('3.')} Regenerate with research: ${chalk.yellow(`task-master analyze-complexity --research`)}`, { padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } } ) ); } /** * Generate a prompt for complexity analysis * @param {Object} tasksData - Tasks data object containing tasks array * @returns {string} Generated prompt */ function generateComplexityAnalysisPrompt(tasksData) { const defaultSubtasks = getDefaultSubtasks(null); // Use the getter return `Analyze the complexity of the following tasks and provide recommendations for subtask breakdown: ${tasksData.tasks .map( (task) => ` Task ID: ${task.id} Title: ${task.title} Description: ${task.description} Details: ${task.details} Dependencies: ${JSON.stringify(task.dependencies || [])} Priority: ${task.priority || 'medium'} ` ) .join('\n---\n')} Analyze each task and return a JSON array with the following structure for each task: [ { "taskId": number, "taskTitle": string, "complexityScore": number (1-10), "recommendedSubtasks": number (${Math.max(3, defaultSubtasks - 1)}-${Math.min(8, defaultSubtasks + 2)}), "expansionPrompt": string (a specific prompt for generating good subtasks), "reasoning": string (brief explanation of your assessment) }, ... ] IMPORTANT: Make sure to include an analysis for EVERY task listed above, with the correct taskId matching each task's ID. `; } /** * Confirm overwriting existing tasks.json file * @param {string} tasksPath - Path to the tasks.json file * @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise */ async function confirmTaskOverwrite(tasksPath) { console.log( boxen( chalk.yellow( "It looks like you've already generated tasks for this project.\n" ) + chalk.yellow( 'Executing this command will overwrite any existing tasks.' ), { padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } } ) ); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise((resolve) => { rl.question( chalk.cyan('Are you sure you wish to continue? (y/N): '), resolve ); }); rl.close(); return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'; } /** * Displays the API key status for different providers. * @param {Array<{provider: string, cli: boolean, mcp: boolean}>} statusReport - The report generated by getApiKeyStatusReport. */ function displayApiKeyStatus(statusReport) { if (!statusReport || statusReport.length === 0) { console.log(chalk.yellow('No API key status information available.')); return; } const table = new Table({ head: [ chalk.cyan('Provider'), chalk.cyan('CLI Key (.env)'), chalk.cyan('MCP Key (mcp.json)') ], colWidths: [15, 20, 25], chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' } }); statusReport.forEach(({ provider, cli, mcp }) => { const cliStatus = cli ? chalk.green('✅ Found') : chalk.red('❌ Missing'); const mcpStatus = mcp ? chalk.green('✅ Found') : chalk.red('❌ Missing'); // Capitalize provider name for display const providerName = provider.charAt(0).toUpperCase() + provider.slice(1); table.push([providerName, cliStatus, mcpStatus]); }); console.log(chalk.bold('\n🔑 API Key Status:')); console.log(table.toString()); console.log( chalk.gray( ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.` ) ); } // --- Formatting Helpers (Potentially move some to utils.js if reusable) --- const formatSweScoreWithTertileStars = (score, allModels) => { // ... (Implementation from previous version or refine) ... if (score === null || score === undefined || score <= 0) return 'N/A'; const formattedPercentage = `${(score * 100).toFixed(1)}%`; const validScores = allModels .map((m) => m.sweScore) .filter((s) => s !== null && s !== undefined && s > 0); const sortedScores = [...validScores].sort((a, b) => b - a); const n = sortedScores.length; let stars = chalk.gray('☆☆☆'); if (n > 0) { const topThirdIndex = Math.max(0, Math.floor(n / 3) - 1); const midThirdIndex = Math.max(0, Math.floor((2 * n) / 3) - 1); if (score >= sortedScores[topThirdIndex]) stars = chalk.yellow('★★★'); else if (score >= sortedScores[midThirdIndex]) stars = chalk.yellow('★★') + chalk.gray('☆'); else stars = chalk.yellow('★') + chalk.gray('☆☆'); } return `${formattedPercentage} ${stars}`; }; const formatCost = (costObj) => { // ... (Implementation from previous version or refine) ... if (!costObj) return 'N/A'; if (costObj.input === 0 && costObj.output === 0) { return chalk.green('Free'); } const formatSingleCost = (costValue) => { if (costValue === null || costValue === undefined) return 'N/A'; const isInteger = Number.isInteger(costValue); return `$${costValue.toFixed(isInteger ? 0 : 2)}`; }; return `${formatSingleCost(costObj.input)} in, ${formatSingleCost(costObj.output)} out`; }; // --- Display Functions --- /** * Displays the currently configured active models. * @param {ConfigData} configData - The active configuration data. * @param {AvailableModel[]} allAvailableModels - Needed for SWE score tertiles. */ function displayModelConfiguration(configData, allAvailableModels = []) { console.log(chalk.cyan.bold('\nActive Model Configuration:')); const active = configData.activeModels; const activeTable = new Table({ head: [ 'Role', 'Provider', 'Model ID', 'SWE Score', 'Cost ($/1M tkns)' // 'API Key Status' // Removed, handled by separate displayApiKeyStatus ].map((h) => chalk.cyan.bold(h)), colWidths: [10, 14, 30, 18, 20 /*, 28 */], // Adjusted widths style: { head: ['cyan', 'bold'] } }); activeTable.push([ chalk.white('Main'), active.main.provider, active.main.modelId, formatSweScoreWithTertileStars(active.main.sweScore, allAvailableModels), formatCost(active.main.cost) // getCombinedStatus(active.main.keyStatus) // Removed ]); activeTable.push([ chalk.white('Research'), active.research.provider, active.research.modelId, formatSweScoreWithTertileStars( active.research.sweScore, allAvailableModels ), formatCost(active.research.cost) // getCombinedStatus(active.research.keyStatus) // Removed ]); if (active.fallback && active.fallback.provider && active.fallback.modelId) { activeTable.push([ chalk.white('Fallback'), active.fallback.provider, active.fallback.modelId, formatSweScoreWithTertileStars( active.fallback.sweScore, allAvailableModels ), formatCost(active.fallback.cost) // getCombinedStatus(active.fallback.keyStatus) // Removed ]); } else { activeTable.push([ chalk.white('Fallback'), chalk.gray('-'), chalk.gray('(Not Set)'), chalk.gray('-'), chalk.gray('-') // chalk.gray('-') // Removed ]); } console.log(activeTable.toString()); } /** * Displays the list of available models not currently configured. * @param {AvailableModel[]} availableModels - List of available models. */ function displayAvailableModels(availableModels) { if (!availableModels || availableModels.length === 0) { console.log( chalk.gray('\n(No other models available or all are configured)') ); return; } console.log(chalk.cyan.bold('\nOther Available Models:')); const availableTable = new Table({ head: ['Provider', 'Model ID', 'SWE Score', 'Cost ($/1M tkns)'].map((h) => chalk.cyan.bold(h) ), colWidths: [15, 40, 18, 25], style: { head: ['cyan', 'bold'] } }); availableModels.forEach((model) => { availableTable.push([ model.provider, model.modelId, formatSweScoreWithTertileStars(model.sweScore, availableModels), // Pass itself for comparison formatCost(model.cost) ]); }); console.log(availableTable.toString()); // --- Suggested Actions Section (moved here from models command) --- console.log( boxen( chalk.white.bold('Next Steps:') + '\n' + chalk.cyan( `1. Set main model: ${chalk.yellow('task-master models --set-main <model_id>')}` ) + '\n' + chalk.cyan( `2. Set research model: ${chalk.yellow('task-master models --set-research <model_id>')}` ) + '\n' + chalk.cyan( `3. Set fallback model: ${chalk.yellow('task-master models --set-fallback <model_id>')}` ) + '\n' + chalk.cyan( `4. Run interactive setup: ${chalk.yellow('task-master models --setup')}` ) + '\n' + chalk.cyan( `5. Use custom ollama/openrouter models: ${chalk.yellow('task-master models --openrouter|ollama --set-main|research|fallback <model_id>')}` ), { padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } } ) ); } /** * Displays AI usage telemetry summary in the CLI. * @param {object} telemetryData - The telemetry data object. * @param {string} outputType - 'cli' or 'mcp' (though typically only called for 'cli'). */ function displayAiUsageSummary(telemetryData, outputType = 'cli') { if ( (outputType !== 'cli' && outputType !== 'text') || !telemetryData || isSilentMode() ) { return; // Only display for CLI and if data exists and not in silent mode } const { modelUsed, providerName, inputTokens, outputTokens, totalTokens, totalCost, commandName } = telemetryData; let summary = chalk.bold.blue('AI Usage Summary:') + '\n'; summary += chalk.gray(` Command: ${commandName}\n`); summary += chalk.gray(` Provider: ${providerName}\n`); summary += chalk.gray(` Model: ${modelUsed}\n`); summary += chalk.gray( ` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n` ); summary += chalk.gray(` Est. Cost: $${totalCost.toFixed(6)}`); console.log( boxen(summary, { padding: 1, margin: { top: 1 }, borderColor: 'blue', borderStyle: 'round', title: '💡 Telemetry', titleAlignment: 'center' }) ); } /** * Display multiple tasks in a compact summary format with interactive drill-down * @param {string} tasksPath - Path to the tasks.json file * @param {Array<string>} taskIds - Array of task IDs to display * @param {string} complexityReportPath - Path to complexity report * @param {string} statusFilter - Optional status filter for subtasks * @param {Object} context - Context object containing projectRoot and tag * @param {string} [context.projectRoot] - Project root path * @param {string} [context.tag] - Tag for the task */ async function displayMultipleTasksSummary( tasksPath, taskIds, complexityReportPath = null, statusFilter = null, context = {} ) { displayBanner(); // Extract projectRoot and tag from context const projectRoot = context.projectRoot || null; const tag = context.tag || null; // Read the tasks file with proper projectRoot for tag resolution const data = readJSON(tasksPath, projectRoot, tag); if (!data || !data.tasks) { log('error', 'No valid tasks found.'); process.exit(1); } // Read complexity report once const complexityReport = readComplexityReport(complexityReportPath); // Find all requested tasks const foundTasks = []; const notFoundIds = []; taskIds.forEach((id) => { const { task } = findTaskById( data.tasks, id, complexityReport, statusFilter ); if (task) { foundTasks.push(task); } else { notFoundIds.push(id); } }); // Show not found tasks if (notFoundIds.length > 0) { console.log( boxen(chalk.yellow(`Tasks not found: ${notFoundIds.join(', ')}`), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1, bottom: 1 } }) ); } if (foundTasks.length === 0) { console.log( boxen(chalk.red('No valid tasks found to display'), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'red', borderStyle: 'round', margin: { top: 1 } }) ); return; } // Display header console.log( boxen( chalk.white.bold( `Task Summary (${foundTasks.length} task${foundTasks.length === 1 ? '' : 's'})` ), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } } ) ); // Calculate terminal width for responsive layout const terminalWidth = process.stdout.columns || 100; const availableWidth = terminalWidth - 10; // Create compact summary table const summaryTable = new Table({ head: [ chalk.cyan.bold('ID'), chalk.cyan.bold('Title'), chalk.cyan.bold('Status'), chalk.cyan.bold('Priority'), chalk.cyan.bold('Subtasks'), chalk.cyan.bold('Progress') ], colWidths: [ Math.floor(availableWidth * 0.08), // ID: 8% Math.floor(availableWidth * 0.35), // Title: 35% Math.floor(availableWidth * 0.12), // Status: 12% Math.floor(availableWidth * 0.1), // Priority: 10% Math.floor(availableWidth * 0.15), // Subtasks: 15% Math.floor(availableWidth * 0.2) // Progress: 20% ], style: { head: [], border: [], 'padding-top': 0, 'padding-bottom': 0, compact: true }, chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, wordWrap: true }); // Add each task to the summary table foundTasks.forEach((task) => { // Handle subtask case if (task.isSubtask || task.parentTask) { const parentId = task.parentTask ? task.parentTask.id : 'Unknown'; summaryTable.push([ `${parentId}.${task.id}`, truncate(task.title, Math.floor(availableWidth * 0.35) - 3), getStatusWithColor(task.status || 'pending', true), chalk.gray('(subtask)'), chalk.gray('N/A'), chalk.gray('N/A') ]); return; } // Handle regular task const priorityColors = { high: chalk.red.bold, medium: chalk.yellow, low: chalk.gray }; const priorityColor = priorityColors[task.priority || 'medium'] || chalk.white; // Calculate subtask summary let subtaskSummary = chalk.gray('None'); let progressBar = chalk.gray('N/A'); if (task.subtasks && task.subtasks.length > 0) { const total = task.subtasks.length; const completed = task.subtasks.filter( (st) => st.status === 'done' || st.status === 'completed' ).length; const inProgress = task.subtasks.filter( (st) => st.status === 'in-progress' ).length; const pending = task.subtasks.filter( (st) => st.status === 'pending' ).length; // Compact subtask count with status indicators subtaskSummary = `${chalk.green(completed)}/${total}`; if (inProgress > 0) subtaskSummary += ` ${chalk.hex('#FFA500')(`+${inProgress}`)}`; if (pending > 0) subtaskSummary += ` ${chalk.yellow(`(${pending})`)}`; // Mini progress bar (shorter than usual) const completionPercentage = (completed / total) * 100; const barLength = 8; // Compact bar const statusBreakdown = { 'in-progress': (inProgress / total) * 100, pending: (pending / total) * 100 }; progressBar = createProgressBar( completionPercentage, barLength, statusBreakdown ); } summaryTable.push([ task.id.toString(), truncate(task.title, Math.floor(availableWidth * 0.35) - 3), getStatusWithColor(task.status || 'pending', true), priorityColor(task.priority || 'medium'), subtaskSummary, progressBar ]); }); console.log(summaryTable.toString()); // Interactive drill-down prompt if (foundTasks.length > 1) { console.log( boxen( chalk.white.bold('Interactive Options:') + '\n' + chalk.cyan('• Press Enter to view available actions for all tasks') + '\n' + chalk.cyan( '• Type a task ID (e.g., "3" or "3.2") to view that specific task' ) + '\n' + chalk.cyan('• Type "q" to quit'), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } ) ); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const choice = await new Promise((resolve) => { rl.question(chalk.cyan('Your choice: '), resolve); }); rl.close(); if (choice.toLowerCase() === 'q') { return; } else if (choice.trim() === '') { // Show action menu for selected tasks console.log( boxen( chalk.white.bold('Available Actions for Selected Tasks:') + '\n' + chalk.cyan('1.') + ' Mark all as in-progress' + '\n' + chalk.cyan('2.') + ' Mark all as done' + '\n' + chalk.cyan('3.') + ' Show next available task' + '\n' + chalk.cyan('4.') + ' Expand all tasks (generate subtasks)' + '\n' + chalk.cyan('5.') + ' View dependency relationships' + '\n' + chalk.cyan('6.') + ' Generate task files' + '\n' + chalk.gray('Or type a task ID to view details'), { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1 } } ) ); const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout }); const actionChoice = await new Promise((resolve) => { rl2.question(chalk.cyan('Choose action (1-6): '), resolve); }); rl2.close(); const taskIdList = foundTasks.map((t) => t.id).join(','); switch (actionChoice.trim()) { case '1': console.log( chalk.blue( `\n→ Command: task-master set-status --id=${taskIdList} --status=in-progress` ) ); console.log( chalk.green( '✓ Copy and run this command to mark all tasks as in-progress' ) ); break; case '2': console.log( chalk.blue( `\n→ Command: task-master set-status --id=${taskIdList} --status=done` ) ); console.log( chalk.green('✓ Copy and run this command to mark all tasks as done') ); break; case '3': console.log(chalk.blue(`\n→ Command: task-master next`)); console.log( chalk.green( '✓ Copy and run this command to see the next available task' ) ); break; case '4': console.log( chalk.blue( `\n→ Command: task-master expand --id=${taskIdList} --research` ) ); console.log( chalk.green( '✓ Copy and run this command to expand all selected tasks into subtasks' ) ); break; case '5': { // Show dependency visualization console.log(chalk.white.bold('\nDependency Relationships:')); let hasDependencies = false; foundTasks.forEach((task) => { if (task.dependencies && task.dependencies.length > 0) { console.log( chalk.cyan( `Task ${task.id} depends on: ${task.dependencies.join(', ')}` ) ); hasDependencies = true; } }); if (!hasDependencies) { console.log(chalk.gray('No dependencies found for selected tasks')); } break; } case '6': console.log(chalk.blue(`\n→ Command: task-master generate`)); console.log( chalk.green('✓ Copy and run this command to generate task files') ); break; default: if (actionChoice.trim().length > 0) { console.log(chalk.yellow(`Invalid choice: ${actionChoice.trim()}`)); console.log(chalk.gray('Please choose 1-6 or type a task ID')); } } } else { // Show specific task await displayTaskById( tasksPath, choice.trim(), complexityReportPath, statusFilter, context ); } } else { // Single task - show suggested actions const task = foundTasks[0]; console.log( boxen( chalk.white.bold('Suggested Actions:') + '\n' + `${chalk.cyan('1.')} View full details: ${chalk.yellow(`task-master show ${task.id}`)}\n` + `${chalk.cyan('2.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` + `${chalk.cyan('3.')} Mark as done: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}`, { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } ) ); } } /** * Display context analysis results with beautiful formatting * @param {Object} analysisData - Analysis data from ContextGatherer * @param {string} semanticQuery - The original query used for semantic search * @param {number} contextSize - Size of gathered context in characters */ function displayContextAnalysis(analysisData, semanticQuery, contextSize) { if (isSilentMode() || !analysisData) return; const { highRelevance, mediumRelevance, recentTasks, allRelevantTasks } = analysisData; // Create the context analysis display let analysisContent = chalk.white.bold('Context Analysis') + '\n\n'; // Query info analysisContent += chalk.gray('Query: ') + chalk.white(`"${semanticQuery}"`) + '\n'; analysisContent += chalk.gray('Context size: ') + chalk.cyan(`${contextSize.toLocaleString()} characters`) + '\n'; analysisContent += chalk.gray('Tasks found: ') + chalk.yellow(`${allRelevantTasks.length} relevant tasks`) + '\n\n'; // High relevance matches if (highRelevance.length > 0) { analysisContent += chalk.green.bold('🎯 High Relevance Matches:') + '\n'; highRelevance.slice(0, 3).forEach((task) => { analysisContent += chalk.green(` • Task ${task.id}: ${truncate(task.title, 50)}`) + '\n'; }); if (highRelevance.length > 3) { analysisContent += chalk.green( ` • ... and ${highRelevance.length - 3} more high relevance tasks` ) + '\n'; } analysisContent += '\n'; } // Medium relevance matches if (mediumRelevance.length > 0) { analysisContent += chalk.yellow.bold('📋 Medium Relevance Matches:') + '\n'; mediumRelevance.slice(0, 3).forEach((task) => { analysisContent += chalk.yellow(` • Task ${task.id}: ${truncate(task.title, 50)}`) + '\n'; }); if (mediumRelevance.length > 3) { analysisContent += chalk.yellow( ` • ... and ${mediumRelevance.length - 3} more medium relevance tasks` ) + '\n'; } analysisContent += '\n'; } // Recent tasks (if they contributed) const recentTasksNotInRelevance = recentTasks.filter( (task) => !highRelevance.some((hr) => hr.id === task.id) && !mediumRelevance.some((mr) => mr.id === task.id) ); if (recentTasksNotInRelevance.length > 0) { analysisContent += chalk.cyan.bold('🕒 Recent Tasks (for context):') + '\n'; recentTasksNotInRelevance.slice(0, 2).forEach((task) => { analysisContent += chalk.cyan(` • Task ${task.id}: ${truncate(task.title, 50)}`) + '\n'; }); if (recentTasksNotInRelevance.length > 2) { analysisContent += chalk.cyan( ` • ... and ${recentTasksNotInRelevance.length - 2} more recent tasks` ) + '\n'; } } console.log( boxen(analysisContent, { padding: { top: 1, bottom: 1, left: 2, right: 2 }, margin: { top: 1, bottom: 0 }, borderStyle: 'round', borderColor: 'blue', title: chalk.blue('🔍 Context Gathering'), titleAlignment: 'center' }) ); } // Export UI functions export { displayBanner, displayTaggedTasksFYI, startLoadingIndicator, stopLoadingIndicator, createProgressBar, getStatusWithColor, formatDependenciesWithStatus, displayHelp, getComplexityWithColor, displayNextTask, displayTaskById, displayComplexityReport, generateComplexityAnalysisPrompt, confirmTaskOverwrite, displayApiKeyStatus, displayModelConfiguration, displayAvailableModels, displayAiUsageSummary, displayMultipleTasksSummary, succeedLoadingIndicator, failLoadingIndicator, warnLoadingIndicator, infoLoadingIndicator, displayContextAnalysis, displayCurrentTagIndicator, formatTaskIdForDisplay }; /** * Display enhanced error message for cross-tag dependency conflicts * @param {Array} conflicts - Array of cross-tag dependency conflicts * @param {string} sourceTag - Source tag name * @param {string} targetTag - Target tag name * @param {string} sourceIds - Source task IDs (comma-separated) */ export function displayCrossTagDependencyError( conflicts, sourceTag, targetTag, sourceIds ) { console.log( chalk.red(`\n❌ Cannot move tasks from "${sourceTag}" to "${targetTag}"`) ); console.log(chalk.yellow(`\nCross-tag dependency conflicts detected:`)); if (conflicts.length > 0) { conflicts.forEach((conflict) => { console.log(` • ${conflict.message}`); }); } console.log(chalk.cyan(`\nResolution options:`)); console.log( ` 1. Move with dependencies: task-master move --from=${sourceIds} --from-tag=${sourceTag} --to-tag=${targetTag} --with-dependencies` ); console.log( ` 2. Break dependencies: task-master move --from=${sourceIds} --from-tag=${sourceTag} --to-tag=${targetTag} --ignore-dependencies` ); console.log( ` 3. Validate and fix dependencies: task-master validate-dependencies && task-master fix-dependencies` ); if (conflicts.length > 0) { console.log( ` 4. Move dependencies first: task-master move --from=${conflicts.map((c) => c.dependencyId).join(',')} --from-tag=${conflicts[0].dependencyTag} --to-tag=${targetTag}` ); } } /** * Helper function to format task ID for display, handling edge cases with explicit labels * Builds on the existing formatTaskId utility but adds user-friendly display for edge cases * @param {*} taskId - The task ID to format * @returns {string} Formatted task ID for display */ function formatTaskIdForDisplay(taskId) { if (taskId === null) return 'null'; if (taskId === undefined) return 'undefined'; if (taskId === '') return '(empty)'; // Use existing formatTaskId for normal cases, with fallback to 'unknown' return formatTaskId(taskId) || 'unknown'; } /** * Display enhanced error message for subtask movement restriction * @param {string} taskId - The subtask ID that cannot be moved * @param {string} sourceTag - Source tag name * @param {string} targetTag - Target tag name */ export function displaySubtaskMoveError(taskId, sourceTag, targetTag) { // Handle null/undefined taskId but preserve the actual value for display const displayTaskId = formatTaskIdForDisplay(taskId); // Safe taskId for operations that need a valid string const safeTaskId = taskId || 'unknown'; // Validate taskId format before splitting let parentId = safeTaskId; if (safeTaskId.includes('.')) { const parts = safeTaskId.split('.'); // Check if it's a valid subtask format (parentId.subtaskId) if (parts.length === 2 && parts[0] && parts[1]) { parentId = parts[0]; } else { // Invalid format - log warning and use the original taskId console.log( chalk.yellow( `\n⚠️ Warning: Unexpected taskId format "${safeTaskId}". Using as-is for command suggestions.` ) ); parentId = safeTaskId; } } console.log( chalk.red(`\n❌ Cannot move subtask ${displayTaskId} directly between tags`) ); console.log(chalk.yellow(`\nSubtask movement restriction:`)); console.log(` • Subtasks cannot be moved directly between tags`); console.log(` • They must be promoted to full tasks first`); console.log(` • Source tag: "${sourceTag}"`); console.log(` • Target tag: "${targetTag}"`); console.log(chalk.cyan(`\nResolution options:`)); console.log( ` 1. Promote subtask to full task: task-master remove-subtask --id=${displayTaskId} --convert` ); console.log( ` 2. Then move the promoted task: task-master move --from=${parentId} --from-tag=${sourceTag} --to-tag=${targetTag}` ); console.log( ` 3. Or move the parent task with all subtasks: task-master move --from=${parentId} --from-tag=${sourceTag} --to-tag=${targetTag} --with-dependencies` ); } /** * Display enhanced error message for invalid tag combinations * @param {string} sourceTag - Source tag name * @param {string} targetTag - Target tag name * @param {string} reason - Reason for the error */ export function displayInvalidTagCombinationError( sourceTag, targetTag, reason ) { console.log(chalk.red(`\n❌ Invalid tag combination`)); console.log(chalk.yellow(`\nError details:`)); console.log(` • Source tag: "${sourceTag}"`); console.log(` • Target tag: "${targetTag}"`); console.log(` • Reason: ${reason}`); console.log(chalk.cyan(`\nResolution options:`)); console.log(` 1. Use different tags for cross-tag moves`); console.log( ` 2. Use within-tag move: task-master move --from=<id> --to=<id> --tag=${sourceTag}` ); console.log(` 3. Check available tags: task-master tags`); } /** * Display helpful hints for dependency validation commands * @param {string} context - Context for the hints (e.g., 'before-move', 'after-error') */ export function displayDependencyValidationHints(context = 'general') { const hints = { 'before-move': [ '💡 Tip: Run "task-master validate-dependencies" to check for dependency issues before moving tasks', '💡 Tip: Use "task-master fix-dependencies" to automatically resolve common dependency problems', '💡 Tip: Consider using --with-dependencies flag to move dependent tasks together' ], 'after-error': [ '🔧 Quick fix: Run "task-master validate-dependencies" to identify specific issues', '🔧 Quick fix: Use "task-master fix-dependencies" to automatically resolve problems', '🔧 Quick fix: Check "task-master show <id>" to see task dependencies before moving' ], general: [ '💡 Use "task-master validate-dependencies" to check for dependency issues', '💡 Use "task-master fix-dependencies" to automatically resolve problems', '💡 Use "task-master show <id>" to view task dependencies', '💡 Use --with-dependencies flag to move dependent tasks together' ] }; const relevantHints = hints[context] || hints.general; console.log(chalk.cyan(`\nHelpful hints:`)); // Convert to Set to ensure only unique hints are displayed const uniqueHints = new Set(relevantHints); uniqueHints.forEach((hint) => { console.log(` ${hint}`); }); } ```